Add stock 0.9.42 source.
This is from http://www.gnu.org/software/libmicrohttpd/ (and
was verified to match the source from a local mirror).
Bug: 23101922
Change-Id: I7efc9d5065b49b8957ce41bb6afd2ce1f39c7cac
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..d496c95
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,31 @@
+# This Makefile.am is in the public domain
+if HAVE_CURL
+curltests = testcurl
+if HAVE_ZZUF
+if HAVE_SOCAT
+zzuftests = testzzuf
+endif
+endif
+endif
+if ENABLE_SPDY
+if HAVE_OPENSSL
+microspdy = microspdy
+if HAVE_CURL
+microspdy += spdy2http
+endif
+#if HAVE_SPDYLAY
+microspdy += testspdy
+#endif
+endif
+endif
+
+SUBDIRS = include platform microhttpd $(microspdy) $(curltests) $(zzuftests) .
+
+if BUILD_EXAMPLES
+SUBDIRS += examples
+endif
+
+EXTRA_DIST = \
+ datadir/cert-and-key.pem \
+ datadir/cert-and-key-for-wireshark.pem \
+ datadir/spdy-draft.txt
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..8c4db5b
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,678 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@ENABLE_SPDY_TRUE@@HAVE_CURL_TRUE@@HAVE_OPENSSL_TRUE@am__append_1 = spdy2http
+@BUILD_EXAMPLES_TRUE@am__append_2 = examples
+subdir = src
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = include platform microhttpd microspdy spdy2http \
+ testspdy testcurl testzzuf . examples
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+@HAVE_CURL_TRUE@curltests = testcurl
+@HAVE_CURL_TRUE@@HAVE_SOCAT_TRUE@@HAVE_ZZUF_TRUE@zzuftests = testzzuf
+#if HAVE_SPDYLAY
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@microspdy = microspdy \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@ $(am__append_1) testspdy
+#endif
+SUBDIRS = include platform microhttpd $(microspdy) $(curltests) \
+ $(zzuftests) . $(am__append_2)
+EXTRA_DIST = \
+ datadir/cert-and-key.pem \
+ datadir/cert-and-key-for-wireshark.pem \
+ datadir/spdy-draft.txt
+
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/datadir/cert-and-key-for-wireshark.pem b/src/datadir/cert-and-key-for-wireshark.pem
new file mode 100644
index 0000000..eacd4b4
--- /dev/null
+++ b/src/datadir/cert-and-key-for-wireshark.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDc1k7EFEspRcr6PdPmvAd02hBDUG2O5dDkoRK+6tgEBvQxsTxz
+50TGwJ8RbSV+qUOnncZBwhnI4i71QSEezMP6I6liRA+fUtdh3cZFvdDpxgU6P15y
+5JxfnnDeZJR5O4tfMxN99t34EOEMruZZ0CNYJJgbmIteE0hLI418oUs7cwIDAQAB
+AoGAW3WOLXrSHge/pp/QkLCyzdw5/AblONdJCkcDQnp0eEaA/8uNY9sWCtJfjpIL
+g0eKs3KOV1GR6DZ0iDIvC1h2mO6pwyrJhRYHKPO9pnx7xpv1T9zYTuVwoMfVjPfO
+UCWFedSsSKR76+oP0TrwPDqp3JoMFcAyAqZKMg2JrRUpL3ECQQD92cVSYxSEKwX3
+cHWXVp1mSkuRMR/KX70NC/9XpYr8FEjvtXBTkmp7oG3TaDwd6nhSAodtY+Zkseyj
+k5NXM6sNAkEA3rT6opc1YK0pL3uweg+AFP4lRUYrrft1bDELhLmVm9Nf4xEdTnDO
+IotiX4GYQ64Bm8J+yxVU204soGynVXbgfwJBALQLCqq+X0TGhvrSpnRqGET+mM4n
+u1Z7xMhGJBpz7TmQ4ZIya7K6fA+m3347xbeqHyB7brYlTrlIgIAcITqOCNkCQQDE
+3tuJC34mHi0ASqkw3a7t39R2rpdCT733jEuQYrY8b9id061CgDnZE7o8j0VY3uOR
+G5gWUp8W1r5gemxaAqJlAkEAwVImBKyKy3oIMsd3WsoqYCMBM+cpX8H2JAEEISPT
+Y5zSPZ7kT9JgY2zJwXf3qL+uG1oKZ6f0wn4BYujctTbXwQ==
+-----END RSA PRIVATE KEY-----
diff --git a/src/datadir/cert-and-key.pem b/src/datadir/cert-and-key.pem
new file mode 100644
index 0000000..e33608c
--- /dev/null
+++ b/src/datadir/cert-and-key.pem
@@ -0,0 +1,34 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDc1k7EFEspRcr6PdPmvAd02hBDUG2O5dDkoRK+6tgEBvQxsTxz
+50TGwJ8RbSV+qUOnncZBwhnI4i71QSEezMP6I6liRA+fUtdh3cZFvdDpxgU6P15y
+5JxfnnDeZJR5O4tfMxN99t34EOEMruZZ0CNYJJgbmIteE0hLI418oUs7cwIDAQAB
+AoGAW3WOLXrSHge/pp/QkLCyzdw5/AblONdJCkcDQnp0eEaA/8uNY9sWCtJfjpIL
+g0eKs3KOV1GR6DZ0iDIvC1h2mO6pwyrJhRYHKPO9pnx7xpv1T9zYTuVwoMfVjPfO
+UCWFedSsSKR76+oP0TrwPDqp3JoMFcAyAqZKMg2JrRUpL3ECQQD92cVSYxSEKwX3
+cHWXVp1mSkuRMR/KX70NC/9XpYr8FEjvtXBTkmp7oG3TaDwd6nhSAodtY+Zkseyj
+k5NXM6sNAkEA3rT6opc1YK0pL3uweg+AFP4lRUYrrft1bDELhLmVm9Nf4xEdTnDO
+IotiX4GYQ64Bm8J+yxVU204soGynVXbgfwJBALQLCqq+X0TGhvrSpnRqGET+mM4n
+u1Z7xMhGJBpz7TmQ4ZIya7K6fA+m3347xbeqHyB7brYlTrlIgIAcITqOCNkCQQDE
+3tuJC34mHi0ASqkw3a7t39R2rpdCT733jEuQYrY8b9id061CgDnZE7o8j0VY3uOR
+G5gWUp8W1r5gemxaAqJlAkEAwVImBKyKy3oIMsd3WsoqYCMBM+cpX8H2JAEEISPT
+Y5zSPZ7kT9JgY2zJwXf3qL+uG1oKZ6f0wn4BYujctTbXwQ==
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDJTCCAo6gAwIBAgIJAIjfJkuxM1pAMA0GCSqGSIb3DQEBBQUAMGsxCzAJBgNV
+BAYTAkJHMQ4wDAYDVQQIEwVTb2ZpYTEOMAwGA1UEBxMFU29maWExCzAJBgNVBAoT
+AkFVMQ8wDQYDVQQDEwZBbmRyZXkxHjAcBgkqhkiG9w0BCQEWD3Jvb3RAZ29vZ2xl
+LmNvbTAeFw0xMjA5MDgxMTU2MzNaFw0xMzA5MDgxMTU2MzNaMGsxCzAJBgNVBAYT
+AkJHMQ4wDAYDVQQIEwVTb2ZpYTEOMAwGA1UEBxMFU29maWExCzAJBgNVBAoTAkFV
+MQ8wDQYDVQQDEwZBbmRyZXkxHjAcBgkqhkiG9w0BCQEWD3Jvb3RAZ29vZ2xlLmNv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3NZOxBRLKUXK+j3T5rwHdNoQ
+Q1BtjuXQ5KESvurYBAb0MbE8c+dExsCfEW0lfqlDp53GQcIZyOIu9UEhHszD+iOp
+YkQPn1LXYd3GRb3Q6cYFOj9ecuScX55w3mSUeTuLXzMTffbd+BDhDK7mWdAjWCSY
+G5iLXhNISyONfKFLO3MCAwEAAaOB0DCBzTAdBgNVHQ4EFgQUPp4dR3IT0t6bSE3Y
+b91MyHJU2+8wgZ0GA1UdIwSBlTCBkoAUPp4dR3IT0t6bSE3Yb91MyHJU2++hb6Rt
+MGsxCzAJBgNVBAYTAkJHMQ4wDAYDVQQIEwVTb2ZpYTEOMAwGA1UEBxMFU29maWEx
+CzAJBgNVBAoTAkFVMQ8wDQYDVQQDEwZBbmRyZXkxHjAcBgkqhkiG9w0BCQEWD3Jv
+b3RAZ29vZ2xlLmNvbYIJAIjfJkuxM1pAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
+AQEFBQADgYEAeBPoIRueOJ+SwpVniE2gmnvogWH9+irJnapDKGZrC/JsTA7ArqRd
+EHO1xZxqF+v+v128LmwWAdaazgMUHjR7e7B0xo4Tcp+9+voczc/GBTO0wnp6HT76
+2kUB6rPwwg9bycW8hAJiJJtr3IW5eYMtXDqM4RrbxhA1n2EAaZPVa5s=
+-----END CERTIFICATE-----
diff --git a/src/datadir/spdy-draft.txt b/src/datadir/spdy-draft.txt
new file mode 100644
index 0000000..c31648c
--- /dev/null
+++ b/src/datadir/spdy-draft.txt
@@ -0,0 +1,2856 @@
+
+
+
+Network Working Group M. Belshe
+Internet-Draft Twist
+Expires: August 4, 2012 R. Peon
+ Google, Inc
+ Feb 2012
+
+
+ SPDY Protocol
+ draft-mbelshe-httpbis-spdy-00
+
+Abstract
+
+ This document describes SPDY, a protocol designed for low-latency
+ transport of content over the World Wide Web. SPDY introduces two
+ layers of protocol. The lower layer is a general purpose framing
+ layer which can be used atop a reliable transport (likely TCP) for
+ multiplexed, prioritized, and compressed data communication of many
+ concurrent streams. The upper layer of the protocol provides HTTP-
+ like RFC2616 [RFC2616] semantics for compatibility with existing HTTP
+ application servers.
+
+Status of this Memo
+
+ This Internet-Draft is submitted in full conformance with the
+ provisions of BCP 78 and BCP 79.
+
+ Internet-Drafts are working documents of the Internet Engineering
+ Task Force (IETF). Note that other groups may also distribute
+ working documents as Internet-Drafts. The list of current Internet-
+ Drafts is at http://datatracker.ietf.org/drafts/current/.
+
+ Internet-Drafts are draft documents valid for a maximum of six months
+ and may be updated, replaced, or obsoleted by other documents at any
+ time. It is inappropriate to use Internet-Drafts as reference
+ material or to cite them other than as "work in progress."
+
+ This Internet-Draft will expire on August 4, 2012.
+
+Copyright Notice
+
+ Copyright (c) 2012 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 1]
+
+Internet-Draft SPDY Feb 2012
+
+
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+Table of Contents
+
+ 1. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
+ 1.1. Document Organization . . . . . . . . . . . . . . . . . . 4
+ 1.2. Definitions . . . . . . . . . . . . . . . . . . . . . . . 5
+ 2. SPDY Framing Layer . . . . . . . . . . . . . . . . . . . . . . 6
+ 2.1. Session (Connections) . . . . . . . . . . . . . . . . . . 6
+ 2.2. Framing . . . . . . . . . . . . . . . . . . . . . . . . . 6
+ 2.2.1. Control frames . . . . . . . . . . . . . . . . . . . . 6
+ 2.2.2. Data frames . . . . . . . . . . . . . . . . . . . . . 7
+ 2.3. Streams . . . . . . . . . . . . . . . . . . . . . . . . . 8
+ 2.3.1. Stream frames . . . . . . . . . . . . . . . . . . . . 9
+ 2.3.2. Stream creation . . . . . . . . . . . . . . . . . . . 9
+ 2.3.3. Stream priority . . . . . . . . . . . . . . . . . . . 10
+ 2.3.4. Stream headers . . . . . . . . . . . . . . . . . . . . 10
+ 2.3.5. Stream data exchange . . . . . . . . . . . . . . . . . 10
+ 2.3.6. Stream half-close . . . . . . . . . . . . . . . . . . 10
+ 2.3.7. Stream close . . . . . . . . . . . . . . . . . . . . . 11
+ 2.4. Error Handling . . . . . . . . . . . . . . . . . . . . . . 11
+ 2.4.1. Session Error Handling . . . . . . . . . . . . . . . . 11
+ 2.4.2. Stream Error Handling . . . . . . . . . . . . . . . . 12
+ 2.5. Data flow . . . . . . . . . . . . . . . . . . . . . . . . 12
+ 2.6. Control frame types . . . . . . . . . . . . . . . . . . . 12
+ 2.6.1. SYN_STREAM . . . . . . . . . . . . . . . . . . . . . . 12
+ 2.6.2. SYN_REPLY . . . . . . . . . . . . . . . . . . . . . . 14
+ 2.6.3. RST_STREAM . . . . . . . . . . . . . . . . . . . . . . 15
+ 2.6.4. SETTINGS . . . . . . . . . . . . . . . . . . . . . . . 16
+ 2.6.5. PING . . . . . . . . . . . . . . . . . . . . . . . . . 19
+ 2.6.6. GOAWAY . . . . . . . . . . . . . . . . . . . . . . . . 20
+ 2.6.7. HEADERS . . . . . . . . . . . . . . . . . . . . . . . 21
+ 2.6.8. WINDOW_UPDATE . . . . . . . . . . . . . . . . . . . . 22
+ 2.6.9. CREDENTIAL . . . . . . . . . . . . . . . . . . . . . . 24
+ 2.6.10. Name/Value Header Block . . . . . . . . . . . . . . . 26
+ 3. HTTP Layering over SPDY . . . . . . . . . . . . . . . . . . . 33
+ 3.1. Connection Management . . . . . . . . . . . . . . . . . . 33
+ 3.1.1. Use of GOAWAY . . . . . . . . . . . . . . . . . . . . 33
+ 3.2. HTTP Request/Response . . . . . . . . . . . . . . . . . . 34
+ 3.2.1. Request . . . . . . . . . . . . . . . . . . . . . . . 34
+ 3.2.2. Response . . . . . . . . . . . . . . . . . . . . . . . 35
+ 3.2.3. Authentication . . . . . . . . . . . . . . . . . . . . 36
+ 3.3. Server Push Transactions . . . . . . . . . . . . . . . . . 37
+ 3.3.1. Server implementation . . . . . . . . . . . . . . . . 38
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 2]
+
+Internet-Draft SPDY Feb 2012
+
+
+ 3.3.2. Client implementation . . . . . . . . . . . . . . . . 39
+ 4. Design Rationale and Notes . . . . . . . . . . . . . . . . . . 40
+ 4.1. Separation of Framing Layer and Application Layer . . . . 40
+ 4.2. Error handling - Framing Layer . . . . . . . . . . . . . . 40
+ 4.3. One Connection Per Domain . . . . . . . . . . . . . . . . 40
+ 4.4. Fixed vs Variable Length Fields . . . . . . . . . . . . . 41
+ 4.5. Compression Context(s) . . . . . . . . . . . . . . . . . . 41
+ 4.6. Unidirectional streams . . . . . . . . . . . . . . . . . . 42
+ 4.7. Data Compression . . . . . . . . . . . . . . . . . . . . . 42
+ 4.8. Server Push . . . . . . . . . . . . . . . . . . . . . . . 42
+ 5. Security Considerations . . . . . . . . . . . . . . . . . . . 43
+ 5.1. Use of Same-origin constraints . . . . . . . . . . . . . . 43
+ 5.2. HTTP Headers and SPDY Headers . . . . . . . . . . . . . . 43
+ 5.3. Cross-Protocol Attacks . . . . . . . . . . . . . . . . . . 43
+ 5.4. Server Push Implicit Headers . . . . . . . . . . . . . . . 43
+ 6. Privacy Considerations . . . . . . . . . . . . . . . . . . . . 44
+ 6.1. Long Lived Connections . . . . . . . . . . . . . . . . . . 44
+ 6.2. SETTINGS frame . . . . . . . . . . . . . . . . . . . . . . 44
+ 7. Incompatibilities with SPDY draft #2 . . . . . . . . . . . . . 45
+ 8. Requirements Notation . . . . . . . . . . . . . . . . . . . . 46
+ 9. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 47
+ 10. Normative References . . . . . . . . . . . . . . . . . . . . . 48
+ Appendix A. Changes . . . . . . . . . . . . . . . . . . . . . . . 50
+ Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 51
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 3]
+
+Internet-Draft SPDY Feb 2012
+
+
+1. Overview
+
+ One of the bottlenecks of HTTP implementations is that HTTP relies on
+ multiple connections for concurrency. This causes several problems,
+ including additional round trips for connection setup, slow-start
+ delays, and connection rationing by the client, where it tries to
+ avoid opening too many connections to any single server. HTTP
+ pipelining helps some, but only achieves partial multiplexing. In
+ addition, pipelining has proven non-deployable in existing browsers
+ due to intermediary interference.
+
+ SPDY adds a framing layer for multiplexing multiple, concurrent
+ streams across a single TCP connection (or any reliable transport
+ stream). The framing layer is optimized for HTTP-like request-
+ response streams, such that applications which run over HTTP today
+ can work over SPDY with little or no change on behalf of the web
+ application writer.
+
+ The SPDY session offers four improvements over HTTP:
+
+ Multiplexed requests: There is no limit to the number of requests
+ that can be issued concurrently over a single SPDY connection.
+
+ Prioritized requests: Clients can request certain resources to be
+ delivered first. This avoids the problem of congesting the
+ network channel with non-critical resources when a high-priority
+ request is pending.
+
+ Compressed headers: Clients today send a significant amount of
+ redundant data in the form of HTTP headers. Because a single web
+ page may require 50 or 100 subrequests, this data is significant.
+
+ Server pushed streams: Server Push enables content to be pushed
+ from servers to clients without a request.
+
+ SPDY attempts to preserve the existing semantics of HTTP. All
+ features such as cookies, ETags, Vary headers, Content-Encoding
+ negotiations, etc work as they do with HTTP; SPDY only replaces the
+ way the data is written to the network.
+
+1.1. Document Organization
+
+ The SPDY Specification is split into two parts: a framing layer
+ (Section 2), which multiplexes a TCP connection into independent,
+ length-prefixed frames, and an HTTP layer (Section 3), which
+ specifies the mechanism for overlaying HTTP request/response pairs on
+ top of the framing layer. While some of the framing layer concepts
+ are isolated from the HTTP layer, building a generic framing layer
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 4]
+
+Internet-Draft SPDY Feb 2012
+
+
+ has not been a goal. The framing layer is tailored to the needs of
+ the HTTP protocol and server push.
+
+1.2. Definitions
+
+ client: The endpoint initiating the SPDY session.
+
+ connection: A transport-level connection between two endpoints.
+
+ endpoint: Either the client or server of a connection.
+
+ frame: A header-prefixed sequence of bytes sent over a SPDY
+ session.
+
+ server: The endpoint which did not initiate the SPDY session.
+
+ session: A synonym for a connection.
+
+ session error: An error on the SPDY session.
+
+ stream: A bi-directional flow of bytes across a virtual channel
+ within a SPDY session.
+
+ stream error: An error on an individual SPDY stream.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 5]
+
+Internet-Draft SPDY Feb 2012
+
+
+2. SPDY Framing Layer
+
+2.1. Session (Connections)
+
+ The SPDY framing layer (or "session") runs atop a reliable transport
+ layer such as TCP [RFC0793]. The client is the TCP connection
+ initiator. SPDY connections are persistent connections.
+
+ For best performance, it is expected that clients will not close open
+ connections until the user navigates away from all web pages
+ referencing a connection, or until the server closes the connection.
+ Servers are encouraged to leave connections open for as long as
+ possible, but can terminate idle connections if necessary. When
+ either endpoint closes the transport-level connection, it MUST first
+ send a GOAWAY (Section 2.6.6) frame so that the endpoints can
+ reliably determine if requests finished before the close.
+
+2.2. Framing
+
+ Once the connection is established, clients and servers exchange
+ framed messages. There are two types of frames: control frames
+ (Section 2.2.1) and data frames (Section 2.2.2). Frames always have
+ a common header which is 8 bytes in length.
+
+ The first bit is a control bit indicating whether a frame is a
+ control frame or data frame. Control frames carry a version number,
+ a frame type, flags, and a length. Data frames contain the stream
+ ID, flags, and the length for the payload carried after the common
+ header. The simple header is designed to make reading and writing of
+ frames easy.
+
+ All integer values, including length, version, and type, are in
+ network byte order. SPDY does not enforce alignment of types in
+ dynamically sized frames.
+
+2.2.1. Control frames
+
+ +----------------------------------+
+ |C| Version(15bits) | Type(16bits) |
+ +----------------------------------+
+ | Flags (8) | Length (24 bits) |
+ +----------------------------------+
+ | Data |
+ +----------------------------------+
+
+ Control bit: The 'C' bit is a single bit indicating if this is a
+ control message. For control frames this value is always 1.
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 6]
+
+Internet-Draft SPDY Feb 2012
+
+
+ Version: The version number of the SPDY protocol. This document
+ describes SPDY version 3.
+
+ Type: The type of control frame. See Control Frames for the complete
+ list of control frames.
+
+ Flags: Flags related to this frame. Flags for control frames and
+ data frames are different.
+
+ Length: An unsigned 24-bit value representing the number of bytes
+ after the length field.
+
+ Data: data associated with this control frame. The format and length
+ of this data is controlled by the control frame type.
+
+ Control frame processing requirements:
+
+ Note that full length control frames (16MB) can be large for
+ implementations running on resource-limited hardware. In such
+ cases, implementations MAY limit the maximum length frame
+ supported. However, all implementations MUST be able to receive
+ control frames of at least 8192 octets in length.
+
+2.2.2. Data frames
+
+ +----------------------------------+
+ |C| Stream-ID (31bits) |
+ +----------------------------------+
+ | Flags (8) | Length (24 bits) |
+ +----------------------------------+
+ | Data |
+ +----------------------------------+
+
+ Control bit: For data frames this value is always 0.
+
+ Stream-ID: A 31-bit value identifying the stream.
+
+ Flags: Flags related to this frame. Valid flags are:
+
+ 0x01 = FLAG_FIN - signifies that this frame represents the last
+ frame to be transmitted on this stream. See Stream Close
+ (Section 2.3.7) below.
+
+ 0x02 = FLAG_COMPRESS - indicates that the data in this frame has
+ been compressed.
+
+ Length: An unsigned 24-bit value representing the number of bytes
+ after the length field. The total size of a data frame is 8 bytes +
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 7]
+
+Internet-Draft SPDY Feb 2012
+
+
+ length. It is valid to have a zero-length data frame.
+
+ Data: The variable-length data payload; the length was defined in the
+ length field.
+
+ Data frame processing requirements:
+
+ If an endpoint receives a data frame for a stream-id which is not
+ open and the endpoint has not sent a GOAWAY (Section 2.6.6) frame,
+ it MUST send issue a stream error (Section 2.4.2) with the error
+ code INVALID_STREAM for the stream-id.
+
+ If the endpoint which created the stream receives a data frame
+ before receiving a SYN_REPLY on that stream, it is a protocol
+ error, and the recipient MUST issue a stream error (Section 2.4.2)
+ with the status code PROTOCOL_ERROR for the stream-id.
+
+ Implementors note: If an endpoint receives multiple data frames
+ for invalid stream-ids, it MAY close the session.
+
+ All SPDY endpoints MUST accept compressed data frames.
+ Compression of data frames is always done using zlib compression.
+ Each stream initializes and uses its own compression context
+ dedicated to use within that stream. Endpoints are encouraged to
+ use application level compression rather than SPDY stream level
+ compression.
+
+ Each SPDY stream sending compressed frames creates its own zlib
+ context for that stream, and these compression contexts MUST be
+ distinct from the compression contexts used with SYN_STREAM/
+ SYN_REPLY/HEADER compression. (Thus, if both endpoints of a
+ stream are compressing data on the stream, there will be two zlib
+ contexts, one for sending and one for receiving).
+
+2.3. Streams
+
+ Streams are independent sequences of bi-directional data divided into
+ frames with several properties:
+
+ Streams may be created by either the client or server.
+
+ Streams optionally carry a set of name/value header pairs.
+
+ Streams can concurrently send data interleaved with other streams.
+
+ Streams may be cancelled.
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 8]
+
+Internet-Draft SPDY Feb 2012
+
+
+2.3.1. Stream frames
+
+ SPDY defines 3 control frames to manage the lifecycle of a stream:
+
+ SYN_STREAM - Open a new stream
+
+ SYN_REPLY - Remote acknowledgement of a new, open stream
+
+ RST_STREAM - Close a stream
+
+2.3.2. Stream creation
+
+ A stream is created by sending a control frame with the type set to
+ SYN_STREAM (Section 2.6.1). If the server is initiating the stream,
+ the Stream-ID must be even. If the client is initiating the stream,
+ the Stream-ID must be odd. 0 is not a valid Stream-ID. Stream-IDs
+ from each side of the connection must increase monotonically as new
+ streams are created. E.g. Stream 2 may be created after stream 3,
+ but stream 7 must not be created after stream 9. Stream IDs do not
+ wrap: when a client or server cannot create a new stream id without
+ exceeding a 31 bit value, it MUST NOT create a new stream.
+
+ The stream-id MUST increase with each new stream. If an endpoint
+ receives a SYN_STREAM with a stream id which is less than any
+ previously received SYN_STREAM, it MUST issue a session error
+ (Section 2.4.1) with the status PROTOCOL_ERROR.
+
+ It is a protocol error to send two SYN_STREAMs with the same
+ stream-id. If a recipient receives a second SYN_STREAM for the same
+ stream, it MUST issue a stream error (Section 2.4.2) with the status
+ code PROTOCOL_ERROR.
+
+ Upon receipt of a SYN_STREAM, the recipient can reject the stream by
+ sending a stream error (Section 2.4.2) with the error code
+ REFUSED_STREAM. Note, however, that the creating endpoint may have
+ already sent additional frames for that stream which cannot be
+ immediately stopped.
+
+ Once the stream is created, the creator may immediately send HEADERS
+ or DATA frames for that stream, without needing to wait for the
+ recipient to acknowledge.
+
+2.3.2.1. Unidirectional streams
+
+ When an endpoint creates a stream with the FLAG_UNIDIRECTIONAL flag
+ set, it creates a unidirectional stream which the creating endpoint
+ can use to send frames, but the receiving endpoint cannot. The
+ receiving endpoint is implicitly already in the half-closed
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 9]
+
+Internet-Draft SPDY Feb 2012
+
+
+ (Section 2.3.6) state.
+
+2.3.2.2. Bidirectional streams
+
+ SYN_STREAM frames which do not use the FLAG_UNIDIRECTIONAL flag are
+ bidirectional streams. Both endpoints can send data on a bi-
+ directional stream.
+
+2.3.3. Stream priority
+
+ The creator of a stream assigns a priority for that stream. Priority
+ is represented as an integer from 0 to 7. 0 represents the highest
+ priority and 7 represents the lowest priority.
+
+ The sender and recipient SHOULD use best-effort to process streams in
+ the order of highest priority to lowest priority.
+
+2.3.4. Stream headers
+
+ Streams carry optional sets of name/value pair headers which carry
+ metadata about the stream. After the stream has been created, and as
+ long as the sender is not closed (Section 2.3.7) or half-closed
+ (Section 2.3.6), each side may send HEADERS frame(s) containing the
+ header data. Header data can be sent in multiple HEADERS frames, and
+ HEADERS frames may be interleaved with data frames.
+
+2.3.5. Stream data exchange
+
+ Once a stream is created, it can be used to send arbitrary amounts of
+ data. Generally this means that a series of data frames will be sent
+ on the stream until a frame containing the FLAG_FIN flag is set. The
+ FLAG_FIN can be set on a SYN_STREAM (Section 2.6.1), SYN_REPLY
+ (Section 2.6.2), HEADERS (Section 2.6.7) or a DATA (Section 2.2.2)
+ frame. Once the FLAG_FIN has been sent, the stream is considered to
+ be half-closed.
+
+2.3.6. Stream half-close
+
+ When one side of the stream sends a frame with the FLAG_FIN flag set,
+ the stream is half-closed from that endpoint. The sender of the
+ FLAG_FIN MUST NOT send further frames on that stream. When both
+ sides have half-closed, the stream is closed.
+
+ If an endpoint receives a data frame after the stream is half-closed
+ from the sender (e.g. the endpoint has already received a prior frame
+ for the stream with the FIN flag set), it MUST send a RST_STREAM to
+ the sender with the status STREAM_ALREADY_CLOSED.
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 10]
+
+Internet-Draft SPDY Feb 2012
+
+
+2.3.7. Stream close
+
+ There are 3 ways that streams can be terminated:
+
+ Normal termination: Normal stream termination occurs when both
+ sender and recipient have half-closed the stream by sending a
+ FLAG_FIN.
+
+ Abrupt termination: Either the client or server can send a
+ RST_STREAM control frame at any time. A RST_STREAM contains an
+ error code to indicate the reason for failure. When a RST_STREAM
+ is sent from the stream originator, it indicates a failure to
+ complete the stream and that no further data will be sent on the
+ stream. When a RST_STREAM is sent from the stream recipient, the
+ sender, upon receipt, should stop sending any data on the stream.
+ The stream recipient should be aware that there is a race between
+ data already in transit from the sender and the time the
+ RST_STREAM is received. See Stream Error Handling (Section 2.4.2)
+
+ TCP connection teardown: If the TCP connection is torn down while
+ un-closed streams exist, then the endpoint must assume that the
+ stream was abnormally interrupted and may be incomplete.
+
+ If an endpoint receives a data frame after the stream is closed, it
+ must send a RST_STREAM to the sender with the status PROTOCOL_ERROR.
+
+2.4. Error Handling
+
+ The SPDY framing layer has only two types of errors, and they are
+ always handled consistently. Any reference in this specification to
+ "issue a session error" refers to Section 2.4.1. Any reference to
+ "issue a stream error" refers to Section 2.4.2.
+
+2.4.1. Session Error Handling
+
+ A session error is any error which prevents further processing of the
+ framing layer or which corrupts the session compression state. When
+ a session error occurs, the endpoint encountering the error MUST
+ first send a GOAWAY (Section 2.6.6) frame with the stream id of most
+ recently received stream from the remote endpoint, and the error code
+ for why the session is terminating. After sending the GOAWAY frame,
+ the endpoint MUST close the TCP connection.
+
+ Note that the session compression state is dependent upon both
+ endpoints always processing all compressed data. If an endpoint
+ partially processes a frame containing compressed data without
+ updating compression state properly, future control frames which use
+ compression will be always be errored. Implementations SHOULD always
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 11]
+
+Internet-Draft SPDY Feb 2012
+
+
+ try to process compressed data so that errors which could be handled
+ as stream errors do not become session errors.
+
+ Note that because this GOAWAY is sent during a session error case, it
+ is possible that the GOAWAY will not be reliably received by the
+ receiving endpoint. It is a best-effort attempt to communicate with
+ the remote about why the session is going down.
+
+2.4.2. Stream Error Handling
+
+ A stream error is an error related to a specific stream-id which does
+ not affect processing of other streams at the framing layer. Upon a
+ stream error, the endpoint MUST send a RST_STREAM (Section 2.6.3)
+ frame which contains the stream id of the stream where the error
+ occurred and the error status which caused the error. After sending
+ the RST_STREAM, the stream is closed to the sending endpoint. After
+ sending the RST_STREAM, if the sender receives any frames other than
+ a RST_STREAM for that stream id, it will result in sending additional
+ RST_STREAM frames. An endpoint MUST NOT send a RST_STREAM in
+ response to an RST_STREAM, as doing so would lead to RST_STREAM
+ loops. Sending a RST_STREAM does not cause the SPDY session to be
+ closed.
+
+ If an endpoint has multiple RST_STREAM frames to send in succession
+ for the same stream-id and the same error code, it MAY coalesce them
+ into a single RST_STREAM frame. (This can happen if a stream is
+ closed, but the remote sends multiple data frames. There is no
+ reason to send a RST_STREAM for each frame in succession).
+
+2.5. Data flow
+
+ Because TCP provides a single stream of data on which SPDY
+ multiplexes multiple logical streams, clients and servers must
+ intelligently interleave data messages for concurrent sessions.
+
+2.6. Control frame types
+
+2.6.1. SYN_STREAM
+
+ The SYN_STREAM control frame allows the sender to asynchronously
+ create a stream between the endpoints. See Stream Creation
+ (Section 2.3.2)
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 12]
+
+Internet-Draft SPDY Feb 2012
+
+
++------------------------------------+
+|1| version | 1 |
++------------------------------------+
+| Flags (8) | Length (24 bits) |
++------------------------------------+
+|X| Stream-ID (31bits) |
++------------------------------------+
+|X| Associated-To-Stream-ID (31bits) |
++------------------------------------+
+| Pri|Unused | Slot | |
++-------------------+ |
+| Number of Name/Value pairs (int32) | <+
++------------------------------------+ |
+| Length of name (int32) | | This section is the "Name/Value
++------------------------------------+ | Header Block", and is compressed.
+| Name (string) | |
++------------------------------------+ |
+| Length of value (int32) | |
++------------------------------------+ |
+| Value (string) | |
++------------------------------------+ |
+| (repeats) | <+
+
+ Flags: Flags related to this frame. Valid flags are:
+
+ 0x01 = FLAG_FIN - marks this frame as the last frame to be
+ transmitted on this stream and puts the sender in the half-closed
+ (Section 2.3.6) state.
+
+ 0x02 = FLAG_UNIDIRECTIONAL - a stream created with this flag puts
+ the recipient in the half-closed (Section 2.3.6) state.
+
+ Length: The length is the number of bytes which follow the length
+ field in the frame. For SYN_STREAM frames, this is 10 bytes plus the
+ length of the compressed Name/Value block.
+
+ Stream-ID: The 31-bit identifier for this stream. This stream-id
+ will be used in frames which are part of this stream.
+
+ Associated-To-Stream-ID: The 31-bit identifier for a stream which
+ this stream is associated to. If this stream is independent of all
+ other streams, it should be 0.
+
+ Priority: A 3-bit priority (Section 2.3.3) field.
+
+ Unused: 5 bits of unused space, reserved for future use.
+
+ Slot: An 8 bit unsigned integer specifying the index in the server's
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 13]
+
+Internet-Draft SPDY Feb 2012
+
+
+ CREDENTIAL vector of the client certificate to be used for this
+ request. see CREDENTIAL frame (Section 2.6.9). The value 0 means no
+ client certificate should be associated with this stream.
+
+ Name/Value Header Block: A set of name/value pairs carried as part of
+ the SYN_STREAM. see Name/Value Header Block (Section 2.6.10).
+
+ If an endpoint receives a SYN_STREAM which is larger than the
+ implementation supports, it MAY send a RST_STREAM with error code
+ FRAME_TOO_LARGE. All implementations MUST support the minimum size
+ limits defined in the Control Frames section (Section 2.2.1).
+
+2.6.2. SYN_REPLY
+
+ SYN_REPLY indicates the acceptance of a stream creation by the
+ recipient of a SYN_STREAM frame.
+
++------------------------------------+
+|1| version | 2 |
++------------------------------------+
+| Flags (8) | Length (24 bits) |
++------------------------------------+
+|X| Stream-ID (31bits) |
++------------------------------------+
+| Number of Name/Value pairs (int32) | <+
++------------------------------------+ |
+| Length of name (int32) | | This section is the "Name/Value
++------------------------------------+ | Header Block", and is compressed.
+| Name (string) | |
++------------------------------------+ |
+| Length of value (int32) | |
++------------------------------------+ |
+| Value (string) | |
++------------------------------------+ |
+| (repeats) | <+
+
+ Flags: Flags related to this frame. Valid flags are:
+
+ 0x01 = FLAG_FIN - marks this frame as the last frame to be
+ transmitted on this stream and puts the sender in the half-closed
+ (Section 2.3.6) state.
+
+ Length: The length is the number of bytes which follow the length
+ field in the frame. For SYN_REPLY frames, this is 4 bytes plus the
+ length of the compressed Name/Value block.
+
+ Stream-ID: The 31-bit identifier for this stream.
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 14]
+
+Internet-Draft SPDY Feb 2012
+
+
+ If an endpoint receives multiple SYN_REPLY frames for the same active
+ stream ID, it MUST issue a stream error (Section 2.4.2) with the
+ error code STREAM_IN_USE.
+
+ Name/Value Header Block: A set of name/value pairs carried as part of
+ the SYN_STREAM. see Name/Value Header Block (Section 2.6.10).
+
+ If an endpoint receives a SYN_REPLY which is larger than the
+ implementation supports, it MAY send a RST_STREAM with error code
+ FRAME_TOO_LARGE. All implementations MUST support the minimum size
+ limits defined in the Control Frames section (Section 2.2.1).
+
+2.6.3. RST_STREAM
+
+ The RST_STREAM frame allows for abnormal termination of a stream.
+ When sent by the creator of a stream, it indicates the creator wishes
+ to cancel the stream. When sent by the recipient of a stream, it
+ indicates an error or that the recipient did not want to accept the
+ stream, so the stream should be closed.
+
+ +----------------------------------+
+ |1| version | 3 |
+ +----------------------------------+
+ | Flags (8) | 8 |
+ +----------------------------------+
+ |X| Stream-ID (31bits) |
+ +----------------------------------+
+ | Status code |
+ +----------------------------------+
+
+ Flags: Flags related to this frame. RST_STREAM does not define any
+ flags. This value must be 0.
+
+ Length: An unsigned 24-bit value representing the number of bytes
+ after the length field. For RST_STREAM control frames, this value is
+ always 8.
+
+ Stream-ID: The 31-bit identifier for this stream.
+
+ Status code: (32 bits) An indicator for why the stream is being
+ terminated.The following status codes are defined:
+
+ 1 - PROTOCOL_ERROR. This is a generic error, and should only be
+ used if a more specific error is not available.
+
+ 2 - INVALID_STREAM. This is returned when a frame is received for
+ a stream which is not active.
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 15]
+
+Internet-Draft SPDY Feb 2012
+
+
+ 3 - REFUSED_STREAM. Indicates that the stream was refused before
+ any processing has been done on the stream.
+
+ 4 - UNSUPPORTED_VERSION. Indicates that the recipient of a stream
+ does not support the SPDY version requested.
+
+ 5 - CANCEL. Used by the creator of a stream to indicate that the
+ stream is no longer needed.
+
+ 6 - INTERNAL_ERROR. This is a generic error which can be used
+ when the implementation has internally failed, not due to anything
+ in the protocol.
+
+ 7 - FLOW_CONTROL_ERROR. The endpoint detected that its peer
+ violated the flow control protocol.
+
+ 8 - STREAM_IN_USE. The endpoint received a SYN_REPLY for a stream
+ already open.
+
+ 9 - STREAM_ALREADY_CLOSED. The endpoint received a data or
+ SYN_REPLY frame for a stream which is half closed.
+
+ 10 - INVALID_CREDENTIALS. The server received a request for a
+ resource whose origin does not have valid credentials in the
+ client certificate vector.
+
+ 11 - FRAME_TOO_LARGE. The endpoint received a frame which this
+ implementation could not support. If FRAME_TOO_LARGE is sent for
+ a SYN_STREAM, HEADERS, or SYN_REPLY frame without fully processing
+ the compressed portion of those frames, then the compression state
+ will be out-of-sync with the other endpoint. In this case,
+ senders of FRAME_TOO_LARGE MUST close the session.
+
+ Note: 0 is not a valid status code for a RST_STREAM.
+
+ After receiving a RST_STREAM on a stream, the recipient must not send
+ additional frames for that stream, and the stream moves into the
+ closed state.
+
+2.6.4. SETTINGS
+
+ A SETTINGS frame contains a set of id/value pairs for communicating
+ configuration data about how the two endpoints may communicate.
+ SETTINGS frames can be sent at any time by either endpoint, are
+ optionally sent, and are fully asynchronous. When the server is the
+ sender, the sender can request that configuration data be persisted
+ by the client across SPDY sessions and returned to the server in
+ future communications.
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 16]
+
+Internet-Draft SPDY Feb 2012
+
+
+ Persistence of SETTINGS ID/Value pairs is done on a per origin/IP
+ pair (the "origin" is the set of scheme, host, and port from the URI.
+ See [RFC6454]). That is, when a client connects to a server, and the
+ server persists settings within the client, the client SHOULD return
+ the persisted settings on future connections to the same origin AND
+ IP address and TCP port. Clients MUST NOT request servers to use the
+ persistence features of the SETTINGS frames, and servers MUST ignore
+ persistence related flags sent by a client.
+
+ +----------------------------------+
+ |1| version | 4 |
+ +----------------------------------+
+ | Flags (8) | Length (24 bits) |
+ +----------------------------------+
+ | Number of entries |
+ +----------------------------------+
+ | ID/Value Pairs |
+ | ... |
+
+ Control bit: The control bit is always 1 for this message.
+
+ Version: The SPDY version number.
+
+ Type: The message type for a SETTINGS message is 4.
+
+ Flags: FLAG_SETTINGS_CLEAR_SETTINGS (0x1): When set, the client
+ should clear any previously persisted SETTINGS ID/Value pairs. If
+ this frame contains ID/Value pairs with the
+ FLAG_SETTINGS_PERSIST_VALUE set, then the client will first clear its
+ existing, persisted settings, and then persist the values with the
+ flag set which are contained within this frame. Because persistence
+ is only implemented on the client, this flag can only be used when
+ the sender is the server.
+
+ Length: An unsigned 24-bit value representing the number of bytes
+ after the length field. The total size of a SETTINGS frame is 8
+ bytes + length.
+
+ Number of entries: A 32-bit value representing the number of ID/value
+ pairs in this message.
+
+ ID: A 32-bit ID number, comprised of 8 bits of flags and 24 bits of
+ unique ID.
+
+ ID.flags:
+
+ FLAG_SETTINGS_PERSIST_VALUE (0x1): When set, the sender of this
+ SETTINGS frame is requesting that the recipient persist the ID/
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 17]
+
+Internet-Draft SPDY Feb 2012
+
+
+ Value and return it in future SETTINGS frames sent from the
+ sender to this recipient. Because persistence is only
+ implemented on the client, this flag is only sent by the
+ server.
+
+ FLAG_SETTINGS_PERSISTED (0x2): When set, the sender is
+ notifying the recipient that this ID/Value pair was previously
+ sent to the sender by the recipient with the
+ FLAG_SETTINGS_PERSIST_VALUE, and the sender is returning it.
+ Because persistence is only implemented on the client, this
+ flag is only sent by the client.
+
+ Defined IDs:
+
+ 1 - SETTINGS_UPLOAD_BANDWIDTH allows the sender to send its
+ expected upload bandwidth on this channel. This number is an
+ estimate. The value should be the integral number of kilobytes
+ per second that the sender predicts as an expected maximum
+ upload channel capacity.
+
+ 2 - SETTINGS_DOWNLOAD_BANDWIDTH allows the sender to send its
+ expected download bandwidth on this channel. This number is an
+ estimate. The value should be the integral number of kilobytes
+ per second that the sender predicts as an expected maximum
+ download channel capacity.
+
+ 3 - SETTINGS_ROUND_TRIP_TIME allows the sender to send its
+ expected round-trip-time on this channel. The round trip time
+ is defined as the minimum amount of time to send a control
+ frame from this client to the remote and receive a response.
+ The value is represented in milliseconds.
+
+ 4 - SETTINGS_MAX_CONCURRENT_STREAMS allows the sender to inform
+ the remote endpoint the maximum number of concurrent streams
+ which it will allow. By default there is no limit. For
+ implementors it is recommended that this value be no smaller
+ than 100.
+
+ 5 - SETTINGS_CURRENT_CWND allows the sender to inform the
+ remote endpoint of the current TCP CWND value.
+
+ 6 - SETTINGS_DOWNLOAD_RETRANS_RATE allows the sender to inform
+ the remote endpoint the retransmission rate (bytes
+ retransmitted / total bytes transmitted).
+
+ 7 - SETTINGS_INITIAL_WINDOW_SIZE allows the sender to inform
+ the remote endpoint the initial window size (in bytes) for new
+ streams.
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 18]
+
+Internet-Draft SPDY Feb 2012
+
+
+ 8 - SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE allows the server
+ to inform the client if the new size of the client certificate
+ vector.
+
+ Value: A 32-bit value.
+
+ The message is intentionally extensible for future information which
+ may improve client-server communications. The sender does not need
+ to send every type of ID/value. It must only send those for which it
+ has accurate values to convey. When multiple ID/value pairs are
+ sent, they should be sent in order of lowest id to highest id. A
+ single SETTINGS frame MUST not contain multiple values for the same
+ ID. If the recipient of a SETTINGS frame discovers multiple values
+ for the same ID, it MUST ignore all values except the first one.
+
+ A server may send multiple SETTINGS frames containing different ID/
+ Value pairs. When the same ID/Value is sent twice, the most recent
+ value overrides any previously sent values. If the server sends IDs
+ 1, 2, and 3 with the FLAG_SETTINGS_PERSIST_VALUE in a first SETTINGS
+ frame, and then sends IDs 4 and 5 with the
+ FLAG_SETTINGS_PERSIST_VALUE, when the client returns the persisted
+ state on its next SETTINGS frame, it SHOULD send all 5 settings (1,
+ 2, 3, 4, and 5 in this example) to the server.
+
+2.6.5. PING
+
+ The PING control frame is a mechanism for measuring a minimal round-
+ trip time from the sender. It can be sent from the client or the
+ server. Recipients of a PING frame should send an identical frame to
+ the sender as soon as possible (if there is other pending data
+ waiting to be sent, PING should take highest priority). Each ping
+ sent by a sender should use a unique ID.
+
+ +----------------------------------+
+ |1| version | 6 |
+ +----------------------------------+
+ | 0 (flags) | 4 (length) |
+ +----------------------------------|
+ | 32-bit ID |
+ +----------------------------------+
+
+ Control bit: The control bit is always 1 for this message.
+
+ Version: The SPDY version number.
+
+ Type: The message type for a PING message is 6.
+
+ Length: This frame is always 4 bytes long.
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 19]
+
+Internet-Draft SPDY Feb 2012
+
+
+ ID: A unique ID for this ping, represented as an unsigned 32 bit
+ value. When the client initiates a ping, it must use an odd numbered
+ ID. When the server initiates a ping, it must use an even numbered
+ ping. Use of odd/even IDs is required in order to avoid accidental
+ looping on PINGs (where each side initiates an identical PING at the
+ same time).
+
+ Note: If a sender uses all possible PING ids (e.g. has sent all 2^31
+ possible IDs), it can wrap and start re-using IDs.
+
+ If a server receives an even numbered PING which it did not initiate,
+ it must ignore the PING. If a client receives an odd numbered PING
+ which it did not initiate, it must ignore the PING.
+
+2.6.6. GOAWAY
+
+ The GOAWAY control frame is a mechanism to tell the remote side of
+ the connection to stop creating streams on this session. It can be
+ sent from the client or the server. Once sent, the sender will not
+ respond to any new SYN_STREAMs on this session. Recipients of a
+ GOAWAY frame must not send additional streams on this session,
+ although a new session can be established for new streams. The
+ purpose of this message is to allow an endpoint to gracefully stop
+ accepting new streams (perhaps for a reboot or maintenance), while
+ still finishing processing of previously established streams.
+
+ There is an inherent race condition between an endpoint sending
+ SYN_STREAMs and the remote sending a GOAWAY message. To deal with
+ this case, the GOAWAY contains a last-stream-id indicating the
+ stream-id of the last stream which was created on the sending
+ endpoint in this session. If the receiver of the GOAWAY sent new
+ SYN_STREAMs for sessions after this last-stream-id, they were not
+ processed by the server and the receiver may treat the stream as
+ though it had never been created at all (hence the receiver may want
+ to re-create the stream later on a new session).
+
+ Endpoints should always send a GOAWAY message before closing a
+ connection so that the remote can know whether a stream has been
+ partially processed or not. (For example, if an HTTP client sends a
+ POST at the same time that a server closes a connection, the client
+ cannot know if the server started to process that POST request if the
+ server does not send a GOAWAY frame to indicate where it stopped
+ working).
+
+ After sending a GOAWAY message, the sender must ignore all SYN_STREAM
+ frames for new streams.
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 20]
+
+Internet-Draft SPDY Feb 2012
+
+
+ +----------------------------------+
+ |1| version | 7 |
+ +----------------------------------+
+ | 0 (flags) | 8 (length) |
+ +----------------------------------|
+ |X| Last-good-stream-ID (31 bits) |
+ +----------------------------------+
+ | Status code |
+ +----------------------------------+
+
+ Control bit: The control bit is always 1 for this message.
+
+ Version: The SPDY version number.
+
+ Type: The message type for a GOAWAY message is 7.
+
+ Length: This frame is always 8 bytes long.
+
+ Last-good-stream-Id: The last stream id which was replied to (with
+ either a SYN_REPLY or RST_STREAM) by the sender of the GOAWAY
+ message. If no streams were replied to, this value MUST be 0.
+
+ Status: The reason for closing the session.
+
+ 0 - OK. This is a normal session teardown.
+
+ 1 - PROTOCOL_ERROR. This is a generic error, and should only be
+ used if a more specific error is not available.
+
+ 11 - INTERNAL_ERROR. This is a generic error which can be used
+ when the implementation has internally failed, not due to anything
+ in the protocol.
+
+2.6.7. HEADERS
+
+ The HEADERS frame augments a stream with additional headers. It may
+ be optionally sent on an existing stream at any time. Specific
+ application of the headers in this frame is application-dependent.
+ The name/value header block within this frame is compressed.
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 21]
+
+Internet-Draft SPDY Feb 2012
+
+
++------------------------------------+
+|1| version | 8 |
++------------------------------------+
+| Flags (8) | Length (24 bits) |
++------------------------------------+
+|X| Stream-ID (31bits) |
++------------------------------------+
+| Number of Name/Value pairs (int32) | <+
++------------------------------------+ |
+| Length of name (int32) | | This section is the "Name/Value
++------------------------------------+ | Header Block", and is compressed.
+| Name (string) | |
++------------------------------------+ |
+| Length of value (int32) | |
++------------------------------------+ |
+| Value (string) | |
++------------------------------------+ |
+| (repeats) | <+
+
+ Flags: Flags related to this frame. Valid flags are:
+
+ 0x01 = FLAG_FIN - marks this frame as the last frame to be
+ transmitted on this stream and puts the sender in the half-closed
+ (Section 2.3.6) state.
+
+ Length: An unsigned 24 bit value representing the number of bytes
+ after the length field. The minimum length of the length field is 4
+ (when the number of name value pairs is 0).
+
+ Stream-ID: The stream this HEADERS block is associated with.
+
+ Name/Value Header Block: A set of name/value pairs carried as part of
+ the SYN_STREAM. see Name/Value Header Block (Section 2.6.10).
+
+2.6.8. WINDOW_UPDATE
+
+ The WINDOW_UPDATE control frame is used to implement per stream flow
+ control in SPDY. Flow control in SPDY is per hop, that is, only
+ between the two endpoints of a SPDY connection. If there are one or
+ more intermediaries between the client and the origin server, flow
+ control signals are not explicitly forwarded by the intermediaries.
+ (However, throttling of data transfer by any recipient may have the
+ effect of indirectly propagating flow control information upstream
+ back to the original sender.) Flow control only applies to the data
+ portion of data frames. Recipients must buffer all control frames.
+ If a recipient fails to buffer an entire control frame, it MUST issue
+ a stream error (Section 2.4.2) with the status code
+ FLOW_CONTROL_ERROR for the stream.
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 22]
+
+Internet-Draft SPDY Feb 2012
+
+
+ Flow control in SPDY is implemented by a data transfer window kept by
+ the sender of each stream. The data transfer window is a simple
+ uint32 that indicates how many bytes of data the sender can transmit.
+ After a stream is created, but before any data frames have been
+ transmitted, the sender begins with the initial window size. This
+ window size is a measure of the buffering capability of the
+ recipient. The sender must not send a data frame with data length
+ greater than the transfer window size. After sending each data
+ frame, the sender decrements its transfer window size by the amount
+ of data transmitted. When the window size becomes less than or equal
+ to 0, the sender must pause transmitting data frames. At the other
+ end of the stream, the recipient sends a WINDOW_UPDATE control back
+ to notify the sender that it has consumed some data and freed up
+ buffer space to receive more data.
+
+ +----------------------------------+
+ |1| version | 9 |
+ +----------------------------------+
+ | 0 (flags) | 8 (length) |
+ +----------------------------------+
+ |X| Stream-ID (31-bits) |
+ +----------------------------------+
+ |X| Delta-Window-Size (31-bits) |
+ +----------------------------------+
+
+ Control bit: The control bit is always 1 for this message.
+
+ Version: The SPDY version number.
+
+ Type: The message type for a WINDOW_UPDATE message is 9.
+
+ Length: The length field is always 8 for this frame (there are 8
+ bytes after the length field).
+
+ Stream-ID: The stream ID that this WINDOW_UPDATE control frame is
+ for.
+
+ Delta-Window-Size: The additional number of bytes that the sender can
+ transmit in addition to existing remaining window size. The legal
+ range for this field is 1 to 2^31 - 1 (0x7fffffff) bytes.
+
+ The window size as kept by the sender must never exceed 2^31
+ (although it can become negative in one special case). If a sender
+ receives a WINDOW_UPDATE that causes the its window size to exceed
+ this limit, it must send RST_STREAM with status code
+ FLOW_CONTROL_ERROR to terminate the stream.
+
+ When a SPDY connection is first established, the default initial
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 23]
+
+Internet-Draft SPDY Feb 2012
+
+
+ window size for all streams is 64KB. An endpoint can use the
+ SETTINGS control frame to adjust the initial window size for the
+ connection. That is, its peer can start out using the 64KB default
+ initial window size when sending data frames before receiving the
+ SETTINGS. Because SETTINGS is asynchronous, there may be a race
+ condition if the recipient wants to decrease the initial window size,
+ but its peer immediately sends 64KB on the creation of a new
+ connection, before waiting for the SETTINGS to arrive. This is one
+ case where the window size kept by the sender will become negative.
+ Once the sender detects this condition, it must stop sending data
+ frames and wait for the recipient to catch up. The recipient has two
+ choices:
+
+ immediately send RST_STREAM with FLOW_CONTROL_ERROR status code.
+
+ allow the head of line blocking (as there is only one stream for
+ the session and the amount of data in flight is bounded by the
+ default initial window size), and send WINDOW_UPDATE as it
+ consumes data.
+
+ In the case of option 2, both sides must compute the window size
+ based on the initial window size in the SETTINGS. For example, if
+ the recipient sets the initial window size to be 16KB, and the sender
+ sends 64KB immediately on connection establishment, the sender will
+ discover its window size is -48KB on receipt of the SETTINGS. As the
+ recipient consumes the first 16KB, it must send a WINDOW_UPDATE of
+ 16KB back to the sender. This interaction continues until the
+ sender's window size becomes positive again, and it can resume
+ transmitting data frames.
+
+ After the recipient reads in a data frame with FLAG_FIN that marks
+ the end of the data stream, it should not send WINDOW_UPDATE frames
+ as it consumes the last data frame. A sender should ignore all the
+ WINDOW_UPDATE frames associated with the stream after it send the
+ last frame for the stream.
+
+ The data frames from the sender and the WINDOW_UPDATE frames from the
+ recipient are completely asynchronous with respect to each other.
+ This property allows a recipient to aggressively update the window
+ size kept by the sender to prevent the stream from stalling.
+
+2.6.9. CREDENTIAL
+
+ The CREDENTIAL control frame is used by the client to send additional
+ client certificates to the server. A SPDY client may decide to send
+ requests for resources from different origins on the same SPDY
+ session if it decides that that server handles both origins. For
+ example if the IP address associated with both hostnames matches and
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 24]
+
+Internet-Draft SPDY Feb 2012
+
+
+ the SSL server certificate presented in the initial handshake is
+ valid for both hostnames. However, because the SSL connection can
+ contain at most one client certificate, the client needs a mechanism
+ to send additional client certificates to the server.
+
+ The server is required to maintain a vector of client certificates
+ associated with a SPDY session. When the client needs to send a
+ client certificate to the server, it will send a CREDENTIAL frame
+ that specifies the index of the slot in which to store the
+ certificate as well as proof that the client posesses the
+ corresponding private key. The initial size of this vector must be
+ 8. If the client provides a client certificate during the first TLS
+ handshake, the contents of this certificate must be copied into the
+ first slot (index 1) in the CREDENTIAL vector, though it may be
+ overwritten by subsequent CREDENTIAL frames. The server must
+ exclusively use the CREDNETIAL vector when evaluating the client
+ certificates associated with an origin. The server may change the
+ size of this vector by sending a SETTINGS frame with the setting
+ SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE value specified. In the
+ event that the new size is smaller than the current size, truncation
+ occurs preserving lower-index slots as possible.
+
+ TLS renegotiation with client authentication is incompatible with
+ SPDY given the multiplexed nature of SPDY. Specifically, imagine
+ that the client has 2 requests outstanding to the server for two
+ different pages (in different tabs). When the renegotiation + client
+ certificate request comes in, the browser is unable to determine
+ which resource triggered the client certificate request, in order to
+ prompt the user accordingly.
+
+ +----------------------------------+
+ |1|000000000000001|0000000000001011|
+ +----------------------------------+
+ | flags (8) | Length (24 bits) |
+ +----------------------------------+
+ | Slot (16 bits) | |
+ +-----------------+ |
+ | Proof Length (32 bits) |
+ +----------------------------------+
+ | Proof |
+ +----------------------------------+ <+
+ | Certificate Length (32 bits) | |
+ +----------------------------------+ | Repeated until end of frame
+ | Certificate | |
+ +----------------------------------+ <+
+
+ Slot: The index in the server's client certificate vector where this
+ certificate should be stored. If there is already a certificate
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 25]
+
+Internet-Draft SPDY Feb 2012
+
+
+ stored at this index, it will be overwritten. The index is one
+ based, not zero based; zero is an invalid slot index.
+
+ Proof: Cryptographic proof that the client has possession of the
+ private key associated with the certificate. The format is a TLS
+ digitally-signed element
+ (http://tools.ietf.org/html/rfc5246#section-4.7). The signature
+ algorithm must be the same as that used in the CertificateVerify
+ message. However, since the MD5+SHA1 signature type used in TLS 1.0
+ connections can not be correctly encoded in a digitally-signed
+ element, SHA1 must be used when MD5+SHA1 was used in the SSL
+ connection. The signature is calculated over a 32 byte TLS extractor
+ value (http://tools.ietf.org/html/rfc5705) with a label of "EXPORTER
+ SPDY certificate proof" using the empty string as context. ForRSA
+ certificates the signature would be a PKCS#1 v1.5 signature. For
+ ECDSA, it would be an ECDSA-Sig-Value
+ (http://tools.ietf.org/html/rfc5480#appendix-A). For a 1024-bit RSA
+ key, the CREDENTIAL message would be ~500 bytes.
+
+ Certificate: The certificate chain, starting with the leaf
+ certificate. Each certificate must be encoded as a 32 bit length,
+ followed by a DER encoded certificate. The certificate must be of
+ the same type (RSA, ECDSA, etc) as the client certificate associated
+ with the SSL connection.
+
+ If the server receives a request for a resource with unacceptable
+ credential (either missing or invalid), it must reply with a
+ RST_STREAM frame with the status code INVALID_CREDENTIALS. Upon
+ receipt of a RST_STREAM frame with INVALID_CREDENTIALS, the client
+ should initiate a new stream directly to the requested origin and
+ resend the request. Note, SPDY does not allow the server to request
+ different client authentication for different resources in the same
+ origin.
+
+ If the server receives an invalid CREDENTIAL frame, it MUST respond
+ with a GOAWAY frame and shutdown the session.
+
+2.6.10. Name/Value Header Block
+
+ The Name/Value Header Block is found in the SYN_STREAM, SYN_REPLY and
+ HEADERS control frames, and shares a common format:
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 26]
+
+Internet-Draft SPDY Feb 2012
+
+
+ +------------------------------------+
+ | Number of Name/Value pairs (int32) |
+ +------------------------------------+
+ | Length of name (int32) |
+ +------------------------------------+
+ | Name (string) |
+ +------------------------------------+
+ | Length of value (int32) |
+ +------------------------------------+
+ | Value (string) |
+ +------------------------------------+
+ | (repeats) |
+
+ Number of Name/Value pairs: The number of repeating name/value pairs
+ following this field.
+
+ List of Name/Value pairs:
+
+ Length of Name: a 32-bit value containing the number of octets in
+ the name field. Note that in practice, this length must not
+ exceed 2^24, as that is the maximum size of a SPDY frame.
+
+ Name: 0 or more octets, 8-bit sequences of data, excluding 0.
+
+ Length of Value: a 32-bit value containing the number of octets in
+ the value field. Note that in practice, this length must not
+ exceed 2^24, as that is the maximum size of a SPDY frame.
+
+ Value: 0 or more octets, 8-bit sequences of data, excluding 0.
+
+ Each header name must have at least one value. Header names are
+ encoded using the US-ASCII character set [ASCII] and must be all
+ lower case. The length of each name must be greater than zero. A
+ recipient of a zero-length name MUST issue a stream error
+ (Section 2.4.2) with the status code PROTOCOL_ERROR for the
+ stream-id.
+
+ Duplicate header names are not allowed. To send two identically
+ named headers, send a header with two values, where the values are
+ separated by a single NUL (0) byte. A header value can either be
+ empty (e.g. the length is zero) or it can contain multiple, NUL-
+ separated values, each with length greater than zero. The value
+ never starts nor ends with a NUL character. Recipients of illegal
+ value fields MUST issue a stream error (Section 2.4.2) with the
+ status code PROTOCOL_ERROR for the stream-id.
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 27]
+
+Internet-Draft SPDY Feb 2012
+
+
+2.6.10.1. Compression
+
+ The Name/Value Header Block is a section of the SYN_STREAM,
+ SYN_REPLY, and HEADERS frames used to carry header meta-data. This
+ block is always compressed using zlib compression. Within this
+ specification, any reference to 'zlib' is referring to the ZLIB
+ Compressed Data Format Specification Version 3.3 as part of RFC1950.
+ [RFC1950]
+
+ For each HEADERS compression instance, the initial state is
+ initialized using the following dictionary [UDELCOMPRESSION]:
+
+ const unsigned char SPDY_dictionary_txt[] = {
+ 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, \\ - - - - o p t i
+ 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, \\ o n s - - - - h
+ 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, \\ e a d - - - - p
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, \\ o s t - - - - p
+ 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, \\ u t - - - - d e
+ 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, \\ l e t e - - - -
+ 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, \\ t r a c e - - -
+ 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, \\ - a c c e p t -
+ 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, \\ - - - a c c e p
+ 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, \\ t - c h a r s e
+ 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, \\ t - - - - a c c
+ 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, \\ e p t - e n c o
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, \\ d i n g - - - -
+ 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, \\ a c c e p t - l
+ 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, \\ a n g u a g e -
+ 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, \\ - - - a c c e p
+ 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, \\ t - r a n g e s
+ 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, \\ - - - - a g e -
+ 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, \\ - - - a l l o w
+ 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, \\ - - - - a u t h
+ 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, \\ o r i z a t i o
+ 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, \\ n - - - - c a c
+ 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, \\ h e - c o n t r
+ 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, \\ o l - - - - c o
+ 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, \\ n n e c t i o n
+ 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, \\ - - - - c o n t
+ 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, \\ e n t - b a s e
+ 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, \\ - - - - c o n t
+ 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, \\ e n t - e n c o
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, \\ d i n g - - - -
+ 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, \\ c o n t e n t -
+ 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, \\ l a n g u a g e
+ 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, \\ - - - - c o n t
+ 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, \\ e n t - l e n g
+ 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, \\ t h - - - - c o
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 28]
+
+Internet-Draft SPDY Feb 2012
+
+
+ 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, \\ n t e n t - l o
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, \\ c a t i o n - -
+ 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, \\ - - c o n t e n
+ 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, \\ t - m d 5 - - -
+ 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, \\ - c o n t e n t
+ 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, \\ - r a n g e - -
+ 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, \\ - - c o n t e n
+ 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, \\ t - t y p e - -
+ 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, \\ - - d a t e - -
+ 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, \\ - - e t a g - -
+ 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, \\ - - e x p e c t
+ 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, \\ - - - - e x p i
+ 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, \\ r e s - - - - f
+ 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, \\ r o m - - - - h
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, \\ o s t - - - - i
+ 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, \\ f - m a t c h -
+ 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, \\ - - - i f - m o
+ 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, \\ d i f i e d - s
+ 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, \\ i n c e - - - -
+ 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, \\ i f - n o n e -
+ 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, \\ m a t c h - - -
+ 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, \\ - i f - r a n g
+ 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, \\ e - - - - i f -
+ 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, \\ u n m o d i f i
+ 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, \\ e d - s i n c e
+ 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, \\ - - - - l a s t
+ 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, \\ - m o d i f i e
+ 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, \\ d - - - - l o c
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, \\ a t i o n - - -
+ 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, \\ - m a x - f o r
+ 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, \\ w a r d s - - -
+ 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, \\ - p r a g m a -
+ 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, \\ - - - p r o x y
+ 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, \\ - a u t h e n t
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, \\ i c a t e - - -
+ 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, \\ - p r o x y - a
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, \\ u t h o r i z a
+ 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, \\ t i o n - - - -
+ 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, \\ r a n g e - - -
+ 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, \\ - r e f e r e r
+ 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, \\ - - - - r e t r
+ 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, \\ y - a f t e r -
+ 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, \\ - - - s e r v e
+ 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, \\ r - - - - t e -
+ 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, \\ - - - t r a i l
+ 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, \\ e r - - - - t r
+ 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, \\ a n s f e r - e
+ 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, \\ n c o d i n g -
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 29]
+
+Internet-Draft SPDY Feb 2012
+
+
+ 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, \\ - - - u p g r a
+ 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, \\ d e - - - - u s
+ 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, \\ e r - a g e n t
+ 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, \\ - - - - v a r y
+ 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, \\ - - - - v i a -
+ 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, \\ - - - w a r n i
+ 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, \\ n g - - - - w w
+ 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, \\ w - a u t h e n
+ 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, \\ t i c a t e - -
+ 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, \\ - - m e t h o d
+ 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, \\ - - - - g e t -
+ 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, \\ - - - s t a t u
+ 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, \\ s - - - - 2 0 0
+ 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, \\ - O K - - - - v
+ 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, \\ e r s i o n - -
+ 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, \\ - - H T T P - 1
+ 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, \\ - 1 - - - - u r
+ 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, \\ l - - - - p u b
+ 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, \\ l i c - - - - s
+ 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, \\ e t - c o o k i
+ 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, \\ e - - - - k e e
+ 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, \\ p - a l i v e -
+ 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, \\ - - - o r i g i
+ 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, \\ n 1 0 0 1 0 1 2
+ 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, \\ 0 1 2 0 2 2 0 5
+ 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, \\ 2 0 6 3 0 0 3 0
+ 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, \\ 2 3 0 3 3 0 4 3
+ 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, \\ 0 5 3 0 6 3 0 7
+ 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, \\ 4 0 2 4 0 5 4 0
+ 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, \\ 6 4 0 7 4 0 8 4
+ 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, \\ 0 9 4 1 0 4 1 1
+ 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, \\ 4 1 2 4 1 3 4 1
+ 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, \\ 4 4 1 5 4 1 6 4
+ 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, \\ 1 7 5 0 2 5 0 4
+ 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, \\ 5 0 5 2 0 3 - N
+ 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, \\ o n - A u t h o
+ 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, \\ r i t a t i v e
+ 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, \\ - I n f o r m a
+ 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, \\ t i o n 2 0 4 -
+ 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, \\ N o - C o n t e
+ 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, \\ n t 3 0 1 - M o
+ 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, \\ v e d - P e r m
+ 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, \\ a n e n t l y 4
+ 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, \\ 0 0 - B a d - R
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, \\ e q u e s t 4 0
+ 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, \\ 1 - U n a u t h
+ 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, \\ o r i z e d 4 0
+ 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, \\ 3 - F o r b i d
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 30]
+
+Internet-Draft SPDY Feb 2012
+
+
+ 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, \\ d e n 4 0 4 - N
+ 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, \\ o t - F o u n d
+ 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, \\ 5 0 0 - I n t e
+ 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, \\ r n a l - S e r
+ 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, \\ v e r - E r r o
+ 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, \\ r 5 0 1 - N o t
+ 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, \\ - I m p l e m e
+ 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, \\ n t e d 5 0 3 -
+ 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, \\ S e r v i c e -
+ 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, \\ U n a v a i l a
+ 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, \\ b l e J a n - F
+ 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, \\ e b - M a r - A
+ 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, \\ p r - M a y - J
+ 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, \\ u n - J u l - A
+ 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, \\ u g - S e p t -
+ 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, \\ O c t - N o v -
+ 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, \\ D e c - 0 0 - 0
+ 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, \\ 0 - 0 0 - M o n
+ 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, \\ - - T u e - - W
+ 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, \\ e d - - T h u -
+ 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, \\ - F r i - - S a
+ 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, \\ t - - S u n - -
+ 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, \\ G M T c h u n k
+ 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, \\ e d - t e x t -
+ 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, \\ h t m l - i m a
+ 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, \\ g e - p n g - i
+ 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, \\ m a g e - j p g
+ 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, \\ - i m a g e - g
+ 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, \\ i f - a p p l i
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, \\ c a t i o n - x
+ 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, \\ m l - a p p l i
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, \\ c a t i o n - x
+ 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, \\ h t m l - x m l
+ 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, \\ - t e x t - p l
+ 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, \\ a i n - t e x t
+ 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, \\ - j a v a s c r
+ 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, \\ i p t - p u b l
+ 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, \\ i c p r i v a t
+ 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, \\ e m a x - a g e
+ 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, \\ - g z i p - d e
+ 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, \\ f l a t e - s d
+ 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, \\ c h c h a r s e
+ 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, \\ t - u t f - 8 c
+ 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, \\ h a r s e t - i
+ 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, \\ s o - 8 8 5 9 -
+ 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, \\ 1 - u t f - - -
+ 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e \\ - e n q - 0 -
+ };
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 31]
+
+Internet-Draft SPDY Feb 2012
+
+
+ The entire contents of the name/value header block is compressed
+ using zlib. There is a single zlib stream for all name value pairs
+ in one direction on a connection. SPDY uses a SYNC_FLUSH between
+ each compressed frame.
+
+ Implementation notes: the compression engine can be tuned to favor
+ speed or size. Optimizing for size increases memory use and CPU
+ consumption. Because header blocks are generally small, implementors
+ may want to reduce the window-size of the compression engine from the
+ default 15bits (a 32KB window) to more like 11bits (a 2KB window).
+ The exact setting is chosen by the compressor, the decompressor will
+ work with any setting.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 32]
+
+Internet-Draft SPDY Feb 2012
+
+
+3. HTTP Layering over SPDY
+
+ SPDY is intended to be as compatible as possible with current web-
+ based applications. This means that, from the perspective of the
+ server business logic or application API, the features of HTTP are
+ unchanged. To achieve this, all of the application request and
+ response header semantics are preserved, although the syntax of
+ conveying those semantics has changed. Thus, the rules from the
+ HTTP/1.1 specification in RFC2616 [RFC2616] apply with the changes in
+ the sections below.
+
+3.1. Connection Management
+
+ Clients SHOULD NOT open more than one SPDY session to a given origin
+ [RFC6454] concurrently.
+
+ Note that it is possible for one SPDY session to be finishing (e.g. a
+ GOAWAY message has been sent, but not all streams have finished),
+ while another SPDY session is starting.
+
+3.1.1. Use of GOAWAY
+
+ SPDY provides a GOAWAY message which can be used when closing a
+ connection from either the client or server. Without a server GOAWAY
+ message, HTTP has a race condition where the client sends a request
+ (a new SYN_STREAM) just as the server is closing the connection, and
+ the client cannot know if the server received the stream or not. By
+ using the last-stream-id in the GOAWAY, servers can indicate to the
+ client if a request was processed or not.
+
+ Note that some servers will choose to send the GOAWAY and immediately
+ terminate the connection without waiting for active streams to
+ finish. The client will be able to determine this because SPDY
+ streams are determinstically closed. This abrupt termination will
+ force the client to heuristically decide whether to retry the pending
+ requests. Clients always need to be capable of dealing with this
+ case because they must deal with accidental connection termination
+ cases, which are the same as the server never having sent a GOAWAY.
+
+ More sophisticated servers will use GOAWAY to implement a graceful
+ teardown. They will send the GOAWAY and provide some time for the
+ active streams to finish before terminating the connection.
+
+ If a SPDY client closes the connection, it should also send a GOAWAY
+ message. This allows the server to know if any server-push streams
+ were received by the client.
+
+ If the endpoint closing the connection has not received any
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 33]
+
+Internet-Draft SPDY Feb 2012
+
+
+ SYN_STREAMs from the remote, the GOAWAY will contain a last-stream-id
+ of 0.
+
+3.2. HTTP Request/Response
+
+3.2.1. Request
+
+ The client initiates a request by sending a SYN_STREAM frame. For
+ requests which do not contain a body, the SYN_STREAM frame MUST set
+ the FLAG_FIN, indicating that the client intends to send no further
+ data on this stream. For requests which do contain a body, the
+ SYN_STREAM will not contain the FLAG_FIN, and the body will follow
+ the SYN_STREAM in a series of DATA frames. The last DATA frame will
+ set the FLAG_FIN to indicate the end of the body.
+
+ The SYN_STREAM Name/Value section will contain all of the HTTP
+ headers which are associated with an HTTP request. The header block
+ in SPDY is mostly unchanged from today's HTTP header block, with the
+ following differences:
+
+ The first line of the request is unfolded into name/value pairs
+ like other HTTP headers and MUST be present:
+
+ ":method" - the HTTP method for this request (e.g. "GET",
+ "POST", "HEAD", etc)
+
+ ":path" - the url-path for this url with "/" prefixed. (See
+ RFC1738 [RFC1738]). For example, for
+ "http://www.google.com/search?q=dogs" the path would be
+ "/search?q=dogs".
+
+ ":version" - the HTTP version of this request (e.g.
+ "HTTP/1.1")
+
+ In addition, the following two name/value pairs must also be
+ present in every request:
+
+ ":host" - the hostport (See RFC1738 [RFC1738]) portion of the
+ URL for this request (e.g. "www.google.com:1234"). This header
+ is the same as the HTTP 'Host' header.
+
+ ":scheme" - the scheme portion of the URL for this request
+ (e.g. "https"))
+
+ Header names are all lowercase.
+
+ The Connection, Host, Keep-Alive, Proxy-Connection, and Transfer-
+ Encoding headers are not valid and MUST not be sent.
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 34]
+
+Internet-Draft SPDY Feb 2012
+
+
+ User-agents MUST support gzip compression. Regardless of the
+ Accept-Encoding sent by the user-agent, the server may always send
+ content encoded with gzip or deflate encoding.
+
+ If a server receives a request where the sum of the data frame
+ payload lengths does not equal the size of the Content-Length
+ header, the server MUST return a 400 (Bad Request) error.
+
+ POST-specific changes:
+
+ Although POSTs are inherently chunked, POST requests SHOULD
+ also be accompanied by a Content-Length header. There are two
+ reasons for this: First, it assists with upload progress meters
+ for an improved user experience. But second, we know from
+ early versions of SPDY that failure to send a content length
+ header is incompatible with many existing HTTP server
+ implementations. Existing user-agents do not omit the Content-
+ Length header, and server implementations have come to depend
+ upon this.
+
+ The user-agent is free to prioritize requests as it sees fit. If the
+ user-agent cannot make progress without receiving a resource, it
+ should attempt to raise the priority of that resource. Resources
+ such as images, SHOULD generally use the lowest priority.
+
+ If a client sends a SYN_STREAM without all of the method, host, path,
+ scheme, and version headers, the server MUST reply with a HTTP 400
+ Bad Request reply.
+
+3.2.2. Response
+
+ The server responds to a client request with a SYN_REPLY frame.
+ Symmetric to the client's upload stream, server will send data after
+ the SYN_REPLY frame via a series of DATA frames, and the last data
+ frame will contain the FLAG_FIN to indicate successful end-of-stream.
+ If a response (like a 202 or 204 response) contains no body, the
+ SYN_REPLY frame may contain the FLAG_FIN flag to indicate no further
+ data will be sent on the stream.
+
+ The response status line is unfolded into name/value pairs like
+ other HTTP headers and must be present:
+
+ ":status" - The HTTP response status code (e.g. "200" or "200
+ OK")
+
+ ":version" - The HTTP response version (e.g. "HTTP/1.1")
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 35]
+
+Internet-Draft SPDY Feb 2012
+
+
+ All header names must be lowercase.
+
+ The Connection, Keep-Alive, Proxy-Connection, and Transfer-
+ Encoding headers are not valid and MUST not be sent.
+
+ Responses MAY be accompanied by a Content-Length header for
+ advisory purposes. (e.g. for UI progress meters)
+
+ If a client receives a response where the sum of the data frame
+ payload lengths does not equal the size of the Content-Length
+ header, the client MUST ignore the content length header.
+
+ If a client receives a SYN_REPLY without a status or without a
+ version header, the client must reply with a RST_STREAM frame
+ indicating a PROTOCOL ERROR.
+
+3.2.3. Authentication
+
+ When a client sends a request to an origin server that requires
+ authentication, the server can reply with a "401 Unauthorized"
+ response, and include a WWW-Authenticate challenge header that
+ defines the authentication scheme to be used. The client then
+ retries the request with an Authorization header appropriate to the
+ specified authentication scheme.
+
+ There are four options for proxy authentication, Basic, Digest, NTLM
+ and Negotiate (SPNEGO). The first two options were defined in
+ RFC2617 [RFC2617], and are stateless. The second two options were
+ developed by Microsoft and specified in RFC4559 [RFC4559], and are
+ stateful; otherwise known as multi-round authentication, or
+ connection authentication.
+
+3.2.3.1. Stateless Authentication
+
+ Stateless Authentication over SPDY is identical to how it is
+ performed over HTTP. If multiple SPDY streams are concurrently sent
+ to a single server, each will authenticate independently, similar to
+ how two HTTP connections would independently authenticate to a proxy
+ server.
+
+3.2.3.2. Stateful Authentication
+
+ Unfortunately, the stateful authentication mechanisms were
+ implemented and defined in a such a way that directly violates
+ RFC2617 - they do not include a "realm" as part of the request. This
+ is problematic in SPDY because it makes it impossible for a client to
+ disambiguate two concurrent server authentication challenges.
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 36]
+
+Internet-Draft SPDY Feb 2012
+
+
+ To deal with this case, SPDY servers using Stateful Authentication
+ MUST implement one of two changes:
+
+ Servers can add a "realm=<desired realm>" header so that the two
+ authentication requests can be disambiguated and run concurrently.
+ Unfortunately, given how these mechanisms work, this is probably
+ not practical.
+
+ Upon sending the first stateful challenge response, the server
+ MUST buffer and defer all further frames which are not part of
+ completing the challenge until the challenge has completed.
+ Completing the authentication challenge may take multiple round
+ trips. Once the client receives a "401 Authenticate" response for
+ a stateful authentication type, it MUST stop sending new requests
+ to the server until the authentication has completed by receiving
+ a non-401 response on at least one stream.
+
+3.3. Server Push Transactions
+
+ SPDY enables a server to send multiple replies to a client for a
+ single request. The rationale for this feature is that sometimes a
+ server knows that it will need to send multiple resources in response
+ to a single request. Without server push features, the client must
+ first download the primary resource, then discover the secondary
+ resource(s), and request them. Pushing of resources avoids the
+ round-trip delay, but also creates a potential race where a server
+ can be pushing content which a user-agent is in the process of
+ requesting. The following mechanics attempt to prevent the race
+ condition while enabling the performance benefit.
+
+ Browsers receiving a pushed response MUST validate that the server is
+ authorized to push the URL using the browser same-origin [RFC6454]
+ policy. For example, a SPDY connection to www.foo.com is generally
+ not permitted to push a response for www.evil.com.
+
+ If the browser accepts a pushed response (e.g. it does not send a
+ RST_STREAM), the browser MUST attempt to cache the pushed response in
+ same way that it would cache any other response. This means
+ validating the response headers and inserting into the disk cache.
+
+ Because pushed responses have no request, they have no request
+ headers associated with them. At the framing layer, SPDY pushed
+ streams contain an "associated-stream-id" which indicates the
+ requested stream for which the pushed stream is related. The pushed
+ stream inherits all of the headers from the associated-stream-id with
+ the exception of ":host", ":scheme", and ":path", which are provided
+ as part of the pushed response stream headers. The browser MUST
+ store these inherited and implied request headers with the cached
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 37]
+
+Internet-Draft SPDY Feb 2012
+
+
+ resource.
+
+ Implementation note: With server push, it is theoretically possible
+ for servers to push unreasonable amounts of content or resources to
+ the user-agent. Browsers MUST implement throttles to protect against
+ unreasonable push attacks.
+
+3.3.1. Server implementation
+
+ When the server intends to push a resource to the user-agent, it
+ opens a new stream by sending a unidirectional SYN_STREAM. The
+ SYN_STREAM MUST include an Associated-To-Stream-ID, and MUST set the
+ FLAG_UNIDIRECTIONAL flag. The SYN_STREAM MUST include headers for
+ ":scheme", ":host", ":path", which represent the URL for the resource
+ being pushed. Subsequent headers may follow in HEADERS frames. The
+ purpose of the association is so that the user-agent can
+ differentiate which request induced the pushed stream; without it, if
+ the user-agent had two tabs open to the same page, each pushing
+ unique content under a fixed URL, the user-agent would not be able to
+ differentiate the requests.
+
+ The Associated-To-Stream-ID must be the ID of an existing, open
+ stream. The reason for this restriction is to have a clear endpoint
+ for pushed content. If the user-agent requested a resource on stream
+ 11, the server replies on stream 11. It can push any number of
+ additional streams to the client before sending a FLAG_FIN on stream
+ 11. However, once the originating stream is closed no further push
+ streams may be associated with it. The pushed streams do not need to
+ be closed (FIN set) before the originating stream is closed, they
+ only need to be created before the originating stream closes.
+
+ It is illegal for a server to push a resource with the Associated-To-
+ Stream-ID of 0.
+
+ To minimize race conditions with the client, the SYN_STREAM for the
+ pushed resources MUST be sent prior to sending any content which
+ could allow the client to discover the pushed resource and request
+ it.
+
+ The server MUST only push resources which would have been returned
+ from a GET request.
+
+ Note: If the server does not have all of the Name/Value Response
+ headers available at the time it issues the HEADERS frame for the
+ pushed resource, it may later use an additional HEADERS frame to
+ augment the name/value pairs to be associated with the pushed stream.
+ The subsequent HEADERS frame(s) must not contain a header for
+ ':host', ':scheme', or ':path' (e.g. the server can't change the
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 38]
+
+Internet-Draft SPDY Feb 2012
+
+
+ identity of the resource to be pushed). The HEADERS frame must not
+ contain duplicate headers with a previously sent HEADERS frame. The
+ server must send a HEADERS frame including the scheme/host/port
+ headers before sending any data frames on the stream.
+
+3.3.2. Client implementation
+
+ When fetching a resource the client has 3 possibilities:
+
+ the resource is not being pushed
+
+ the resource is being pushed, but the data has not yet arrived
+
+ the resource is being pushed, and the data has started to arrive
+
+ When a SYN_STREAM and HEADERS frame which contains an Associated-To-
+ Stream-ID is received, the client must not issue GET requests for the
+ resource in the pushed stream, and instead wait for the pushed stream
+ to arrive.
+
+ If a client receives a server push stream with stream-id 0, it MUST
+ issue a session error (Section 2.4.1) with the status code
+ PROTOCOL_ERROR.
+
+ When a client receives a SYN_STREAM from the server without a the
+ ':host', ':scheme', and ':path' headers in the Name/Value section, it
+ MUST reply with a RST_STREAM with error code HTTP_PROTOCOL_ERROR.
+
+ To cancel individual server push streams, the client can issue a
+ stream error (Section 2.4.2) with error code CANCEL. Upon receipt,
+ the server MUST stop sending on this stream immediately (this is an
+ Abrupt termination).
+
+ To cancel all server push streams related to a request, the client
+ may issue a stream error (Section 2.4.2) with error code CANCEL on
+ the associated-stream-id. By cancelling that stream, the server MUST
+ immediately stop sending frames for any streams with
+ in-association-to for the original stream.
+
+ If the server sends a HEADER frame containing duplicate headers with
+ a previous HEADERS frame for the same stream, the client must issue a
+ stream error (Section 2.4.2) with error code PROTOCOL ERROR.
+
+ If the server sends a HEADERS frame after sending a data frame for
+ the same stream, the client MAY ignore the HEADERS frame. Ignoring
+ the HEADERS frame after a data frame prevents handling of HTTP's
+ trailing headers
+ (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.40).
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 39]
+
+Internet-Draft SPDY Feb 2012
+
+
+4. Design Rationale and Notes
+
+ Authors' notes: The notes in this section have no bearing on the SPDY
+ protocol as specified within this document, and none of these notes
+ should be considered authoritative about how the protocol works.
+ However, these notes may prove useful in future debates about how to
+ resolve protocol ambiguities or how to evolve the protocol going
+ forward. They may be removed before the final draft.
+
+4.1. Separation of Framing Layer and Application Layer
+
+ Readers may note that this specification sometimes blends the framing
+ layer (Section 2) with requirements of a specific application - HTTP
+ (Section 3). This is reflected in the request/response nature of the
+ streams, the definition of the HEADERS and compression contexts which
+ are very similar to HTTP, and other areas as well.
+
+ This blending is intentional - the primary goal of this protocol is
+ to create a low-latency protocol for use with HTTP. Isolating the
+ two layers is convenient for description of the protocol and how it
+ relates to existing HTTP implementations. However, the ability to
+ reuse the SPDY framing layer is a non goal.
+
+4.2. Error handling - Framing Layer
+
+ Error handling at the SPDY layer splits errors into two groups: Those
+ that affect an individual SPDY stream, and those that do not.
+
+ When an error is confined to a single stream, but general framing is
+ in tact, SPDY attempts to use the RST_STREAM as a mechanism to
+ invalidate the stream but move forward without aborting the
+ connection altogether.
+
+ For errors occuring outside of a single stream context, SPDY assumes
+ the entire session is hosed. In this case, the endpoint detecting
+ the error should initiate a connection close.
+
+4.3. One Connection Per Domain
+
+ SPDY attempts to use fewer connections than other protocols have
+ traditionally used. The rationale for this behavior is because it is
+ very difficult to provide a consistent level of service (e.g. TCP
+ slow-start), prioritization, or optimal compression when the client
+ is connecting to the server through multiple channels.
+
+ Through lab measurements, we have seen consistent latency benefits by
+ using fewer connections from the client. The overall number of
+ packets sent by SPDY can be as much as 40% less than HTTP. Handling
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 40]
+
+Internet-Draft SPDY Feb 2012
+
+
+ large numbers of concurrent connections on the server also does
+ become a scalability problem, and SPDY reduces this load.
+
+ The use of multiple connections is not without benefit, however.
+ Because SPDY multiplexes multiple, independent streams onto a single
+ stream, it creates a potential for head-of-line blocking problems at
+ the transport level. In tests so far, the negative effects of head-
+ of-line blocking (especially in the presence of packet loss) is
+ outweighed by the benefits of compression and prioritization.
+
+4.4. Fixed vs Variable Length Fields
+
+ SPDY favors use of fixed length 32bit fields in cases where smaller,
+ variable length encodings could have been used. To some, this seems
+ like a tragic waste of bandwidth. SPDY choses the simple encoding
+ for speed and simplicity.
+
+ The goal of SPDY is to reduce latency on the network. The overhead
+ of SPDY frames is generally quite low. Each data frame is only an 8
+ byte overhead for a 1452 byte payload (~0.6%). At the time of this
+ writing, bandwidth is already plentiful, and there is a strong trend
+ indicating that bandwidth will continue to increase. With an average
+ worldwide bandwidth of 1Mbps, and assuming that a variable length
+ encoding could reduce the overhead by 50%, the latency saved by using
+ a variable length encoding would be less than 100 nanoseconds. More
+ interesting are the effects when the larger encodings force a packet
+ boundary, in which case a round-trip could be induced. However, by
+ addressing other aspects of SPDY and TCP interactions, we believe
+ this is completely mitigated.
+
+4.5. Compression Context(s)
+
+ When isolating the compression contexts used for communicating with
+ multiple origins, we had a few choices to make. We could have
+ maintained a map (or list) of compression contexts usable for each
+ origin. The basic case is easy - each HEADERS frame would need to
+ identify the context to use for that frame. However, compression
+ contexts are not cheap, so the lifecycle of each context would need
+ to be bounded. For proxy servers, where we could churn through many
+ contexts, this would be a concern. We considered using a static set
+ of contexts, say 16 of them, which would bound the memory use. We
+ also considered dynamic contexts, which could be created on the fly,
+ and would need to be subsequently destroyed. All of these are
+ complicated, and ultimately we decided that such a mechanism creates
+ too many problems to solve.
+
+ Alternatively, we've chosen the simple approach, which is to simply
+ provide a flag for resetting the compression context. For the common
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 41]
+
+Internet-Draft SPDY Feb 2012
+
+
+ case (no proxy), this fine because most requests are to the same
+ origin and we never need to reset the context. For cases where we
+ are using two different origins over a single SPDY session, we simply
+ reset the compression state between each transition.
+
+4.6. Unidirectional streams
+
+ Many readers notice that unidirectional streams are both a bit
+ confusing in concept and also somewhat redundant. If the recipient
+ of a stream doesn't wish to send data on a stream, it could simply
+ send a SYN_REPLY with the FLAG_FIN bit set. The FLAG_UNIDIRECTIONAL
+ is, therefore, not necessary.
+
+ It is true that we don't need the UNIDIRECTIONAL markings. It is
+ added because it avoids the recipient of pushed streams from needing
+ to send a set of empty frames (e.g. the SYN_STREAM w/ FLAG_FIN) which
+ otherwise serve no purpose.
+
+4.7. Data Compression
+
+ Generic compression of data portion of the streams (as opposed to
+ compression of the headers) without knowing the content of the stream
+ is redundant. There is no value in compressing a stream which is
+ already compressed. Because of this, SPDY does allow data
+ compression to be optional. We included it because study of existing
+ websites shows that many sites are not using compression as they
+ should, and users suffer because of it. We wanted a mechanism where,
+ at the SPDY layer, site administrators could simply force compression
+ - it is better to compress twice than to not compress.
+
+ Overall, however, with this feature being optional and sometimes
+ redundant, it is unclear if it is useful at all. We will likely
+ remove it from the specification.
+
+4.8. Server Push
+
+ A subtle but important point is that server push streams must be
+ declared before the associated stream is closed. The reason for this
+ is so that proxies have a lifetime for which they can discard
+ information about previous streams. If a pushed stream could
+ associate itself with an already-closed stream, then endpoints would
+ not have a specific lifecycle for when they could disavow knowledge
+ of the streams which went before.
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 42]
+
+Internet-Draft SPDY Feb 2012
+
+
+5. Security Considerations
+
+5.1. Use of Same-origin constraints
+
+ This specification uses the same-origin policy [RFC6454] in all cases
+ where verification of content is required.
+
+5.2. HTTP Headers and SPDY Headers
+
+ At the application level, HTTP uses name/value pairs in its headers.
+ Because SPDY merges the existing HTTP headers with SPDY headers,
+ there is a possibility that some HTTP applications already use a
+ particular header name. To avoid any conflicts, all headers
+ introduced for layering HTTP over SPDY are prefixed with ":". ":" is
+ not a valid sequence in HTTP header naming, preventing any possible
+ conflict.
+
+5.3. Cross-Protocol Attacks
+
+ By utilizing TLS, we believe that SPDY introduces no new cross-
+ protocol attacks. TLS encrypts the contents of all transmission
+ (except the handshake itself), making it difficult for attackers to
+ control the data which could be used in a cross-protocol attack.
+
+5.4. Server Push Implicit Headers
+
+ Pushed resources do not have an associated request. In order for
+ existing HTTP cache control validations (such as the Vary header) to
+ work, however, all cached resources must have a set of request
+ headers. For this reason, browsers MUST be careful to inherit
+ request headers from the associated stream for the push. This
+ includes the 'Cookie' header.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 43]
+
+Internet-Draft SPDY Feb 2012
+
+
+6. Privacy Considerations
+
+6.1. Long Lived Connections
+
+ SPDY aims to keep connections open longer between clients and servers
+ in order to reduce the latency when a user makes a request. The
+ maintenance of these connections over time could be used to expose
+ private information. For example, a user using a browser hours after
+ the previous user stopped using that browser may be able to learn
+ about what the previous user was doing. This is a problem with HTTP
+ in its current form as well, however the short lived connections make
+ it less of a risk.
+
+6.2. SETTINGS frame
+
+ The SPDY SETTINGS frame allows servers to store out-of-band
+ transmitted information about the communication between client and
+ server on the client. Although this is intended only to be used to
+ reduce latency, renegade servers could use it as a mechanism to store
+ identifying information about the client in future requests.
+
+ Clients implementing privacy modes, such as Google Chrome's
+ "incognito mode", may wish to disable client-persisted SETTINGS
+ storage.
+
+ Clients MUST clear persisted SETTINGS information when clearing the
+ cookies.
+
+ TODO: Put range maximums on each type of setting to limit
+ inappropriate uses.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 44]
+
+Internet-Draft SPDY Feb 2012
+
+
+7. Incompatibilities with SPDY draft #2
+
+ Here is a list of the major changes between this draft and draft #2.
+
+ Addition of flow control
+
+ Increased 16 bit length fields in SYN_STREAM and SYN_REPLY to 32
+ bits.
+
+ Changed definition of compression for DATA frames
+
+ Updated compression dictionary
+
+ Fixed off-by-one on the compression dictionary for headers
+
+ Increased priority field from 2bits to 3bits.
+
+ Removed NOOP frame
+
+ Split the request "url" into "scheme", "host", and "path"
+
+ Added the requirement that POSTs contain content-length.
+
+ Removed wasted 16bits of unused space from the end of the
+ SYN_REPLY and HEADERS frames.
+
+ Fixed bug: Priorities were described backward (0 was lowest
+ instead of highest).
+
+ Fixed bug: Name/Value header counts were duplicated in both the
+ Name Value header block and also the containing frame.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 45]
+
+Internet-Draft SPDY Feb 2012
+
+
+8. Requirements Notation
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in RFC 2119 [RFC2119].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 46]
+
+Internet-Draft SPDY Feb 2012
+
+
+9. Acknowledgements
+
+ Many individuals have contributed to the design and evolution of
+ SPDY: Adam Langley, Wan-Teh Chang, Jim Morrison, Mark Nottingham,
+ Alyssa Wilk, Costin Manolache, William Chan, Vitaliy Lvin, Joe Chan,
+ Adam Barth, Ryan Hamilton, Gavin Peters, Kent Alstad, Kevin Lindsay,
+ Paul Amer, Fan Yang, Jonathan Leighton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 47]
+
+Internet-Draft SPDY Feb 2012
+
+
+10. Normative References
+
+ [RFC0793] Postel, J., "Transmission Control Protocol", STD 7,
+ RFC 793, September 1981.
+
+ [RFC1738] Berners-Lee, T., Masinter, L., and M. McCahill, "Uniform
+ Resource Locators (URL)", RFC 1738, December 1994.
+
+ [RFC1950] Deutsch, L. and J-L. Gailly, "ZLIB Compressed Data Format
+ Specification version 3.3", RFC 1950, May 1996.
+
+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [RFC2285] Mandeville, R., "Benchmarking Terminology for LAN
+ Switching Devices", RFC 2285, February 1998.
+
+ [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
+ Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
+ Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
+
+ [RFC2617] Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S.,
+ Leach, P., Luotonen, A., and L. Stewart, "HTTP
+ Authentication: Basic and Digest Access Authentication",
+ RFC 2617, June 1999.
+
+ [RFC4559] Jaganathan, K., Zhu, L., and J. Brezak, "SPNEGO-based
+ Kerberos and NTLM HTTP Authentication in Microsoft
+ Windows", RFC 4559, June 2006.
+
+ [RFC4366] Blake-Wilson, S., Nystrom, M., Hopwood, D., Mikkelsen, J.,
+ and T. Wright, "Transport Layer Security (TLS)
+ Extensions", RFC 4366, April 2006.
+
+ [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security
+ (TLS) Protocol Version 1.2", RFC 5246, August 2008.
+
+ [RFC6454] Barth, A., "The Web Origin Concept", RFC 6454,
+ December 2011.
+
+ [TLSNPN] Langley, A., "TLS Next Protocol Negotiation",
+ <http://tools.ietf.org/html/
+ draft-agl-tls-nextprotoneg-01>.
+
+ [ASCII] "US-ASCII. Coded Character Set - 7-Bit American Standard
+ Code for Information Interchange. Standard ANSI X3.4-1986,
+ ANSI, 1986.".
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 48]
+
+Internet-Draft SPDY Feb 2012
+
+
+ [UDELCOMPRESSION]
+ Yang, F., Amer, P., and J. Leighton, "A Methodology to
+ Derive SPDY's Initial Dictionary for Zlib Compression",
+ <http://www.eecis.udel.edu/~amer/PEL/poc/pdf/
+ SPDY-Fan.pdf>.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 49]
+
+Internet-Draft SPDY Feb 2012
+
+
+Appendix A. Changes
+
+ To be removed by RFC Editor before publication
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 50]
+
+Internet-Draft SPDY Feb 2012
+
+
+Authors' Addresses
+
+ Mike Belshe
+ Twist
+
+ Email: mbelshe@chromium.org
+
+
+ Roberto Peon
+ Google, Inc
+
+ Email: fenix@google.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Belshe & Peon Expires August 4, 2012 [Page 51]
+
diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am
new file mode 100644
index 0000000..edc80dc
--- /dev/null
+++ b/src/examples/Makefile.am
@@ -0,0 +1,198 @@
+# This Makefile.am is in the public domain
+SUBDIRS = .
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -DDATA_DIR=\"$(top_srcdir)/src/datadir/\"
+
+AM_CFLAGS = @LIBGCRYPT_CFLAGS@
+
+CPU_COUNT_DEF = -DCPU_COUNT=$(CPU_COUNT)
+
+if USE_COVERAGE
+ AM_CFLAGS += --coverage
+endif
+
+if ENABLE_SPDY
+spdyex = \
+ spdy_event_loop \
+ spdy_fileserver \
+ spdy_response_with_callback
+
+if HAVE_SPDYLAY
+spdyex += mhd2spdy
+endif
+endif
+
+
+# example programs
+noinst_PROGRAMS = \
+ benchmark \
+ benchmark_https \
+ chunked_example \
+ minimal_example \
+ dual_stack_example \
+ minimal_example_comet \
+ querystring_example \
+ fileserver_example \
+ fileserver_example_dirs \
+ fileserver_example_external_select \
+ refuse_post_example \
+ $(spdyex)
+
+
+if ENABLE_HTTPS
+noinst_PROGRAMS += https_fileserver_example
+endif
+if HAVE_POSTPROCESSOR
+noinst_PROGRAMS += \
+ post_example
+if HAVE_MAGIC
+noinst_PROGRAMS += \
+ demo \
+ demo_https
+endif
+endif
+
+if ENABLE_DAUTH
+noinst_PROGRAMS += \
+ digest_auth_example
+endif
+
+if ENABLE_BAUTH
+noinst_PROGRAMS += \
+ authorization_example
+endif
+
+if HAVE_W32
+AM_CFLAGS += -DWINDOWS
+endif
+
+minimal_example_SOURCES = \
+ minimal_example.c
+minimal_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+chunked_example_SOURCES = \
+ chunked_example.c
+chunked_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+demo_SOURCES = \
+ demo.c
+demo_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+demo_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+demo_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) -lmagic
+
+demo_https_SOURCES = \
+ demo_https.c
+demo_https_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+demo_https_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+demo_https_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) -lmagic
+
+mhd2spdy_SOURCES = \
+ mhd2spdy.c \
+ mhd2spdy_spdy.c mhd2spdy_spdy.h \
+ mhd2spdy_http.c mhd2spdy_http.h \
+ mhd2spdy_structures.c mhd2spdy_structures.h
+mhd2spdy_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ -lssl -lcrypto -lspdylay
+
+benchmark_SOURCES = \
+ benchmark.c
+benchmark_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+benchmark_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+benchmark_https_SOURCES = \
+ benchmark_https.c
+benchmark_https_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+benchmark_https_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+dual_stack_example_SOURCES = \
+ dual_stack_example.c
+dual_stack_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+post_example_SOURCES = \
+ post_example.c
+post_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+minimal_example_comet_SOURCES = \
+ minimal_example_comet.c
+minimal_example_comet_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+authorization_example_SOURCES = \
+ authorization_example.c
+authorization_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+digest_auth_example_SOURCES = \
+ digest_auth_example.c
+digest_auth_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+refuse_post_example_SOURCES = \
+ refuse_post_example.c
+refuse_post_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+querystring_example_SOURCES = \
+ querystring_example.c
+querystring_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+fileserver_example_SOURCES = \
+ fileserver_example.c
+fileserver_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+fileserver_example_dirs_SOURCES = \
+ fileserver_example_dirs.c
+fileserver_example_dirs_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+fileserver_example_external_select_SOURCES = \
+ fileserver_example_external_select.c
+fileserver_example_external_select_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+https_fileserver_example_SOURCES = \
+https_fileserver_example.c
+https_fileserver_example_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+https_fileserver_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+
+spdy_event_loop_SOURCES = \
+ spdy_event_loop.c
+spdy_event_loop_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz
+
+spdy_fileserver_SOURCES = \
+ spdy_fileserver.c
+spdy_fileserver_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz
+
+spdy_response_with_callback_SOURCES = \
+ spdy_response_with_callback.c
+spdy_response_with_callback_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz
diff --git a/src/examples/Makefile.in b/src/examples/Makefile.in
new file mode 100644
index 0000000..5643f96
--- /dev/null
+++ b/src/examples/Makefile.in
@@ -0,0 +1,1246 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@USE_COVERAGE_TRUE@am__append_1 = --coverage
+@ENABLE_SPDY_TRUE@@HAVE_SPDYLAY_TRUE@am__append_2 = mhd2spdy
+noinst_PROGRAMS = benchmark$(EXEEXT) benchmark_https$(EXEEXT) \
+ chunked_example$(EXEEXT) minimal_example$(EXEEXT) \
+ dual_stack_example$(EXEEXT) minimal_example_comet$(EXEEXT) \
+ querystring_example$(EXEEXT) fileserver_example$(EXEEXT) \
+ fileserver_example_dirs$(EXEEXT) \
+ fileserver_example_external_select$(EXEEXT) \
+ refuse_post_example$(EXEEXT) $(am__EXEEXT_2) $(am__EXEEXT_3) \
+ $(am__EXEEXT_4) $(am__EXEEXT_5) $(am__EXEEXT_6) \
+ $(am__EXEEXT_7)
+@ENABLE_HTTPS_TRUE@am__append_3 = https_fileserver_example
+@HAVE_POSTPROCESSOR_TRUE@am__append_4 = \
+@HAVE_POSTPROCESSOR_TRUE@ post_example
+
+@HAVE_MAGIC_TRUE@@HAVE_POSTPROCESSOR_TRUE@am__append_5 = \
+@HAVE_MAGIC_TRUE@@HAVE_POSTPROCESSOR_TRUE@ demo \
+@HAVE_MAGIC_TRUE@@HAVE_POSTPROCESSOR_TRUE@ demo_https
+
+@ENABLE_DAUTH_TRUE@am__append_6 = \
+@ENABLE_DAUTH_TRUE@ digest_auth_example
+
+@ENABLE_BAUTH_TRUE@am__append_7 = \
+@ENABLE_BAUTH_TRUE@ authorization_example
+
+@HAVE_W32_TRUE@am__append_8 = -DWINDOWS
+subdir = src/examples
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@ENABLE_SPDY_TRUE@@HAVE_SPDYLAY_TRUE@am__EXEEXT_1 = mhd2spdy$(EXEEXT)
+@ENABLE_SPDY_TRUE@am__EXEEXT_2 = spdy_event_loop$(EXEEXT) \
+@ENABLE_SPDY_TRUE@ spdy_fileserver$(EXEEXT) \
+@ENABLE_SPDY_TRUE@ spdy_response_with_callback$(EXEEXT) \
+@ENABLE_SPDY_TRUE@ $(am__EXEEXT_1)
+@ENABLE_HTTPS_TRUE@am__EXEEXT_3 = https_fileserver_example$(EXEEXT)
+@HAVE_POSTPROCESSOR_TRUE@am__EXEEXT_4 = post_example$(EXEEXT)
+@HAVE_MAGIC_TRUE@@HAVE_POSTPROCESSOR_TRUE@am__EXEEXT_5 = \
+@HAVE_MAGIC_TRUE@@HAVE_POSTPROCESSOR_TRUE@ demo$(EXEEXT) \
+@HAVE_MAGIC_TRUE@@HAVE_POSTPROCESSOR_TRUE@ demo_https$(EXEEXT)
+@ENABLE_DAUTH_TRUE@am__EXEEXT_6 = digest_auth_example$(EXEEXT)
+@ENABLE_BAUTH_TRUE@am__EXEEXT_7 = authorization_example$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am_authorization_example_OBJECTS = authorization_example.$(OBJEXT)
+authorization_example_OBJECTS = $(am_authorization_example_OBJECTS)
+authorization_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_benchmark_OBJECTS = benchmark-benchmark.$(OBJEXT)
+benchmark_OBJECTS = $(am_benchmark_OBJECTS)
+benchmark_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_benchmark_https_OBJECTS = \
+ benchmark_https-benchmark_https.$(OBJEXT)
+benchmark_https_OBJECTS = $(am_benchmark_https_OBJECTS)
+benchmark_https_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_chunked_example_OBJECTS = chunked_example.$(OBJEXT)
+chunked_example_OBJECTS = $(am_chunked_example_OBJECTS)
+chunked_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_demo_OBJECTS = demo-demo.$(OBJEXT)
+demo_OBJECTS = $(am_demo_OBJECTS)
+am__DEPENDENCIES_1 =
+demo_DEPENDENCIES = $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1)
+demo_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(demo_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+am_demo_https_OBJECTS = demo_https-demo_https.$(OBJEXT)
+demo_https_OBJECTS = $(am_demo_https_OBJECTS)
+demo_https_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1)
+demo_https_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(demo_https_CFLAGS) \
+ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+am_digest_auth_example_OBJECTS = digest_auth_example.$(OBJEXT)
+digest_auth_example_OBJECTS = $(am_digest_auth_example_OBJECTS)
+digest_auth_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_dual_stack_example_OBJECTS = dual_stack_example.$(OBJEXT)
+dual_stack_example_OBJECTS = $(am_dual_stack_example_OBJECTS)
+dual_stack_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_fileserver_example_OBJECTS = fileserver_example.$(OBJEXT)
+fileserver_example_OBJECTS = $(am_fileserver_example_OBJECTS)
+fileserver_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_fileserver_example_dirs_OBJECTS = \
+ fileserver_example_dirs.$(OBJEXT)
+fileserver_example_dirs_OBJECTS = \
+ $(am_fileserver_example_dirs_OBJECTS)
+fileserver_example_dirs_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_fileserver_example_external_select_OBJECTS = \
+ fileserver_example_external_select.$(OBJEXT)
+fileserver_example_external_select_OBJECTS = \
+ $(am_fileserver_example_external_select_OBJECTS)
+fileserver_example_external_select_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_https_fileserver_example_OBJECTS = \
+ https_fileserver_example-https_fileserver_example.$(OBJEXT)
+https_fileserver_example_OBJECTS = \
+ $(am_https_fileserver_example_OBJECTS)
+https_fileserver_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_mhd2spdy_OBJECTS = mhd2spdy.$(OBJEXT) mhd2spdy_spdy.$(OBJEXT) \
+ mhd2spdy_http.$(OBJEXT) mhd2spdy_structures.$(OBJEXT)
+mhd2spdy_OBJECTS = $(am_mhd2spdy_OBJECTS)
+mhd2spdy_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_minimal_example_OBJECTS = minimal_example.$(OBJEXT)
+minimal_example_OBJECTS = $(am_minimal_example_OBJECTS)
+minimal_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_minimal_example_comet_OBJECTS = minimal_example_comet.$(OBJEXT)
+minimal_example_comet_OBJECTS = $(am_minimal_example_comet_OBJECTS)
+minimal_example_comet_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_post_example_OBJECTS = post_example.$(OBJEXT)
+post_example_OBJECTS = $(am_post_example_OBJECTS)
+post_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_querystring_example_OBJECTS = querystring_example.$(OBJEXT)
+querystring_example_OBJECTS = $(am_querystring_example_OBJECTS)
+querystring_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_refuse_post_example_OBJECTS = refuse_post_example.$(OBJEXT)
+refuse_post_example_OBJECTS = $(am_refuse_post_example_OBJECTS)
+refuse_post_example_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_spdy_event_loop_OBJECTS = spdy_event_loop.$(OBJEXT)
+spdy_event_loop_OBJECTS = $(am_spdy_event_loop_OBJECTS)
+spdy_event_loop_DEPENDENCIES = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la
+am_spdy_fileserver_OBJECTS = spdy_fileserver.$(OBJEXT)
+spdy_fileserver_OBJECTS = $(am_spdy_fileserver_OBJECTS)
+spdy_fileserver_DEPENDENCIES = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la
+am_spdy_response_with_callback_OBJECTS = \
+ spdy_response_with_callback.$(OBJEXT)
+spdy_response_with_callback_OBJECTS = \
+ $(am_spdy_response_with_callback_OBJECTS)
+spdy_response_with_callback_DEPENDENCIES = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(authorization_example_SOURCES) $(benchmark_SOURCES) \
+ $(benchmark_https_SOURCES) $(chunked_example_SOURCES) \
+ $(demo_SOURCES) $(demo_https_SOURCES) \
+ $(digest_auth_example_SOURCES) $(dual_stack_example_SOURCES) \
+ $(fileserver_example_SOURCES) \
+ $(fileserver_example_dirs_SOURCES) \
+ $(fileserver_example_external_select_SOURCES) \
+ $(https_fileserver_example_SOURCES) $(mhd2spdy_SOURCES) \
+ $(minimal_example_SOURCES) $(minimal_example_comet_SOURCES) \
+ $(post_example_SOURCES) $(querystring_example_SOURCES) \
+ $(refuse_post_example_SOURCES) $(spdy_event_loop_SOURCES) \
+ $(spdy_fileserver_SOURCES) \
+ $(spdy_response_with_callback_SOURCES)
+DIST_SOURCES = $(authorization_example_SOURCES) $(benchmark_SOURCES) \
+ $(benchmark_https_SOURCES) $(chunked_example_SOURCES) \
+ $(demo_SOURCES) $(demo_https_SOURCES) \
+ $(digest_auth_example_SOURCES) $(dual_stack_example_SOURCES) \
+ $(fileserver_example_SOURCES) \
+ $(fileserver_example_dirs_SOURCES) \
+ $(fileserver_example_external_select_SOURCES) \
+ $(https_fileserver_example_SOURCES) $(mhd2spdy_SOURCES) \
+ $(minimal_example_SOURCES) $(minimal_example_comet_SOURCES) \
+ $(post_example_SOURCES) $(querystring_example_SOURCES) \
+ $(refuse_post_example_SOURCES) $(spdy_event_loop_SOURCES) \
+ $(spdy_fileserver_SOURCES) \
+ $(spdy_response_with_callback_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+SUBDIRS = .
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -DDATA_DIR=\"$(top_srcdir)/src/datadir/\"
+
+AM_CFLAGS = @LIBGCRYPT_CFLAGS@ $(am__append_1) $(am__append_8)
+CPU_COUNT_DEF = -DCPU_COUNT=$(CPU_COUNT)
+@ENABLE_SPDY_TRUE@spdyex = spdy_event_loop spdy_fileserver \
+@ENABLE_SPDY_TRUE@ spdy_response_with_callback $(am__append_2)
+minimal_example_SOURCES = \
+ minimal_example.c
+
+minimal_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+chunked_example_SOURCES = \
+ chunked_example.c
+
+chunked_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+demo_SOURCES = \
+ demo.c
+
+demo_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+
+demo_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+
+demo_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) -lmagic
+
+demo_https_SOURCES = \
+ demo_https.c
+
+demo_https_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+
+demo_https_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+
+demo_https_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) -lmagic
+
+mhd2spdy_SOURCES = \
+ mhd2spdy.c \
+ mhd2spdy_spdy.c mhd2spdy_spdy.h \
+ mhd2spdy_http.c mhd2spdy_http.h \
+ mhd2spdy_structures.c mhd2spdy_structures.h
+
+mhd2spdy_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ -lssl -lcrypto -lspdylay
+
+benchmark_SOURCES = \
+ benchmark.c
+
+benchmark_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+
+benchmark_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+benchmark_https_SOURCES = \
+ benchmark_https.c
+
+benchmark_https_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+
+benchmark_https_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+dual_stack_example_SOURCES = \
+ dual_stack_example.c
+
+dual_stack_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+post_example_SOURCES = \
+ post_example.c
+
+post_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+minimal_example_comet_SOURCES = \
+ minimal_example_comet.c
+
+minimal_example_comet_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+authorization_example_SOURCES = \
+ authorization_example.c
+
+authorization_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+digest_auth_example_SOURCES = \
+ digest_auth_example.c
+
+digest_auth_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+refuse_post_example_SOURCES = \
+ refuse_post_example.c
+
+refuse_post_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+querystring_example_SOURCES = \
+ querystring_example.c
+
+querystring_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+fileserver_example_SOURCES = \
+ fileserver_example.c
+
+fileserver_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+fileserver_example_dirs_SOURCES = \
+ fileserver_example_dirs.c
+
+fileserver_example_dirs_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+fileserver_example_external_select_SOURCES = \
+ fileserver_example_external_select.c
+
+fileserver_example_external_select_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+https_fileserver_example_SOURCES = \
+https_fileserver_example.c
+
+https_fileserver_example_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+
+https_fileserver_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+spdy_event_loop_SOURCES = \
+ spdy_event_loop.c
+
+spdy_event_loop_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz
+
+spdy_fileserver_SOURCES = \
+ spdy_fileserver.c
+
+spdy_fileserver_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz
+
+spdy_response_with_callback_SOURCES = \
+ spdy_response_with_callback.c
+
+spdy_response_with_callback_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/examples/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/examples/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+authorization_example$(EXEEXT): $(authorization_example_OBJECTS) $(authorization_example_DEPENDENCIES) $(EXTRA_authorization_example_DEPENDENCIES)
+ @rm -f authorization_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(authorization_example_OBJECTS) $(authorization_example_LDADD) $(LIBS)
+
+benchmark$(EXEEXT): $(benchmark_OBJECTS) $(benchmark_DEPENDENCIES) $(EXTRA_benchmark_DEPENDENCIES)
+ @rm -f benchmark$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(benchmark_OBJECTS) $(benchmark_LDADD) $(LIBS)
+
+benchmark_https$(EXEEXT): $(benchmark_https_OBJECTS) $(benchmark_https_DEPENDENCIES) $(EXTRA_benchmark_https_DEPENDENCIES)
+ @rm -f benchmark_https$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(benchmark_https_OBJECTS) $(benchmark_https_LDADD) $(LIBS)
+
+chunked_example$(EXEEXT): $(chunked_example_OBJECTS) $(chunked_example_DEPENDENCIES) $(EXTRA_chunked_example_DEPENDENCIES)
+ @rm -f chunked_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(chunked_example_OBJECTS) $(chunked_example_LDADD) $(LIBS)
+
+demo$(EXEEXT): $(demo_OBJECTS) $(demo_DEPENDENCIES) $(EXTRA_demo_DEPENDENCIES)
+ @rm -f demo$(EXEEXT)
+ $(AM_V_CCLD)$(demo_LINK) $(demo_OBJECTS) $(demo_LDADD) $(LIBS)
+
+demo_https$(EXEEXT): $(demo_https_OBJECTS) $(demo_https_DEPENDENCIES) $(EXTRA_demo_https_DEPENDENCIES)
+ @rm -f demo_https$(EXEEXT)
+ $(AM_V_CCLD)$(demo_https_LINK) $(demo_https_OBJECTS) $(demo_https_LDADD) $(LIBS)
+
+digest_auth_example$(EXEEXT): $(digest_auth_example_OBJECTS) $(digest_auth_example_DEPENDENCIES) $(EXTRA_digest_auth_example_DEPENDENCIES)
+ @rm -f digest_auth_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(digest_auth_example_OBJECTS) $(digest_auth_example_LDADD) $(LIBS)
+
+dual_stack_example$(EXEEXT): $(dual_stack_example_OBJECTS) $(dual_stack_example_DEPENDENCIES) $(EXTRA_dual_stack_example_DEPENDENCIES)
+ @rm -f dual_stack_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dual_stack_example_OBJECTS) $(dual_stack_example_LDADD) $(LIBS)
+
+fileserver_example$(EXEEXT): $(fileserver_example_OBJECTS) $(fileserver_example_DEPENDENCIES) $(EXTRA_fileserver_example_DEPENDENCIES)
+ @rm -f fileserver_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(fileserver_example_OBJECTS) $(fileserver_example_LDADD) $(LIBS)
+
+fileserver_example_dirs$(EXEEXT): $(fileserver_example_dirs_OBJECTS) $(fileserver_example_dirs_DEPENDENCIES) $(EXTRA_fileserver_example_dirs_DEPENDENCIES)
+ @rm -f fileserver_example_dirs$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(fileserver_example_dirs_OBJECTS) $(fileserver_example_dirs_LDADD) $(LIBS)
+
+fileserver_example_external_select$(EXEEXT): $(fileserver_example_external_select_OBJECTS) $(fileserver_example_external_select_DEPENDENCIES) $(EXTRA_fileserver_example_external_select_DEPENDENCIES)
+ @rm -f fileserver_example_external_select$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(fileserver_example_external_select_OBJECTS) $(fileserver_example_external_select_LDADD) $(LIBS)
+
+https_fileserver_example$(EXEEXT): $(https_fileserver_example_OBJECTS) $(https_fileserver_example_DEPENDENCIES) $(EXTRA_https_fileserver_example_DEPENDENCIES)
+ @rm -f https_fileserver_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(https_fileserver_example_OBJECTS) $(https_fileserver_example_LDADD) $(LIBS)
+
+mhd2spdy$(EXEEXT): $(mhd2spdy_OBJECTS) $(mhd2spdy_DEPENDENCIES) $(EXTRA_mhd2spdy_DEPENDENCIES)
+ @rm -f mhd2spdy$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(mhd2spdy_OBJECTS) $(mhd2spdy_LDADD) $(LIBS)
+
+minimal_example$(EXEEXT): $(minimal_example_OBJECTS) $(minimal_example_DEPENDENCIES) $(EXTRA_minimal_example_DEPENDENCIES)
+ @rm -f minimal_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(minimal_example_OBJECTS) $(minimal_example_LDADD) $(LIBS)
+
+minimal_example_comet$(EXEEXT): $(minimal_example_comet_OBJECTS) $(minimal_example_comet_DEPENDENCIES) $(EXTRA_minimal_example_comet_DEPENDENCIES)
+ @rm -f minimal_example_comet$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(minimal_example_comet_OBJECTS) $(minimal_example_comet_LDADD) $(LIBS)
+
+post_example$(EXEEXT): $(post_example_OBJECTS) $(post_example_DEPENDENCIES) $(EXTRA_post_example_DEPENDENCIES)
+ @rm -f post_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(post_example_OBJECTS) $(post_example_LDADD) $(LIBS)
+
+querystring_example$(EXEEXT): $(querystring_example_OBJECTS) $(querystring_example_DEPENDENCIES) $(EXTRA_querystring_example_DEPENDENCIES)
+ @rm -f querystring_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(querystring_example_OBJECTS) $(querystring_example_LDADD) $(LIBS)
+
+refuse_post_example$(EXEEXT): $(refuse_post_example_OBJECTS) $(refuse_post_example_DEPENDENCIES) $(EXTRA_refuse_post_example_DEPENDENCIES)
+ @rm -f refuse_post_example$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(refuse_post_example_OBJECTS) $(refuse_post_example_LDADD) $(LIBS)
+
+spdy_event_loop$(EXEEXT): $(spdy_event_loop_OBJECTS) $(spdy_event_loop_DEPENDENCIES) $(EXTRA_spdy_event_loop_DEPENDENCIES)
+ @rm -f spdy_event_loop$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(spdy_event_loop_OBJECTS) $(spdy_event_loop_LDADD) $(LIBS)
+
+spdy_fileserver$(EXEEXT): $(spdy_fileserver_OBJECTS) $(spdy_fileserver_DEPENDENCIES) $(EXTRA_spdy_fileserver_DEPENDENCIES)
+ @rm -f spdy_fileserver$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(spdy_fileserver_OBJECTS) $(spdy_fileserver_LDADD) $(LIBS)
+
+spdy_response_with_callback$(EXEEXT): $(spdy_response_with_callback_OBJECTS) $(spdy_response_with_callback_DEPENDENCIES) $(EXTRA_spdy_response_with_callback_DEPENDENCIES)
+ @rm -f spdy_response_with_callback$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(spdy_response_with_callback_OBJECTS) $(spdy_response_with_callback_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/authorization_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/benchmark-benchmark.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/benchmark_https-benchmark_https.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chunked_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/demo-demo.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/demo_https-demo_https.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/digest_auth_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dual_stack_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileserver_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileserver_example_dirs.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileserver_example_external_select.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/https_fileserver_example-https_fileserver_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mhd2spdy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mhd2spdy_http.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mhd2spdy_spdy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mhd2spdy_structures.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/minimal_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/minimal_example_comet.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/post_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/querystring_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refuse_post_example.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spdy_event_loop.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spdy_fileserver.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spdy_response_with_callback.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+benchmark-benchmark.o: benchmark.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(benchmark_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT benchmark-benchmark.o -MD -MP -MF $(DEPDIR)/benchmark-benchmark.Tpo -c -o benchmark-benchmark.o `test -f 'benchmark.c' || echo '$(srcdir)/'`benchmark.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/benchmark-benchmark.Tpo $(DEPDIR)/benchmark-benchmark.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='benchmark.c' object='benchmark-benchmark.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(benchmark_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o benchmark-benchmark.o `test -f 'benchmark.c' || echo '$(srcdir)/'`benchmark.c
+
+benchmark-benchmark.obj: benchmark.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(benchmark_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT benchmark-benchmark.obj -MD -MP -MF $(DEPDIR)/benchmark-benchmark.Tpo -c -o benchmark-benchmark.obj `if test -f 'benchmark.c'; then $(CYGPATH_W) 'benchmark.c'; else $(CYGPATH_W) '$(srcdir)/benchmark.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/benchmark-benchmark.Tpo $(DEPDIR)/benchmark-benchmark.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='benchmark.c' object='benchmark-benchmark.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(benchmark_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o benchmark-benchmark.obj `if test -f 'benchmark.c'; then $(CYGPATH_W) 'benchmark.c'; else $(CYGPATH_W) '$(srcdir)/benchmark.c'; fi`
+
+benchmark_https-benchmark_https.o: benchmark_https.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(benchmark_https_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT benchmark_https-benchmark_https.o -MD -MP -MF $(DEPDIR)/benchmark_https-benchmark_https.Tpo -c -o benchmark_https-benchmark_https.o `test -f 'benchmark_https.c' || echo '$(srcdir)/'`benchmark_https.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/benchmark_https-benchmark_https.Tpo $(DEPDIR)/benchmark_https-benchmark_https.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='benchmark_https.c' object='benchmark_https-benchmark_https.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(benchmark_https_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o benchmark_https-benchmark_https.o `test -f 'benchmark_https.c' || echo '$(srcdir)/'`benchmark_https.c
+
+benchmark_https-benchmark_https.obj: benchmark_https.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(benchmark_https_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT benchmark_https-benchmark_https.obj -MD -MP -MF $(DEPDIR)/benchmark_https-benchmark_https.Tpo -c -o benchmark_https-benchmark_https.obj `if test -f 'benchmark_https.c'; then $(CYGPATH_W) 'benchmark_https.c'; else $(CYGPATH_W) '$(srcdir)/benchmark_https.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/benchmark_https-benchmark_https.Tpo $(DEPDIR)/benchmark_https-benchmark_https.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='benchmark_https.c' object='benchmark_https-benchmark_https.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(benchmark_https_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o benchmark_https-benchmark_https.obj `if test -f 'benchmark_https.c'; then $(CYGPATH_W) 'benchmark_https.c'; else $(CYGPATH_W) '$(srcdir)/benchmark_https.c'; fi`
+
+demo-demo.o: demo.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(demo_CPPFLAGS) $(CPPFLAGS) $(demo_CFLAGS) $(CFLAGS) -MT demo-demo.o -MD -MP -MF $(DEPDIR)/demo-demo.Tpo -c -o demo-demo.o `test -f 'demo.c' || echo '$(srcdir)/'`demo.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/demo-demo.Tpo $(DEPDIR)/demo-demo.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='demo.c' object='demo-demo.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(demo_CPPFLAGS) $(CPPFLAGS) $(demo_CFLAGS) $(CFLAGS) -c -o demo-demo.o `test -f 'demo.c' || echo '$(srcdir)/'`demo.c
+
+demo-demo.obj: demo.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(demo_CPPFLAGS) $(CPPFLAGS) $(demo_CFLAGS) $(CFLAGS) -MT demo-demo.obj -MD -MP -MF $(DEPDIR)/demo-demo.Tpo -c -o demo-demo.obj `if test -f 'demo.c'; then $(CYGPATH_W) 'demo.c'; else $(CYGPATH_W) '$(srcdir)/demo.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/demo-demo.Tpo $(DEPDIR)/demo-demo.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='demo.c' object='demo-demo.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(demo_CPPFLAGS) $(CPPFLAGS) $(demo_CFLAGS) $(CFLAGS) -c -o demo-demo.obj `if test -f 'demo.c'; then $(CYGPATH_W) 'demo.c'; else $(CYGPATH_W) '$(srcdir)/demo.c'; fi`
+
+demo_https-demo_https.o: demo_https.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(demo_https_CPPFLAGS) $(CPPFLAGS) $(demo_https_CFLAGS) $(CFLAGS) -MT demo_https-demo_https.o -MD -MP -MF $(DEPDIR)/demo_https-demo_https.Tpo -c -o demo_https-demo_https.o `test -f 'demo_https.c' || echo '$(srcdir)/'`demo_https.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/demo_https-demo_https.Tpo $(DEPDIR)/demo_https-demo_https.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='demo_https.c' object='demo_https-demo_https.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(demo_https_CPPFLAGS) $(CPPFLAGS) $(demo_https_CFLAGS) $(CFLAGS) -c -o demo_https-demo_https.o `test -f 'demo_https.c' || echo '$(srcdir)/'`demo_https.c
+
+demo_https-demo_https.obj: demo_https.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(demo_https_CPPFLAGS) $(CPPFLAGS) $(demo_https_CFLAGS) $(CFLAGS) -MT demo_https-demo_https.obj -MD -MP -MF $(DEPDIR)/demo_https-demo_https.Tpo -c -o demo_https-demo_https.obj `if test -f 'demo_https.c'; then $(CYGPATH_W) 'demo_https.c'; else $(CYGPATH_W) '$(srcdir)/demo_https.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/demo_https-demo_https.Tpo $(DEPDIR)/demo_https-demo_https.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='demo_https.c' object='demo_https-demo_https.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(demo_https_CPPFLAGS) $(CPPFLAGS) $(demo_https_CFLAGS) $(CFLAGS) -c -o demo_https-demo_https.obj `if test -f 'demo_https.c'; then $(CYGPATH_W) 'demo_https.c'; else $(CYGPATH_W) '$(srcdir)/demo_https.c'; fi`
+
+https_fileserver_example-https_fileserver_example.o: https_fileserver_example.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(https_fileserver_example_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT https_fileserver_example-https_fileserver_example.o -MD -MP -MF $(DEPDIR)/https_fileserver_example-https_fileserver_example.Tpo -c -o https_fileserver_example-https_fileserver_example.o `test -f 'https_fileserver_example.c' || echo '$(srcdir)/'`https_fileserver_example.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/https_fileserver_example-https_fileserver_example.Tpo $(DEPDIR)/https_fileserver_example-https_fileserver_example.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='https_fileserver_example.c' object='https_fileserver_example-https_fileserver_example.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(https_fileserver_example_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o https_fileserver_example-https_fileserver_example.o `test -f 'https_fileserver_example.c' || echo '$(srcdir)/'`https_fileserver_example.c
+
+https_fileserver_example-https_fileserver_example.obj: https_fileserver_example.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(https_fileserver_example_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT https_fileserver_example-https_fileserver_example.obj -MD -MP -MF $(DEPDIR)/https_fileserver_example-https_fileserver_example.Tpo -c -o https_fileserver_example-https_fileserver_example.obj `if test -f 'https_fileserver_example.c'; then $(CYGPATH_W) 'https_fileserver_example.c'; else $(CYGPATH_W) '$(srcdir)/https_fileserver_example.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/https_fileserver_example-https_fileserver_example.Tpo $(DEPDIR)/https_fileserver_example-https_fileserver_example.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='https_fileserver_example.c' object='https_fileserver_example-https_fileserver_example.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(https_fileserver_example_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o https_fileserver_example-https_fileserver_example.obj `if test -f 'https_fileserver_example.c'; then $(CYGPATH_W) 'https_fileserver_example.c'; else $(CYGPATH_W) '$(srcdir)/https_fileserver_example.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool \
+ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/examples/authorization_example.c b/src/examples/authorization_example.c
new file mode 100644
index 0000000..e6e69ad
--- /dev/null
+++ b/src/examples/authorization_example.c
@@ -0,0 +1,108 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2008 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file authorization_example.c
+ * @brief example for how to use libmicrohttpd with HTTP authentication
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>libmicrohttpd demo</body></html>"
+
+#define DENIED "<html><head><title>Access denied</title></head><body>Access denied</body></html>"
+
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+ char *user;
+ char *pass;
+ int fail;
+
+ if (0 != strcmp (method, "GET"))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+
+ /* require: "Aladdin" with password "open sesame" */
+ pass = NULL;
+ user = MHD_basic_auth_get_username_password (connection, &pass);
+ fail = ( (user == NULL) || (0 != strcmp (user, "Aladdin")) || (0 != strcmp (pass, "open sesame") ) );
+ if (fail)
+ {
+ response = MHD_create_response_from_buffer (strlen (DENIED),
+ (void *) DENIED,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_basic_auth_fail_response (connection,"TestRealm",response);
+ }
+ else
+ {
+ response = MHD_create_response_from_buffer (strlen (me),
+ (void *) me,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ }
+
+ MHD_destroy_response (response);
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 3)
+ {
+ printf ("%s PORT SECONDS-TO-RUN\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, PAGE, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ sleep (atoi (argv[2]));
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/benchmark.c b/src/examples/benchmark.c
new file mode 100644
index 0000000..d287b2f
--- /dev/null
+++ b/src/examples/benchmark.c
@@ -0,0 +1,159 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2013 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file benchmark.c
+ * @brief minimal code to benchmark MHD GET performance
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>libmicrohttpd demo</body></html>"
+
+
+#define SMALL (1024 * 128)
+
+/**
+ * Number of threads to run in the thread pool. Should (roughly) match
+ * the number of cores on your system.
+ */
+#define NUMBER_OF_THREADS CPU_COUNT
+
+static unsigned int small_deltas[SMALL];
+
+static struct MHD_Response *response;
+
+
+
+/**
+ * Signature of the callback used by MHD to notify the
+ * application about completed requests.
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see MHD_OPTION_NOTIFY_COMPLETED
+ */
+static void
+completed_callback (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct timeval *tv = *con_cls;
+ struct timeval tve;
+ uint64_t delta;
+
+ if (NULL == tv)
+ return;
+ gettimeofday (&tve, NULL);
+
+ delta = 0;
+ if (tve.tv_usec >= tv->tv_usec)
+ delta += (tve.tv_sec - tv->tv_sec) * 1000000LL
+ + (tve.tv_usec - tv->tv_usec);
+ else
+ delta += (tve.tv_sec - tv->tv_sec) * 1000000LL
+ - tv->tv_usec + tve.tv_usec;
+ if (delta < SMALL)
+ small_deltas[delta]++;
+ else
+ fprintf (stdout, "D: %llu 1\n", (unsigned long long) delta);
+ free (tv);
+}
+
+
+static void *
+uri_logger_cb (void *cls,
+ const char *uri)
+{
+ struct timeval *tv = malloc (sizeof (struct timeval));
+
+ if (NULL != tv)
+ gettimeofday (tv, NULL);
+ return tv;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ if (0 != strcmp (method, "GET"))
+ return MHD_NO; /* unexpected method */
+ return MHD_queue_response (connection, MHD_HTTP_OK, response);
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+ unsigned int i;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ response = MHD_create_response_from_buffer (strlen (PAGE),
+ (void *) PAGE,
+ MHD_RESPMEM_PERSISTENT);
+#if 0
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONNECTION,
+ "close");
+#endif
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_SUPPRESS_DATE_NO_CLOCK
+#if EPOLL_SUPPORT
+ | MHD_USE_EPOLL_LINUX_ONLY | MHD_USE_EPOLL_TURBO
+#endif
+ ,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
+ MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) NUMBER_OF_THREADS,
+ MHD_OPTION_URI_LOG_CALLBACK, &uri_logger_cb, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_callback, NULL,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 1000,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ MHD_destroy_response (response);
+ for (i=0;i<SMALL;i++)
+ if (0 != small_deltas[i])
+ fprintf (stdout, "D: %d %u\n", i, small_deltas[i]);
+ return 0;
+}
diff --git a/src/examples/benchmark_https.c b/src/examples/benchmark_https.c
new file mode 100644
index 0000000..735a913
--- /dev/null
+++ b/src/examples/benchmark_https.c
@@ -0,0 +1,207 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2013 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file benchmark_https.c
+ * @brief minimal code to benchmark MHD GET performance with HTTPS
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>libmicrohttpd demo</body></html>"
+
+
+#define SMALL (1024 * 128)
+
+/**
+ * Number of threads to run in the thread pool. Should (roughly) match
+ * the number of cores on your system.
+ */
+#define NUMBER_OF_THREADS CPU_COUNT
+
+static unsigned int small_deltas[SMALL];
+
+static struct MHD_Response *response;
+
+
+
+/**
+ * Signature of the callback used by MHD to notify the
+ * application about completed requests.
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see MHD_OPTION_NOTIFY_COMPLETED
+ */
+static void
+completed_callback (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct timeval *tv = *con_cls;
+ struct timeval tve;
+ uint64_t delta;
+
+ if (NULL == tv)
+ return;
+ gettimeofday (&tve, NULL);
+
+ delta = 0;
+ if (tve.tv_usec >= tv->tv_usec)
+ delta += (tve.tv_sec - tv->tv_sec) * 1000000LL
+ + (tve.tv_usec - tv->tv_usec);
+ else
+ delta += (tve.tv_sec - tv->tv_sec) * 1000000LL
+ - tv->tv_usec + tve.tv_usec;
+ if (delta < SMALL)
+ small_deltas[delta]++;
+ else
+ fprintf (stdout, "D: %llu 1\n", (unsigned long long) delta);
+ free (tv);
+}
+
+
+static void *
+uri_logger_cb (void *cls,
+ const char *uri)
+{
+ struct timeval *tv = malloc (sizeof (struct timeval));
+
+ if (NULL != tv)
+ gettimeofday (tv, NULL);
+ return tv;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ if (0 != strcmp (method, "GET"))
+ return MHD_NO; /* unexpected method */
+ return MHD_queue_response (connection, MHD_HTTP_OK, response);
+}
+
+
+/* test server key */
+const char srv_signed_key_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEowIBAAKCAQEAvfTdv+3fgvVTKRnP/HVNG81cr8TrUP/iiyuve/THMzvFXhCW\n"
+ "+K03KwEku55QvnUndwBfU/ROzLlv+5hotgiDRNFT3HxurmhouySBrJNJv7qWp8IL\n"
+ "q4sw32vo0fbMu5BZF49bUXK9L3kW2PdhTtSQPWHEzNrCxO+YgCilKHkY3vQNfdJ0\n"
+ "20Q5EAAEseD1YtWCIpRvJzYlZMpjYB1ubTl24kwrgOKUJYKqM4jmF4DVQp4oOK/6\n"
+ "QYGGh1QmHRPAy3CBII6sbb+sZT9cAqU6GYQVB35lm4XAgibXV6KgmpVxVQQ69U6x\n"
+ "yoOl204xuekZOaG9RUPId74Rtmwfi1TLbBzo2wIDAQABAoIBADu09WSICNq5cMe4\n"
+ "+NKCLlgAT1NiQpLls1gKRbDhKiHU9j8QWNvWWkJWrCya4QdUfLCfeddCMeiQmv3K\n"
+ "lJMvDs+5OjJSHFoOsGiuW2Ias7IjnIojaJalfBml6frhJ84G27IXmdz6gzOiTIer\n"
+ "DjeAgcwBaKH5WwIay2TxIaScl7AwHBauQkrLcyb4hTmZuQh6ArVIN6+pzoVuORXM\n"
+ "bpeNWl2l/HSN3VtUN6aCAKbN/X3o0GavCCMn5Fa85uJFsab4ss/uP+2PusU71+zP\n"
+ "sBm6p/2IbGvF5k3VPDA7X5YX61sukRjRBihY8xSnNYx1UcoOsX6AiPnbhifD8+xQ\n"
+ "Tlf8oJUCgYEA0BTfzqNpr9Wxw5/QXaSdw7S/0eP5a0C/nwURvmfSzuTD4equzbEN\n"
+ "d+dI/s2JMxrdj/I4uoAfUXRGaabevQIjFzC9uyE3LaOyR2zhuvAzX+vVcs6bSXeU\n"
+ "pKpCAcN+3Z3evMaX2f+z/nfSUAl2i4J2R+/LQAWJW4KwRky/m+cxpfUCgYEA6bN1\n"
+ "b73bMgM8wpNt6+fcmS+5n0iZihygQ2U2DEud8nZJL4Nrm1dwTnfZfJBnkGj6+0Q0\n"
+ "cOwj2KS0/wcEdJBP0jucU4v60VMhp75AQeHqidIde0bTViSRo3HWKXHBIFGYoU3T\n"
+ "LyPyKndbqsOObnsFXHn56Nwhr2HLf6nw4taGQY8CgYBoSW36FLCNbd6QGvLFXBGt\n"
+ "2lMhEM8az/K58kJ4WXSwOLtr6MD/WjNT2tkcy0puEJLm6BFCd6A6pLn9jaKou/92\n"
+ "SfltZjJPb3GUlp9zn5tAAeSSi7YMViBrfuFiHObij5LorefBXISLjuYbMwL03MgH\n"
+ "Ocl2JtA2ywMp2KFXs8GQWQKBgFyIVv5ogQrbZ0pvj31xr9HjqK6d01VxIi+tOmpB\n"
+ "4ocnOLEcaxX12BzprW55ytfOCVpF1jHD/imAhb3YrHXu0fwe6DXYXfZV4SSG2vB7\n"
+ "IB9z14KBN5qLHjNGFpMQXHSMek+b/ftTU0ZnPh9uEM5D3YqRLVd7GcdUhHvG8P8Q\n"
+ "C9aXAoGBAJtID6h8wOGMP0XYX5YYnhlC7dOLfk8UYrzlp3xhqVkzKthTQTj6wx9R\n"
+ "GtC4k7U1ki8oJsfcIlBNXd768fqDVWjYju5rzShMpo8OCTS6ipAblKjCxPPVhIpv\n"
+ "tWPlbSn1qj6wylstJ5/3Z+ZW5H4wIKp5jmLiioDhcP0L/Ex3Zx8O\n"
+ "-----END RSA PRIVATE KEY-----\n";
+
+/* test server CA signed certificates */
+const char srv_signed_cert_pem[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIDGzCCAgWgAwIBAgIES0KCvTALBgkqhkiG9w0BAQUwFzEVMBMGA1UEAxMMdGVz\n"
+ "dF9jYV9jZXJ0MB4XDTEwMDEwNTAwMDcyNVoXDTQ1MDMxMjAwMDcyNVowFzEVMBMG\n"
+ "A1UEAxMMdGVzdF9jYV9jZXJ0MIIBHzALBgkqhkiG9w0BAQEDggEOADCCAQkCggEA\n"
+ "vfTdv+3fgvVTKRnP/HVNG81cr8TrUP/iiyuve/THMzvFXhCW+K03KwEku55QvnUn\n"
+ "dwBfU/ROzLlv+5hotgiDRNFT3HxurmhouySBrJNJv7qWp8ILq4sw32vo0fbMu5BZ\n"
+ "F49bUXK9L3kW2PdhTtSQPWHEzNrCxO+YgCilKHkY3vQNfdJ020Q5EAAEseD1YtWC\n"
+ "IpRvJzYlZMpjYB1ubTl24kwrgOKUJYKqM4jmF4DVQp4oOK/6QYGGh1QmHRPAy3CB\n"
+ "II6sbb+sZT9cAqU6GYQVB35lm4XAgibXV6KgmpVxVQQ69U6xyoOl204xuekZOaG9\n"
+ "RUPId74Rtmwfi1TLbBzo2wIDAQABo3YwdDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM\n"
+ "MAoGCCsGAQUFBwMBMA8GA1UdDwEB/wQFAwMHIAAwHQYDVR0OBBYEFOFi4ilKOP1d\n"
+ "XHlWCMwmVKr7mgy8MB8GA1UdIwQYMBaAFP2olB4s2T/xuoQ5pT2RKojFwZo2MAsG\n"
+ "CSqGSIb3DQEBBQOCAQEAHVWPxazupbOkG7Did+dY9z2z6RjTzYvurTtEKQgzM2Vz\n"
+ "GQBA+3pZ3c5mS97fPIs9hZXfnQeelMeZ2XP1a+9vp35bJjZBBhVH+pqxjCgiUflg\n"
+ "A3Zqy0XwwVCgQLE2HyaU3DLUD/aeIFK5gJaOSdNTXZLv43K8kl4cqDbMeRpVTbkt\n"
+ "YmG4AyEOYRNKGTqMEJXJoxD5E3rBUNrVI/XyTjYrulxbNPcMWEHKNeeqWpKDYTFo\n"
+ "Bb01PCthGXiq/4A2RLAFosadzRa8SBpoSjPPfZ0b2w4MJpReHqKbR5+T2t6hzml6\n"
+ "4ToyOKPDmamiTuN5KzLN3cw7DQlvWMvqSOChPLnA3Q==\n"
+ "-----END CERTIFICATE-----\n";
+
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+ unsigned int i;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ response = MHD_create_response_from_buffer (strlen (PAGE),
+ (void *) PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL
+#if EPOLL_SUPPORT
+ | MHD_USE_EPOLL_LINUX_ONLY | MHD_USE_EPOLL_TURBO
+#endif
+ ,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
+ MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) NUMBER_OF_THREADS,
+ MHD_OPTION_URI_LOG_CALLBACK, &uri_logger_cb, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_callback, NULL,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 1000,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ MHD_destroy_response (response);
+ for (i=0;i<SMALL;i++)
+ if (0 != small_deltas[i])
+ fprintf (stdout, "D: %d %u\n", i, small_deltas[i]);
+ return 0;
+}
diff --git a/src/examples/chunked_example.c b/src/examples/chunked_example.c
new file mode 100644
index 0000000..08bb82d
--- /dev/null
+++ b/src/examples/chunked_example.c
@@ -0,0 +1,94 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2015 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file chunked_example.c
+ * @brief example for generating chunked encoding with libmicrohttpd
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+
+
+static ssize_t
+callback (void *cls,
+ uint64_t pos,
+ char *buf,
+ size_t max)
+{
+ return MHD_CONTENT_READER_END_OF_STREAM;
+}
+
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (method, "GET"))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ 1024,
+ &callback,
+ NULL,
+ NULL);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (// MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | MHD_USE_POLL,
+ MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ // MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | MHD_USE_POLL,
+ // MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/demo.c b/src/examples/demo.c
new file mode 100644
index 0000000..80ca09c
--- /dev/null
+++ b/src/examples/demo.c
@@ -0,0 +1,907 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2013 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file demo.c
+ * @brief complex demonstration site: create directory index, offer
+ * upload via form and HTTP POST, download with mime type detection
+ * and error reporting (403, etc.) --- and all of this with
+ * high-performance settings (large buffers, thread pool).
+ * If you want to benchmark MHD, this code should be used to
+ * run tests against. Note that the number of threads may need
+ * to be adjusted depending on the number of available cores.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <magic.h>
+#include <limits.h>
+#include <ctype.h>
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+/**
+ * Number of threads to run in the thread pool. Should (roughly) match
+ * the number of cores on your system.
+ */
+#define NUMBER_OF_THREADS CPU_COUNT
+
+/**
+ * How many bytes of a file do we give to libmagic to determine the mime type?
+ * 16k might be a bit excessive, but ought not hurt performance much anyway,
+ * and should definitively be on the safe side.
+ */
+#define MAGIC_HEADER_SIZE (16 * 1024)
+
+
+/**
+ * Page returned for file-not-found.
+ */
+#define FILE_NOT_FOUND_PAGE "<html><head><title>File not found</title></head><body>File not found</body></html>"
+
+
+/**
+ * Page returned for internal errors.
+ */
+#define INTERNAL_ERROR_PAGE "<html><head><title>Internal error</title></head><body>Internal error</body></html>"
+
+
+/**
+ * Page returned for refused requests.
+ */
+#define REQUEST_REFUSED_PAGE "<html><head><title>Request refused</title></head><body>Request refused (file exists?)</body></html>"
+
+
+/**
+ * Head of index page.
+ */
+#define INDEX_PAGE_HEADER "<html>\n<head><title>Welcome</title></head>\n<body>\n"\
+ "<h1>Upload</h1>\n"\
+ "<form method=\"POST\" enctype=\"multipart/form-data\" action=\"/\">\n"\
+ "<dl><dt>Content type:</dt><dd>"\
+ "<input type=\"radio\" name=\"category\" value=\"books\">Book</input>"\
+ "<input type=\"radio\" name=\"category\" value=\"images\">Image</input>"\
+ "<input type=\"radio\" name=\"category\" value=\"music\">Music</input>"\
+ "<input type=\"radio\" name=\"category\" value=\"software\">Software</input>"\
+ "<input type=\"radio\" name=\"category\" value=\"videos\">Videos</input>\n"\
+ "<input type=\"radio\" name=\"category\" value=\"other\" checked>Other</input></dd>"\
+ "<dt>Language:</dt><dd>"\
+ "<input type=\"radio\" name=\"language\" value=\"no-lang\" checked>none</input>"\
+ "<input type=\"radio\" name=\"language\" value=\"en\">English</input>"\
+ "<input type=\"radio\" name=\"language\" value=\"de\">German</input>"\
+ "<input type=\"radio\" name=\"language\" value=\"fr\">French</input>"\
+ "<input type=\"radio\" name=\"language\" value=\"es\">Spanish</input></dd>\n"\
+ "<dt>File:</dt><dd>"\
+ "<input type=\"file\" name=\"upload\"/></dd></dl>"\
+ "<input type=\"submit\" value=\"Send!\"/>\n"\
+ "</form>\n"\
+ "<h1>Download</h1>\n"\
+ "<ol>\n"
+
+/**
+ * Footer of index page.
+ */
+#define INDEX_PAGE_FOOTER "</ol>\n</body>\n</html>"
+
+
+/**
+ * NULL-terminated array of supported upload categories. Should match HTML
+ * in the form.
+ */
+static const char * const categories[] =
+ {
+ "books",
+ "images",
+ "music",
+ "software",
+ "videos",
+ "other",
+ NULL,
+ };
+
+
+/**
+ * Specification of a supported language.
+ */
+struct Language
+{
+ /**
+ * Directory name for the language.
+ */
+ const char *dirname;
+
+ /**
+ * Long name for humans.
+ */
+ const char *longname;
+
+};
+
+/**
+ * NULL-terminated array of supported upload categories. Should match HTML
+ * in the form.
+ */
+static const struct Language languages[] =
+ {
+ { "no-lang", "No language specified" },
+ { "en", "English" },
+ { "de", "German" },
+ { "fr", "French" },
+ { "es", "Spanish" },
+ { NULL, NULL },
+ };
+
+
+/**
+ * Response returned if the requested file does not exist (or is not accessible).
+ */
+static struct MHD_Response *file_not_found_response;
+
+/**
+ * Response returned for internal errors.
+ */
+static struct MHD_Response *internal_error_response;
+
+/**
+ * Response returned for '/' (GET) to list the contents of the directory and allow upload.
+ */
+static struct MHD_Response *cached_directory_response;
+
+/**
+ * Response returned for refused uploads.
+ */
+static struct MHD_Response *request_refused_response;
+
+/**
+ * Mutex used when we update the cached directory response object.
+ */
+static pthread_mutex_t mutex;
+
+/**
+ * Global handle to MAGIC data.
+ */
+static magic_t magic;
+
+
+/**
+ * Mark the given response as HTML for the brower.
+ *
+ * @param response response to mark
+ */
+static void
+mark_as_html (struct MHD_Response *response)
+{
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/html");
+}
+
+
+/**
+ * Replace the existing 'cached_directory_response' with the
+ * given response.
+ *
+ * @param response new directory response
+ */
+static void
+update_cached_response (struct MHD_Response *response)
+{
+ (void) pthread_mutex_lock (&mutex);
+ if (NULL != cached_directory_response)
+ MHD_destroy_response (cached_directory_response);
+ cached_directory_response = response;
+ (void) pthread_mutex_unlock (&mutex);
+}
+
+
+/**
+ * Context keeping the data for the response we're building.
+ */
+struct ResponseDataContext
+{
+ /**
+ * Response data string.
+ */
+ char *buf;
+
+ /**
+ * Number of bytes allocated for 'buf'.
+ */
+ size_t buf_len;
+
+ /**
+ * Current position where we append to 'buf'. Must be smaller or equal to 'buf_len'.
+ */
+ size_t off;
+
+};
+
+
+/**
+ * Create a listing of the files in 'dirname' in HTML.
+ *
+ * @param rdc where to store the list of files
+ * @param dirname name of the directory to list
+ * @return MHD_YES on success, MHD_NO on error
+ */
+static int
+list_directory (struct ResponseDataContext *rdc,
+ const char *dirname)
+{
+ char fullname[PATH_MAX];
+ struct stat sbuf;
+ DIR *dir;
+ struct dirent *de;
+
+ if (NULL == (dir = opendir (dirname)))
+ return MHD_NO;
+ while (NULL != (de = readdir (dir)))
+ {
+ if ('.' == de->d_name[0])
+ continue;
+ if (sizeof (fullname) <= (size_t)
+ snprintf (fullname, sizeof (fullname),
+ "%s/%s",
+ dirname, de->d_name))
+ continue; /* ugh, file too long? how can this be!? */
+ if (0 != stat (fullname, &sbuf))
+ continue; /* ugh, failed to 'stat' */
+ if (! S_ISREG (sbuf.st_mode))
+ continue; /* not a regular file, skip */
+ if (rdc->off + 1024 > rdc->buf_len)
+ {
+ void *r;
+
+ if ( (2 * rdc->buf_len + 1024) < rdc->buf_len)
+ break; /* more than SIZE_T _index_ size? Too big for us */
+ rdc->buf_len = 2 * rdc->buf_len + 1024;
+ if (NULL == (r = realloc (rdc->buf, rdc->buf_len)))
+ break; /* out of memory */
+ rdc->buf = r;
+ }
+ rdc->off += snprintf (&rdc->buf[rdc->off],
+ rdc->buf_len - rdc->off,
+ "<li><a href=\"/%s\">%s</a></li>\n",
+ fullname,
+ de->d_name);
+ }
+ (void) closedir (dir);
+ return MHD_YES;
+}
+
+
+/**
+ * Re-scan our local directory and re-build the index.
+ */
+static void
+update_directory ()
+{
+ static size_t initial_allocation = 32 * 1024; /* initial size for response buffer */
+ struct MHD_Response *response;
+ struct ResponseDataContext rdc;
+ unsigned int language_idx;
+ unsigned int category_idx;
+ const struct Language *language;
+ const char *category;
+ char dir_name[128];
+ struct stat sbuf;
+
+ rdc.buf_len = initial_allocation;
+ if (NULL == (rdc.buf = malloc (rdc.buf_len)))
+ {
+ update_cached_response (NULL);
+ return;
+ }
+ rdc.off = snprintf (rdc.buf, rdc.buf_len,
+ "%s",
+ INDEX_PAGE_HEADER);
+ for (language_idx = 0; NULL != languages[language_idx].dirname; language_idx++)
+ {
+ language = &languages[language_idx];
+
+ if (0 != stat (language->dirname, &sbuf))
+ continue; /* empty */
+ /* we ensured always +1k room, filenames are ~256 bytes,
+ so there is always still enough space for the header
+ without need for an additional reallocation check. */
+ rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
+ "<h2>%s</h2>\n",
+ language->longname);
+ for (category_idx = 0; NULL != categories[category_idx]; category_idx++)
+ {
+ category = categories[category_idx];
+ snprintf (dir_name, sizeof (dir_name),
+ "%s/%s",
+ language->dirname,
+ category);
+ if (0 != stat (dir_name, &sbuf))
+ continue; /* empty */
+
+ /* we ensured always +1k room, filenames are ~256 bytes,
+ so there is always still enough space for the header
+ without need for an additional reallocation check. */
+ rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
+ "<h3>%s</h3>\n",
+ category);
+
+ if (MHD_NO == list_directory (&rdc, dir_name))
+ {
+ free (rdc.buf);
+ update_cached_response (NULL);
+ return;
+ }
+ }
+ }
+ /* we ensured always +1k room, filenames are ~256 bytes,
+ so there is always still enough space for the footer
+ without need for a final reallocation check. */
+ rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
+ "%s",
+ INDEX_PAGE_FOOTER);
+ initial_allocation = rdc.buf_len; /* remember for next time */
+ response = MHD_create_response_from_buffer (rdc.off,
+ rdc.buf,
+ MHD_RESPMEM_MUST_FREE);
+ mark_as_html (response);
+#if FORCE_CLOSE
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONNECTION,
+ "close");
+#endif
+ update_cached_response (response);
+}
+
+
+/**
+ * Context we keep for an upload.
+ */
+struct UploadContext
+{
+ /**
+ * Handle where we write the uploaded file to.
+ */
+ int fd;
+
+ /**
+ * Name of the file on disk (used to remove on errors).
+ */
+ char *filename;
+
+ /**
+ * Language for the upload.
+ */
+ char *language;
+
+ /**
+ * Category for the upload.
+ */
+ char *category;
+
+ /**
+ * Post processor we're using to process the upload.
+ */
+ struct MHD_PostProcessor *pp;
+
+ /**
+ * Handle to connection that we're processing the upload for.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Response to generate, NULL to use directory.
+ */
+ struct MHD_Response *response;
+};
+
+
+/**
+ * Append the 'size' bytes from 'data' to '*ret', adding
+ * 0-termination. If '*ret' is NULL, allocate an empty string first.
+ *
+ * @param ret string to update, NULL or 0-terminated
+ * @param data data to append
+ * @param size number of bytes in 'data'
+ * @return MHD_NO on allocation failure, MHD_YES on success
+ */
+static int
+do_append (char **ret,
+ const char *data,
+ size_t size)
+{
+ char *buf;
+ size_t old_len;
+
+ if (NULL == *ret)
+ old_len = 0;
+ else
+ old_len = strlen (*ret);
+ buf = malloc (old_len + size + 1);
+ if (NULL == buf)
+ return MHD_NO;
+ memcpy (buf, *ret, old_len);
+ if (NULL != *ret)
+ free (*ret);
+ memcpy (&buf[old_len], data, size);
+ buf[old_len + size] = '\0';
+ *ret = buf;
+ return MHD_YES;
+}
+
+
+/**
+ * Iterator over key-value pairs where the value
+ * maybe made available in increments and/or may
+ * not be zero-terminated. Used for processing
+ * POST data.
+ *
+ * @param cls user-specified closure
+ * @param kind type of the value, always MHD_POSTDATA_KIND when called from MHD
+ * @param key 0-terminated key for the value
+ * @param filename name of the uploaded file, NULL if not known
+ * @param content_type mime-type of the data, NULL if not known
+ * @param transfer_encoding encoding of the data, NULL if not known
+ * @param data pointer to size bytes of data at the
+ * specified offset
+ * @param off offset of data in the overall value
+ * @param size number of bytes in data available
+ * @return MHD_YES to continue iterating,
+ * MHD_NO to abort the iteration
+ */
+static int
+process_upload_data (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *data,
+ uint64_t off,
+ size_t size)
+{
+ struct UploadContext *uc = cls;
+ int i;
+
+ if (0 == strcmp (key, "category"))
+ return do_append (&uc->category, data, size);
+ if (0 == strcmp (key, "language"))
+ return do_append (&uc->language, data, size);
+ if (0 != strcmp (key, "upload"))
+ {
+ fprintf (stderr,
+ "Ignoring unexpected form value `%s'\n",
+ key);
+ return MHD_YES; /* ignore */
+ }
+ if (NULL == filename)
+ {
+ fprintf (stderr, "No filename, aborting upload\n");
+ return MHD_NO; /* no filename, error */
+ }
+ if ( (NULL == uc->category) ||
+ (NULL == uc->language) )
+ {
+ fprintf (stderr,
+ "Missing form data for upload `%s'\n",
+ filename);
+ uc->response = request_refused_response;
+ return MHD_NO;
+ }
+ if (-1 == uc->fd)
+ {
+ char fn[PATH_MAX];
+
+ if ( (NULL != strstr (filename, "..")) ||
+ (NULL != strchr (filename, '/')) ||
+ (NULL != strchr (filename, '\\')) )
+ {
+ uc->response = request_refused_response;
+ return MHD_NO;
+ }
+ /* create directories -- if they don't exist already */
+#ifdef WINDOWS
+ (void) mkdir (uc->language);
+#else
+ (void) mkdir (uc->language, S_IRWXU);
+#endif
+ snprintf (fn, sizeof (fn),
+ "%s/%s",
+ uc->language,
+ uc->category);
+#ifdef WINDOWS
+ (void) mkdir (fn);
+#else
+ (void) mkdir (fn, S_IRWXU);
+#endif
+ /* open file */
+ snprintf (fn, sizeof (fn),
+ "%s/%s/%s",
+ uc->language,
+ uc->category,
+ filename);
+ for (i=strlen (fn)-1;i>=0;i--)
+ if (! isprint ((int) fn[i]))
+ fn[i] = '_';
+ uc->fd = open (fn,
+ O_CREAT | O_EXCL
+#if O_LARGEFILE
+ | O_LARGEFILE
+#endif
+ | O_WRONLY,
+ S_IRUSR | S_IWUSR);
+ if (-1 == uc->fd)
+ {
+ fprintf (stderr,
+ "Error opening file `%s' for upload: %s\n",
+ fn,
+ strerror (errno));
+ uc->response = request_refused_response;
+ return MHD_NO;
+ }
+ uc->filename = strdup (fn);
+ }
+ if ( (0 != size) &&
+ (size != (size_t) write (uc->fd, data, size)) )
+ {
+ /* write failed; likely: disk full */
+ fprintf (stderr,
+ "Error writing to file `%s': %s\n",
+ uc->filename,
+ strerror (errno));
+ uc->response = internal_error_response;
+ close (uc->fd);
+ uc->fd = -1;
+ if (NULL != uc->filename)
+ {
+ unlink (uc->filename);
+ free (uc->filename);
+ uc->filename = NULL;
+ }
+ return MHD_NO;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Function called whenever a request was completed.
+ * Used to clean up 'struct UploadContext' objects.
+ *
+ * @param cls client-defined closure, NULL
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the MHD_AccessHandlerCallback, points to NULL if this was
+ * not an upload
+ * @param toe reason for request termination
+ */
+static void
+response_completed_callback (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct UploadContext *uc = *con_cls;
+
+ if (NULL == uc)
+ return; /* this request wasn't an upload request */
+ if (NULL != uc->pp)
+ {
+ MHD_destroy_post_processor (uc->pp);
+ uc->pp = NULL;
+ }
+ if (-1 != uc->fd)
+ {
+ (void) close (uc->fd);
+ if (NULL != uc->filename)
+ {
+ fprintf (stderr,
+ "Upload of file `%s' failed (incomplete or aborted), removing file.\n",
+ uc->filename);
+ (void) unlink (uc->filename);
+ }
+ }
+ if (NULL != uc->filename)
+ free (uc->filename);
+ free (uc);
+}
+
+
+/**
+ * Return the current directory listing.
+ *
+ * @param connection connection to return the directory for
+ * @return MHD_YES on success, MHD_NO on error
+ */
+static int
+return_directory_response (struct MHD_Connection *connection)
+{
+ int ret;
+
+ (void) pthread_mutex_lock (&mutex);
+ if (NULL == cached_directory_response)
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ internal_error_response);
+ else
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ cached_directory_response);
+ (void) pthread_mutex_unlock (&mutex);
+ return ret;
+}
+
+
+/**
+ * Main callback from MHD, used to generate the page.
+ *
+ * @param cls NULL
+ * @param connection connection handle
+ * @param url requested URL
+ * @param method GET, PUT, POST, etc.
+ * @param version HTTP version
+ * @param upload_data data from upload (PUT/POST)
+ * @param upload_data_size number of bytes in "upload_data"
+ * @param ptr our context
+ * @return MHD_YES on success, MHD_NO to drop connection
+ */
+static int
+generate_page (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size, void **ptr)
+{
+ struct MHD_Response *response;
+ int ret;
+ int fd;
+ struct stat buf;
+
+ if (0 != strcmp (url, "/"))
+ {
+ /* should be file download */
+ char file_data[MAGIC_HEADER_SIZE];
+ ssize_t got;
+ const char *mime;
+
+ if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
+ return MHD_NO; /* unexpected method (we're not polite...) */
+ if ( (0 == stat (&url[1], &buf)) &&
+ (NULL == strstr (&url[1], "..")) &&
+ ('/' != url[1]))
+ fd = open (&url[1], O_RDONLY);
+ else
+ fd = -1;
+ if (-1 == fd)
+ return MHD_queue_response (connection,
+ MHD_HTTP_NOT_FOUND,
+ file_not_found_response);
+ /* read beginning of the file to determine mime type */
+ got = read (fd, file_data, sizeof (file_data));
+ if (-1 != got)
+ mime = magic_buffer (magic, file_data, got);
+ else
+ mime = NULL;
+ (void) lseek (fd, 0, SEEK_SET);
+
+ if (NULL == (response = MHD_create_response_from_fd (buf.st_size,
+ fd)))
+ {
+ /* internal error (i.e. out of memory) */
+ (void) close (fd);
+ return MHD_NO;
+ }
+
+ /* add mime type if we had one */
+ if (NULL != mime)
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+ }
+
+ if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
+ {
+ /* upload! */
+ struct UploadContext *uc = *ptr;
+
+ if (NULL == uc)
+ {
+ if (NULL == (uc = malloc (sizeof (struct UploadContext))))
+ return MHD_NO; /* out of memory, close connection */
+ memset (uc, 0, sizeof (struct UploadContext));
+ uc->fd = -1;
+ uc->connection = connection;
+ uc->pp = MHD_create_post_processor (connection,
+ 64 * 1024 /* buffer size */,
+ &process_upload_data, uc);
+ if (NULL == uc->pp)
+ {
+ /* out of memory, close connection */
+ free (uc);
+ return MHD_NO;
+ }
+ *ptr = uc;
+ return MHD_YES;
+ }
+ if (0 != *upload_data_size)
+ {
+ if (NULL == uc->response)
+ (void) MHD_post_process (uc->pp,
+ upload_data,
+ *upload_data_size);
+ *upload_data_size = 0;
+ return MHD_YES;
+ }
+ /* end of upload, finish it! */
+ MHD_destroy_post_processor (uc->pp);
+ uc->pp = NULL;
+ if (-1 != uc->fd)
+ {
+ close (uc->fd);
+ uc->fd = -1;
+ }
+ if (NULL != uc->response)
+ {
+ return MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ uc->response);
+ }
+ else
+ {
+ update_directory ();
+ return return_directory_response (connection);
+ }
+ }
+ if (0 == strcmp (method, MHD_HTTP_METHOD_GET))
+ {
+ return return_directory_response (connection);
+ }
+
+ /* unexpected request, refuse */
+ return MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ request_refused_response);
+}
+
+
+/**
+ * Function called if we get a SIGPIPE. Does nothing.
+ *
+ * @param sig will be SIGPIPE (ignored)
+ */
+static void
+catcher (int sig)
+{
+ /* do nothing */
+}
+
+
+/**
+ * setup handlers to ignore SIGPIPE.
+ */
+#ifndef MINGW
+static void
+ignore_sigpipe ()
+{
+ struct sigaction oldsig;
+ struct sigaction sig;
+
+ sig.sa_handler = &catcher;
+ sigemptyset (&sig.sa_mask);
+#ifdef SA_INTERRUPT
+ sig.sa_flags = SA_INTERRUPT; /* SunOS */
+#else
+ sig.sa_flags = SA_RESTART;
+#endif
+ if (0 != sigaction (SIGPIPE, &sig, &oldsig))
+ fprintf (stderr,
+ "Failed to install SIGPIPE handler: %s\n", strerror (errno));
+}
+#endif
+
+
+/**
+ * Entry point to demo. Note: this HTTP server will make all
+ * files in the current directory and its subdirectories available
+ * to anyone. Press ENTER to stop the server once it has started.
+ *
+ * @param argc number of arguments in argv
+ * @param argv first and only argument should be the port number
+ * @return 0 on success
+ */
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+ unsigned int port;
+
+ if ( (argc != 2) ||
+ (1 != sscanf (argv[1], "%u", &port)) ||
+ (UINT16_MAX < port) )
+ {
+ fprintf (stderr,
+ "%s PORT\n", argv[0]);
+ return 1;
+ }
+ #ifndef MINGW
+ ignore_sigpipe ();
+ #endif
+ magic = magic_open (MAGIC_MIME_TYPE);
+ (void) magic_load (magic, NULL);
+
+ (void) pthread_mutex_init (&mutex, NULL);
+ file_not_found_response = MHD_create_response_from_buffer (strlen (FILE_NOT_FOUND_PAGE),
+ (void *) FILE_NOT_FOUND_PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ mark_as_html (file_not_found_response);
+ request_refused_response = MHD_create_response_from_buffer (strlen (REQUEST_REFUSED_PAGE),
+ (void *) REQUEST_REFUSED_PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ mark_as_html (request_refused_response);
+ internal_error_response = MHD_create_response_from_buffer (strlen (INTERNAL_ERROR_PAGE),
+ (void *) INTERNAL_ERROR_PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ mark_as_html (internal_error_response);
+ update_directory ();
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG
+#if EPOLL_SUPPORT
+ | MHD_USE_EPOLL_LINUX_ONLY
+#endif
+ ,
+ port,
+ NULL, NULL,
+ &generate_page, NULL,
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024),
+#if PRODUCTION
+ MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64),
+#endif
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) (120 /* seconds */),
+ MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) NUMBER_OF_THREADS,
+ MHD_OPTION_NOTIFY_COMPLETED, &response_completed_callback, NULL,
+ MHD_OPTION_END);
+ if (NULL == d)
+ return 1;
+ fprintf (stderr, "HTTP server running. Press ENTER to stop the server\n");
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ MHD_destroy_response (file_not_found_response);
+ MHD_destroy_response (request_refused_response);
+ MHD_destroy_response (internal_error_response);
+ update_cached_response (NULL);
+ (void) pthread_mutex_destroy (&mutex);
+ magic_close (magic);
+ return 0;
+}
+
+/* end of demo.c */
diff --git a/src/examples/demo_https.c b/src/examples/demo_https.c
new file mode 100644
index 0000000..f34a715
--- /dev/null
+++ b/src/examples/demo_https.c
@@ -0,0 +1,960 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2013 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file demo_https.c
+ * @brief complex demonstration site: create directory index, offer
+ * upload via form and HTTP POST, download with mime type detection
+ * and error reporting (403, etc.) --- and all of this with
+ * high-performance settings (large buffers, thread pool).
+ * If you want to benchmark MHD, this code should be used to
+ * run tests against. Note that the number of threads may need
+ * to be adjusted depending on the number of available cores.
+ * Logic is identical to demo.c, just adds HTTPS support.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <magic.h>
+#include <limits.h>
+#include <ctype.h>
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+/**
+ * Number of threads to run in the thread pool. Should (roughly) match
+ * the number of cores on your system.
+ */
+#define NUMBER_OF_THREADS CPU_COUNT
+
+/**
+ * How many bytes of a file do we give to libmagic to determine the mime type?
+ * 16k might be a bit excessive, but ought not hurt performance much anyway,
+ * and should definitively be on the safe side.
+ */
+#define MAGIC_HEADER_SIZE (16 * 1024)
+
+
+/**
+ * Page returned for file-not-found.
+ */
+#define FILE_NOT_FOUND_PAGE "<html><head><title>File not found</title></head><body>File not found</body></html>"
+
+
+/**
+ * Page returned for internal errors.
+ */
+#define INTERNAL_ERROR_PAGE "<html><head><title>Internal error</title></head><body>Internal error</body></html>"
+
+
+/**
+ * Page returned for refused requests.
+ */
+#define REQUEST_REFUSED_PAGE "<html><head><title>Request refused</title></head><body>Request refused (file exists?)</body></html>"
+
+
+/**
+ * Head of index page.
+ */
+#define INDEX_PAGE_HEADER "<html>\n<head><title>Welcome</title></head>\n<body>\n"\
+ "<h1>Upload</h1>\n"\
+ "<form method=\"POST\" enctype=\"multipart/form-data\" action=\"/\">\n"\
+ "<dl><dt>Content type:</dt><dd>"\
+ "<input type=\"radio\" name=\"category\" value=\"books\">Book</input>"\
+ "<input type=\"radio\" name=\"category\" value=\"images\">Image</input>"\
+ "<input type=\"radio\" name=\"category\" value=\"music\">Music</input>"\
+ "<input type=\"radio\" name=\"category\" value=\"software\">Software</input>"\
+ "<input type=\"radio\" name=\"category\" value=\"videos\">Videos</input>\n"\
+ "<input type=\"radio\" name=\"category\" value=\"other\" checked>Other</input></dd>"\
+ "<dt>Language:</dt><dd>"\
+ "<input type=\"radio\" name=\"language\" value=\"no-lang\" checked>none</input>"\
+ "<input type=\"radio\" name=\"language\" value=\"en\">English</input>"\
+ "<input type=\"radio\" name=\"language\" value=\"de\">German</input>"\
+ "<input type=\"radio\" name=\"language\" value=\"fr\">French</input>"\
+ "<input type=\"radio\" name=\"language\" value=\"es\">Spanish</input></dd>\n"\
+ "<dt>File:</dt><dd>"\
+ "<input type=\"file\" name=\"upload\"/></dd></dl>"\
+ "<input type=\"submit\" value=\"Send!\"/>\n"\
+ "</form>\n"\
+ "<h1>Download</h1>\n"\
+ "<ol>\n"
+
+/**
+ * Footer of index page.
+ */
+#define INDEX_PAGE_FOOTER "</ol>\n</body>\n</html>"
+
+
+/**
+ * NULL-terminated array of supported upload categories. Should match HTML
+ * in the form.
+ */
+static const char * const categories[] =
+ {
+ "books",
+ "images",
+ "music",
+ "software",
+ "videos",
+ "other",
+ NULL,
+ };
+
+
+/**
+ * Specification of a supported language.
+ */
+struct Language
+{
+ /**
+ * Directory name for the language.
+ */
+ const char *dirname;
+
+ /**
+ * Long name for humans.
+ */
+ const char *longname;
+
+};
+
+/**
+ * NULL-terminated array of supported upload categories. Should match HTML
+ * in the form.
+ */
+static const struct Language languages[] =
+ {
+ { "no-lang", "No language specified" },
+ { "en", "English" },
+ { "de", "German" },
+ { "fr", "French" },
+ { "es", "Spanish" },
+ { NULL, NULL },
+ };
+
+
+/**
+ * Response returned if the requested file does not exist (or is not accessible).
+ */
+static struct MHD_Response *file_not_found_response;
+
+/**
+ * Response returned for internal errors.
+ */
+static struct MHD_Response *internal_error_response;
+
+/**
+ * Response returned for '/' (GET) to list the contents of the directory and allow upload.
+ */
+static struct MHD_Response *cached_directory_response;
+
+/**
+ * Response returned for refused uploads.
+ */
+static struct MHD_Response *request_refused_response;
+
+/**
+ * Mutex used when we update the cached directory response object.
+ */
+static pthread_mutex_t mutex;
+
+/**
+ * Global handle to MAGIC data.
+ */
+static magic_t magic;
+
+
+/**
+ * Mark the given response as HTML for the brower.
+ *
+ * @param response response to mark
+ */
+static void
+mark_as_html (struct MHD_Response *response)
+{
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/html");
+}
+
+
+/**
+ * Replace the existing 'cached_directory_response' with the
+ * given response.
+ *
+ * @param response new directory response
+ */
+static void
+update_cached_response (struct MHD_Response *response)
+{
+ (void) pthread_mutex_lock (&mutex);
+ if (NULL != cached_directory_response)
+ MHD_destroy_response (cached_directory_response);
+ cached_directory_response = response;
+ (void) pthread_mutex_unlock (&mutex);
+}
+
+
+/**
+ * Context keeping the data for the response we're building.
+ */
+struct ResponseDataContext
+{
+ /**
+ * Response data string.
+ */
+ char *buf;
+
+ /**
+ * Number of bytes allocated for 'buf'.
+ */
+ size_t buf_len;
+
+ /**
+ * Current position where we append to 'buf'. Must be smaller or equal to 'buf_len'.
+ */
+ size_t off;
+
+};
+
+
+/**
+ * Create a listing of the files in 'dirname' in HTML.
+ *
+ * @param rdc where to store the list of files
+ * @param dirname name of the directory to list
+ * @return MHD_YES on success, MHD_NO on error
+ */
+static int
+list_directory (struct ResponseDataContext *rdc,
+ const char *dirname)
+{
+ char fullname[PATH_MAX];
+ struct stat sbuf;
+ DIR *dir;
+ struct dirent *de;
+
+ if (NULL == (dir = opendir (dirname)))
+ return MHD_NO;
+ while (NULL != (de = readdir (dir)))
+ {
+ if ('.' == de->d_name[0])
+ continue;
+ if (sizeof (fullname) <= (size_t)
+ snprintf (fullname, sizeof (fullname),
+ "%s/%s",
+ dirname, de->d_name))
+ continue; /* ugh, file too long? how can this be!? */
+ if (0 != stat (fullname, &sbuf))
+ continue; /* ugh, failed to 'stat' */
+ if (! S_ISREG (sbuf.st_mode))
+ continue; /* not a regular file, skip */
+ if (rdc->off + 1024 > rdc->buf_len)
+ {
+ void *r;
+
+ if ( (2 * rdc->buf_len + 1024) < rdc->buf_len)
+ break; /* more than SIZE_T _index_ size? Too big for us */
+ rdc->buf_len = 2 * rdc->buf_len + 1024;
+ if (NULL == (r = realloc (rdc->buf, rdc->buf_len)))
+ break; /* out of memory */
+ rdc->buf = r;
+ }
+ rdc->off += snprintf (&rdc->buf[rdc->off],
+ rdc->buf_len - rdc->off,
+ "<li><a href=\"/%s\">%s</a></li>\n",
+ fullname,
+ de->d_name);
+ }
+ (void) closedir (dir);
+ return MHD_YES;
+}
+
+
+/**
+ * Re-scan our local directory and re-build the index.
+ */
+static void
+update_directory ()
+{
+ static size_t initial_allocation = 32 * 1024; /* initial size for response buffer */
+ struct MHD_Response *response;
+ struct ResponseDataContext rdc;
+ unsigned int language_idx;
+ unsigned int category_idx;
+ const struct Language *language;
+ const char *category;
+ char dir_name[128];
+ struct stat sbuf;
+
+ rdc.buf_len = initial_allocation;
+ if (NULL == (rdc.buf = malloc (rdc.buf_len)))
+ {
+ update_cached_response (NULL);
+ return;
+ }
+ rdc.off = snprintf (rdc.buf, rdc.buf_len,
+ "%s",
+ INDEX_PAGE_HEADER);
+ for (language_idx = 0; NULL != languages[language_idx].dirname; language_idx++)
+ {
+ language = &languages[language_idx];
+
+ if (0 != stat (language->dirname, &sbuf))
+ continue; /* empty */
+ /* we ensured always +1k room, filenames are ~256 bytes,
+ so there is always still enough space for the header
+ without need for an additional reallocation check. */
+ rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
+ "<h2>%s</h2>\n",
+ language->longname);
+ for (category_idx = 0; NULL != categories[category_idx]; category_idx++)
+ {
+ category = categories[category_idx];
+ snprintf (dir_name, sizeof (dir_name),
+ "%s/%s",
+ language->dirname,
+ category);
+ if (0 != stat (dir_name, &sbuf))
+ continue; /* empty */
+
+ /* we ensured always +1k room, filenames are ~256 bytes,
+ so there is always still enough space for the header
+ without need for an additional reallocation check. */
+ rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
+ "<h3>%s</h3>\n",
+ category);
+
+ if (MHD_NO == list_directory (&rdc, dir_name))
+ {
+ free (rdc.buf);
+ update_cached_response (NULL);
+ return;
+ }
+ }
+ }
+ /* we ensured always +1k room, filenames are ~256 bytes,
+ so there is always still enough space for the footer
+ without need for a final reallocation check. */
+ rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
+ "%s",
+ INDEX_PAGE_FOOTER);
+ initial_allocation = rdc.buf_len; /* remember for next time */
+ response = MHD_create_response_from_buffer (rdc.off,
+ rdc.buf,
+ MHD_RESPMEM_MUST_FREE);
+ mark_as_html (response);
+#if FORCE_CLOSE
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONNECTION,
+ "close");
+#endif
+ update_cached_response (response);
+}
+
+
+/**
+ * Context we keep for an upload.
+ */
+struct UploadContext
+{
+ /**
+ * Handle where we write the uploaded file to.
+ */
+ int fd;
+
+ /**
+ * Name of the file on disk (used to remove on errors).
+ */
+ char *filename;
+
+ /**
+ * Language for the upload.
+ */
+ char *language;
+
+ /**
+ * Category for the upload.
+ */
+ char *category;
+
+ /**
+ * Post processor we're using to process the upload.
+ */
+ struct MHD_PostProcessor *pp;
+
+ /**
+ * Handle to connection that we're processing the upload for.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Response to generate, NULL to use directory.
+ */
+ struct MHD_Response *response;
+};
+
+
+/**
+ * Append the 'size' bytes from 'data' to '*ret', adding
+ * 0-termination. If '*ret' is NULL, allocate an empty string first.
+ *
+ * @param ret string to update, NULL or 0-terminated
+ * @param data data to append
+ * @param size number of bytes in 'data'
+ * @return MHD_NO on allocation failure, MHD_YES on success
+ */
+static int
+do_append (char **ret,
+ const char *data,
+ size_t size)
+{
+ char *buf;
+ size_t old_len;
+
+ if (NULL == *ret)
+ old_len = 0;
+ else
+ old_len = strlen (*ret);
+ buf = malloc (old_len + size + 1);
+ if (NULL == buf)
+ return MHD_NO;
+ memcpy (buf, *ret, old_len);
+ if (NULL != *ret)
+ free (*ret);
+ memcpy (&buf[old_len], data, size);
+ buf[old_len + size] = '\0';
+ *ret = buf;
+ return MHD_YES;
+}
+
+
+/**
+ * Iterator over key-value pairs where the value
+ * maybe made available in increments and/or may
+ * not be zero-terminated. Used for processing
+ * POST data.
+ *
+ * @param cls user-specified closure
+ * @param kind type of the value, always MHD_POSTDATA_KIND when called from MHD
+ * @param key 0-terminated key for the value
+ * @param filename name of the uploaded file, NULL if not known
+ * @param content_type mime-type of the data, NULL if not known
+ * @param transfer_encoding encoding of the data, NULL if not known
+ * @param data pointer to size bytes of data at the
+ * specified offset
+ * @param off offset of data in the overall value
+ * @param size number of bytes in data available
+ * @return MHD_YES to continue iterating,
+ * MHD_NO to abort the iteration
+ */
+static int
+process_upload_data (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *data,
+ uint64_t off,
+ size_t size)
+{
+ struct UploadContext *uc = cls;
+ int i;
+
+ if (0 == strcmp (key, "category"))
+ return do_append (&uc->category, data, size);
+ if (0 == strcmp (key, "language"))
+ return do_append (&uc->language, data, size);
+ if (0 != strcmp (key, "upload"))
+ {
+ fprintf (stderr,
+ "Ignoring unexpected form value `%s'\n",
+ key);
+ return MHD_YES; /* ignore */
+ }
+ if (NULL == filename)
+ {
+ fprintf (stderr, "No filename, aborting upload\n");
+ return MHD_NO; /* no filename, error */
+ }
+ if ( (NULL == uc->category) ||
+ (NULL == uc->language) )
+ {
+ fprintf (stderr,
+ "Missing form data for upload `%s'\n",
+ filename);
+ uc->response = request_refused_response;
+ return MHD_NO;
+ }
+ if (-1 == uc->fd)
+ {
+ char fn[PATH_MAX];
+
+ if ( (NULL != strstr (filename, "..")) ||
+ (NULL != strchr (filename, '/')) ||
+ (NULL != strchr (filename, '\\')) )
+ {
+ uc->response = request_refused_response;
+ return MHD_NO;
+ }
+ /* create directories -- if they don't exist already */
+#ifdef WINDOWS
+ (void) mkdir (uc->language);
+#else
+ (void) mkdir (uc->language, S_IRWXU);
+#endif
+ snprintf (fn, sizeof (fn),
+ "%s/%s",
+ uc->language,
+ uc->category);
+#ifdef WINDOWS
+ (void) mkdir (fn);
+#else
+ (void) mkdir (fn, S_IRWXU);
+#endif
+ /* open file */
+ snprintf (fn, sizeof (fn),
+ "%s/%s/%s",
+ uc->language,
+ uc->category,
+ filename);
+ for (i=strlen (fn)-1;i>=0;i--)
+ if (! isprint ((int) fn[i]))
+ fn[i] = '_';
+ uc->fd = open (fn,
+ O_CREAT | O_EXCL
+#if O_LARGEFILE
+ | O_LARGEFILE
+#endif
+ | O_WRONLY,
+ S_IRUSR | S_IWUSR);
+ if (-1 == uc->fd)
+ {
+ fprintf (stderr,
+ "Error opening file `%s' for upload: %s\n",
+ fn,
+ strerror (errno));
+ uc->response = request_refused_response;
+ return MHD_NO;
+ }
+ uc->filename = strdup (fn);
+ }
+ if ( (0 != size) &&
+ (size != (size_t) write (uc->fd, data, size)) )
+ {
+ /* write failed; likely: disk full */
+ fprintf (stderr,
+ "Error writing to file `%s': %s\n",
+ uc->filename,
+ strerror (errno));
+ uc->response = internal_error_response;
+ close (uc->fd);
+ uc->fd = -1;
+ if (NULL != uc->filename)
+ {
+ unlink (uc->filename);
+ free (uc->filename);
+ uc->filename = NULL;
+ }
+ return MHD_NO;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Function called whenever a request was completed.
+ * Used to clean up 'struct UploadContext' objects.
+ *
+ * @param cls client-defined closure, NULL
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the MHD_AccessHandlerCallback, points to NULL if this was
+ * not an upload
+ * @param toe reason for request termination
+ */
+static void
+response_completed_callback (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct UploadContext *uc = *con_cls;
+
+ if (NULL == uc)
+ return; /* this request wasn't an upload request */
+ if (NULL != uc->pp)
+ {
+ MHD_destroy_post_processor (uc->pp);
+ uc->pp = NULL;
+ }
+ if (-1 != uc->fd)
+ {
+ (void) close (uc->fd);
+ if (NULL != uc->filename)
+ {
+ fprintf (stderr,
+ "Upload of file `%s' failed (incomplete or aborted), removing file.\n",
+ uc->filename);
+ (void) unlink (uc->filename);
+ }
+ }
+ if (NULL != uc->filename)
+ free (uc->filename);
+ free (uc);
+}
+
+
+/**
+ * Return the current directory listing.
+ *
+ * @param connection connection to return the directory for
+ * @return MHD_YES on success, MHD_NO on error
+ */
+static int
+return_directory_response (struct MHD_Connection *connection)
+{
+ int ret;
+
+ (void) pthread_mutex_lock (&mutex);
+ if (NULL == cached_directory_response)
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ internal_error_response);
+ else
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ cached_directory_response);
+ (void) pthread_mutex_unlock (&mutex);
+ return ret;
+}
+
+
+/**
+ * Main callback from MHD, used to generate the page.
+ *
+ * @param cls NULL
+ * @param connection connection handle
+ * @param url requested URL
+ * @param method GET, PUT, POST, etc.
+ * @param version HTTP version
+ * @param upload_data data from upload (PUT/POST)
+ * @param upload_data_size number of bytes in "upload_data"
+ * @param ptr our context
+ * @return MHD_YES on success, MHD_NO to drop connection
+ */
+static int
+generate_page (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size, void **ptr)
+{
+ struct MHD_Response *response;
+ int ret;
+ int fd;
+ struct stat buf;
+
+ if (0 != strcmp (url, "/"))
+ {
+ /* should be file download */
+ char file_data[MAGIC_HEADER_SIZE];
+ ssize_t got;
+ const char *mime;
+
+ if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
+ return MHD_NO; /* unexpected method (we're not polite...) */
+ if ( (0 == stat (&url[1], &buf)) &&
+ (NULL == strstr (&url[1], "..")) &&
+ ('/' != url[1]))
+ fd = open (&url[1], O_RDONLY);
+ else
+ fd = -1;
+ if (-1 == fd)
+ return MHD_queue_response (connection,
+ MHD_HTTP_NOT_FOUND,
+ file_not_found_response);
+ /* read beginning of the file to determine mime type */
+ got = read (fd, file_data, sizeof (file_data));
+ if (-1 != got)
+ mime = magic_buffer (magic, file_data, got);
+ else
+ mime = NULL;
+ (void) lseek (fd, 0, SEEK_SET);
+
+ if (NULL == (response = MHD_create_response_from_fd (buf.st_size,
+ fd)))
+ {
+ /* internal error (i.e. out of memory) */
+ (void) close (fd);
+ return MHD_NO;
+ }
+
+ /* add mime type if we had one */
+ if (NULL != mime)
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+ }
+
+ if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
+ {
+ /* upload! */
+ struct UploadContext *uc = *ptr;
+
+ if (NULL == uc)
+ {
+ if (NULL == (uc = malloc (sizeof (struct UploadContext))))
+ return MHD_NO; /* out of memory, close connection */
+ memset (uc, 0, sizeof (struct UploadContext));
+ uc->fd = -1;
+ uc->connection = connection;
+ uc->pp = MHD_create_post_processor (connection,
+ 64 * 1024 /* buffer size */,
+ &process_upload_data, uc);
+ if (NULL == uc->pp)
+ {
+ /* out of memory, close connection */
+ free (uc);
+ return MHD_NO;
+ }
+ *ptr = uc;
+ return MHD_YES;
+ }
+ if (0 != *upload_data_size)
+ {
+ if (NULL == uc->response)
+ (void) MHD_post_process (uc->pp,
+ upload_data,
+ *upload_data_size);
+ *upload_data_size = 0;
+ return MHD_YES;
+ }
+ /* end of upload, finish it! */
+ MHD_destroy_post_processor (uc->pp);
+ uc->pp = NULL;
+ if (-1 != uc->fd)
+ {
+ close (uc->fd);
+ uc->fd = -1;
+ }
+ if (NULL != uc->response)
+ {
+ return MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ uc->response);
+ }
+ else
+ {
+ update_directory ();
+ return return_directory_response (connection);
+ }
+ }
+ if (0 == strcmp (method, MHD_HTTP_METHOD_GET))
+ {
+ return return_directory_response (connection);
+ }
+
+ /* unexpected request, refuse */
+ return MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ request_refused_response);
+}
+
+
+/**
+ * Function called if we get a SIGPIPE. Does nothing.
+ *
+ * @param sig will be SIGPIPE (ignored)
+ */
+static void
+catcher (int sig)
+{
+ /* do nothing */
+}
+
+
+/**
+ * setup handlers to ignore SIGPIPE.
+ */
+#ifndef MINGW
+static void
+ignore_sigpipe ()
+{
+ struct sigaction oldsig;
+ struct sigaction sig;
+
+ sig.sa_handler = &catcher;
+ sigemptyset (&sig.sa_mask);
+#ifdef SA_INTERRUPT
+ sig.sa_flags = SA_INTERRUPT; /* SunOS */
+#else
+ sig.sa_flags = SA_RESTART;
+#endif
+ if (0 != sigaction (SIGPIPE, &sig, &oldsig))
+ fprintf (stderr,
+ "Failed to install SIGPIPE handler: %s\n", strerror (errno));
+}
+#endif
+
+/* test server key */
+const char srv_signed_key_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEowIBAAKCAQEAvfTdv+3fgvVTKRnP/HVNG81cr8TrUP/iiyuve/THMzvFXhCW\n"
+ "+K03KwEku55QvnUndwBfU/ROzLlv+5hotgiDRNFT3HxurmhouySBrJNJv7qWp8IL\n"
+ "q4sw32vo0fbMu5BZF49bUXK9L3kW2PdhTtSQPWHEzNrCxO+YgCilKHkY3vQNfdJ0\n"
+ "20Q5EAAEseD1YtWCIpRvJzYlZMpjYB1ubTl24kwrgOKUJYKqM4jmF4DVQp4oOK/6\n"
+ "QYGGh1QmHRPAy3CBII6sbb+sZT9cAqU6GYQVB35lm4XAgibXV6KgmpVxVQQ69U6x\n"
+ "yoOl204xuekZOaG9RUPId74Rtmwfi1TLbBzo2wIDAQABAoIBADu09WSICNq5cMe4\n"
+ "+NKCLlgAT1NiQpLls1gKRbDhKiHU9j8QWNvWWkJWrCya4QdUfLCfeddCMeiQmv3K\n"
+ "lJMvDs+5OjJSHFoOsGiuW2Ias7IjnIojaJalfBml6frhJ84G27IXmdz6gzOiTIer\n"
+ "DjeAgcwBaKH5WwIay2TxIaScl7AwHBauQkrLcyb4hTmZuQh6ArVIN6+pzoVuORXM\n"
+ "bpeNWl2l/HSN3VtUN6aCAKbN/X3o0GavCCMn5Fa85uJFsab4ss/uP+2PusU71+zP\n"
+ "sBm6p/2IbGvF5k3VPDA7X5YX61sukRjRBihY8xSnNYx1UcoOsX6AiPnbhifD8+xQ\n"
+ "Tlf8oJUCgYEA0BTfzqNpr9Wxw5/QXaSdw7S/0eP5a0C/nwURvmfSzuTD4equzbEN\n"
+ "d+dI/s2JMxrdj/I4uoAfUXRGaabevQIjFzC9uyE3LaOyR2zhuvAzX+vVcs6bSXeU\n"
+ "pKpCAcN+3Z3evMaX2f+z/nfSUAl2i4J2R+/LQAWJW4KwRky/m+cxpfUCgYEA6bN1\n"
+ "b73bMgM8wpNt6+fcmS+5n0iZihygQ2U2DEud8nZJL4Nrm1dwTnfZfJBnkGj6+0Q0\n"
+ "cOwj2KS0/wcEdJBP0jucU4v60VMhp75AQeHqidIde0bTViSRo3HWKXHBIFGYoU3T\n"
+ "LyPyKndbqsOObnsFXHn56Nwhr2HLf6nw4taGQY8CgYBoSW36FLCNbd6QGvLFXBGt\n"
+ "2lMhEM8az/K58kJ4WXSwOLtr6MD/WjNT2tkcy0puEJLm6BFCd6A6pLn9jaKou/92\n"
+ "SfltZjJPb3GUlp9zn5tAAeSSi7YMViBrfuFiHObij5LorefBXISLjuYbMwL03MgH\n"
+ "Ocl2JtA2ywMp2KFXs8GQWQKBgFyIVv5ogQrbZ0pvj31xr9HjqK6d01VxIi+tOmpB\n"
+ "4ocnOLEcaxX12BzprW55ytfOCVpF1jHD/imAhb3YrHXu0fwe6DXYXfZV4SSG2vB7\n"
+ "IB9z14KBN5qLHjNGFpMQXHSMek+b/ftTU0ZnPh9uEM5D3YqRLVd7GcdUhHvG8P8Q\n"
+ "C9aXAoGBAJtID6h8wOGMP0XYX5YYnhlC7dOLfk8UYrzlp3xhqVkzKthTQTj6wx9R\n"
+ "GtC4k7U1ki8oJsfcIlBNXd768fqDVWjYju5rzShMpo8OCTS6ipAblKjCxPPVhIpv\n"
+ "tWPlbSn1qj6wylstJ5/3Z+ZW5H4wIKp5jmLiioDhcP0L/Ex3Zx8O\n"
+ "-----END RSA PRIVATE KEY-----\n";
+
+/* test server CA signed certificates */
+const char srv_signed_cert_pem[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIDGzCCAgWgAwIBAgIES0KCvTALBgkqhkiG9w0BAQUwFzEVMBMGA1UEAxMMdGVz\n"
+ "dF9jYV9jZXJ0MB4XDTEwMDEwNTAwMDcyNVoXDTQ1MDMxMjAwMDcyNVowFzEVMBMG\n"
+ "A1UEAxMMdGVzdF9jYV9jZXJ0MIIBHzALBgkqhkiG9w0BAQEDggEOADCCAQkCggEA\n"
+ "vfTdv+3fgvVTKRnP/HVNG81cr8TrUP/iiyuve/THMzvFXhCW+K03KwEku55QvnUn\n"
+ "dwBfU/ROzLlv+5hotgiDRNFT3HxurmhouySBrJNJv7qWp8ILq4sw32vo0fbMu5BZ\n"
+ "F49bUXK9L3kW2PdhTtSQPWHEzNrCxO+YgCilKHkY3vQNfdJ020Q5EAAEseD1YtWC\n"
+ "IpRvJzYlZMpjYB1ubTl24kwrgOKUJYKqM4jmF4DVQp4oOK/6QYGGh1QmHRPAy3CB\n"
+ "II6sbb+sZT9cAqU6GYQVB35lm4XAgibXV6KgmpVxVQQ69U6xyoOl204xuekZOaG9\n"
+ "RUPId74Rtmwfi1TLbBzo2wIDAQABo3YwdDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM\n"
+ "MAoGCCsGAQUFBwMBMA8GA1UdDwEB/wQFAwMHIAAwHQYDVR0OBBYEFOFi4ilKOP1d\n"
+ "XHlWCMwmVKr7mgy8MB8GA1UdIwQYMBaAFP2olB4s2T/xuoQ5pT2RKojFwZo2MAsG\n"
+ "CSqGSIb3DQEBBQOCAQEAHVWPxazupbOkG7Did+dY9z2z6RjTzYvurTtEKQgzM2Vz\n"
+ "GQBA+3pZ3c5mS97fPIs9hZXfnQeelMeZ2XP1a+9vp35bJjZBBhVH+pqxjCgiUflg\n"
+ "A3Zqy0XwwVCgQLE2HyaU3DLUD/aeIFK5gJaOSdNTXZLv43K8kl4cqDbMeRpVTbkt\n"
+ "YmG4AyEOYRNKGTqMEJXJoxD5E3rBUNrVI/XyTjYrulxbNPcMWEHKNeeqWpKDYTFo\n"
+ "Bb01PCthGXiq/4A2RLAFosadzRa8SBpoSjPPfZ0b2w4MJpReHqKbR5+T2t6hzml6\n"
+ "4ToyOKPDmamiTuN5KzLN3cw7DQlvWMvqSOChPLnA3Q==\n"
+ "-----END CERTIFICATE-----\n";
+
+
+/**
+ * Entry point to demo. Note: this HTTP server will make all
+ * files in the current directory and its subdirectories available
+ * to anyone. Press ENTER to stop the server once it has started.
+ *
+ * @param argc number of arguments in argv
+ * @param argv first and only argument should be the port number
+ * @return 0 on success
+ */
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+ unsigned int port;
+
+ if ( (argc != 2) ||
+ (1 != sscanf (argv[1], "%u", &port)) ||
+ (UINT16_MAX < port) )
+ {
+ fprintf (stderr,
+ "%s PORT\n", argv[0]);
+ return 1;
+ }
+ #ifndef MINGW
+ ignore_sigpipe ();
+ #endif
+ magic = magic_open (MAGIC_MIME_TYPE);
+ (void) magic_load (magic, NULL);
+
+ (void) pthread_mutex_init (&mutex, NULL);
+ file_not_found_response = MHD_create_response_from_buffer (strlen (FILE_NOT_FOUND_PAGE),
+ (void *) FILE_NOT_FOUND_PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ mark_as_html (file_not_found_response);
+ request_refused_response = MHD_create_response_from_buffer (strlen (REQUEST_REFUSED_PAGE),
+ (void *) REQUEST_REFUSED_PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ mark_as_html (request_refused_response);
+ internal_error_response = MHD_create_response_from_buffer (strlen (INTERNAL_ERROR_PAGE),
+ (void *) INTERNAL_ERROR_PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ mark_as_html (internal_error_response);
+ update_directory ();
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | MHD_USE_SSL
+#if EPOLL_SUPPORT
+ | MHD_USE_EPOLL_LINUX_ONLY
+#endif
+ ,
+ port,
+ NULL, NULL,
+ &generate_page, NULL,
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024),
+#if PRODUCTION
+ MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64),
+#endif
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) (120 /* seconds */),
+ MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) NUMBER_OF_THREADS,
+ MHD_OPTION_NOTIFY_COMPLETED, &response_completed_callback, NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem,
+ MHD_OPTION_END);
+ if (NULL == d)
+ return 1;
+ fprintf (stderr, "HTTP server running. Press ENTER to stop the server\n");
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ MHD_destroy_response (file_not_found_response);
+ MHD_destroy_response (request_refused_response);
+ MHD_destroy_response (internal_error_response);
+ update_cached_response (NULL);
+ (void) pthread_mutex_destroy (&mutex);
+ magic_close (magic);
+ return 0;
+}
+
+/* end of demo_https.c */
diff --git a/src/examples/digest_auth_example.c b/src/examples/digest_auth_example.c
new file mode 100644
index 0000000..62c99cf
--- /dev/null
+++ b/src/examples/digest_auth_example.c
@@ -0,0 +1,140 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2010 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file digest_auth_example.c
+ * @brief minimal example for how to use digest auth with libmicrohttpd
+ * @author Amr Ali
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+#include <stdlib.h>
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>Access granted</body></html>"
+
+#define DENIED "<html><head><title>libmicrohttpd demo</title></head><body>Access denied</body></html>"
+
+#define MY_OPAQUE_STR "11733b200778ce33060f31c9af70a870ba96ddd4"
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ struct MHD_Response *response;
+ char *username;
+ const char *password = "testpass";
+ const char *realm = "test@example.com";
+ int ret;
+
+ username = MHD_digest_auth_get_username(connection);
+ if (username == NULL)
+ {
+ response = MHD_create_response_from_buffer(strlen (DENIED),
+ DENIED,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_auth_fail_response(connection, realm,
+ MY_OPAQUE_STR,
+ response,
+ MHD_NO);
+ MHD_destroy_response(response);
+ return ret;
+ }
+ ret = MHD_digest_auth_check(connection, realm,
+ username,
+ password,
+ 300);
+ free(username);
+ if ( (ret == MHD_INVALID_NONCE) ||
+ (ret == MHD_NO) )
+ {
+ response = MHD_create_response_from_buffer(strlen (DENIED),
+ DENIED,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ return MHD_NO;
+ ret = MHD_queue_auth_fail_response(connection, realm,
+ MY_OPAQUE_STR,
+ response,
+ (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO);
+ MHD_destroy_response(response);
+ return ret;
+ }
+ response = MHD_create_response_from_buffer(strlen(PAGE), PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ int fd;
+ char rnd[8];
+ ssize_t len;
+ size_t off;
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ fd = open("/dev/urandom", O_RDONLY);
+ if (-1 == fd)
+ {
+ fprintf (stderr, "Failed to open `%s': %s\n",
+ "/dev/urandom",
+ strerror (errno));
+ return 1;
+ }
+ off = 0;
+ while (off < 8)
+ {
+ len = read(fd, rnd, 8);
+ if (len == -1)
+ {
+ fprintf (stderr, "Failed to read `%s': %s\n",
+ "/dev/urandom",
+ strerror (errno));
+ (void) close (fd);
+ return 1;
+ }
+ off += len;
+ }
+ (void) close(fd);
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, PAGE,
+ MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof(rnd), rnd,
+ MHD_OPTION_NONCE_NC_SIZE, 300,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+/* end of digest_auth_example.c */
diff --git a/src/examples/dual_stack_example.c b/src/examples/dual_stack_example.c
new file mode 100644
index 0000000..12d50f4
--- /dev/null
+++ b/src/examples/dual_stack_example.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2012 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file dual_stack_example.c
+ * @brief how to use MHD with both IPv4 and IPv6 support (dual-stack)
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>libmicrohttpd demo</body></html>"
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (method, "GET"))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ response = MHD_create_response_from_buffer (strlen (me),
+ (void *) me,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | MHD_USE_DUAL_STACK,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, PAGE,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
+ MHD_OPTION_END);
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/fileserver_example.c b/src/examples/fileserver_example.c
new file mode 100644
index 0000000..8f5223a
--- /dev/null
+++ b/src/examples/fileserver_example.c
@@ -0,0 +1,119 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file fileserver_example.c
+ * @brief minimal example for how to use libmicrohttpd to serve files
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+#include <unistd.h>
+
+#define PAGE "<html><head><title>File not found</title></head><body>File not found</body></html>"
+
+static ssize_t
+file_reader (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ FILE *file = cls;
+
+ (void) fseek (file, pos, SEEK_SET);
+ return fread (buf, 1, max, file);
+}
+
+static void
+free_callback (void *cls)
+{
+ FILE *file = cls;
+ fclose (file);
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ struct MHD_Response *response;
+ int ret;
+ FILE *file;
+ struct stat buf;
+
+ if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ if (0 == stat (&url[1], &buf))
+ file = fopen (&url[1], "rb");
+ else
+ file = NULL;
+ if (file == NULL)
+ {
+ response = MHD_create_response_from_buffer (strlen (PAGE),
+ (void *) PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response);
+ MHD_destroy_response (response);
+ }
+ else
+ {
+ response = MHD_create_response_from_callback (buf.st_size, 32 * 1024, /* 32k page size */
+ &file_reader,
+ file,
+ &free_callback);
+ if (response == NULL)
+ {
+ fclose (file);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ }
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, PAGE, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/fileserver_example_dirs.c b/src/examples/fileserver_example_dirs.c
new file mode 100644
index 0000000..9d4a193
--- /dev/null
+++ b/src/examples/fileserver_example_dirs.c
@@ -0,0 +1,179 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file fileserver_example.c
+ * @brief example for how to use libmicrohttpd to serve files (with directory support)
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <dirent.h>
+#include <microhttpd.h>
+#include <unistd.h>
+
+#define PAGE "<html><head><title>File not found</title></head><body>File not found</body></html>"
+
+static ssize_t
+file_reader (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ FILE *file = cls;
+
+ (void) fseek (file, pos, SEEK_SET);
+ return fread (buf, 1, max, file);
+}
+
+static void
+file_free_callback (void *cls)
+{
+ FILE *file = cls;
+ fclose (file);
+}
+
+static void
+dir_free_callback (void *cls)
+{
+ DIR *dir = cls;
+ if (dir != NULL)
+ closedir (dir);
+}
+
+static ssize_t
+dir_reader (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ DIR *dir = cls;
+ struct dirent *e;
+
+ if (max < 512)
+ return 0;
+ do
+ {
+ e = readdir (dir);
+ if (e == NULL)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ } while (e->d_name[0] == '.');
+ return snprintf (buf, max,
+ "<a href=\"/%s\">%s</a><br>",
+ e->d_name,
+ e->d_name);
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ struct MHD_Response *response;
+ int ret;
+ FILE *file;
+ DIR *dir;
+ struct stat buf;
+ char emsg[1024];
+
+ if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ if ( (0 == stat (&url[1], &buf)) &&
+ (S_ISREG (buf.st_mode)) )
+ file = fopen (&url[1], "rb");
+ else
+ file = NULL;
+ if (file == NULL)
+ {
+ dir = opendir (".");
+ if (dir == NULL)
+ {
+ /* most likely cause: more concurrent requests than
+ available file descriptors / 2 */
+ snprintf (emsg,
+ sizeof (emsg),
+ "Failed to open directory `.': %s\n",
+ strerror (errno));
+ response = MHD_create_response_from_buffer (strlen (emsg),
+ emsg,
+ MHD_RESPMEM_MUST_COPY);
+ if (response == NULL)
+ return MHD_NO;
+ ret = MHD_queue_response (connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
+ MHD_destroy_response (response);
+ }
+ else
+ {
+ response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ 32 * 1024,
+ &dir_reader,
+ dir,
+ &dir_free_callback);
+ if (response == NULL)
+ {
+ closedir (dir);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ }
+ }
+ else
+ {
+ response = MHD_create_response_from_callback (buf.st_size, 32 * 1024, /* 32k page size */
+ &file_reader,
+ file,
+ &file_free_callback);
+ if (response == NULL)
+ {
+ fclose (file);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ }
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, PAGE, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/fileserver_example_external_select.c b/src/examples/fileserver_example_external_select.c
new file mode 100644
index 0000000..7a27061
--- /dev/null
+++ b/src/examples/fileserver_example_external_select.c
@@ -0,0 +1,149 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file fileserver_example_external_select.c
+ * @brief minimal example for how to use libmicrohttpd to server files
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define PAGE "<html><head><title>File not found</title></head><body>File not found</body></html>"
+
+static ssize_t
+file_reader (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ FILE *file = cls;
+
+ (void) fseek (file, pos, SEEK_SET);
+ return fread (buf, 1, max, file);
+}
+
+static void
+free_callback (void *cls)
+{
+ FILE *file = cls;
+ fclose (file);
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ struct MHD_Response *response;
+ int ret;
+ FILE *file;
+ struct stat buf;
+
+ if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ if ( (0 == stat (&url[1], &buf)) &&
+ (S_ISREG (buf.st_mode)) )
+ file = fopen (&url[1], "rb");
+ else
+ file = NULL;
+ if (file == NULL)
+ {
+ response = MHD_create_response_from_buffer (strlen (PAGE),
+ (void *) PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response);
+ MHD_destroy_response (response);
+ }
+ else
+ {
+ response = MHD_create_response_from_callback (buf.st_size, 32 * 1024, /* 32k page size */
+ &file_reader,
+ file,
+ &free_callback);
+ if (response == NULL)
+ {
+ fclose (file);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ }
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+ time_t end;
+ time_t t;
+ struct timeval tv;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ MHD_UNSIGNED_LONG_LONG mhd_timeout;
+
+ if (argc != 3)
+ {
+ printf ("%s PORT SECONDS-TO-RUN\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, PAGE, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ end = time (NULL) + atoi (argv[2]);
+ while ((t = time (NULL)) < end)
+ {
+ tv.tv_sec = end - t;
+ tv.tv_usec = 0;
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ break; /* fatal internal error */
+ if (MHD_get_timeout (d, &mhd_timeout) == MHD_YES)
+ {
+ if (((MHD_UNSIGNED_LONG_LONG)tv.tv_sec) < mhd_timeout / 1000LL)
+ {
+ tv.tv_sec = mhd_timeout / 1000LL;
+ tv.tv_usec = (mhd_timeout - (tv.tv_sec * 1000LL)) * 1000LL;
+ }
+ }
+ select (max + 1, &rs, &ws, &es, &tv);
+ MHD_run (d);
+ }
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/https_fileserver_example.c b/src/examples/https_fileserver_example.c
new file mode 100644
index 0000000..fe0c2de
--- /dev/null
+++ b/src/examples/https_fileserver_example.c
@@ -0,0 +1,207 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file https_fileserver_example.c
+ * @brief a simple HTTPS file server using TLS.
+ *
+ * Usage :
+ *
+ * 'http_fileserver_example HTTP-PORT SECONDS-TO-RUN'
+ *
+ * The certificate & key are required by the server to operate, Omitting the
+ * path arguments will cause the server to use the hard coded example certificate & key.
+ *
+ * 'certtool' may be used to generate these if required.
+ *
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+#include <sys/stat.h>
+#include <gnutls/gnutls.h>
+#include <gcrypt.h>
+
+#define BUF_SIZE 1024
+#define MAX_URL_LEN 255
+
+// TODO remove if unused
+#define CAFILE "ca.pem"
+#define CRLFILE "crl.pem"
+
+#define EMPTY_PAGE "<html><head><title>File not found</title></head><body>File not found</body></html>"
+
+/* Test Certificate */
+const char cert_pem[] =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIICpjCCAZCgAwIBAgIESEPtjjALBgkqhkiG9w0BAQUwADAeFw0wODA2MDIxMjU0\n"
+ "MzhaFw0wOTA2MDIxMjU0NDZaMAAwggEfMAsGCSqGSIb3DQEBAQOCAQ4AMIIBCQKC\n"
+ "AQC03TyUvK5HmUAirRp067taIEO4bibh5nqolUoUdo/LeblMQV+qnrv/RNAMTx5X\n"
+ "fNLZ45/kbM9geF8qY0vsPyQvP4jumzK0LOJYuIwmHaUm9vbXnYieILiwCuTgjaud\n"
+ "3VkZDoQ9fteIo+6we9UTpVqZpxpbLulBMh/VsvX0cPJ1VFC7rT59o9hAUlFf9jX/\n"
+ "GmKdYI79MtgVx0OPBjmmSD6kicBBfmfgkO7bIGwlRtsIyMznxbHu6VuoX/eVxrTv\n"
+ "rmCwgEXLWRZ6ru8MQl5YfqeGXXRVwMeXU961KefbuvmEPccgCxm8FZ1C1cnDHFXh\n"
+ "siSgAzMBjC/b6KVhNQ4KnUdZAgMBAAGjLzAtMAwGA1UdEwEB/wQCMAAwHQYDVR0O\n"
+ "BBYEFJcUvpjvE5fF/yzUshkWDpdYiQh/MAsGCSqGSIb3DQEBBQOCAQEARP7eKSB2\n"
+ "RNd6XjEjK0SrxtoTnxS3nw9sfcS7/qD1+XHdObtDFqGNSjGYFB3Gpx8fpQhCXdoN\n"
+ "8QUs3/5ZVa5yjZMQewWBgz8kNbnbH40F2y81MHITxxCe1Y+qqHWwVaYLsiOTqj2/\n"
+ "0S3QjEJ9tvklmg7JX09HC4m5QRYfWBeQLD1u8ZjA1Sf1xJriomFVyRLI2VPO2bNe\n"
+ "JDMXWuP+8kMC7gEvUnJ7A92Y2yrhu3QI3bjPk8uSpHea19Q77tul1UVBJ5g+zpH3\n"
+ "OsF5p0MyaVf09GTzcLds5nE/osTdXGUyHJapWReVmPm3Zn6gqYlnzD99z+DPIgIV\n"
+ "RhZvQx74NQnS6g==\n" "-----END CERTIFICATE-----\n";
+
+const char key_pem[] =
+ "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEowIBAAKCAQEAtN08lLyuR5lAIq0adOu7WiBDuG4m4eZ6qJVKFHaPy3m5TEFf\n"
+ "qp67/0TQDE8eV3zS2eOf5GzPYHhfKmNL7D8kLz+I7psytCziWLiMJh2lJvb2152I\n"
+ "niC4sArk4I2rnd1ZGQ6EPX7XiKPusHvVE6VamacaWy7pQTIf1bL19HDydVRQu60+\n"
+ "faPYQFJRX/Y1/xpinWCO/TLYFcdDjwY5pkg+pInAQX5n4JDu2yBsJUbbCMjM58Wx\n"
+ "7ulbqF/3lca0765gsIBFy1kWeq7vDEJeWH6nhl10VcDHl1PetSnn27r5hD3HIAsZ\n"
+ "vBWdQtXJwxxV4bIkoAMzAYwv2+ilYTUOCp1HWQIDAQABAoIBAArOQv3R7gmqDspj\n"
+ "lDaTFOz0C4e70QfjGMX0sWnakYnDGn6DU19iv3GnX1S072ejtgc9kcJ4e8VUO79R\n"
+ "EmqpdRR7k8dJr3RTUCyjzf/C+qiCzcmhCFYGN3KRHA6MeEnkvRuBogX4i5EG1k5l\n"
+ "/5t+YBTZBnqXKWlzQLKoUAiMLPg0eRWh+6q7H4N7kdWWBmTpako7TEqpIwuEnPGx\n"
+ "u3EPuTR+LN6lF55WBePbCHccUHUQaXuav18NuDkcJmCiMArK9SKb+h0RqLD6oMI/\n"
+ "dKD6n8cZXeMBkK+C8U/K0sN2hFHACsu30b9XfdnljgP9v+BP8GhnB0nCB6tNBCPo\n"
+ "32srOwECgYEAxWh3iBT4lWqL6bZavVbnhmvtif4nHv2t2/hOs/CAq8iLAw0oWGZc\n"
+ "+JEZTUDMvFRlulr0kcaWra+4fN3OmJnjeuFXZq52lfMgXBIKBmoSaZpIh2aDY1Rd\n"
+ "RbEse7nQl9hTEPmYspiXLGtnAXW7HuWqVfFFP3ya8rUS3t4d07Hig8ECgYEA6ou6\n"
+ "OHiBRTbtDqLIv8NghARc/AqwNWgEc9PelCPe5bdCOLBEyFjqKiT2MttnSSUc2Zob\n"
+ "XhYkHC6zN1Mlq30N0e3Q61YK9LxMdU1vsluXxNq2rfK1Scb1oOlOOtlbV3zA3VRF\n"
+ "hV3t1nOA9tFmUrwZi0CUMWJE/zbPAyhwWotKyZkCgYEAh0kFicPdbABdrCglXVae\n"
+ "SnfSjVwYkVuGd5Ze0WADvjYsVkYBHTvhgRNnRJMg+/vWz3Sf4Ps4rgUbqK8Vc20b\n"
+ "AU5G6H6tlCvPRGm0ZxrwTWDHTcuKRVs+pJE8C/qWoklE/AAhjluWVoGwUMbPGuiH\n"
+ "6Gf1bgHF6oj/Sq7rv/VLZ8ECgYBeq7ml05YyLuJutuwa4yzQ/MXfghzv4aVyb0F3\n"
+ "QCdXR6o2IYgR6jnSewrZKlA9aPqFJrwHNR6sNXlnSmt5Fcf/RWO/qgJQGLUv3+rG\n"
+ "7kuLTNDR05azSdiZc7J89ID3Bkb+z2YkV+6JUiPq/Ei1+nDBEXb/m+/HqALU/nyj\n"
+ "P3gXeQKBgBusb8Rbd+KgxSA0hwY6aoRTPRt8LNvXdsB9vRcKKHUFQvxUWiUSS+L9\n"
+ "/Qu1sJbrUquKOHqksV5wCnWnAKyJNJlhHuBToqQTgKXjuNmVdYSe631saiI7PHyC\n"
+ "eRJ6DxULPxABytJrYCRrNqmXi5TCiqR2mtfalEMOPxz8rUU8dYyx\n"
+ "-----END RSA PRIVATE KEY-----\n";
+
+static ssize_t
+file_reader (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ FILE *file = cls;
+
+ (void) fseek (file, pos, SEEK_SET);
+ return fread (buf, 1, max, file);
+}
+
+static void
+file_free_callback (void *cls)
+{
+ FILE *file = cls;
+ fclose (file);
+}
+
+/* HTTP access handler call back */
+static int
+http_ahc (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ struct MHD_Response *response;
+ int ret;
+ FILE *file;
+ struct stat buf;
+
+ if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+
+ if ( (0 == stat (&url[1], &buf)) &&
+ (S_ISREG (buf.st_mode)) )
+ file = fopen (&url[1], "rb");
+ else
+ file = NULL;
+ if (file == NULL)
+ {
+ response = MHD_create_response_from_buffer (strlen (EMPTY_PAGE),
+ (void *) EMPTY_PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response);
+ MHD_destroy_response (response);
+ }
+ else
+ {
+ response = MHD_create_response_from_callback (buf.st_size, 32 * 1024, /* 32k PAGE_NOT_FOUND size */
+ &file_reader, file,
+ &file_free_callback);
+ if (response == NULL)
+ {
+ fclose (file);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ }
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *TLS_daemon;
+
+ if (argc == 2)
+ {
+ /* TODO check if this is truly necessary - disallow usage of the blocking /dev/random */
+ /* gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM, 0); */
+ TLS_daemon =
+ MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG |
+ MHD_USE_SSL, atoi (argv[1]), NULL, NULL, &http_ahc,
+ NULL, MHD_OPTION_CONNECTION_TIMEOUT, 256,
+ MHD_OPTION_HTTPS_MEM_KEY, key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
+ MHD_OPTION_END);
+ }
+ else
+ {
+ printf ("Usage: %s HTTP-PORT\n", argv[0]);
+ return 1;
+ }
+
+ if (TLS_daemon == NULL)
+ {
+ fprintf (stderr, "Error: failed to start TLS_daemon\n");
+ return 1;
+ }
+ else
+ {
+ printf ("MHD daemon listening on port %d\n", atoi (argv[1]));
+ }
+
+ (void) getc (stdin);
+
+ MHD_stop_daemon (TLS_daemon);
+
+ return 0;
+}
diff --git a/src/examples/mhd2spdy.c b/src/examples/mhd2spdy.c
new file mode 100644
index 0000000..a227508
--- /dev/null
+++ b/src/examples/mhd2spdy.c
@@ -0,0 +1,322 @@
+/*
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file mhd2spdy.c
+ * @brief The main file of the HTTP-to-SPDY proxy with the 'main' function
+ * and event loop. No threads are used.
+ * Currently only GET is supported.
+ * TODOs:
+ * - non blocking SSL connect
+ * - check certificate
+ * - on closing spdy session, close sockets for all requests
+ * @author Andrey Uzunov
+ */
+
+
+#include "mhd2spdy_structures.h"
+#include "mhd2spdy_spdy.h"
+#include "mhd2spdy_http.h"
+
+
+static int run = 1;
+//static int spdy_close = 0;
+
+
+static void
+catch_signal(int signal)
+{
+ (void)signal;
+ //spdy_close = 1;
+ run = 0;
+}
+
+
+void
+print_stat()
+{
+ if(!glob_opt.statistics)
+ return;
+
+ printf("--------------------------\n");
+ printf("Statistics (TLS overhead is ignored when used):\n");
+ //printf("HTTP bytes received: %lld\n", glob_stat.http_bytes_received);
+ //printf("HTTP bytes sent: %lld\n", glob_stat.http_bytes_sent);
+ printf("SPDY bytes sent: %lld\n", glob_stat.spdy_bytes_sent);
+ printf("SPDY bytes received: %lld\n", glob_stat.spdy_bytes_received);
+ printf("SPDY bytes received and dropped: %lld\n", glob_stat.spdy_bytes_received_and_dropped);
+}
+
+
+int
+run_everything ()
+{
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int maxfd = -1;
+ int maxfd_s = -1;
+ struct MHD_Daemon *daemon;
+ nfds_t spdy_npollfds = 1;
+ struct URI * spdy2http_uri = NULL;
+ struct SPDY_Connection *connection;
+ struct SPDY_Connection *connections[MAX_SPDY_CONNECTIONS];
+ struct SPDY_Connection *connection_for_delete;
+
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
+ PRINT_INFO("signal failed");
+
+ if (signal(SIGINT, catch_signal) == SIG_ERR)
+ PRINT_INFO("signal failed");
+
+ glob_opt.streams_opened = 0;
+ glob_opt.responses_pending = 0;
+ //glob_opt.global_memory = 0;
+
+ srand(time(NULL));
+
+ if(init_parse_uri(&glob_opt.uri_preg))
+ DIE("Regexp compilation failed");
+
+ if(NULL != glob_opt.spdy2http_str)
+ {
+ ret = parse_uri(&glob_opt.uri_preg, glob_opt.spdy2http_str, &spdy2http_uri);
+ if(ret != 0)
+ DIE("spdy_parse_uri failed");
+ }
+
+ SSL_load_error_strings();
+ SSL_library_init();
+ glob_opt.ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if(glob_opt.ssl_ctx == NULL) {
+ PRINT_INFO2("SSL_CTX_new %s", ERR_error_string(ERR_get_error(), NULL));
+ abort();
+ }
+ spdy_ssl_init_ssl_ctx(glob_opt.ssl_ctx, &glob_opt.spdy_proto_version);
+
+ daemon = MHD_start_daemon (
+ MHD_SUPPRESS_DATE_NO_CLOCK,
+ glob_opt.listen_port,
+ NULL, NULL, &http_cb_request, NULL,
+ MHD_OPTION_URI_LOG_CALLBACK, &http_cb_log, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &http_cb_request_completed, NULL,
+ MHD_OPTION_END);
+ if(NULL==daemon)
+ DIE("MHD_start_daemon failed");
+
+ do
+ {
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+
+ if(NULL == glob_opt.spdy_connection && NULL != glob_opt.spdy2http_str)
+ {
+ glob_opt.spdy_connection = spdy_connect(spdy2http_uri, spdy2http_uri->port, strcmp("https", spdy2http_uri->scheme)==0);
+ if(NULL == glob_opt.spdy_connection && glob_opt.only_proxy)
+ PRINT_INFO("cannot connect to the proxy");
+ }
+
+ FD_ZERO(&rs);
+ FD_ZERO(&ws);
+ FD_ZERO(&es);
+
+ ret = MHD_get_timeout(daemon, &timeoutlong);
+ if(MHD_NO == ret || timeoutlong > 5000)
+ timeout.tv_sec = 5;
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ if(MHD_NO == MHD_get_fdset (daemon,
+ &rs,
+ &ws,
+ &es,
+ &maxfd))
+ {
+ PRINT_INFO("MHD_get_fdset error");
+ }
+ assert(-1 != maxfd);
+
+ maxfd_s = spdy_get_selectfdset(
+ &rs,
+ &ws,
+ &es,
+ connections, MAX_SPDY_CONNECTIONS, &spdy_npollfds);
+ if(maxfd_s > maxfd)
+ maxfd = maxfd_s;
+
+ PRINT_INFO2("MHD timeout %lld %lld", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec);
+
+ glob_opt.spdy_data_received = false;
+
+ ret = select(maxfd+1, &rs, &ws, &es, &timeout);
+ PRINT_INFO2("timeout now %lld %lld ret is %i", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec, ret);
+
+ switch(ret)
+ {
+ case -1:
+ PRINT_INFO2("select error: %i", errno);
+ break;
+ case 0:
+ //break;
+ default:
+ PRINT_INFO("run");
+ //MHD_run_from_select(daemon,&rs, &ws, &es); //not closing FDs at some time in past
+ MHD_run(daemon);
+ spdy_run_select(&rs, &ws, &es, connections, spdy_npollfds);
+ if(glob_opt.spdy_data_received)
+ {
+ PRINT_INFO("MHD run again");
+ //MHD_run_from_select(daemon,&rs, &ws, &es); //not closing FDs at some time in past
+ MHD_run(daemon);
+ }
+ break;
+ }
+ }
+ while(run);
+
+ MHD_stop_daemon (daemon);
+
+ //TODO SSL_free brakes
+ spdy_free_connection(glob_opt.spdy_connection);
+
+ connection = glob_opt.spdy_connections_head;
+ while(NULL != connection)
+ {
+ connection_for_delete = connection;
+ connection = connection_for_delete->next;
+ glob_opt.streams_opened -= connection_for_delete->streams_opened;
+ DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection_for_delete);
+ spdy_free_connection(connection_for_delete);
+ }
+
+ free_uri(spdy2http_uri);
+
+ deinit_parse_uri(&glob_opt.uri_preg);
+
+ SSL_CTX_free(glob_opt.ssl_ctx);
+ ERR_free_strings();
+ EVP_cleanup();
+
+ PRINT_INFO2("spdy streams: %i; http requests: %i", glob_opt.streams_opened, glob_opt.responses_pending);
+ //PRINT_INFO2("memory allocated %zu bytes", glob_opt.global_memory);
+
+ print_stat();
+
+ return 0;
+}
+
+
+void
+display_usage()
+{
+ printf(
+ "Usage: mhd2spdy [-ovs] [-b <SPDY2HTTP-PROXY>] -p <PORT>\n\n"
+ "OPTIONS:\n"
+ " -p, --port Listening port.\n"
+ " -b, --backend-proxy If set, he proxy will send requests to\n"
+ " that SPDY server or proxy. Set the address\n"
+ " in the form 'http://host:port'. Use 'https'\n"
+ " for SPDY over TLS, or 'http' for plain SPDY\n"
+ " communication with the backend.\n"
+ " -o, --only-proxy If set, the proxy will always forward the\n"
+ " requests to the backend proxy. If not set,\n"
+ " the proxy will first try to establsh SPDY\n"
+ " connection to the requested server. If the\n"
+ " server does not support SPDY and TLS, the\n"
+ " backend proxy will be used for the request.\n"
+ " -v, --verbose Print debug information.\n"
+ " -s, --statistics Print simple statistics on exit.\n\n"
+
+ );
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ int getopt_ret;
+ int option_index;
+ struct option long_options[] = {
+ {"port", required_argument, 0, 'p'},
+ {"backend-proxy", required_argument, 0, 'b'},
+ {"verbose", no_argument, 0, 'v'},
+ {"only-proxy", no_argument, 0, 'o'},
+ {"statistics", no_argument, 0, 's'},
+ {0, 0, 0, 0}
+ };
+
+ while (1)
+ {
+ getopt_ret = getopt_long( argc, argv, "p:b:vos", long_options, &option_index);
+ if (getopt_ret == -1)
+ break;
+
+ switch(getopt_ret)
+ {
+ case 'p':
+ glob_opt.listen_port = atoi(optarg);
+ break;
+
+ case 'b':
+ glob_opt.spdy2http_str = strdup(optarg);
+ if(NULL == glob_opt.spdy2http_str)
+ return 1;
+ break;
+
+ case 'v':
+ glob_opt.verbose = true;
+ break;
+
+ case 'o':
+ glob_opt.only_proxy = true;
+ break;
+
+ case 's':
+ glob_opt.statistics = true;
+ break;
+
+ case 0:
+ PRINT_INFO("0 from getopt");
+ break;
+
+ case '?':
+ display_usage();
+ return 1;
+
+ default:
+ DIE("default from getopt");
+ }
+ }
+
+ if(
+ 0 == glob_opt.listen_port
+ || (glob_opt.only_proxy && NULL == glob_opt.spdy2http_str)
+ )
+ {
+ display_usage();
+ return 1;
+ }
+
+ return run_everything();
+}
diff --git a/src/examples/mhd2spdy_http.c b/src/examples/mhd2spdy_http.c
new file mode 100644
index 0000000..895f07f
--- /dev/null
+++ b/src/examples/mhd2spdy_http.c
@@ -0,0 +1,422 @@
+/*
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file mhd2spdy_http.c
+ * @brief HTTP part of the proxy. libmicrohttpd is used for the server side.
+ * @author Andrey Uzunov
+ */
+
+#include "mhd2spdy_structures.h"
+#include "mhd2spdy_http.h"
+#include "mhd2spdy_spdy.h"
+
+
+void *
+http_cb_log(void * cls,
+const char * uri)
+{
+ (void)cls;
+
+ struct HTTP_URI * http_uri;
+
+ PRINT_INFO2("log uri '%s'\n", uri);
+
+ //TODO not freed once in a while
+ if(NULL == (http_uri = au_malloc(sizeof(struct HTTP_URI ))))
+ return NULL;
+ http_uri->uri = strdup(uri);
+ return http_uri;
+}
+
+
+static int
+http_cb_iterate(void *cls,
+ enum MHD_ValueKind kind,
+ const char *name,
+ const char *value)
+{
+ (void)kind;
+
+ static char * const forbidden[] = {"Transfer-Encoding",
+ "Proxy-Connection",
+ "Keep-Alive",
+ "Connection"};
+ static int forbidden_size = 4;
+ int i;
+ struct SPDY_Headers *spdy_headers = (struct SPDY_Headers *)cls;
+
+ if(0 == strcasecmp(name, "Host"))
+ spdy_headers->nv[9] = (char *)value;
+ else
+ {
+ for(i=0; i<forbidden_size; ++i)
+ if(0 == strcasecmp(forbidden[i], name))
+ return MHD_YES;
+ spdy_headers->nv[spdy_headers->cnt++] = (char *)name;
+ spdy_headers->nv[spdy_headers->cnt++] = (char *)value;
+ }
+
+ return MHD_YES;
+}
+
+
+static ssize_t
+http_cb_response (void *cls,
+ uint64_t pos,
+ char *buffer,
+ size_t max)
+{
+ (void)pos;
+
+ int ret;
+ struct Proxy *proxy = (struct Proxy *)cls;
+ void *newbody;
+ const union MHD_ConnectionInfo *info;
+ int val = 1;
+
+ PRINT_INFO2("http_cb_response for %s", proxy->url);
+
+ if(proxy->spdy_error)
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+
+ if(0 == proxy->http_body_size && (proxy->done || !proxy->spdy_active))
+ {
+ PRINT_INFO("sent end of stream");
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+
+ if(!proxy->http_body_size)//nothing to write now
+ {
+ //flush data
+ info = MHD_get_connection_info (proxy->http_connection,
+ MHD_CONNECTION_INFO_CONNECTION_FD);
+ ret = setsockopt(info->connect_fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
+ if(ret == -1) {
+ DIE("setsockopt");
+ }
+
+ PRINT_INFO("FLUSH data");
+ return 0;
+ }
+
+ if(max >= proxy->http_body_size)
+ {
+ ret = proxy->http_body_size;
+ newbody = NULL;
+ }
+ else
+ {
+ ret = max;
+ if(NULL == (newbody = au_malloc(proxy->http_body_size - max)))
+ {
+ PRINT_INFO("no memory");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+ memcpy(newbody, proxy->http_body + max, proxy->http_body_size - max);
+ }
+ memcpy(buffer, proxy->http_body, ret);
+ free(proxy->http_body);
+ proxy->http_body = newbody;
+ proxy->http_body_size -= ret;
+
+ if(proxy->length >= 0)
+ {
+ proxy->length -= ret;
+ }
+
+ PRINT_INFO2("response_callback, size: %i",ret);
+
+ return ret;
+}
+
+
+static void
+http_cb_response_done(void *cls)
+{
+ (void)cls;
+ //TODO remove
+}
+
+int
+http_cb_request (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **ptr)
+{
+ (void)cls;
+ (void)url;
+ (void)upload_data;
+ (void)upload_data_size;
+
+ int ret;
+ struct Proxy *proxy;
+ struct SPDY_Headers spdy_headers;
+ bool with_body = false;
+ struct HTTP_URI *http_uri;
+ const char *header_value;
+
+ if (NULL == ptr || NULL == *ptr)
+ return MHD_NO;
+
+ http_uri = (struct HTTP_URI *)*ptr;
+
+ if(NULL == http_uri->proxy)
+ {
+ //first call for this request
+ if (0 != strcmp (method, MHD_HTTP_METHOD_GET) && 0 != strcmp (method, MHD_HTTP_METHOD_POST))
+ {
+ free(http_uri->uri);
+ free(http_uri);
+ PRINT_INFO2("unexpected method %s", method);
+ return MHD_NO;
+ }
+
+ if(NULL == (proxy = au_malloc(sizeof(struct Proxy))))
+ {
+ free(http_uri->uri);
+ free(http_uri);
+ PRINT_INFO("No memory");
+ return MHD_NO;
+ }
+
+ ++glob_opt.responses_pending;
+ proxy->id = rand();
+ proxy->http_active = true;
+ proxy->http_connection = connection;
+ http_uri->proxy = proxy;
+ return MHD_YES;
+ }
+
+ proxy = http_uri->proxy;
+
+ if(proxy->spdy_error || proxy->http_error)
+ return MHD_NO; // handled at different place TODO? leaks?
+
+ if(proxy->spdy_active)
+ {
+ if(0 == strcmp (method, MHD_HTTP_METHOD_POST))
+ {
+ PRINT_INFO("POST processing");
+
+ int rc= spdylay_session_resume_data(proxy->spdy_connection->session, proxy->stream_id);
+ PRINT_INFO2("rc is %i stream is %i", rc, proxy->stream_id);
+ proxy->spdy_connection->want_io |= WANT_WRITE;
+
+ if(0 == *upload_data_size)
+ {
+ PRINT_INFO("POST http EOF");
+ proxy->receiving_done = true;
+ return MHD_YES;
+ }
+
+ if(!copy_buffer(upload_data, *upload_data_size, &proxy->received_body, &proxy->received_body_size))
+ {
+ //TODO handle it better?
+ PRINT_INFO("not enough memory (malloc/realloc returned NULL)");
+ return MHD_NO;
+ }
+
+ *upload_data_size = 0;
+
+ return MHD_YES;
+ }
+
+ //already handled
+ PRINT_INFO("unnecessary call to http_cb_request");
+ return MHD_YES;
+ }
+
+ //second call for this request
+
+ PRINT_INFO2("received request for '%s %s %s'", method, http_uri->uri, version);
+
+ proxy->url = http_uri->uri;
+
+ header_value = MHD_lookup_connection_value(connection,
+ MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
+
+ with_body = 0 == strcmp (method, MHD_HTTP_METHOD_POST)
+ && (NULL == header_value || 0 != strcmp ("0", header_value));
+
+ PRINT_INFO2("body will be sent %i", with_body);
+
+ ret = parse_uri(&glob_opt.uri_preg, proxy->url, &proxy->uri);
+ if(ret != 0)
+ DIE("parse_uri failed");
+ proxy->http_uri = http_uri;
+
+ spdy_headers.num = MHD_get_connection_values (connection,
+ MHD_HEADER_KIND,
+ NULL,
+ NULL);
+ if(NULL == (spdy_headers.nv = au_malloc(((spdy_headers.num + 5) * 2 + 1) * sizeof(char *))))
+ DIE("no memory");
+ spdy_headers.nv[0] = ":method"; spdy_headers.nv[1] = method;
+ spdy_headers.nv[2] = ":path"; spdy_headers.nv[3] = proxy->uri->path_and_more;
+ spdy_headers.nv[4] = ":version"; spdy_headers.nv[5] = (char *)version;
+ spdy_headers.nv[6] = ":scheme"; spdy_headers.nv[7] = proxy->uri->scheme;
+ spdy_headers.nv[8] = ":host"; spdy_headers.nv[9] = NULL;
+ //nv[14] = NULL;
+ spdy_headers.cnt = 10;
+ MHD_get_connection_values (connection,
+ MHD_HEADER_KIND,
+ &http_cb_iterate,
+ &spdy_headers);
+
+ spdy_headers.nv[spdy_headers.cnt] = NULL;
+ if(NULL == spdy_headers.nv[9])
+ spdy_headers.nv[9] = proxy->uri->host_and_port;
+
+ if(0 != spdy_request(spdy_headers.nv, proxy, with_body))
+ {
+ free(spdy_headers.nv);
+ //free_proxy(proxy);
+
+ return MHD_NO;
+ }
+ free(spdy_headers.nv);
+
+ proxy->http_response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ 4096,
+ &http_cb_response,
+ proxy,
+ &http_cb_response_done);
+
+ if (NULL == proxy->http_response)
+ DIE("no response");
+
+ if(MHD_NO == MHD_add_response_header (proxy->http_response,
+ "Proxy-Connection", "keep-alive"))
+ PRINT_INFO("SPDY_name_value_add failed: ");
+ if(MHD_NO == MHD_add_response_header (proxy->http_response,
+ "Connection", "Keep-Alive"))
+ PRINT_INFO("SPDY_name_value_add failed: ");
+ if(MHD_NO == MHD_add_response_header (proxy->http_response,
+ "Keep-Alive", "timeout=5, max=100"))
+ PRINT_INFO("SPDY_name_value_add failed: ");
+
+ proxy->spdy_active = true;
+
+ return MHD_YES;
+}
+
+
+void
+http_create_response(struct Proxy* proxy,
+ char **nv)
+{
+ size_t i;
+
+ if(!proxy->http_active)
+ return;
+
+ for(i = 0; nv[i]; i += 2) {
+ if(0 == strcmp(":status", nv[i]))
+ {
+ char tmp[4];
+ memcpy(&tmp,nv[i+1],3);
+ tmp[3]=0;
+ proxy->status = atoi(tmp);
+ continue;
+ }
+ else if(0 == strcmp(":version", nv[i]))
+ {
+ proxy->version = nv[i+1];
+ continue;
+ }
+ else if(0 == strcmp("content-length", nv[i]))
+ {
+ continue;
+ }
+
+ char *header = *(nv+i);
+ if(MHD_NO == MHD_add_response_header (proxy->http_response,
+ header, nv[i+1]))
+ {
+ PRINT_INFO2("SPDY_name_value_add failed: '%s' '%s'", header, nv[i+1]);
+ }
+ PRINT_INFO2("adding '%s: %s'",header, nv[i+1]);
+ }
+
+ if(MHD_NO == MHD_queue_response (proxy->http_connection, proxy->status, proxy->http_response)){
+ PRINT_INFO("No queue");
+ //TODO
+ //abort();
+ proxy->http_error = true;
+ }
+
+ MHD_destroy_response (proxy->http_response);
+ proxy->http_response = NULL;
+}
+
+void
+http_cb_request_completed (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ (void)cls;
+ (void)connection;
+ struct HTTP_URI *http_uri;
+ struct Proxy *proxy;
+
+ http_uri = (struct HTTP_URI *)*con_cls;
+ if(NULL == http_uri)
+ return;
+ proxy = (struct Proxy *)http_uri->proxy;
+ assert(NULL != proxy);
+
+ PRINT_INFO2("http_cb_request_completed %i for %s; id %i",toe, http_uri->uri, proxy->id);
+
+ if(NULL != proxy->http_response)
+ {
+ MHD_destroy_response (proxy->http_response);
+ proxy->http_response = NULL;
+ }
+
+ if(proxy->spdy_active)
+ {
+ proxy->http_active = false;
+ if(MHD_REQUEST_TERMINATED_COMPLETED_OK != toe)
+ {
+ proxy->http_error = true;
+ if(proxy->stream_id > 0 /*&& NULL != proxy->spdy_connection->session*/)
+ {
+ //send RST_STREAM_STATUS_CANCEL
+ PRINT_INFO2("send rst_stream %i %i",proxy->spdy_active, proxy->stream_id );
+ spdylay_submit_rst_stream(proxy->spdy_connection->session, proxy->stream_id, 5);
+ }
+ /*else
+ {
+ DLL_remove(proxy->spdy_connection->proxies_head, proxy->spdy_connection->proxies_tail, proxy);
+ free_proxy(proxy);
+ }*/
+ }
+ }
+ else
+ {
+ PRINT_INFO2("proxy free http id %i ", proxy->id);
+ free_proxy(proxy);
+ }
+
+ --glob_opt.responses_pending;
+}
diff --git a/src/examples/mhd2spdy_http.h b/src/examples/mhd2spdy_http.h
new file mode 100644
index 0000000..89d3889
--- /dev/null
+++ b/src/examples/mhd2spdy_http.h
@@ -0,0 +1,54 @@
+/*
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file mhd2spdy_http.h
+ * @brief HTTP part of the proxy. libmicrohttpd is used for the server side.
+ * @author Andrey Uzunov
+ */
+
+#ifndef HTTP_H
+#define HTTP_H
+
+#include "mhd2spdy_structures.h"
+
+
+int
+http_cb_request (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **ptr);
+
+
+void * http_cb_log(void * cls, const char * uri);
+
+
+void
+http_create_response(struct Proxy* proxy, char **nv);
+
+
+void
+http_cb_request_completed (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe);
+
+#endif
diff --git a/src/examples/mhd2spdy_spdy.c b/src/examples/mhd2spdy_spdy.c
new file mode 100644
index 0000000..95ec43c
--- /dev/null
+++ b/src/examples/mhd2spdy_spdy.c
@@ -0,0 +1,1150 @@
+/*
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file mhd2spdy_spdy.c
+ * @brief SPDY part of the proxy. libspdylay is used for the client side.
+ * The example spdycli.c from spdylay was used as basis;
+ * however, multiple changes were made.
+ * @author Tatsuhiro Tsujikawa
+ * @author Andrey Uzunov
+ */
+
+#include "mhd2spdy_structures.h"
+#include "mhd2spdy_spdy.h"
+#include "mhd2spdy_http.h"
+
+
+/*
+ * Prints error containing the function name |func| and message |msg|
+ * and exit.
+ */
+static void
+spdy_dief(const char *func,
+ const char *msg)
+{
+ fprintf(stderr, "FATAL: %s: %s\n", func, msg);
+ exit(EXIT_FAILURE);
+}
+
+
+/*
+ * Prints error containing the function name |func| and error code
+ * |error_code| and exit.
+ */
+void
+spdy_diec(const char *func,
+ int error_code)
+{
+ fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code,
+ spdylay_strerror(error_code));
+ exit(EXIT_FAILURE);
+}
+
+
+static ssize_t
+spdy_cb_data_source_read(spdylay_session *session, int32_t stream_id, uint8_t *buf, size_t length, int *eof, spdylay_data_source *source, void *user_data)
+{
+ (void)session;
+ (void)stream_id;
+ (void)user_data;
+
+ ssize_t ret;
+ assert(NULL != source);
+ assert(NULL != source->ptr);
+ struct Proxy *proxy = (struct Proxy *)(source->ptr);
+ void *newbody;
+
+
+ if(length < 1)
+ {
+ PRINT_INFO("spdy_cb_data_source_read: length is 0");
+ return 0;
+ }
+
+ if(!proxy->received_body_size)//nothing to write now
+ {
+ if(proxy->receiving_done)
+ {
+ PRINT_INFO("POST spdy EOF");
+ *eof = 1;
+ }
+ PRINT_INFO("POST SPDYLAY_ERR_DEFERRED");
+ return SPDYLAY_ERR_DEFERRED;//TODO SPDYLAY_ERR_DEFERRED should be used
+ }
+
+ if(length >= proxy->received_body_size)
+ {
+ ret = proxy->received_body_size;
+ newbody = NULL;
+ }
+ else
+ {
+ ret = length;
+ if(NULL == (newbody = malloc(proxy->received_body_size - length)))
+ {
+ PRINT_INFO("no memory");
+ return SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ memcpy(newbody, proxy->received_body + length, proxy->received_body_size - length);
+ }
+ memcpy(buf, proxy->received_body, ret);
+ free(proxy->received_body);
+ proxy->received_body = newbody;
+ proxy->received_body_size -= ret;
+
+ if(0 == proxy->received_body_size && proxy->receiving_done)
+ {
+ PRINT_INFO("POST spdy EOF");
+ *eof = 1;
+ }
+
+ PRINT_INFO2("given POST bytes to spdylay: %zd", ret);
+
+ return ret;
+}
+
+
+/*
+ * The implementation of spdylay_send_callback type. Here we write
+ * |data| with size |length| to the network and return the number of
+ * bytes actually written. See the documentation of
+ * spdylay_send_callback for the details.
+ */
+static ssize_t
+spdy_cb_send(spdylay_session *session,
+ const uint8_t *data,
+ size_t length,
+ int flags,
+ void *user_data)
+{
+ (void)session;
+ (void)flags;
+
+ //PRINT_INFO("spdy_cb_send called");
+ struct SPDY_Connection *connection;
+ ssize_t rv;
+ connection = (struct SPDY_Connection*)user_data;
+ connection->want_io = IO_NONE;
+
+ if(glob_opt.ignore_rst_stream
+ && 16 == length
+ && 0x80 == data[0]
+ && 0x00 == data[2]
+ && 0x03 == data[3]
+ )
+ {
+ PRINT_INFO2("ignoring RST_STREAM for stream_id %i %i %i %i", data[8], data[9], data[10], data[11]);
+ glob_opt.ignore_rst_stream = false;
+ return 16;
+ }
+ glob_opt.ignore_rst_stream = false;
+
+ if(connection->is_tls)
+ {
+ ERR_clear_error();
+ rv = SSL_write(connection->ssl, data, length);
+ if(rv < 0) {
+ int err = SSL_get_error(connection->ssl, rv);
+ if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ connection->want_io |= (err == SSL_ERROR_WANT_READ ?
+ WANT_READ : WANT_WRITE);
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ } else {
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ }
+ }
+ else
+ {
+ rv = write(connection->fd,
+ data,
+ length);
+
+ if (rv < 0)
+ {
+ switch(errno)
+ {
+ case EAGAIN:
+ #if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+ #endif
+ connection->want_io |= WANT_WRITE;
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ break;
+
+ default:
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ }
+ }
+
+ PRINT_INFO2("%zd bytes written by spdy", rv);
+
+ if(rv > 0)
+ UPDATE_STAT(glob_stat.spdy_bytes_sent, rv);
+
+ return rv;
+}
+
+
+/*
+ * The implementation of spdylay_recv_callback type. Here we read data
+ * from the network and write them in |buf|. The capacity of |buf| is
+ * |length| bytes. Returns the number of bytes stored in |buf|. See
+ * the documentation of spdylay_recv_callback for the details.
+ */
+static ssize_t
+spdy_cb_recv(spdylay_session *session,
+ uint8_t *buf,
+ size_t length,
+ int flags,
+ void *user_data)
+{
+ (void)session;
+ (void)flags;
+
+ struct SPDY_Connection *connection;
+ ssize_t rv;
+
+ connection = (struct SPDY_Connection*)user_data;
+ //prevent monopolizing everything
+ if(!(++connection->counter % 10)) return SPDYLAY_ERR_WOULDBLOCK;
+ connection->want_io = IO_NONE;
+ if(connection->is_tls)
+ {
+ ERR_clear_error();
+ rv = SSL_read(connection->ssl, buf, length);
+ if(rv < 0) {
+ int err = SSL_get_error(connection->ssl, rv);
+ if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ connection->want_io |= (err == SSL_ERROR_WANT_READ ?
+ WANT_READ : WANT_WRITE);
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ } else {
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ } else if(rv == 0) {
+ rv = SPDYLAY_ERR_EOF;
+ }
+ }
+ else
+ {
+ rv = read(connection->fd,
+ buf,
+ length);
+
+ if (rv < 0)
+ {
+ switch(errno)
+ {
+ case EAGAIN:
+ #if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+ #endif
+ connection->want_io |= WANT_READ;
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ break;
+
+ default:
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ }
+ else if(rv == 0)
+ rv = SPDYLAY_ERR_EOF;
+ }
+
+ if(rv > 0)
+ UPDATE_STAT(glob_stat.spdy_bytes_received, rv);
+
+ return rv;
+}
+
+
+static void
+spdy_cb_before_ctrl_send(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame,
+ void *user_data)
+{
+ (void)user_data;
+
+ int32_t stream_id;
+ struct Proxy *proxy;
+
+ switch(type) {
+ case SPDYLAY_SYN_STREAM:
+ stream_id = frame->syn_stream.stream_id;
+ proxy = spdylay_session_get_stream_user_data(session, stream_id);
+ proxy->stream_id = stream_id;
+ ++glob_opt.streams_opened;
+ ++proxy->spdy_connection->streams_opened;
+ PRINT_INFO2("opening stream: str open %i; %s", glob_opt.streams_opened, proxy->url);
+ break;
+ case SPDYLAY_RST_STREAM:
+ //try to ignore duplicate RST_STREAMs
+ //TODO this will ignore RST_STREAMs also for bogus data
+ glob_opt.ignore_rst_stream = NULL==spdylay_session_get_stream_user_data(session, frame->rst_stream.stream_id);
+ PRINT_INFO2("sending RST_STREAM for %i; ignore %i; status %i",
+ frame->rst_stream.stream_id,
+ glob_opt.ignore_rst_stream,
+ frame->rst_stream.status_code);
+ break;
+ default:
+ break;
+ }
+}
+
+
+void
+spdy_cb_on_ctrl_recv(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame,
+ void *user_data)
+{
+ (void)user_data;
+
+ char **nv;
+ int32_t stream_id;
+ struct Proxy * proxy;
+
+ switch(type) {
+ case SPDYLAY_SYN_REPLY:
+ nv = frame->syn_reply.nv;
+ stream_id = frame->syn_reply.stream_id;
+ break;
+ case SPDYLAY_RST_STREAM:
+ stream_id = frame->rst_stream.stream_id;
+ break;
+ case SPDYLAY_HEADERS:
+ nv = frame->headers.nv;
+ stream_id = frame->headers.stream_id;
+ break;
+ default:
+ return;
+ break;
+ }
+
+ proxy = spdylay_session_get_stream_user_data(session, stream_id);
+ if(NULL == proxy)
+ {
+ PRINT_INFO2("received frame type %i for unkonwn stream id %i", type, stream_id);
+ return;
+ //DIE("no proxy obj");
+ }
+
+ switch(type) {
+ case SPDYLAY_SYN_REPLY:
+ PRINT_INFO2("received headers for %s", proxy->url);
+ http_create_response(proxy, nv);
+ break;
+ case SPDYLAY_RST_STREAM:
+ PRINT_INFO2("received reset stream for %s", proxy->url);
+ proxy->spdy_error = true;
+ break;
+ case SPDYLAY_HEADERS:
+ PRINT_INFO2("received headers for %s", proxy->url);
+ http_create_response(proxy, nv);
+ break;
+ default:
+ return;
+ break;
+ }
+
+ glob_opt.spdy_data_received = true;
+}
+
+
+/*
+ * The implementation of spdylay_on_stream_close_callback type. We use
+ * this function to know the response is fully received. Since we just
+ * fetch 1 resource in this program, after reception of the response,
+ * we submit GOAWAY and close the session.
+ */
+static void
+spdy_cb_on_stream_close(spdylay_session *session,
+ int32_t stream_id,
+ spdylay_status_code status_code,
+ void *user_data)
+{
+ (void)status_code;
+ (void)user_data;
+
+ struct Proxy * proxy = spdylay_session_get_stream_user_data(session, stream_id);
+
+ assert(NULL != proxy);
+
+ --glob_opt.streams_opened;
+ --proxy->spdy_connection->streams_opened;
+ PRINT_INFO2("closing stream: str opened %i; remove proxy %i", glob_opt.streams_opened, proxy->id);
+
+ DLL_remove(proxy->spdy_connection->proxies_head, proxy->spdy_connection->proxies_tail, proxy);
+ if(proxy->http_active)
+ {
+ proxy->spdy_active = false;
+ }
+ else
+ {
+ free_proxy(proxy);
+ }
+}
+
+
+/*
+ * The implementation of spdylay_on_data_chunk_recv_callback type. We
+ * use this function to print the received response body.
+ */
+static void
+spdy_cb_on_data_chunk_recv(spdylay_session *session,
+ uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *data,
+ size_t len,
+ void *user_data)
+{
+ (void)flags;
+ (void)user_data;
+
+ struct Proxy *proxy;
+ proxy = spdylay_session_get_stream_user_data(session, stream_id);
+
+ if(NULL == proxy)
+ {
+ PRINT_INFO("proxy in spdy_cb_on_data_chunk_recv is NULL)");
+ return;
+ }
+
+ if(!copy_buffer(data, len, &proxy->http_body, &proxy->http_body_size))
+ {
+ //TODO handle it better?
+ PRINT_INFO("not enough memory (malloc/realloc returned NULL)");
+ return;
+ }
+ /*
+ if(NULL == proxy->http_body)
+ proxy->http_body = au_malloc(len);
+ else
+ proxy->http_body = realloc(proxy->http_body, proxy->http_body_size + len);
+ if(NULL == proxy->http_body)
+ {
+ PRINT_INFO("not enough memory (realloc returned NULL)");
+ return ;
+ }
+
+ memcpy(proxy->http_body + proxy->http_body_size, data, len);
+ proxy->http_body_size += len;
+ */
+ PRINT_INFO2("received data for %s; %zu bytes", proxy->url, len);
+ glob_opt.spdy_data_received = true;
+}
+
+
+static void
+spdy_cb_on_data_recv(spdylay_session *session,
+ uint8_t flags,
+ int32_t stream_id,
+ int32_t length,
+ void *user_data)
+{
+ (void)length;
+ (void)user_data;
+
+ if(flags & SPDYLAY_DATA_FLAG_FIN)
+ {
+ struct Proxy *proxy;
+ proxy = spdylay_session_get_stream_user_data(session, stream_id);
+ proxy->done = true;
+ PRINT_INFO2("last data frame received for %s", proxy->url);
+ }
+}
+
+
+/*
+ * Setup callback functions. Spdylay API offers many callback
+ * functions, but most of them are optional. The send_callback is
+ * always required. Since we use spdylay_session_recv(), the
+ * recv_callback is also required.
+ */
+static void
+spdy_setup_spdylay_callbacks(spdylay_session_callbacks *callbacks)
+{
+ memset(callbacks, 0, sizeof(spdylay_session_callbacks));
+ callbacks->send_callback = spdy_cb_send;
+ callbacks->recv_callback = spdy_cb_recv;
+ callbacks->before_ctrl_send_callback = spdy_cb_before_ctrl_send;
+ callbacks->on_ctrl_recv_callback = spdy_cb_on_ctrl_recv;
+ callbacks->on_stream_close_callback = spdy_cb_on_stream_close;
+ callbacks->on_data_chunk_recv_callback = spdy_cb_on_data_chunk_recv;
+ callbacks->on_data_recv_callback = spdy_cb_on_data_recv;
+}
+
+
+/*
+ * Callback function for SSL/TLS NPN. Since this program only supports
+ * SPDY protocol, if server does not offer SPDY protocol the Spdylay
+ * library supports, we terminate program.
+ */
+static int
+spdy_cb_ssl_select_next_proto(SSL* ssl,
+ unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char *in,
+ unsigned int inlen,
+ void *arg)
+{
+ (void)ssl;
+
+ int rv;
+ uint16_t *spdy_proto_version;
+
+ /* spdylay_select_next_protocol() selects SPDY protocol version the
+ Spdylay library supports. */
+ rv = spdylay_select_next_protocol(out, outlen, in, inlen);
+ if(rv <= 0) {
+ PRINT_INFO("Server did not advertise spdy/2 or spdy/3 protocol.");
+ return rv;
+ }
+ spdy_proto_version = (uint16_t*)arg;
+ *spdy_proto_version = rv;
+ return SSL_TLSEXT_ERR_OK;
+}
+
+
+/*
+ * Setup SSL context. We pass |spdy_proto_version| to get negotiated
+ * SPDY protocol version in NPN callback.
+ */
+void
+spdy_ssl_init_ssl_ctx(SSL_CTX *ssl_ctx,
+ uint16_t *spdy_proto_version)
+{
+ /* Disable SSLv2 and enable all workarounds for buggy servers */
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+ /* Set NPN callback */
+ SSL_CTX_set_next_proto_select_cb(ssl_ctx, spdy_cb_ssl_select_next_proto,
+ spdy_proto_version);
+}
+
+
+static int
+spdy_ssl_handshake(SSL *ssl,
+ int fd)
+{
+ int rv;
+
+ if(SSL_set_fd(ssl, fd) == 0)
+ spdy_dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL));
+
+ ERR_clear_error();
+ rv = SSL_connect(ssl);
+ if(rv <= 0)
+ PRINT_INFO2("SSL_connect %s", ERR_error_string(ERR_get_error(), NULL));
+
+ return rv;
+}
+
+
+/*
+ * Connects to the host |host| and port |port|. This function returns
+ * the file descriptor of the client socket.
+ */
+static int
+spdy_socket_connect_to(const char *host,
+ uint16_t port)
+{
+ struct addrinfo hints;
+ int fd = -1;
+ int rv;
+ char service[NI_MAXSERV];
+ struct addrinfo *res, *rp;
+
+ //TODO checks
+ snprintf(service, sizeof(service), "%u", port);
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ rv = getaddrinfo(host, service, &hints, &res);
+ if(rv != 0)
+ {
+ printf("%s\n",host);
+ spdy_dief("getaddrinfo", gai_strerror(rv));
+ }
+ for(rp = res; rp; rp = rp->ai_next)
+ {
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if(fd == -1)
+ continue;
+ while((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 &&
+ errno == EINTR);
+ if(rv == 0)
+ break;
+ close(fd);
+ fd = -1;
+ }
+ freeaddrinfo(res);
+
+ return fd;
+}
+
+
+static void
+spdy_socket_make_non_block(int fd)
+{
+ int flags;
+ int rv;
+
+ while((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR);
+
+ if(flags == -1)
+ spdy_dief("fcntl", strerror(errno));
+
+ while((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR);
+
+ if(rv == -1)
+ spdy_dief("fcntl", strerror(errno));
+}
+
+
+/*
+ * Setting TCP_NODELAY is not mandatory for the SPDY protocol.
+ */
+static void
+spdy_socket_set_tcp_nodelay(int fd)
+{
+ int val = 1;
+ int rv;
+
+ rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
+ if(rv == -1)
+ spdy_dief("setsockopt", strerror(errno));
+}
+
+/*
+ * Update |pollfd| based on the state of |connection|.
+ */
+ /*
+void
+spdy_ctl_poll(struct pollfd *pollfd,
+ struct SPDY_Connection *connection)
+{
+ pollfd->events = 0;
+ if(spdylay_session_want_read(connection->session) ||
+ connection->want_io & WANT_READ)
+ {
+ pollfd->events |= POLLIN;
+ }
+ if(spdylay_session_want_write(connection->session) ||
+ connection->want_io & WANT_WRITE)
+ {
+ pollfd->events |= POLLOUT;
+ }
+}*/
+
+
+/*
+ * Update |selectfd| based on the state of |connection|.
+ */
+bool
+spdy_ctl_select(fd_set * read_fd_set,
+ fd_set * write_fd_set,
+ fd_set * except_fd_set,
+ struct SPDY_Connection *connection)
+{
+ (void)except_fd_set;
+
+ bool ret = false;
+
+ if(spdylay_session_want_read(connection->session) ||
+ connection->want_io & WANT_READ)
+ {
+ FD_SET(connection->fd, read_fd_set);
+ ret = true;
+ }
+ if(spdylay_session_want_write(connection->session) ||
+ connection->want_io & WANT_WRITE)
+ {
+ FD_SET(connection->fd, write_fd_set);
+ ret = true;
+ }
+
+ return ret;
+}
+
+
+/*
+ * Performs the network I/O.
+ */
+int
+spdy_exec_io(struct SPDY_Connection *connection)
+{
+ int rv;
+
+ rv = spdylay_session_recv(connection->session);
+ if(rv != 0)
+ {
+ PRINT_INFO2("spdylay_session_recv %i", rv);
+ return rv;
+ }
+ rv = spdylay_session_send(connection->session);
+ if(rv != 0)
+ PRINT_INFO2("spdylay_session_send %i", rv);
+
+ return rv;
+}
+
+
+/*
+ * Fetches the resource denoted by |uri|.
+ */
+struct SPDY_Connection *
+spdy_connect(const struct URI *uri,
+ uint16_t port,
+ bool is_tls)
+{
+ spdylay_session_callbacks callbacks;
+ int fd;
+ SSL *ssl=NULL;
+ struct SPDY_Connection * connection = NULL;
+ int rv;
+
+ spdy_setup_spdylay_callbacks(&callbacks);
+
+ /* Establish connection and setup SSL */
+ PRINT_INFO2("connecting to %s:%i", uri->host, port);
+ fd = spdy_socket_connect_to(uri->host, port);
+ if(fd == -1)
+ {
+ PRINT_INFO("Could not open file descriptor");
+ return NULL;
+ }
+
+ if(is_tls)
+ {
+ ssl = SSL_new(glob_opt.ssl_ctx);
+ if(ssl == NULL) {
+ spdy_dief("SSL_new", ERR_error_string(ERR_get_error(), NULL));
+ }
+
+ //TODO non-blocking
+ /* To simplify the program, we perform SSL/TLS handshake in blocking
+ I/O. */
+ glob_opt.spdy_proto_version = 0;
+ rv = spdy_ssl_handshake(ssl, fd);
+ if(rv <= 0 || (glob_opt.spdy_proto_version != 3 && glob_opt.spdy_proto_version != 2))
+ {
+ PRINT_INFO("Closing SSL");
+ //no spdy on the other side
+ goto free_and_fail;
+ }
+ }
+ else
+ {
+ glob_opt.spdy_proto_version = 3;
+ }
+
+ if(NULL == (connection = au_malloc(sizeof(struct SPDY_Connection))))
+ goto free_and_fail;
+
+ connection->is_tls = is_tls;
+ connection->ssl = ssl;
+ connection->want_io = IO_NONE;
+ if(NULL == (connection->host = strdup(uri->host)))
+ goto free_and_fail;
+
+ /* Here make file descriptor non-block */
+ spdy_socket_make_non_block(fd);
+ spdy_socket_set_tcp_nodelay(fd);
+
+ PRINT_INFO2("[INFO] SPDY protocol version = %d\n", glob_opt.spdy_proto_version);
+ rv = spdylay_session_client_new(&(connection->session), glob_opt.spdy_proto_version,
+ &callbacks, connection);
+ if(rv != 0) {
+ spdy_diec("spdylay_session_client_new", rv);
+ }
+
+ connection->fd = fd;
+
+ return connection;
+
+ //for GOTO
+ free_and_fail:
+ if(NULL != connection)
+ {
+ free(connection->host);
+ free(connection);
+ }
+
+ if(is_tls)
+ SSL_shutdown(ssl);
+
+ close(fd);
+
+ if(is_tls)
+ SSL_free(ssl);
+
+ return NULL;
+}
+
+
+void
+spdy_free_connection(struct SPDY_Connection * connection)
+{
+ struct Proxy *proxy;
+ struct Proxy *proxy_next;
+
+ if(NULL != connection)
+ {
+ for(proxy = connection->proxies_head; NULL != proxy; proxy=proxy_next)
+ {
+ proxy_next = proxy->next;
+ DLL_remove(connection->proxies_head, connection->proxies_tail, proxy);
+ proxy->spdy_active = false;
+ proxy->spdy_error = true;
+ PRINT_INFO2("spdy_free_connection for id %i", proxy->id);
+ if(!proxy->http_active)
+ {
+ free_proxy(proxy);
+ }
+ }
+ spdylay_session_del(connection->session);
+ SSL_free(connection->ssl);
+ free(connection->host);
+ free(connection);
+ //connection->session = NULL;
+ }
+}
+
+
+int
+spdy_request(const char **nv,
+ struct Proxy *proxy,
+ bool with_body)
+{
+ int ret;
+ uint16_t port;
+ struct SPDY_Connection *connection;
+ spdylay_data_provider post_data;
+
+ if(glob_opt.only_proxy)
+ {
+ connection = glob_opt.spdy_connection;
+ }
+ else
+ {
+ connection = glob_opt.spdy_connections_head;
+ while(NULL != connection)
+ {
+ if(0 == strcasecmp(proxy->uri->host, connection->host))
+ break;
+ connection = connection->next;
+ }
+
+ if(NULL == connection)
+ {
+ //connect to host
+ port = proxy->uri->port;
+ if(0 == port) port = 443;
+ connection = spdy_connect(proxy->uri, port, true);
+ if(NULL != connection)
+ {
+ DLL_insert(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection);
+ glob_opt.total_spdy_connections++;
+ }
+ else
+ connection = glob_opt.spdy_connection;
+ }
+ }
+
+ if(NULL == connection)
+ {
+ PRINT_INFO("there is no proxy!");
+ return -1;
+ }
+
+ proxy->spdy_connection = connection;
+ if(with_body)
+ {
+ post_data.source.ptr = proxy;
+ post_data.read_callback = &spdy_cb_data_source_read;
+ ret = spdylay_submit_request(connection->session, 0, nv, &post_data, proxy);
+ }
+ else
+ ret = spdylay_submit_request(connection->session, 0, nv, NULL, proxy);
+
+ if(ret != 0) {
+ spdy_diec("spdylay_spdy_submit_request", ret);
+ }
+ PRINT_INFO2("adding proxy %i", proxy->id);
+ if(NULL != connection->proxies_head)
+ PRINT_INFO2("before proxy %i", connection->proxies_head->id);
+ DLL_insert(connection->proxies_head, connection->proxies_tail, proxy);
+
+ return ret;
+}
+
+/*
+void
+spdy_get_pollfdset(struct pollfd fds[],
+ struct SPDY_Connection *connections[],
+ unsigned int max_size,
+ nfds_t *real_size)
+{
+ struct SPDY_Connection *connection;
+ struct Proxy *proxy;
+
+ *real_size = 0;
+ if(max_size<1)
+ return;
+
+ if(NULL != glob_opt.spdy_connection)
+ {
+ spdy_ctl_poll(&(fds[*real_size]), glob_opt.spdy_connection);
+ if(!fds[*real_size].events)
+ {
+ //PRINT_INFO("TODO drop connection");
+ glob_opt.streams_opened -= glob_opt.spdy_connection->streams_opened;
+
+ for(proxy = glob_opt.spdy_connection->proxies_head; NULL != proxy; proxy=proxy->next)
+ {
+ abort();
+ DLL_remove(glob_opt.spdy_connection->proxies_head, glob_opt.spdy_connection->proxies_tail, proxy);
+ proxy->spdy_active = false;
+ }
+ spdy_free_connection(glob_opt.spdy_connection);
+ glob_opt.spdy_connection = NULL;
+ }
+ else
+ {
+ fds[*real_size].fd = glob_opt.spdy_connection->fd;
+ connections[*real_size] = glob_opt.spdy_connection;
+ ++(*real_size);
+ }
+ }
+
+ connection = glob_opt.spdy_connections_head;
+
+ while(NULL != connection && *real_size < max_size)
+ {
+ assert(!glob_opt.only_proxy);
+ spdy_ctl_poll(&(fds[*real_size]), connection);
+ if(!fds[*real_size].events)
+ {
+ //PRINT_INFO("TODO drop connection");
+ glob_opt.streams_opened -= connection->streams_opened;
+ DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection);
+ glob_opt.total_spdy_connections--;
+
+ for(proxy = connection->proxies_head; NULL != proxy; proxy=proxy->next)
+ {
+ abort();
+ DLL_remove(connection->proxies_head, connection->proxies_tail, proxy);
+ proxy->spdy_active = false;
+ }
+ spdy_free_connection(connection);
+ }
+ else
+ {
+ fds[*real_size].fd = connection->fd;
+ connections[*real_size] = connection;
+ ++(*real_size);
+ }
+ connection = connection->next;
+ }
+
+ //, "TODO max num of conn reached; close something"
+ assert(NULL == connection);
+}
+*/
+
+int
+spdy_get_selectfdset(fd_set * read_fd_set,
+ fd_set * write_fd_set,
+ fd_set * except_fd_set,
+ struct SPDY_Connection *connections[],
+ unsigned int max_size,
+ nfds_t *real_size)
+{
+ struct SPDY_Connection *connection;
+ struct SPDY_Connection *next_connection;
+ bool ret;
+ int maxfd = 0;
+
+ *real_size = 0;
+ if(max_size<1)
+ return 0;
+
+ if(NULL != glob_opt.spdy_connection)
+ {
+ ret = spdy_ctl_select(read_fd_set,
+ write_fd_set,
+ except_fd_set, glob_opt.spdy_connection);
+ if(!ret)
+ {
+ glob_opt.streams_opened -= glob_opt.spdy_connection->streams_opened;
+
+ PRINT_INFO("spdy_free_connection in spdy_get_selectfdset");
+ spdy_free_connection(glob_opt.spdy_connection);
+ glob_opt.spdy_connection = NULL;
+ }
+ else
+ {
+ connections[*real_size] = glob_opt.spdy_connection;
+ ++(*real_size);
+ if(maxfd < glob_opt.spdy_connection->fd) maxfd = glob_opt.spdy_connection->fd;
+ }
+ }
+
+ connection = glob_opt.spdy_connections_head;
+
+ while(NULL != connection && *real_size < max_size)
+ {
+ assert(!glob_opt.only_proxy);
+ ret = spdy_ctl_select(read_fd_set,
+ write_fd_set,
+ except_fd_set, connection);
+
+ next_connection = connection->next;
+ if(!ret)
+ {
+ glob_opt.streams_opened -= connection->streams_opened;
+ DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection);
+ glob_opt.total_spdy_connections--;
+
+ PRINT_INFO("spdy_free_connection in spdy_get_selectfdset");
+ spdy_free_connection(connection);
+ }
+ else
+ {
+ connections[*real_size] = connection;
+ ++(*real_size);
+ if(maxfd < connection->fd) maxfd = connection->fd;
+ }
+ connection = next_connection;
+ }
+
+ //, "TODO max num of conn reached; close something"
+ assert(NULL == connection);
+
+ return maxfd;
+}
+
+/*
+void
+spdy_run(struct pollfd fds[],
+ struct SPDY_Connection *connections[],
+ int size)
+{
+ int i;
+ int ret;
+ struct Proxy *proxy;
+
+ for(i=0; i<size; ++i)
+ {
+ // PRINT_INFO2("exec about to be called for %s", connections[i]->host);
+ if(fds[i].revents & (POLLIN | POLLOUT))
+ {
+ ret = spdy_exec_io(connections[i]);
+ //PRINT_INFO2("%i",ret);
+ //if((spdy_pollfds[i].revents & POLLHUP) || (spdy_pollfds[0].revents & POLLERR))
+ // PRINT_INFO("SPDY SPDY_Connection error");
+
+ //TODO POLLRDHUP
+ // always close on ret != 0?
+
+ if(0 != ret)
+ {
+ glob_opt.streams_opened -= connections[i]->streams_opened;
+ if(connections[i] == glob_opt.spdy_connection)
+ {
+ glob_opt.spdy_connection = NULL;
+ }
+ else
+ {
+ DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connections[i]);
+ glob_opt.total_spdy_connections--;
+ }
+ for(proxy = connections[i]->proxies_head; NULL != proxy; proxy=proxy->next)
+ {
+ abort();
+ DLL_remove(connections[i]->proxies_head, connections[i]->proxies_tail, proxy);
+ proxy->spdy_active = false;
+ proxy->spdy_error = true;
+ PRINT_INFO2("spdy_free_connection for id %i", proxy->id);
+ }
+ PRINT_INFO("spdy_free_connection in loop");
+ spdy_free_connection(connections[i]);
+ }
+ }
+ else
+ PRINT_INFO("not called");
+ }
+}
+*/
+
+void
+spdy_run_select(fd_set * read_fd_set,
+ fd_set * write_fd_set,
+ fd_set * except_fd_set,
+ struct SPDY_Connection *connections[],
+ int size)
+{
+ int i;
+ int ret;
+
+ for(i=0; i<size; ++i)
+ {
+ // PRINT_INFO2("exec about to be called for %s", connections[i]->host);
+ if(FD_ISSET(connections[i]->fd, read_fd_set) || FD_ISSET(connections[i]->fd, write_fd_set) || FD_ISSET(connections[i]->fd, except_fd_set))
+ {
+ //raise(SIGINT);
+ ret = spdy_exec_io(connections[i]);
+
+ if(0 != ret)
+ {
+ glob_opt.streams_opened -= connections[i]->streams_opened;
+ if(connections[i] == glob_opt.spdy_connection)
+ {
+ glob_opt.spdy_connection = NULL;
+ }
+ else
+ {
+ DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connections[i]);
+ glob_opt.total_spdy_connections--;
+ }
+ PRINT_INFO("in spdy_run_select");
+ spdy_free_connection(connections[i]);
+ }
+ }
+ else
+ {
+ PRINT_INFO("not called");
+ //PRINT_INFO2("connection->want_io %i",connections[i]->want_io);
+ //PRINT_INFO2("read %i",spdylay_session_want_read(connections[i]->session));
+ //PRINT_INFO2("write %i",spdylay_session_want_write(connections[i]->session));
+ //raise(SIGINT);
+ }
+ }
+}
diff --git a/src/examples/mhd2spdy_spdy.h b/src/examples/mhd2spdy_spdy.h
new file mode 100644
index 0000000..4207c62
--- /dev/null
+++ b/src/examples/mhd2spdy_spdy.h
@@ -0,0 +1,102 @@
+/*
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file mhd2spdy_spdy.h
+ * @brief SPDY part of the proxy. libspdylay is used for the client side.
+ * @author Andrey Uzunov
+ */
+
+#ifndef SPDY_H
+#define SPDY_H
+
+#include "mhd2spdy_structures.h"
+
+
+struct SPDY_Connection *
+spdy_connect(const struct URI *uri,
+ uint16_t port,
+ bool is_tls);
+
+
+void
+spdy_ctl_poll(struct pollfd *pollfd,
+ struct SPDY_Connection *connection);
+
+
+bool
+spdy_ctl_select(fd_set * read_fd_set,
+ fd_set * write_fd_set,
+ fd_set * except_fd_set,
+ struct SPDY_Connection *connection);
+
+
+int
+spdy_exec_io(struct SPDY_Connection *connection);
+
+
+void
+spdy_diec(const char *func,
+ int error_code);
+
+
+int
+spdy_request(const char **nv,
+ struct Proxy *proxy,
+ bool with_body);
+
+
+void
+spdy_ssl_init_ssl_ctx(SSL_CTX *ssl_ctx,
+ uint16_t *spdy_proto_version);
+
+
+void
+spdy_free_connection(struct SPDY_Connection * connection);
+
+
+void
+spdy_get_pollfdset(struct pollfd fds[],
+ struct SPDY_Connection *connections[],
+ unsigned int max_size,
+ nfds_t *real_size);
+
+
+int
+spdy_get_selectfdset(fd_set * read_fd_set,
+ fd_set * write_fd_set,
+ fd_set * except_fd_set,
+ struct SPDY_Connection *connections[],
+ unsigned int max_size,
+ nfds_t *real_size);
+
+
+void
+spdy_run(struct pollfd fds[],
+ struct SPDY_Connection *connections[],
+ int size);
+
+
+void
+spdy_run_select(fd_set * read_fd_set,
+ fd_set * write_fd_set,
+ fd_set * except_fd_set,
+ struct SPDY_Connection *connections[],
+ int size);
+
+
+#endif
diff --git a/src/examples/mhd2spdy_structures.c b/src/examples/mhd2spdy_structures.c
new file mode 100644
index 0000000..6d4a407
--- /dev/null
+++ b/src/examples/mhd2spdy_structures.c
@@ -0,0 +1,162 @@
+/*
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file mhd2spdy_structures.h
+ * @brief Common functions, macros.
+ * @author Andrey Uzunov
+ */
+
+#include "mhd2spdy_structures.h"
+
+
+void
+free_uri(struct URI * uri)
+{
+ if(NULL != uri)
+ {
+ free(uri->full_uri);
+ free(uri->scheme);
+ free(uri->host_and_port);
+ free(uri->host);
+ free(uri->path);
+ free(uri->path_and_more);
+ free(uri->query);
+ free(uri->fragment);
+ uri->port = 0;
+ free(uri);
+ }
+}
+
+
+int
+init_parse_uri(regex_t * preg)
+{
+ // RFC 2396
+ // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ /*
+ scheme = $2
+ authority = $4
+ path = $5
+ query = $7
+ fragment = $9
+ */
+
+ return regcomp(preg, "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?", REG_EXTENDED);
+}
+
+void
+deinit_parse_uri(regex_t * preg)
+{
+ regfree(preg);
+}
+
+int
+parse_uri(regex_t * preg,
+ char * full_uri,
+ struct URI ** uri)
+{
+ int ret;
+ char *colon;
+ long long port;
+ size_t nmatch = 10;
+ regmatch_t pmatch[10];
+
+ if (0 != (ret = regexec(preg, full_uri, nmatch, pmatch, 0)))
+ return ret;
+
+ *uri = au_malloc(sizeof(struct URI));
+ if(NULL == *uri)
+ return -200;
+
+ (*uri)->full_uri = strdup(full_uri);
+
+ asprintf(&((*uri)->scheme), "%.*s",pmatch[2].rm_eo - pmatch[2].rm_so, &full_uri[pmatch[2].rm_so]);
+ asprintf(&((*uri)->host_and_port), "%.*s",pmatch[4].rm_eo - pmatch[4].rm_so, &full_uri[pmatch[4].rm_so]);
+ asprintf(&((*uri)->path), "%.*s",pmatch[5].rm_eo - pmatch[5].rm_so, &full_uri[pmatch[5].rm_so]);
+ asprintf(&((*uri)->path_and_more), "%.*s",pmatch[9].rm_eo - pmatch[5].rm_so, &full_uri[pmatch[5].rm_so]);
+ asprintf(&((*uri)->query), "%.*s",pmatch[7].rm_eo - pmatch[7].rm_so, &full_uri[pmatch[7].rm_so]);
+ asprintf(&((*uri)->fragment), "%.*s",pmatch[9].rm_eo - pmatch[9].rm_so, &full_uri[pmatch[9].rm_so]);
+
+ colon = strrchr((*uri)->host_and_port, ':');
+ if(NULL == colon)
+ {
+ (*uri)->host = strdup((*uri)->host_and_port);
+ (*uri)->port = 0;
+
+ return 0;
+ }
+
+ port = atoi(colon + 1);
+ if(port<1 || port >= 256 * 256)
+ {
+ free_uri(*uri);
+ return -100;
+ }
+ (*uri)->port = port;
+ asprintf(&((*uri)->host), "%.*s", (int)(colon - (*uri)->host_and_port), (*uri)->host_and_port);
+
+ return 0;
+}
+
+
+void
+free_proxy(struct Proxy *proxy)
+{
+ PRINT_INFO2("free proxy called for '%s'", proxy->url);
+ if(NULL != proxy->http_body && proxy->http_body_size > 0)
+ UPDATE_STAT(glob_stat.spdy_bytes_received_and_dropped, proxy->http_body_size);
+ free(proxy->http_body);
+ free_uri(proxy->uri);
+ free(proxy->url);
+ free(proxy->http_uri);
+ free(proxy);
+}
+
+
+void *au_malloc(size_t size)
+{
+ void *new_memory;
+
+ new_memory = malloc(size);
+ if(NULL != new_memory)
+ {
+ glob_opt.global_memory += size;
+ memset(new_memory, 0, size);
+ }
+ return new_memory;
+}
+
+
+bool
+copy_buffer(const void *src, size_t src_size, void **dst, size_t *dst_size)
+{
+ if(0 == src_size)
+ return true;
+
+ if(NULL == *dst)
+ *dst = malloc(src_size);
+ else
+ *dst = realloc(*dst, src_size + *dst_size);
+ if(NULL == *dst)
+ return false;
+
+ memcpy(*dst + *dst_size, src, src_size);
+ *dst_size += src_size;
+
+ return true;
+}
diff --git a/src/examples/mhd2spdy_structures.h b/src/examples/mhd2spdy_structures.h
new file mode 100644
index 0000000..f567934
--- /dev/null
+++ b/src/examples/mhd2spdy_structures.h
@@ -0,0 +1,296 @@
+/*
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file mhd2spdy_structures.h
+ * @brief Common structures, functions, macros and global variables.
+ * @author Andrey Uzunov
+ */
+#ifndef STRUCTURES_H
+#define STRUCTURES_H
+
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <assert.h>
+#include <microhttpd.h>
+#include <signal.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <spdylay/spdylay.h>
+#include <getopt.h>
+
+
+/* WANT_READ if SSL connection needs more input; or WANT_WRITE if it
+ needs more output; or IO_NONE. This is necessary because SSL/TLS
+ re-negotiation is possible at any time. Spdylay API offers
+ similar functions like spdylay_session_want_read() and
+ spdylay_session_want_write() but they do not take into account
+ SSL connection. */
+enum
+{
+ IO_NONE,
+ WANT_READ,
+ WANT_WRITE
+};
+
+
+struct Proxy;
+
+
+struct SPDY_Connection {
+ SSL *ssl;
+ spdylay_session *session;
+ struct SPDY_Connection *prev;
+ struct SPDY_Connection *next;
+ struct Proxy *proxies_head;
+ struct Proxy *proxies_tail;
+ char *host;
+ int fd;
+ int want_io;
+ uint counter;
+ uint streams_opened;
+ bool is_tls;
+};
+
+
+struct URI
+{
+ char * full_uri;
+ char * scheme;
+ char * host_and_port;
+ char * host;
+ char * path;
+ char * path_and_more;
+ char * query;
+ char * fragment;
+ uint16_t port;
+};
+
+
+struct HTTP_URI;
+
+
+struct Proxy
+{
+ struct MHD_Connection *http_connection;
+ struct MHD_Response *http_response;
+ struct URI *uri;
+ struct HTTP_URI *http_uri;
+ struct SPDY_Connection *spdy_connection;
+ struct Proxy *next;
+ struct Proxy *prev;
+ char *url;
+ char *version;
+ void *http_body;
+ void *received_body;
+ size_t http_body_size;
+ size_t received_body_size;
+ ssize_t length;
+ int status;
+ int id;
+ int32_t stream_id;
+ bool done;
+ bool http_error;
+ bool spdy_error;
+ bool http_active;
+ bool spdy_active;
+ bool receiving_done;
+};
+
+
+struct HTTP_URI
+{
+ char * uri;
+ struct Proxy * proxy;
+};
+
+
+struct SPDY_Headers
+{
+ const char **nv;
+ int num;
+ int cnt;
+};
+
+
+struct global_options
+{
+ char *spdy2http_str;
+ struct SPDY_Connection *spdy_connection;
+ struct SPDY_Connection *spdy_connections_head;
+ struct SPDY_Connection *spdy_connections_tail;
+ int streams_opened;
+ int responses_pending;
+ regex_t uri_preg;
+ size_t global_memory;
+ SSL_CTX *ssl_ctx;
+ uint32_t total_spdy_connections;
+ uint16_t spdy_proto_version;
+ uint16_t listen_port;
+ bool verbose;
+ bool only_proxy;
+ bool spdy_data_received;
+ bool statistics;
+ bool ignore_rst_stream;
+}
+glob_opt;
+
+
+struct global_statistics
+{
+ //unsigned long long http_bytes_sent;
+ //unsigned long long http_bytes_received;
+ unsigned long long spdy_bytes_sent;
+ unsigned long long spdy_bytes_received;
+ unsigned long long spdy_bytes_received_and_dropped;
+}
+glob_stat;
+
+
+//forbidden headers
+#define SPDY_HTTP_HEADER_TRANSFER_ENCODING "transfer-encoding"
+#define SPDY_HTTP_HEADER_PROXY_CONNECTION "proxy-connection"
+#define SPDY_HTTP_HEADER_KEEP_ALIVE "keep-alive"
+#define SPDY_HTTP_HEADER_CONNECTION "connection"
+
+#define MAX_SPDY_CONNECTIONS 100
+
+#define SPDY_MAX_OUTLEN 4096
+
+/**
+ * Insert an element at the head of a DLL. Assumes that head, tail and
+ * element are structs with prev and next fields.
+ *
+ * @param head pointer to the head of the DLL (struct ? *)
+ * @param tail pointer to the tail of the DLL (struct ? *)
+ * @param element element to insert (struct ? *)
+ */
+#define DLL_insert(head,tail,element) do { \
+ (element)->next = (head); \
+ (element)->prev = NULL; \
+ if ((tail) == NULL) \
+ (tail) = element; \
+ else \
+ (head)->prev = element; \
+ (head) = (element); } while (0)
+
+
+/**
+ * Remove an element from a DLL. Assumes
+ * that head, tail and element are structs
+ * with prev and next fields.
+ *
+ * @param head pointer to the head of the DLL (struct ? *)
+ * @param tail pointer to the tail of the DLL (struct ? *)
+ * @param element element to remove (struct ? *)
+ */
+#define DLL_remove(head,tail,element) do { \
+ if ((element)->prev == NULL) \
+ (head) = (element)->next; \
+ else \
+ (element)->prev->next = (element)->next; \
+ if ((element)->next == NULL) \
+ (tail) = (element)->prev; \
+ else \
+ (element)->next->prev = (element)->prev; \
+ (element)->next = NULL; \
+ (element)->prev = NULL; } while (0)
+
+
+#define PRINT_INFO(msg) do{\
+ if(glob_opt.verbose){\
+ printf("%i:%s\n", __LINE__, msg);\
+ fflush(stdout);\
+ }\
+ }\
+ while(0)
+
+
+#define PRINT_INFO2(fmt, ...) do{\
+ if(glob_opt.verbose){\
+ printf("%i\n", __LINE__);\
+ printf(fmt,##__VA_ARGS__);\
+ printf("\n");\
+ fflush(stdout);\
+ }\
+ }\
+ while(0)
+
+
+#define DIE(msg) do{\
+ printf("FATAL ERROR (line %i): %s\n", __LINE__, msg);\
+ fflush(stdout);\
+ exit(EXIT_FAILURE);\
+ }\
+ while(0)
+
+
+#define UPDATE_STAT(stat, value) do{\
+ if(glob_opt.statistics)\
+ {\
+ stat += value;\
+ }\
+ }\
+ while(0)
+
+
+void
+free_uri(struct URI * uri);
+
+
+int
+init_parse_uri(regex_t * preg);
+
+
+void
+deinit_parse_uri(regex_t * preg);
+
+
+int
+parse_uri(regex_t * preg,
+ char * full_uri,
+ struct URI ** uri);
+
+
+void
+free_proxy(struct Proxy *proxy);
+
+
+void *
+au_malloc(size_t size);
+
+
+bool
+copy_buffer(const void *src, size_t src_size, void **dst, size_t *dst_size);
+
+#endif
diff --git a/src/examples/minimal_example.c b/src/examples/minimal_example.c
new file mode 100644
index 0000000..313651c
--- /dev/null
+++ b/src/examples/minimal_example.c
@@ -0,0 +1,83 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file minimal_example.c
+ * @brief minimal example for how to use libmicrohttpd
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>libmicrohttpd demo</body></html>"
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (method, "GET"))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ response = MHD_create_response_from_buffer (strlen (me),
+ (void *) me,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (// MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | MHD_USE_POLL,
+ MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ // MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | MHD_USE_POLL,
+ // MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, PAGE,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/minimal_example_comet.c b/src/examples/minimal_example_comet.c
new file mode 100644
index 0000000..0c9d264
--- /dev/null
+++ b/src/examples/minimal_example_comet.c
@@ -0,0 +1,85 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file minimal_example.c
+ * @brief minimal example for how to generate an infinite stream with libmicrohttpd
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+
+static ssize_t
+data_generator (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ if (max < 80)
+ return 0;
+ memset (buf, 'A', max - 1);
+ buf[79] = '\n';
+ return 80;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (method, "GET"))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ 80,
+ &data_generator, NULL, NULL);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/post_example.c b/src/examples/post_example.c
new file mode 100644
index 0000000..d8d13f9
--- /dev/null
+++ b/src/examples/post_example.c
@@ -0,0 +1,750 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2011 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file post_example.c
+ * @brief example for processing POST requests using libmicrohttpd
+ * @author Christian Grothoff
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+#include <microhttpd.h>
+
+/**
+ * Invalid method page.
+ */
+#define METHOD_ERROR "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
+
+/**
+ * Invalid URL page.
+ */
+#define NOT_FOUND_ERROR "<html><head><title>Not found</title></head><body>Go away.</body></html>"
+
+/**
+ * Front page. (/)
+ */
+#define MAIN_PAGE "<html><head><title>Welcome</title></head><body><form action=\"/2\" method=\"post\">What is your name? <input type=\"text\" name=\"v1\" value=\"%s\" /><input type=\"submit\" value=\"Next\" /></body></html>"
+
+/**
+ * Second page. (/2)
+ */
+#define SECOND_PAGE "<html><head><title>Tell me more</title></head><body><a href=\"/\">previous</a> <form action=\"/S\" method=\"post\">%s, what is your job? <input type=\"text\" name=\"v2\" value=\"%s\" /><input type=\"submit\" value=\"Next\" /></body></html>"
+
+/**
+ * Second page (/S)
+ */
+#define SUBMIT_PAGE "<html><head><title>Ready to submit?</title></head><body><form action=\"/F\" method=\"post\"><a href=\"/2\">previous </a> <input type=\"hidden\" name=\"DONE\" value=\"yes\" /><input type=\"submit\" value=\"Submit\" /></body></html>"
+
+/**
+ * Last page.
+ */
+#define LAST_PAGE "<html><head><title>Thank you</title></head><body>Thank you.</body></html>"
+
+/**
+ * Name of our cookie.
+ */
+#define COOKIE_NAME "session"
+
+
+/**
+ * State we keep for each user/session/browser.
+ */
+struct Session
+{
+ /**
+ * We keep all sessions in a linked list.
+ */
+ struct Session *next;
+
+ /**
+ * Unique ID for this session.
+ */
+ char sid[33];
+
+ /**
+ * Reference counter giving the number of connections
+ * currently using this session.
+ */
+ unsigned int rc;
+
+ /**
+ * Time when this session was last active.
+ */
+ time_t start;
+
+ /**
+ * String submitted via form.
+ */
+ char value_1[64];
+
+ /**
+ * Another value submitted via form.
+ */
+ char value_2[64];
+
+};
+
+
+/**
+ * Data kept per request.
+ */
+struct Request
+{
+
+ /**
+ * Associated session.
+ */
+ struct Session *session;
+
+ /**
+ * Post processor handling form data (IF this is
+ * a POST request).
+ */
+ struct MHD_PostProcessor *pp;
+
+ /**
+ * URL to serve in response to this POST (if this request
+ * was a 'POST')
+ */
+ const char *post_url;
+
+};
+
+
+/**
+ * Linked list of all active sessions. Yes, O(n) but a
+ * hash table would be overkill for a simple example...
+ */
+static struct Session *sessions;
+
+
+
+
+/**
+ * Return the session handle for this connection, or
+ * create one if this is a new user.
+ */
+static struct Session *
+get_session (struct MHD_Connection *connection)
+{
+ struct Session *ret;
+ const char *cookie;
+
+ cookie = MHD_lookup_connection_value (connection,
+ MHD_COOKIE_KIND,
+ COOKIE_NAME);
+ if (cookie != NULL)
+ {
+ /* find existing session */
+ ret = sessions;
+ while (NULL != ret)
+ {
+ if (0 == strcmp (cookie, ret->sid))
+ break;
+ ret = ret->next;
+ }
+ if (NULL != ret)
+ {
+ ret->rc++;
+ return ret;
+ }
+ }
+ /* create fresh session */
+ ret = calloc (1, sizeof (struct Session));
+ if (NULL == ret)
+ {
+ fprintf (stderr, "calloc error: %s\n", strerror (errno));
+ return NULL;
+ }
+ /* not a super-secure way to generate a random session ID,
+ but should do for a simple example... */
+ snprintf (ret->sid,
+ sizeof (ret->sid),
+ "%X%X%X%X",
+ (unsigned int) rand (),
+ (unsigned int) rand (),
+ (unsigned int) rand (),
+ (unsigned int) rand ());
+ ret->rc++;
+ ret->start = time (NULL);
+ ret->next = sessions;
+ sessions = ret;
+ return ret;
+}
+
+
+/**
+ * Type of handler that generates a reply.
+ *
+ * @param cls content for the page (handler-specific)
+ * @param mime mime type to use
+ * @param session session information
+ * @param connection connection to process
+ * @param MHD_YES on success, MHD_NO on failure
+ */
+typedef int (*PageHandler)(const void *cls,
+ const char *mime,
+ struct Session *session,
+ struct MHD_Connection *connection);
+
+
+/**
+ * Entry we generate for each page served.
+ */
+struct Page
+{
+ /**
+ * Acceptable URL for this page.
+ */
+ const char *url;
+
+ /**
+ * Mime type to set for the page.
+ */
+ const char *mime;
+
+ /**
+ * Handler to call to generate response.
+ */
+ PageHandler handler;
+
+ /**
+ * Extra argument to handler.
+ */
+ const void *handler_cls;
+};
+
+
+/**
+ * Add header to response to set a session cookie.
+ *
+ * @param session session to use
+ * @param response response to modify
+ */
+static void
+add_session_cookie (struct Session *session,
+ struct MHD_Response *response)
+{
+ char cstr[256];
+ snprintf (cstr,
+ sizeof (cstr),
+ "%s=%s",
+ COOKIE_NAME,
+ session->sid);
+ if (MHD_NO ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_SET_COOKIE,
+ cstr))
+ {
+ fprintf (stderr,
+ "Failed to set session cookie header!\n");
+ }
+}
+
+
+/**
+ * Handler that returns a simple static HTTP page that
+ * is passed in via 'cls'.
+ *
+ * @param cls a 'const char *' with the HTML webpage to return
+ * @param mime mime type to use
+ * @param session session handle
+ * @param connection connection to use
+ */
+static int
+serve_simple_form (const void *cls,
+ const char *mime,
+ struct Session *session,
+ struct MHD_Connection *connection)
+{
+ int ret;
+ const char *form = cls;
+ struct MHD_Response *response;
+
+ /* return static form */
+ response = MHD_create_response_from_buffer (strlen (form),
+ (void *) form,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ return MHD_NO;
+ add_session_cookie (session, response);
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ mime);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Handler that adds the 'v1' value to the given HTML code.
+ *
+ * @param cls unused
+ * @param mime mime type to use
+ * @param session session handle
+ * @param connection connection to use
+ */
+static int
+fill_v1_form (const void *cls,
+ const char *mime,
+ struct Session *session,
+ struct MHD_Connection *connection)
+{
+ int ret;
+ char *reply;
+ struct MHD_Response *response;
+
+ reply = malloc (strlen (MAIN_PAGE) + strlen (session->value_1) + 1);
+ if (NULL == reply)
+ return MHD_NO;
+ snprintf (reply,
+ strlen (MAIN_PAGE) + strlen (session->value_1) + 1,
+ MAIN_PAGE,
+ session->value_1);
+ /* return static form */
+ response = MHD_create_response_from_buffer (strlen (reply),
+ (void *) reply,
+ MHD_RESPMEM_MUST_FREE);
+ if (NULL == response)
+ return MHD_NO;
+ add_session_cookie (session, response);
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ mime);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Handler that adds the 'v1' and 'v2' values to the given HTML code.
+ *
+ * @param cls unused
+ * @param mime mime type to use
+ * @param session session handle
+ * @param connection connection to use
+ */
+static int
+fill_v1_v2_form (const void *cls,
+ const char *mime,
+ struct Session *session,
+ struct MHD_Connection *connection)
+{
+ int ret;
+ char *reply;
+ struct MHD_Response *response;
+
+ reply = malloc (strlen (SECOND_PAGE) + strlen (session->value_1) + strlen (session->value_2) + 1);
+ if (NULL == reply)
+ return MHD_NO;
+ snprintf (reply,
+ strlen (SECOND_PAGE) + strlen (session->value_1) + strlen (session->value_2) + 1,
+ SECOND_PAGE,
+ session->value_1,
+ session->value_2);
+ /* return static form */
+ response = MHD_create_response_from_buffer (strlen (reply),
+ (void *) reply,
+ MHD_RESPMEM_MUST_FREE);
+ if (NULL == response)
+ return MHD_NO;
+ add_session_cookie (session, response);
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ mime);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Handler used to generate a 404 reply.
+ *
+ * @param cls a 'const char *' with the HTML webpage to return
+ * @param mime mime type to use
+ * @param session session handle
+ * @param connection connection to use
+ */
+static int
+not_found_page (const void *cls,
+ const char *mime,
+ struct Session *session,
+ struct MHD_Connection *connection)
+{
+ int ret;
+ struct MHD_Response *response;
+
+ /* unsupported HTTP method */
+ response = MHD_create_response_from_buffer (strlen (NOT_FOUND_ERROR),
+ (void *) NOT_FOUND_ERROR,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ return MHD_NO;
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_FOUND,
+ response);
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ mime);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * List of all pages served by this HTTP server.
+ */
+static struct Page pages[] =
+ {
+ { "/", "text/html", &fill_v1_form, NULL },
+ { "/2", "text/html", &fill_v1_v2_form, NULL },
+ { "/S", "text/html", &serve_simple_form, SUBMIT_PAGE },
+ { "/F", "text/html", &serve_simple_form, LAST_PAGE },
+ { NULL, NULL, ¬_found_page, NULL } /* 404 */
+ };
+
+
+
+/**
+ * Iterator over key-value pairs where the value
+ * maybe made available in increments and/or may
+ * not be zero-terminated. Used for processing
+ * POST data.
+ *
+ * @param cls user-specified closure
+ * @param kind type of the value
+ * @param key 0-terminated key for the value
+ * @param filename name of the uploaded file, NULL if not known
+ * @param content_type mime-type of the data, NULL if not known
+ * @param transfer_encoding encoding of the data, NULL if not known
+ * @param data pointer to size bytes of data at the
+ * specified offset
+ * @param off offset of data in the overall value
+ * @param size number of bytes in data available
+ * @return MHD_YES to continue iterating,
+ * MHD_NO to abort the iteration
+ */
+static int
+post_iterator (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *data, uint64_t off, size_t size)
+{
+ struct Request *request = cls;
+ struct Session *session = request->session;
+
+ if (0 == strcmp ("DONE", key))
+ {
+ fprintf (stdout,
+ "Session `%s' submitted `%s', `%s'\n",
+ session->sid,
+ session->value_1,
+ session->value_2);
+ return MHD_YES;
+ }
+ if (0 == strcmp ("v1", key))
+ {
+ if (size + off >= sizeof(session->value_1))
+ size = sizeof (session->value_1) - off - 1;
+ memcpy (&session->value_1[off],
+ data,
+ size);
+ session->value_1[size+off] = '\0';
+ return MHD_YES;
+ }
+ if (0 == strcmp ("v2", key))
+ {
+ if (size + off >= sizeof(session->value_2))
+ size = sizeof (session->value_2) - off - 1;
+ memcpy (&session->value_2[off],
+ data,
+ size);
+ session->value_2[size+off] = '\0';
+ return MHD_YES;
+ }
+ fprintf (stderr,
+ "Unsupported form value `%s'\n",
+ key);
+ return MHD_YES;
+}
+
+
+/**
+ * Main MHD callback for handling requests.
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param connection handle identifying the incoming connection
+ * @param url the requested url
+ * @param method the HTTP method used ("GET", "PUT", etc.)
+ * @param version the HTTP version string (i.e. "HTTP/1.1")
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of MHD_get_connection_values; very large POST
+ * data *will* be made available incrementally in
+ * upload_data)
+ * @param upload_data_size set initially to the size of the
+ * upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param ptr pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global "MHD_RequestCompleted" callback (which
+ * can be set with the MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, <tt>*con_cls</tt> will be NULL.
+ * @return MHS_YES if the connection was handled successfully,
+ * MHS_NO if the socket must be closed due to a serios
+ * error while handling the request
+ */
+static int
+create_response (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **ptr)
+{
+ struct MHD_Response *response;
+ struct Request *request;
+ struct Session *session;
+ int ret;
+ unsigned int i;
+
+ request = *ptr;
+ if (NULL == request)
+ {
+ request = calloc (1, sizeof (struct Request));
+ if (NULL == request)
+ {
+ fprintf (stderr, "calloc error: %s\n", strerror (errno));
+ return MHD_NO;
+ }
+ *ptr = request;
+ if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
+ {
+ request->pp = MHD_create_post_processor (connection, 1024,
+ &post_iterator, request);
+ if (NULL == request->pp)
+ {
+ fprintf (stderr, "Failed to setup post processor for `%s'\n",
+ url);
+ return MHD_NO; /* internal error */
+ }
+ }
+ return MHD_YES;
+ }
+ if (NULL == request->session)
+ {
+ request->session = get_session (connection);
+ if (NULL == request->session)
+ {
+ fprintf (stderr, "Failed to setup session for `%s'\n",
+ url);
+ return MHD_NO; /* internal error */
+ }
+ }
+ session = request->session;
+ session->start = time (NULL);
+ if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
+ {
+ /* evaluate POST data */
+ MHD_post_process (request->pp,
+ upload_data,
+ *upload_data_size);
+ if (0 != *upload_data_size)
+ {
+ *upload_data_size = 0;
+ return MHD_YES;
+ }
+ /* done with POST data, serve response */
+ MHD_destroy_post_processor (request->pp);
+ request->pp = NULL;
+ method = MHD_HTTP_METHOD_GET; /* fake 'GET' */
+ if (NULL != request->post_url)
+ url = request->post_url;
+ }
+
+ if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
+ (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
+ {
+ /* find out which page to serve */
+ i=0;
+ while ( (pages[i].url != NULL) &&
+ (0 != strcmp (pages[i].url, url)) )
+ i++;
+ ret = pages[i].handler (pages[i].handler_cls,
+ pages[i].mime,
+ session, connection);
+ if (ret != MHD_YES)
+ fprintf (stderr, "Failed to create page for `%s'\n",
+ url);
+ return ret;
+ }
+ /* unsupported HTTP method */
+ response = MHD_create_response_from_buffer (strlen (METHOD_ERROR),
+ (void *) METHOD_ERROR,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Callback called upon completion of a request.
+ * Decrements session reference counter.
+ *
+ * @param cls not used
+ * @param connection connection that completed
+ * @param con_cls session handle
+ * @param toe status code
+ */
+static void
+request_completed_callback (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct Request *request = *con_cls;
+
+ if (NULL == request)
+ return;
+ if (NULL != request->session)
+ request->session->rc--;
+ if (NULL != request->pp)
+ MHD_destroy_post_processor (request->pp);
+ free (request);
+}
+
+
+/**
+ * Clean up handles of sessions that have been idle for
+ * too long.
+ */
+static void
+expire_sessions ()
+{
+ struct Session *pos;
+ struct Session *prev;
+ struct Session *next;
+ time_t now;
+
+ now = time (NULL);
+ prev = NULL;
+ pos = sessions;
+ while (NULL != pos)
+ {
+ next = pos->next;
+ if (now - pos->start > 60 * 60)
+ {
+ /* expire sessions after 1h */
+ if (NULL == prev)
+ sessions = pos->next;
+ else
+ prev->next = next;
+ free (pos);
+ }
+ else
+ prev = pos;
+ pos = next;
+ }
+}
+
+
+/**
+ * Call with the port number as the only argument.
+ * Never terminates (other than by signals, such as CTRL-C).
+ */
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+ struct timeval tv;
+ struct timeval *tvp;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ MHD_UNSIGNED_LONG_LONG mhd_timeout;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ /* initialize PRNG */
+ srand ((unsigned int) time (NULL));
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL,
+ &create_response, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15,
+ MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
+ MHD_OPTION_END);
+ if (NULL == d)
+ return 1;
+ while (1)
+ {
+ expire_sessions ();
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ break; /* fatal internal error */
+ if (MHD_get_timeout (d, &mhd_timeout) == MHD_YES)
+ {
+ tv.tv_sec = mhd_timeout / 1000;
+ tv.tv_usec = (mhd_timeout - (tv.tv_sec * 1000)) * 1000;
+ tvp = &tv;
+ }
+ else
+ tvp = NULL;
+ select (max + 1, &rs, &ws, &es, tvp);
+ MHD_run (d);
+ }
+ MHD_stop_daemon (d);
+ return 0;
+}
+
diff --git a/src/examples/querystring_example.c b/src/examples/querystring_example.c
new file mode 100644
index 0000000..24f8ae4
--- /dev/null
+++ b/src/examples/querystring_example.c
@@ -0,0 +1,90 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file querystring_example.c
+ * @brief example for how to get the query string from libmicrohttpd
+ * Call with an URI ending with something like "?q=QUERY"
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>Query string for "%s" was "%s"</body></html>"
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ const char *fmt = cls;
+ const char *val;
+ char *me;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (method, "GET"))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "q");
+ me = malloc (snprintf (NULL, 0, fmt, "q", val) + 1);
+ if (me == NULL)
+ return MHD_NO;
+ sprintf (me, fmt, "q", val);
+ response = MHD_create_response_from_buffer (strlen (me), me,
+ MHD_RESPMEM_MUST_FREE);
+ if (response == NULL)
+ {
+ free (me);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, PAGE, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
diff --git a/src/examples/refuse_post_example.c b/src/examples/refuse_post_example.c
new file mode 100644
index 0000000..846546c
--- /dev/null
+++ b/src/examples/refuse_post_example.c
@@ -0,0 +1,100 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file refuse_post_example.c
+ * @brief example for how to refuse a POST request properly
+ * @author Christian Grothoff and Sebastian Gerhardt
+ */
+#include "platform.h"
+#include <microhttpd.h>
+
+const char *askpage = "<html><body>\n\
+ Upload a file, please!<br>\n\
+ <form action=\"/filepost\" method=\"post\" enctype=\"multipart/form-data\">\n\
+ <input name=\"file\" type=\"file\">\n\
+ <input type=\"submit\" value=\" Send \"></form>\n\
+ </body></html>";
+
+#define BUSYPAGE "<html><head><title>Webserver busy</title></head><body>We are too busy to process POSTs right now.</body></html>"
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if ((0 != strcmp (method, "GET")) && (0 != strcmp (method, "POST")))
+ return MHD_NO; /* unexpected method */
+
+ if (&aptr != *ptr)
+ {
+ *ptr = &aptr;
+
+ /* always to busy for POST requests */
+ if (0 == strcmp (method, "POST"))
+ {
+ response = MHD_create_response_from_buffer (strlen (BUSYPAGE),
+ (void *) BUSYPAGE,
+ MHD_RESPMEM_PERSISTENT);
+ ret =
+ MHD_queue_response (connection, MHD_HTTP_SERVICE_UNAVAILABLE,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+ }
+ }
+
+ *ptr = NULL; /* reset when done */
+ response = MHD_create_response_from_buffer (strlen (me),
+ (void *) me,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Daemon *d;
+
+ if (argc != 2)
+ {
+ printf ("%s PORT\n", argv[0]);
+ return 1;
+ }
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ atoi (argv[1]),
+ NULL, NULL, &ahc_echo, (void *) askpage,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ (void) getc (stdin);
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+/* end of refuse_post_example.c */
diff --git a/src/examples/spdy_event_loop.c b/src/examples/spdy_event_loop.c
new file mode 100644
index 0000000..6b7192c
--- /dev/null
+++ b/src/examples/spdy_event_loop.c
@@ -0,0 +1,487 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file event_loop.c
+ * @brief shows how to use the daemon. THIS IS MAINLY A TEST AND DEBUG
+ * PROGRAM
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include "microspdy.h"
+#include <sys/time.h>
+#include <time.h>
+#ifndef MINGW
+#include <arpa/inet.h>
+#endif
+//#include "../framinglayer/structures.h"
+//#include "../applicationlayer/alstructures.h"
+
+static int run = 1;
+
+static int run2 = 1;
+
+
+static uint64_t loops;
+
+static time_t start;
+
+
+static void
+new_session_callback (void *cls,
+ struct SPDY_Session * session)
+{
+ (void)cls;
+
+ char ipstr[1024];
+
+ struct sockaddr *addr;
+ socklen_t addr_len = SPDY_get_remote_addr(session, &addr);
+
+ if(!addr_len)
+ {
+ printf("SPDY_get_remote_addr");
+ abort();
+ }
+
+ if(AF_INET == addr->sa_family)
+ {
+ struct sockaddr_in * addr4 = (struct sockaddr_in *) addr;
+ if(NULL == inet_ntop(AF_INET, &(addr4->sin_addr), ipstr, sizeof(ipstr)))
+ {
+ printf("inet_ntop");
+ abort();
+ }
+ printf("New connection from: %s:%i\n", ipstr, ntohs(addr4->sin_port));
+
+ }
+ else if(AF_INET6 == addr->sa_family)
+ {
+ struct sockaddr_in6 * addr6 = (struct sockaddr_in6 *) addr;
+ if(NULL == inet_ntop(AF_INET6, &(addr6->sin6_addr), ipstr, sizeof(ipstr)))
+ {
+ printf("inet_ntop");
+ abort();
+ }
+ printf("New connection from: %s:%i\n", ipstr, ntohs(addr6->sin6_port));
+
+ }
+}
+
+
+static void
+session_closed_handler (void *cls,
+ struct SPDY_Session * session,
+ int by_client)
+{
+ (void)cls;
+ (void)session;
+
+ //printf("session_closed_handler called\n");
+
+ if(SPDY_YES != by_client)
+ {
+ //killchild(child,"wrong by_client");
+ printf("session closed by server\n");
+ }
+ else
+ {
+ printf("session closed by client\n");
+ }
+
+ //session_closed_called = 1;
+}
+
+
+static void
+response_done_callback(void *cls,
+ struct SPDY_Response *response,
+ struct SPDY_Request *request,
+ enum SPDY_RESPONSE_RESULT status,
+ bool streamopened)
+{
+ (void)streamopened;
+ if(strcmp(cls, "/close (daemon1)") == 0)
+ run = 0;
+ else {
+ if(strcmp(cls, "/close (daemon2)") == 0) run2 = 0;
+ loops = 0;
+ start = time(NULL);
+ }
+ if(SPDY_RESPONSE_RESULT_SUCCESS != status)
+ {
+ printf("not sent frame cause %i", status);
+ }
+ printf("answer for %s was sent\n", (char*)cls);
+ //printf("raw sent headers %s\n", (char *)(response->headers)+8);
+
+ SPDY_destroy_request(request);
+ SPDY_destroy_response(response);
+ free(cls);
+}
+
+/*
+static int
+print_headers (void *cls,
+ const char *name, const char *value)
+{
+ (void)cls;
+ printf("%s: %s\n",name,value);
+ return SPDY_YES;
+}
+ */
+
+
+/*
+void
+new_request_cb (void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers)
+{
+ (void)cls;
+ (void)request;
+ printf("Priority: %i\nHTTP headers, scheme: %s\n\n%s %s %s\nHost: %s\n", priority,scheme,method,path,version,host);
+ SPDY_name_value_iterate(headers, &print_headers, NULL);
+}
+*/
+
+
+static int
+append_headers_to_data (void *cls,
+ const char *name, const char * const *value, int num_values)
+{
+ char **data = cls;
+ void *tofree = *data;
+ int i;
+
+ if(num_values)
+ for(i=0;i<num_values;++i)
+ {
+ asprintf(data,"%s%s: %s\n", *data,name,value[i]);
+ }
+ else
+ asprintf(data,"%s%s: \n", *data,name);
+
+ free(tofree);
+ return SPDY_YES;
+}
+
+
+static void
+standard_request_handler(void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers,
+ bool more)
+{
+ (void)more;
+
+ char *html;
+ char *data;
+ struct SPDY_Response *response=NULL;
+
+ printf("received request for '%s %s %s'\n", method, path, version);
+ if(strcmp(path,"/main.css")==0)
+ {
+ if(NULL != cls)
+ asprintf(&html,"body{color:green;}");
+ else
+ asprintf(&html,"body{color:red;}");
+
+ //struct SPDY_NameValue *headers=SPDY_name_value_create();
+ //SPDY_name_value_add(headers,"content-type","text/css");
+
+ response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,html,strlen(html));
+ free(html);
+ }
+ else
+ {
+ asprintf(&data,"Priority: %i\nHTTP headers, scheme: %s\n\n%s %s %s\nHost: %s\n", priority,scheme,method,path,version,host);
+
+ SPDY_name_value_iterate(headers, &append_headers_to_data, &data);
+
+ if(strcmp(path,"/close")==0)
+ {
+ asprintf(&html,"<html>"
+ "<body><b>Closing now!<br>This is an answer to the following "
+ "request:</b><br><br><pre>%s</pre></body></html>",data);
+ }
+ else
+ {
+ asprintf(&html,"<html><link href=\"main.css\" rel=\"stylesheet\" type=\"text/css\" />"
+ "<body><b>This is an answer to the following "
+ "request:</b><br><br><pre>%s</pre></body></html>",data);
+ }
+
+ free(data);
+
+ response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,html,strlen(html));
+ free(html);
+ }
+
+ if(NULL==response){
+ fprintf(stdout,"no response obj\n");
+ abort();
+ }
+
+ char *pathcls;
+ asprintf(&pathcls, "%s (daemon%i)",path,NULL==cls ? 1 : 2);
+ if(SPDY_queue_response(request,response,true,false,&response_done_callback,pathcls)!=SPDY_YES)
+ {
+ fprintf(stdout,"queue\n");
+ abort();
+ }
+}
+
+
+static int
+new_post_data_cb (void * cls,
+ struct SPDY_Request *request,
+ const void * buf,
+ size_t size,
+ bool more)
+{
+ (void)cls;
+ (void)request;
+ (void)more;
+
+ printf("DATA:\n===============================\n");
+ write(0, buf, size);
+ printf("\n===============================\n");
+ return SPDY_YES;
+}
+
+
+static void
+sig_handler(int signo)
+{
+ (void)signo;
+
+ printf("received signal\n");
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ if(argc != 2) return 1;
+
+ #ifndef MINGW
+ if (signal(SIGPIPE, sig_handler) == SIG_ERR)
+ printf("\ncan't catch SIGPIPE\n");
+ #endif
+
+ SPDY_init();
+
+ /*
+ struct sockaddr_in addr4;
+ struct in_addr inaddr4;
+ inaddr4.s_addr = htonl(INADDR_ANY);
+ addr4.sin_family = AF_INET;
+ addr4.sin_addr = inaddr4;
+ addr4.sin_port = htons(atoi(argv[1]));
+ */
+
+ struct SPDY_Daemon *daemon = SPDY_start_daemon(atoi(argv[1]),
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ &new_session_callback,&session_closed_handler,&standard_request_handler,&new_post_data_cb,NULL,
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT, 10,
+ //SPDY_DAEMON_OPTION_SOCK_ADDR, (struct sockaddr *)&addr4,
+ SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ /*
+ struct sockaddr_in6 addr6;
+ addr6.sin6_family = AF_INET6;
+ addr6.sin6_addr = in6addr_any;
+ addr6.sin6_port = htons(atoi(argv[1]) + 1);
+ */
+
+ struct SPDY_Daemon *daemon2 = SPDY_start_daemon(atoi(argv[1]) + 1,
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ &new_session_callback,NULL,&standard_request_handler,&new_post_data_cb,&main,
+ //SPDY_DAEMON_OPTION_SESSION_TIMEOUT, 0,
+ //SPDY_DAEMON_OPTION_SOCK_ADDR, (struct sockaddr *)&addr6,
+ //SPDY_DAEMON_OPTION_FLAGS, SPDY_DAEMON_FLAG_ONLY_IPV6,
+ SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon2){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ do
+ {
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ volatile int rc; /* select() return code */
+ volatile int ret;
+
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+
+ if(run && daemon != NULL)
+ {
+ loops++;
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ printf("ret=%i; timeoutlong=%llu; sec=%llu; usec=%llu\n", ret, timeoutlong, (long long unsigned)timeout.tv_sec, (long long unsigned)timeout.tv_usec);
+ //raise(SIGINT);
+
+ /* get file descriptors from the transfers */
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+//struct timeval ts1,ts2;
+ //gettimeofday(&ts1, NULL);
+ rc = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+ //gettimeofday(&ts2, NULL);
+ printf("rc %i\n",rc);
+ // printf("time for select %i\n",ts2.tv_usec - ts1.tv_usec);
+ // printf("%i %i %i %i\n",ts1.tv_sec, ts1.tv_usec,ts2.tv_sec, ts2.tv_usec);
+
+ switch(rc) {
+ case -1:
+ /* select error */
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon);
+
+ break;
+ }
+ }
+ else if(daemon != NULL){
+
+ printf("%lu loops in %llu secs\n", loops, (long long unsigned)(time(NULL) - start));
+ SPDY_stop_daemon(daemon);
+ daemon=NULL;
+ }
+
+ if(run2)
+ {
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon2, &timeoutlong);
+ //printf("tout %i\n",timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1)
+ {
+ //do sth else
+ //sleep(1);
+
+ //try new connection
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong;
+ timeout.tv_usec = 0;//(timeoutlong % 1000) * 1000;
+ }
+
+ //printf("ret=%i; timeoutlong=%i; sec=%i; usec=%i\n", ret, timeoutlong, timeout.tv_sec, timeout.tv_usec);
+ //raise(SIGINT);
+
+ /* get file descriptors from the transfers */
+ maxfd = SPDY_get_fdset (daemon2,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ rc = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(rc) {
+ case -1:
+ /* select error */
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon2);
+
+ break;
+ }
+ }
+ else if(daemon2 != NULL){
+ SPDY_stop_daemon(daemon2);
+ daemon2=NULL;
+ }
+ }
+ while(run || run2);
+
+ if(daemon != NULL){
+ SPDY_stop_daemon(daemon);
+ }
+ if(daemon2 != NULL){
+ SPDY_stop_daemon(daemon2);
+ }
+
+ SPDY_deinit();
+
+ return 0;
+}
+
diff --git a/src/examples/spdy_fileserver.c b/src/examples/spdy_fileserver.c
new file mode 100644
index 0000000..0a0254f
--- /dev/null
+++ b/src/examples/spdy_fileserver.c
@@ -0,0 +1,353 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file fileserver.c
+ * @brief Simple example how the lib can be used for serving
+ * files directly read from the system
+ * @author Andrey Uzunov
+ */
+
+//for asprintf
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include "microspdy.h"
+#include "time.h"
+
+
+int run = 1;
+char* basedir;
+
+
+#define GET_MIME_TYPE(fname, mime) do {\
+ unsigned int __len = strlen(fname);\
+ if (__len < 4 || '.' != (fname)[__len - 4]) \
+ { \
+ (mime) = strdup("application/octet-stream");\
+ printf("MIME for %s is applic...\n", (fname));\
+ }\
+ else {\
+ const char * __ext = &(fname)[__len - 3];\
+ if(0 == strcmp(__ext, "jpg")) (mime) = strdup("image/jpeg");\
+ else if(0 == strcmp(__ext, "png")) (mime) = strdup("image/png");\
+ else if(0 == strcmp(__ext, "css")) (mime) = strdup("text/css");\
+ else if(0 == strcmp(__ext, "gif")) (mime) = strdup("image/gif");\
+ else if(0 == strcmp(__ext, "htm")) (mime) = strdup("text/html");\
+ else \
+ { \
+ (mime) = strdup("application/octet-stream");\
+ printf("MIME for %s is applic...\n", (fname));\
+ }\
+ }\
+ if(NULL == (mime))\
+ {\
+ printf("no memory\n");\
+ abort();\
+ }\
+ } while (0)
+
+
+static const char *DAY_NAMES[] =
+ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+
+static const char *MONTH_NAMES[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+//taken from http://stackoverflow.com/questions/2726975/how-can-i-generate-an-rfc1123-date-string-from-c-code-win32
+//and modified for linux
+char *Rfc1123_DateTimeNow()
+{
+ const int RFC1123_TIME_LEN = 29;
+ time_t t;
+ struct tm tm;
+ char * buf = malloc(RFC1123_TIME_LEN+1);
+
+ if (NULL == buf)
+ return NULL;
+ time(&t);
+ gmtime_r( &t, &tm);
+
+ strftime(buf, RFC1123_TIME_LEN+1, "---, %d --- %Y %H:%M:%S GMT", &tm);
+ memcpy(buf, DAY_NAMES[tm.tm_wday], 3);
+ memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
+
+ return buf;
+}
+
+
+ssize_t
+response_callback (void *cls,
+ void *buffer,
+ size_t max,
+ bool *more)
+{
+ FILE *fd =(FILE*)cls;
+
+ int ret = fread(buffer,1,max,fd);
+ *more = feof(fd) == 0;
+
+ //if(!(*more))
+ // fclose(fd);
+
+ return ret;
+}
+
+
+void
+response_done_callback(void *cls,
+ struct SPDY_Response *response,
+ struct SPDY_Request *request,
+ enum SPDY_RESPONSE_RESULT status,
+ bool streamopened)
+{
+ (void)streamopened;
+ (void)status;
+ //printf("answer for %s was sent\n", (char *)cls);
+
+ /*if(SPDY_RESPONSE_RESULT_SUCCESS != status)
+ {
+ printf("answer for %s was NOT sent, %i\n", (char *)cls,status);
+ }*/
+
+ SPDY_destroy_request(request);
+ SPDY_destroy_response(response);
+ if(NULL!=cls)fclose(cls);
+}
+
+void
+standard_request_handler(void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers,
+ bool more)
+{
+ (void)cls;
+ (void)request;
+ (void)priority;
+ (void)host;
+ (void)scheme;
+ (void)headers;
+ (void)method;
+ (void)version;
+ (void)more;
+
+ struct SPDY_Response *response=NULL;
+ struct SPDY_NameValue *resp_headers;
+ char *fname;
+ char *fsize;
+ char *mime=NULL;
+ char *date=NULL;
+ ssize_t filesize = -666;
+ FILE *fd = NULL;
+ int ret = -666;
+
+ //printf("received request for '%s %s %s'\n", method, path, version);
+ if(strlen(path) > 1 && NULL == strstr(path, "..") && '/' == path[0])
+ {
+ asprintf(&fname,"%s%s",basedir,path);
+ if(0 == access(fname, R_OK))
+ {
+ if(NULL == (fd = fopen(fname,"r"))
+ || 0 != (ret = fseek(fd, 0L, SEEK_END))
+ || -1 == (filesize = ftell(fd))
+ || 0 != (ret = fseek(fd, 0L, SEEK_SET)))
+ {
+ printf("Error on opening %s\n%p %i %zd\n",fname, fd, ret, filesize);
+ response = SPDY_build_response(SPDY_HTTP_INTERNAL_SERVER_ERROR,NULL,SPDY_HTTP_VERSION_1_1,NULL,NULL,0);
+ }
+ else
+ {
+ if(NULL == (resp_headers = SPDY_name_value_create()))
+ {
+ printf("SPDY_name_value_create failed\n");
+ abort();
+ }
+
+ date = Rfc1123_DateTimeNow();
+ if(NULL == date
+ || SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_DATE,date))
+ {
+ printf("SPDY_name_value_add or Rfc1123_DateTimeNow failed\n");
+ abort();
+ }
+ free(date);
+
+ if(-1 == asprintf(&fsize, "%zd", filesize)
+ || SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_LENGTH,fsize))
+ {
+ printf("SPDY_name_value_add or asprintf failed\n");
+ abort();
+ }
+ free(fsize);
+
+ GET_MIME_TYPE(path,mime);
+ if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_TYPE,mime))
+ {
+ printf("SPDY_name_value_add failed\n");
+ abort();
+ }
+ free(mime);
+
+ if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_SERVER,"libmicrospdy/fileserver"))
+ {
+ printf("SPDY_name_value_add failed\n");
+ abort();
+ }
+
+ response = SPDY_build_response_with_callback(200,NULL,
+ SPDY_HTTP_VERSION_1_1,resp_headers,&response_callback,fd,SPDY_MAX_SUPPORTED_FRAME_SIZE);
+ SPDY_name_value_destroy(resp_headers);
+ }
+
+ if(NULL==response){
+ printf("no response obj\n");
+ abort();
+ }
+
+ if(SPDY_queue_response(request,response,true,false,&response_done_callback,fd)!=SPDY_YES)
+ {
+ printf("queue\n");
+ abort();
+ }
+
+ free(fname);
+ return;
+ }
+ free(fname);
+ }
+
+ if(strcmp(path,"/close")==0)
+ {
+ run = 0;
+ }
+
+ response = SPDY_build_response(SPDY_HTTP_NOT_FOUND,NULL,SPDY_HTTP_VERSION_1_1,NULL,NULL,0);
+ printf("Not found %s\n",path);
+
+ if(NULL==response){
+ printf("no response obj\n");
+ abort();
+ }
+
+ if(SPDY_queue_response(request,response,true,false,&response_done_callback,NULL)!=SPDY_YES)
+ {
+ printf("queue\n");
+ abort();
+ }
+}
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+ struct SPDY_Daemon *daemon;
+
+ if(argc != 5)
+ {
+ printf("Usage: %s cert-file key-file base-dir port\n", argv[0]);
+ return 1;
+ }
+
+ SPDY_init();
+
+ daemon = SPDY_start_daemon(atoi(argv[4]),
+ argv[1],
+ argv[2],
+ NULL,
+ NULL,
+ &standard_request_handler,
+ NULL,
+ NULL,
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
+ 1800,
+ SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ basedir = argv[3];
+
+ do
+ {
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(ret) {
+ case -1:
+ printf("select error: %i\n", errno);
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon);
+
+ break;
+ }
+ }
+ while(run);
+
+ SPDY_stop_daemon(daemon);
+
+ SPDY_deinit();
+
+ return 0;
+}
+
diff --git a/src/examples/spdy_response_with_callback.c b/src/examples/spdy_response_with_callback.c
new file mode 100644
index 0000000..5bd452d
--- /dev/null
+++ b/src/examples/spdy_response_with_callback.c
@@ -0,0 +1,236 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file response_with_callback.c
+ * @brief shows how to create responses with callbacks
+ * @author Andrey Uzunov
+ */
+
+//for asprintf
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include "microspdy.h"
+
+static int run = 1;
+
+
+static ssize_t
+response_callback (void *cls,
+ void *buffer,
+ size_t max,
+ bool *more)
+{
+ FILE *fd =(FILE*)cls;
+
+ int ret = fread(buffer,1,max,fd);
+ *more = feof(fd) == 0;
+
+ if(!(*more))
+ fclose(fd);
+
+ return ret;
+}
+
+
+static void
+response_done_callback(void *cls,
+ struct SPDY_Response *response,
+ struct SPDY_Request *request,
+ enum SPDY_RESPONSE_RESULT status,
+ bool streamopened)
+{
+ (void)streamopened;
+ (void)status;
+
+ printf("answer for %s was sent\n", (char *)cls);
+
+ SPDY_destroy_request(request);
+ SPDY_destroy_response(response);
+ free(cls);
+}
+
+
+static void
+standard_request_handler(void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers,
+ bool more)
+{
+ (void)cls;
+ (void)request;
+ (void)priority;
+ (void)host;
+ (void)scheme;
+ (void)headers;
+ (void)more;
+
+ char *html;
+ struct SPDY_Response *response=NULL;
+ struct SPDY_NameValue *resp_headers;
+
+ printf("received request for '%s %s %s'\n", method, path, version);
+ if(strcmp(path,"/spdy-draft.txt")==0)
+ {
+ FILE *fd = fopen(DATA_DIR "spdy-draft.txt","r");
+
+ if(NULL == (resp_headers = SPDY_name_value_create()))
+ {
+ fprintf(stdout,"SPDY_name_value_create failed\n");
+ abort();
+ }
+ if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_TYPE,"text/plain"))
+ {
+ fprintf(stdout,"SPDY_name_value_add failed\n");
+ abort();
+ }
+
+ response = SPDY_build_response_with_callback(200,NULL,
+ SPDY_HTTP_VERSION_1_1,resp_headers,&response_callback,fd,SPDY_MAX_SUPPORTED_FRAME_SIZE);
+ SPDY_name_value_destroy(resp_headers);
+ }
+ else
+ {
+ if(strcmp(path,"/close")==0)
+ {
+ asprintf(&html,"<html>"
+ "<body><b>Closing now!</body></html>");
+ run = 0;
+ }
+ else
+ {
+ asprintf(&html,"<html>"
+ "<body><a href=\"/spdy-draft.txt\">/spdy-draft.txt</a><br></body></html>");
+ }
+
+ response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,html,strlen(html));
+ free(html);
+ }
+
+ if(NULL==response){
+ fprintf(stdout,"no response obj\n");
+ abort();
+ }
+
+ void *clspath = strdup(path);
+
+ if(SPDY_queue_response(request,response,true,false,&response_done_callback,clspath)!=SPDY_YES)
+ {
+ fprintf(stdout,"queue\n");
+ abort();
+ }
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+ struct SPDY_Daemon *daemon;
+
+ if(argc != 2)
+ {
+ return 1;
+ }
+
+ SPDY_init();
+
+ daemon = SPDY_start_daemon(atoi(argv[1]),
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ NULL,
+ NULL,
+ &standard_request_handler,
+ NULL,
+ NULL,
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
+ 1800,
+ SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ do
+ {
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(ret) {
+ case -1:
+ printf("select error: %i\n", errno);
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon);
+
+ break;
+ }
+ }
+ while(run);
+
+ SPDY_stop_daemon(daemon);
+
+ SPDY_deinit();
+
+ return 0;
+}
+
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
new file mode 100644
index 0000000..437008f
--- /dev/null
+++ b/src/include/Makefile.am
@@ -0,0 +1,10 @@
+# This Makefile.am is in the public domain
+SUBDIRS = .
+
+if ENABLE_SPDY
+microspdy = microspdy.h
+endif
+
+include_HEADERS = microhttpd.h $(microspdy)
+
+EXTRA_DIST = platform.h platform_interface.h w32functions.h autoinit_funcs.h
diff --git a/src/include/Makefile.in b/src/include/Makefile.in
new file mode 100644
index 0000000..151ac48
--- /dev/null
+++ b/src/include/Makefile.in
@@ -0,0 +1,723 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/include
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(am__include_HEADERS_DIST)
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__include_HEADERS_DIST = microhttpd.h microspdy.h
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(includedir)"
+HEADERS = $(include_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+SUBDIRS = .
+@ENABLE_SPDY_TRUE@microspdy = microspdy.h
+include_HEADERS = microhttpd.h $(microspdy)
+EXTRA_DIST = platform.h platform_interface.h w32functions.h autoinit_funcs.h
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/include/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/include/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-includeHEADERS: $(include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \
+ done
+
+uninstall-includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-includeHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-includeHEADERS install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-includeHEADERS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/include/autoinit_funcs.h b/src/include/autoinit_funcs.h
new file mode 100644
index 0000000..2f309e1
--- /dev/null
+++ b/src/include/autoinit_funcs.h
@@ -0,0 +1,242 @@
+/*
+ * AutoinitFuncs: Automatic Initialization and Deinitialization Functions
+ * CopyrightCopyright (C) 2014 Karlson2k (Evgeny Grin)
+ *
+ * This header is free software; you can redistribute it and / or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This header 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this header; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ General usage is simple: include this header, declare or define two
+ functions with zero parameters (void) and any return type: one for
+ initialization and one for deinitialization, add
+ _SET_INIT_AND_DEINIT_FUNCS(FuncInitName, FuncDeInitName) to the code
+ and functions will be automatically called during application startup
+ and shutdown.
+ This is useful for libraries as libraries doesn't have direct access
+ to main() functions.
+ Example:
+ -------------------------------------------------
+ #include <stdlib.h>
+ #include "autoinit_funcs.h"
+
+ int someVar;
+ void* somePtr;
+
+ void libInit(void)
+ {
+ someVar = 3;
+ somePtr = malloc(100);
+ }
+
+ void libDeinit(void)
+ {
+ free(somePtr);
+ }
+
+ _SET_INIT_AND_DEINIT_FUNCS(libInit,libDeinit);
+ -------------------------------------------------
+
+ If initializer or deinitializer functions is not needed, just define
+ it as empty function.
+
+ This header should work with GCC, clang, MSVC (2010 or later).
+ Supported C and C++ languages; application, static and dynamic (DLL)
+ libraries; non-optimized (Debug) and optimized (Release) compilation
+ and linking.
+
+ For more information see header code and comments in code.
+ */
+#ifndef AUTOINIT_FUNCS_INCLUDED
+#define AUTOINIT_FUNCS_INCLUDED 1
+
+/**
+* Current version of the header.
+* 0x01093001 = 1.9.30-1.
+*/
+#define AUTOINIT_FUNCS_VERSION 0x01000001
+
+#if defined(__GNUC__)
+#/* if possible - check for supported attribute */
+#ifdef __has_attribute
+#if !__has_attribute(constructor) || !__has_attribute(destructor)
+#define _GNUC_ATTR_CONSTR_NOT_SUPPORTED 1
+#endif /* !__has_attribute(constructor) || !__has_attribute(destructor) */
+#endif /* __has_attribute */
+#endif /* __GNUC__ */
+
+#if defined(__GNUC__) && !defined(_GNUC_ATTR_CONSTR_NOT_SUPPORTED)
+
+#define GNUC_SET_INIT_AND_DEINIT(FI,FD) \
+ void __attribute__ ((constructor)) _GNUC_init_helper_##FI(void) \
+ { (void)(FI)(); } \
+ void __attribute__ ((destructor)) _GNUC_deinit_helper_##FD(void) \
+ { (void)(FD)(); } \
+ struct _GNUC_dummy_str_##FI{int i;}
+
+#define _SET_INIT_AND_DEINIT_FUNCS(FI,FD) GNUC_SET_INIT_AND_DEINIT(FI,FD)
+#define _AUTOINIT_FUNCS_ARE_SUPPORTED 1
+
+#elif defined (_MSC_FULL_VER) && _MSC_VER+0 >= 1600
+
+/* Make sure that your project/sources define:
+ _LIB if building a static library (_LIB is ignored if _CONSOLE is defined);
+ _USRDLL if building DLL-library;
+ not defined both _LIB and _USRDLL if building an application */
+
+/* Define AUTOINIT_FUNCS_DECLARE_STATIC_REG if you need macro declaration
+ for registering static initialization functions even if you building DLL */
+/* Define AUTOINIT_FUNCS_FORCE_STATIC_REG if you want to set main macro
+ _SET_INIT_AND_DEINIT_FUNCS to static version even if building a DLL*/
+
+/* Stringify macros */
+#define _INSTRMACRO(a) #a
+#define _STRMACRO(a) _INSTRMACRO(a)
+
+#if !defined(_USRDLL) || defined(AUTOINIT_FUNCS_DECLARE_STATIC_REG)
+
+/* required for atexit() */
+#include <stdlib.h>
+
+/* Use "C" linkage for variable to simplify variable decoration */
+#ifdef __cplusplus
+#define W32_INITVARDECL extern "C"
+#else
+#define W32_INITVARDECL extern
+#endif
+
+/* How variable is decorated by compiler */
+#if defined(_M_X64) || defined(_M_AMD64)
+#define W32_VARDECORPREFIX
+#define W32_DECORVARNAME(v) v
+#define W32_VARDECORPEFIXSTR ""
+#elif defined(_M_IX86) || defined(_X86_)
+#define W32_VARDECORPREFIX _
+#define W32_DECORVARNAME(v) _##v
+#define W32_VARDECORPEFIXSTR "_"
+#else
+#error Do not know how to decorate symbols for this architecture
+#endif
+
+/* Internal variable prefix (can be any) */
+#define W32_INITHELPERVARNAME(f) _initHelperDummy_##f
+#define W32_INITHELPERVARNAMEDECORSTR(f) W32_VARDECORPEFIXSTR _STRMACRO(W32_INITHELPERVARNAME(f))
+
+/* Declare section (segment), put variable pointing to init function to chosen segment,
+ force linker to include variable to avoid omitting by optimizer */
+/* Initialization function must be declared as
+ int __cdecl FuncName(void) */
+/* Return value is ignored for C++ initializers */
+/* For C initializers: startup process is aborted if initializer return non-zero */
+#define W32_FPTR_IN_SEG(S,F) \
+ __pragma(section(S,long,read)) \
+ __pragma(comment(linker, "/INCLUDE:" W32_INITHELPERVARNAMEDECORSTR(F))) \
+ W32_INITVARDECL __declspec(allocate(S)) int(__cdecl *W32_INITHELPERVARNAME(F))(void) = &F
+
+/* Section (segment) names for pointers to initializers */
+#define W32_SEG_INIT_C_USER ".CRT$XCU"
+#define W32_SEG_INIT_C_LIB ".CRT$XCL"
+#define W32_SEG_INIT_CXX_USER ".CRT$XIU"
+#define W32_SEG_INIT_CXX_LIB ".CRT$XIL"
+
+/* Declare macro for different initializers sections */
+/* Macro can be used several times to register several initializers */
+/* Once function is registered as initializer, it will be called automatically
+ during application startup */
+/* "lib" initializers are called before "user" initializers */
+/* "C" initializers are called before "C++" initializers */
+#define W32_REG_INIT_C_USER(F) W32_FPTR_IN_SEG(W32_SEG_INIT_C_USER,F)
+#define W32_REG_INIT_C_LIB(F) W32_FPTR_IN_SEG(W32_SEG_INIT_C_LIB,F)
+#define W32_REG_INIT_CXX_USER(F) W32_FPTR_IN_SEG(W32_SEG_INIT_CXX_USER,F)
+#define W32_REG_INIT_CXX_LIB(F) W32_FPTR_IN_SEG(W32_SEG_INIT_CXX_LIB,F)
+
+/* Choose main register macro based on language and program type */
+/* Assuming that _LIB or _USRDLL is defined for static or DLL-library */
+/* Macro can be used several times to register several initializers */
+/* Once function is registered as initializer, it will be called automatically
+ during application startup */
+/* Define AUTOINIT_FUNCS_FORCE_USER_LVL_INIT to register initializers
+ at user level even if building library */
+#ifdef __cplusplus
+#if ((defined(_LIB) && !defined(_CONSOLE)) || defined(_USRDLL)) && !defined(AUTOINIT_FUNCS_FORCE_USER_LVL_INIT)
+#define W32_REGISTER_INIT(F) W32_REG_INIT_CXX_LIB(F)
+#else /* ! _LIB && ! _DLL */
+#define W32_REGISTER_INIT(F) W32_REG_INIT_CXX_USER(F)
+#endif /* ! _LIB && ! _DLL */
+#else /* !__cplusplus*/
+#if ((defined(_LIB) && !defined(_CONSOLE)) || defined(_USRDLL)) && !defined(AUTOINIT_FUNCS_FORCE_USER_LVL_INIT)
+#define W32_REGISTER_INIT(F) W32_REG_INIT_C_LIB(F)
+#else /* ! _LIB && ! _DLL */
+#define W32_REGISTER_INIT(F) W32_REG_INIT_C_USER(F)
+#endif /* ! _LIB && ! _DLL */
+#endif /* !__cplusplus*/
+
+#else /* _USRDLL */
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* WIN32_LEAN_AND_MEAN */
+/* Required for DllMain */
+#include <Windows.h>
+#endif /* _USRDLL */
+
+
+#if !defined(_USRDLL) || defined(AUTOINIT_FUNCS_FORCE_STATIC_REG)
+#define W32_SET_INIT_AND_DEINIT(FI,FD) \
+ void __cdecl _W32_deinit_helper_##FD(void) \
+ { (void)(FD)(); } \
+ int __cdecl _W32_init_helper_##FI(void) \
+ { (void)(FI)(); atexit(_W32_deinit_helper_##FD); return 0; } \
+ W32_REGISTER_INIT(_W32_init_helper_##FI)
+#else /* _USRDLL */
+
+/* If DllMain is already present in code, define AUTOINIT_FUNCS_CALL_USR_DLLMAIN
+ and rename DllMain to usr_DllMain */
+#ifndef AUTOINIT_FUNCS_CALL_USR_DLLMAIN
+#define W32_SET_INIT_AND_DEINIT(FI,FD) \
+ BOOL WINAPI DllMain(HINSTANCE hinst,DWORD reason,LPVOID unused) \
+ { if(DLL_PROCESS_ATTACH==reason) {(void)(FI)();} \
+ else if(DLL_PROCESS_DETACH==reason) {(void)(FD)();} \
+ return TRUE; \
+ } struct _W32_dummy_strc_##FI{int i;}
+#else /* AUTOINIT_FUNCS_CALL_USR_DLLMAIN */
+#define W32_SET_INIT_AND_DEINIT(FI,FD) \
+ BOOL WINAPI usr_DllMain(HINSTANCE hinst,DWORD reason,LPVOID unused); \
+ BOOL WINAPI DllMain(HINSTANCE hinst,DWORD reason,LPVOID unused) \
+ { if(DLL_PROCESS_ATTACH==reason) {(void)(FI)();} \
+ else if(DLL_PROCESS_DETACH==reason) {(void)(FD)();} \
+ return usr_DllMain(hinst,reason,unused); \
+ } struct _W32_dummy_strc_##FI{int i;}
+#endif /* AUTOINIT_FUNCS_CALL_USR_DLLMAIN */
+#endif /* _USRDLL */
+
+#define _SET_INIT_AND_DEINIT_FUNCS(FI,FD) W32_SET_INIT_AND_DEINIT(FI,FD)
+/* Indicate that automatic initializers/deinitializers are supported */
+#define _AUTOINIT_FUNCS_ARE_SUPPORTED 1
+
+#else /* !__GNUC__ && !_MSC_FULL_VER */
+
+/* Define EMIT_ERROR_IF_AUTOINIT_FUNCS_ARE_NOT_SUPPORTED before inclusion of header to
+ abort compilation if automatic initializers/deinitializers are not supported */
+#ifdef EMIT_ERROR_IF_AUTOINIT_FUNCS_ARE_NOT_SUPPORTED
+#error Compiler/platform don not support automatic calls of user-defined initializer and deinitializer
+#endif /* EMIT_ERROR_IF_AUTOINIT_FUNCS_ARE_NOT_SUPPORTED */
+
+/* Do nothing */
+#define _SET_INIT_AND_DEINIT_FUNCS(FI,FD)
+/* Indicate that automatic initializers/deinitializers are not supported */
+#define _AUTOINIT_FUNCS_ARE_NOT_SUPPORTED 1
+
+#endif /* !__GNUC__ && !_MSC_FULL_VER */
+#endif /* !AUTOINIT_FUNCS_INCLUDED */
diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h
new file mode 100644
index 0000000..ca3bb86
--- /dev/null
+++ b/src/include/microhttpd.h
@@ -0,0 +1,2675 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2006-2015 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file microhttpd.h
+ * @brief public interface to libmicrohttpd
+ * @author Christian Grothoff
+ * @author Chris GauthierDickey
+ *
+ * All symbols defined in this header start with MHD. MHD is a small
+ * HTTP daemon library. As such, it does not have any API for logging
+ * errors (you can only enable or disable logging to stderr). Also,
+ * it may not support all of the HTTP features directly, where
+ * applicable, portions of HTTP may have to be handled by clients of
+ * the library.
+ *
+ * The library is supposed to handle everything that it must handle
+ * (because the API would not allow clients to do this), such as basic
+ * connection management; however, detailed interpretations of headers
+ * -- such as range requests -- and HTTP methods are left to clients.
+ * The library does understand HEAD and will only send the headers of
+ * the response and not the body, even if the client supplied a body.
+ * The library also understands headers that control connection
+ * management (specifically, "Connection: close" and "Expect: 100
+ * continue" are understood and handled automatically).
+ *
+ * MHD understands POST data and is able to decode certain formats
+ * (at the moment only "application/x-www-form-urlencoded" and
+ * "mulitpart/formdata"). Unsupported encodings and large POST
+ * submissions may require the application to manually process
+ * the stream, which is provided to the main application (and thus can be
+ * processed, just not conveniently by MHD).
+ *
+ * The header file defines various constants used by the HTTP protocol.
+ * This does not mean that MHD actually interprets all of these
+ * values. The provided constants are exported as a convenience
+ * for users of the library. MHD does not verify that transmitted
+ * HTTP headers are part of the standard specification; users of the
+ * library are free to define their own extensions of the HTTP
+ * standard and use those with MHD.
+ *
+ * All functions are guaranteed to be completely reentrant and
+ * thread-safe (with the exception of #MHD_set_connection_value,
+ * which must only be used in a particular context).
+ *
+ * NEW: Before including "microhttpd.h" you should add the necessary
+ * includes to define the `uint64_t`, `size_t`, `fd_set`, `socklen_t`
+ * and `struct sockaddr` data types (which headers are needed may
+ * depend on your platform; for possible suggestions consult
+ * "platform.h" in the MHD distribution). If you have done so, you
+ * should also have a line with "#define MHD_PLATFORM_H" which will
+ * prevent this header from trying (and, depending on your platform,
+ * failing) to include the right headers.
+ *
+ * @defgroup event event-loop control
+ * MHD API to start and stop the HTTP server and manage the event loop.
+ * @defgroup response generation of responses
+ * MHD API used to generate responses.
+ * @defgroup request handling of requests
+ * MHD API used to access information about requests.
+ * @defgroup authentication HTTP authentication
+ * MHD API related to basic and digest HTTP authentication.
+ * @defgroup logging logging
+ * MHD API to mange logging and error handling
+ * @defgroup specialized misc. specialized functions
+ * This group includes functions that do not fit into any particular
+ * category and that are rarely used.
+ */
+
+#ifndef MHD_MICROHTTPD_H
+#define MHD_MICROHTTPD_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+/* While we generally would like users to use a configure-driven
+ build process which detects which headers are present and
+ hence works on any platform, we use "standard" includes here
+ to build out-of-the-box for beginning users on common systems.
+
+ Once you have a proper build system and go for more exotic
+ platforms, you should define MHD_PLATFORM_H in some header that
+ you always include *before* "microhttpd.h". Then the following
+ "standard" includes won't be used (which might be a good
+ idea, especially on platforms where they do not exist). */
+#ifndef MHD_PLATFORM_H
+#include <stdarg.h>
+#include <stdint.h>
+#include <sys/types.h>
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include <ws2tcpip.h>
+#if defined(_MSC_FULL_VER) && !defined (_SSIZE_T_DEFINED)
+#define _SSIZE_T_DEFINED
+typedef intptr_t ssize_t;
+#endif // !_SSIZE_T_DEFINED */
+#else
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#endif
+#endif
+
+#if defined(__CYGWIN__) && !defined(_SYS_TYPES_FD_SET)
+/* Do not define __USE_W32_SOCKETS under Cygwin! */
+#error Cygwin with winsock fd_set is not supported
+#endif
+
+/**
+ * Current version of the library.
+ * 0x01093001 = 1.9.30-1.
+ */
+#define MHD_VERSION 0x00094200
+
+/**
+ * MHD-internal return code for "YES".
+ */
+#define MHD_YES 1
+
+/**
+ * MHD-internal return code for "NO".
+ */
+#define MHD_NO 0
+
+/**
+ * MHD digest auth internal code for an invalid nonce.
+ */
+#define MHD_INVALID_NONCE -1
+
+/**
+ * Constant used to indicate unknown size (use when
+ * creating a response).
+ */
+#ifdef UINT64_MAX
+#define MHD_SIZE_UNKNOWN UINT64_MAX
+#else
+#define MHD_SIZE_UNKNOWN ((uint64_t) -1LL)
+#endif
+
+#ifdef SIZE_MAX
+#define MHD_CONTENT_READER_END_OF_STREAM SIZE_MAX
+#define MHD_CONTENT_READER_END_WITH_ERROR (SIZE_MAX - 1)
+#else
+#define MHD_CONTENT_READER_END_OF_STREAM ((size_t) -1LL)
+#define MHD_CONTENT_READER_END_WITH_ERROR (((size_t) -1LL) - 1)
+#endif
+
+#ifndef _MHD_EXTERN
+#if defined(_WIN32) && defined(MHD_W32LIB)
+#define _MHD_EXTERN extern
+#elif defined (_WIN32) && defined(MHD_W32DLL)
+/* Define MHD_W32DLL when using MHD as W32 .DLL to speed up linker a little */
+#define _MHD_EXTERN __declspec(dllimport)
+#else
+#define _MHD_EXTERN extern
+#endif
+#endif
+
+#ifndef MHD_SOCKET_DEFINED
+/**
+ * MHD_socket is type for socket FDs
+ */
+#if !defined(_WIN32) || defined(_SYS_TYPES_FD_SET)
+#define MHD_POSIX_SOCKETS 1
+typedef int MHD_socket;
+#define MHD_INVALID_SOCKET (-1)
+#else /* !defined(_WIN32) || defined(_SYS_TYPES_FD_SET) */
+#define MHD_WINSOCK_SOCKETS 1
+#include <winsock2.h>
+typedef SOCKET MHD_socket;
+#define MHD_INVALID_SOCKET (INVALID_SOCKET)
+#endif /* !defined(_WIN32) || defined(_SYS_TYPES_FD_SET) */
+#define MHD_SOCKET_DEFINED 1
+#endif /* MHD_SOCKET_DEFINED */
+
+/**
+ * Not all architectures and `printf()`'s support the `long long` type.
+ * This gives the ability to replace `long long` with just a `long`,
+ * standard `int` or a `short`.
+ */
+#ifndef MHD_LONG_LONG
+/**
+ * @deprecated use #MHD_UNSIGNED_LONG_LONG instead!
+ */
+#define MHD_LONG_LONG long long
+#define MHD_UNSIGNED_LONG_LONG unsigned long long
+#endif
+/**
+ * Format string for printing a variable of type #MHD_LONG_LONG.
+ * You should only redefine this if you also define #MHD_LONG_LONG.
+ */
+#ifndef MHD_LONG_LONG_PRINTF
+/**
+ * @deprecated use #MHD_UNSIGNED_LONG_LONG_PRINTF instead!
+ */
+#define MHD_LONG_LONG_PRINTF "ll"
+#define MHD_UNSIGNED_LONG_LONG_PRINTF "%llu"
+#endif
+
+
+/**
+ * @defgroup httpcode HTTP response codes.
+ * These are the status codes defined for HTTP responses.
+ * @{
+ */
+#define MHD_HTTP_CONTINUE 100
+#define MHD_HTTP_SWITCHING_PROTOCOLS 101
+#define MHD_HTTP_PROCESSING 102
+
+#define MHD_HTTP_OK 200
+#define MHD_HTTP_CREATED 201
+#define MHD_HTTP_ACCEPTED 202
+#define MHD_HTTP_NON_AUTHORITATIVE_INFORMATION 203
+#define MHD_HTTP_NO_CONTENT 204
+#define MHD_HTTP_RESET_CONTENT 205
+#define MHD_HTTP_PARTIAL_CONTENT 206
+#define MHD_HTTP_MULTI_STATUS 207
+
+#define MHD_HTTP_MULTIPLE_CHOICES 300
+#define MHD_HTTP_MOVED_PERMANENTLY 301
+#define MHD_HTTP_FOUND 302
+#define MHD_HTTP_SEE_OTHER 303
+#define MHD_HTTP_NOT_MODIFIED 304
+#define MHD_HTTP_USE_PROXY 305
+#define MHD_HTTP_SWITCH_PROXY 306
+#define MHD_HTTP_TEMPORARY_REDIRECT 307
+
+#define MHD_HTTP_BAD_REQUEST 400
+#define MHD_HTTP_UNAUTHORIZED 401
+#define MHD_HTTP_PAYMENT_REQUIRED 402
+#define MHD_HTTP_FORBIDDEN 403
+#define MHD_HTTP_NOT_FOUND 404
+#define MHD_HTTP_METHOD_NOT_ALLOWED 405
+#define MHD_HTTP_NOT_ACCEPTABLE 406
+/** @deprecated */
+#define MHD_HTTP_METHOD_NOT_ACCEPTABLE 406
+#define MHD_HTTP_PROXY_AUTHENTICATION_REQUIRED 407
+#define MHD_HTTP_REQUEST_TIMEOUT 408
+#define MHD_HTTP_CONFLICT 409
+#define MHD_HTTP_GONE 410
+#define MHD_HTTP_LENGTH_REQUIRED 411
+#define MHD_HTTP_PRECONDITION_FAILED 412
+#define MHD_HTTP_REQUEST_ENTITY_TOO_LARGE 413
+#define MHD_HTTP_REQUEST_URI_TOO_LONG 414
+#define MHD_HTTP_UNSUPPORTED_MEDIA_TYPE 415
+#define MHD_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE 416
+#define MHD_HTTP_EXPECTATION_FAILED 417
+#define MHD_HTTP_UNPROCESSABLE_ENTITY 422
+#define MHD_HTTP_LOCKED 423
+#define MHD_HTTP_FAILED_DEPENDENCY 424
+#define MHD_HTTP_UNORDERED_COLLECTION 425
+#define MHD_HTTP_UPGRADE_REQUIRED 426
+#define MHD_HTTP_NO_RESPONSE 444
+#define MHD_HTTP_RETRY_WITH 449
+#define MHD_HTTP_BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS 450
+#define MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS 451
+
+#define MHD_HTTP_INTERNAL_SERVER_ERROR 500
+#define MHD_HTTP_NOT_IMPLEMENTED 501
+#define MHD_HTTP_BAD_GATEWAY 502
+#define MHD_HTTP_SERVICE_UNAVAILABLE 503
+#define MHD_HTTP_GATEWAY_TIMEOUT 504
+#define MHD_HTTP_HTTP_VERSION_NOT_SUPPORTED 505
+#define MHD_HTTP_VARIANT_ALSO_NEGOTIATES 506
+#define MHD_HTTP_INSUFFICIENT_STORAGE 507
+#define MHD_HTTP_BANDWIDTH_LIMIT_EXCEEDED 509
+#define MHD_HTTP_NOT_EXTENDED 510
+
+/** @} */ /* end of group httpcode */
+
+/**
+ * Flag to be or-ed with MHD_HTTP status code for
+ * SHOUTcast. This will cause the response to begin
+ * with the SHOUTcast "ICY" line instad of "HTTP".
+ * @ingroup specialized
+ */
+#define MHD_ICY_FLAG ((uint32_t)(((uint32_t)1) << 31))
+
+/**
+ * @defgroup headers HTTP headers
+ * These are the standard headers found in HTTP requests and responses.
+ * @{
+ */
+/* See also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html */
+#define MHD_HTTP_HEADER_ACCEPT "Accept"
+#define MHD_HTTP_HEADER_ACCEPT_CHARSET "Accept-Charset"
+#define MHD_HTTP_HEADER_ACCEPT_ENCODING "Accept-Encoding"
+#define MHD_HTTP_HEADER_ACCEPT_LANGUAGE "Accept-Language"
+#define MHD_HTTP_HEADER_ACCEPT_RANGES "Accept-Ranges"
+#define MHD_HTTP_HEADER_AGE "Age"
+#define MHD_HTTP_HEADER_ALLOW "Allow"
+#define MHD_HTTP_HEADER_AUTHORIZATION "Authorization"
+#define MHD_HTTP_HEADER_CACHE_CONTROL "Cache-Control"
+#define MHD_HTTP_HEADER_CONNECTION "Connection"
+#define MHD_HTTP_HEADER_CONTENT_ENCODING "Content-Encoding"
+#define MHD_HTTP_HEADER_CONTENT_LANGUAGE "Content-Language"
+#define MHD_HTTP_HEADER_CONTENT_LENGTH "Content-Length"
+#define MHD_HTTP_HEADER_CONTENT_LOCATION "Content-Location"
+#define MHD_HTTP_HEADER_CONTENT_MD5 "Content-MD5"
+#define MHD_HTTP_HEADER_CONTENT_RANGE "Content-Range"
+#define MHD_HTTP_HEADER_CONTENT_TYPE "Content-Type"
+#define MHD_HTTP_HEADER_COOKIE "Cookie"
+#define MHD_HTTP_HEADER_DATE "Date"
+#define MHD_HTTP_HEADER_ETAG "ETag"
+#define MHD_HTTP_HEADER_EXPECT "Expect"
+#define MHD_HTTP_HEADER_EXPIRES "Expires"
+#define MHD_HTTP_HEADER_FROM "From"
+#define MHD_HTTP_HEADER_HOST "Host"
+#define MHD_HTTP_HEADER_IF_MATCH "If-Match"
+#define MHD_HTTP_HEADER_IF_MODIFIED_SINCE "If-Modified-Since"
+#define MHD_HTTP_HEADER_IF_NONE_MATCH "If-None-Match"
+#define MHD_HTTP_HEADER_IF_RANGE "If-Range"
+#define MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE "If-Unmodified-Since"
+#define MHD_HTTP_HEADER_LAST_MODIFIED "Last-Modified"
+#define MHD_HTTP_HEADER_LOCATION "Location"
+#define MHD_HTTP_HEADER_MAX_FORWARDS "Max-Forwards"
+#define MHD_HTTP_HEADER_PRAGMA "Pragma"
+#define MHD_HTTP_HEADER_PROXY_AUTHENTICATE "Proxy-Authenticate"
+#define MHD_HTTP_HEADER_PROXY_AUTHORIZATION "Proxy-Authorization"
+#define MHD_HTTP_HEADER_RANGE "Range"
+/* This is not a typo, see HTTP spec */
+#define MHD_HTTP_HEADER_REFERER "Referer"
+#define MHD_HTTP_HEADER_RETRY_AFTER "Retry-After"
+#define MHD_HTTP_HEADER_SERVER "Server"
+#define MHD_HTTP_HEADER_SET_COOKIE "Set-Cookie"
+#define MHD_HTTP_HEADER_SET_COOKIE2 "Set-Cookie2"
+#define MHD_HTTP_HEADER_TE "TE"
+#define MHD_HTTP_HEADER_TRAILER "Trailer"
+#define MHD_HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding"
+#define MHD_HTTP_HEADER_UPGRADE "Upgrade"
+#define MHD_HTTP_HEADER_USER_AGENT "User-Agent"
+#define MHD_HTTP_HEADER_VARY "Vary"
+#define MHD_HTTP_HEADER_VIA "Via"
+#define MHD_HTTP_HEADER_WARNING "Warning"
+#define MHD_HTTP_HEADER_WWW_AUTHENTICATE "WWW-Authenticate"
+#define MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN "Access-Control-Allow-Origin"
+
+/** @} */ /* end of group headers */
+
+/**
+ * @defgroup versions HTTP versions
+ * These strings should be used to match against the first line of the
+ * HTTP header.
+ * @{
+ */
+#define MHD_HTTP_VERSION_1_0 "HTTP/1.0"
+#define MHD_HTTP_VERSION_1_1 "HTTP/1.1"
+
+/** @} */ /* end of group versions */
+
+/**
+ * @defgroup methods HTTP methods
+ * Standard HTTP methods (as strings).
+ * @{
+ */
+#define MHD_HTTP_METHOD_CONNECT "CONNECT"
+#define MHD_HTTP_METHOD_DELETE "DELETE"
+#define MHD_HTTP_METHOD_GET "GET"
+#define MHD_HTTP_METHOD_HEAD "HEAD"
+#define MHD_HTTP_METHOD_OPTIONS "OPTIONS"
+#define MHD_HTTP_METHOD_POST "POST"
+#define MHD_HTTP_METHOD_PUT "PUT"
+#define MHD_HTTP_METHOD_PATCH "PATCH"
+#define MHD_HTTP_METHOD_TRACE "TRACE"
+
+/** @} */ /* end of group methods */
+
+/**
+ * @defgroup postenc HTTP POST encodings
+ * See also: http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
+ * @{
+ */
+#define MHD_HTTP_POST_ENCODING_FORM_URLENCODED "application/x-www-form-urlencoded"
+#define MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA "multipart/form-data"
+
+/** @} */ /* end of group postenc */
+
+
+/**
+ * @brief Handle for the daemon (listening on a socket for HTTP traffic).
+ * @ingroup event
+ */
+struct MHD_Daemon;
+
+/**
+ * @brief Handle for a connection / HTTP request.
+ *
+ * With HTTP/1.1, multiple requests can be run over the same
+ * connection. However, MHD will only show one request per TCP
+ * connection to the client at any given time.
+ * @ingroup request
+ */
+struct MHD_Connection;
+
+/**
+ * @brief Handle for a response.
+ * @ingroup response
+ */
+struct MHD_Response;
+
+/**
+ * @brief Handle for POST processing.
+ * @ingroup response
+ */
+struct MHD_PostProcessor;
+
+
+/**
+ * @brief Flags for the `struct MHD_Daemon`.
+ *
+ * Note that if neither #MHD_USE_THREAD_PER_CONNECTION nor
+ * #MHD_USE_SELECT_INTERNALLY is used, the client wants control over
+ * the process and will call the appropriate microhttpd callbacks.
+ *
+ * Starting the daemon may also fail if a particular option is not
+ * implemented or not supported on the target platform (i.e. no
+ * support for SSL, threads or IPv6).
+ */
+enum MHD_FLAG
+{
+ /**
+ * No options selected.
+ */
+ MHD_NO_FLAG = 0,
+
+ /**
+ * Run in debug mode. If this flag is used, the library should
+ * print error messages and warnings to `stderr`.
+ */
+ MHD_USE_DEBUG = 1,
+
+ /**
+ * Run in HTTPS mode.
+ */
+ MHD_USE_SSL = 2,
+
+ /**
+ * Run using one thread per connection.
+ */
+ MHD_USE_THREAD_PER_CONNECTION = 4,
+
+ /**
+ * Run using an internal thread (or thread pool) doing `select()`.
+ */
+ MHD_USE_SELECT_INTERNALLY = 8,
+
+ /**
+ * Run using the IPv6 protocol (otherwise, MHD will just support
+ * IPv4). If you want MHD to support IPv4 and IPv6 using a single
+ * socket, pass #MHD_USE_DUAL_STACK, otherwise, if you only pass
+ * this option, MHD will try to bind to IPv6-only (resulting in
+ * no IPv4 support).
+ */
+ MHD_USE_IPv6 = 16,
+
+ /**
+ * Be pedantic about the protocol (as opposed to as tolerant as
+ * possible). Specifically, at the moment, this flag causes MHD to
+ * reject HTTP 1.1 connections without a "Host" header. This is
+ * required by the standard, but of course in violation of the "be
+ * as liberal as possible in what you accept" norm. It is
+ * recommended to turn this ON if you are testing clients against
+ * MHD, and OFF in production.
+ */
+ MHD_USE_PEDANTIC_CHECKS = 32,
+
+ /**
+ * Use `poll()` instead of `select()`. This allows sockets with `fd >=
+ * FD_SETSIZE`. This option is not compatible with using an
+ * 'external' `select()` mode (as there is no API to get the file
+ * descriptors for the external select from MHD) and must also not
+ * be used in combination with #MHD_USE_EPOLL_LINUX_ONLY.
+ */
+ MHD_USE_POLL = 64,
+
+ /**
+ * Run using an internal thread (or thread pool) doing `poll()`.
+ */
+ MHD_USE_POLL_INTERNALLY = MHD_USE_SELECT_INTERNALLY | MHD_USE_POLL,
+
+ /**
+ * Suppress (automatically) adding the 'Date:' header to HTTP responses.
+ * This option should ONLY be used on systems that do not have a clock
+ * and that DO provide other mechanisms for cache control. See also
+ * RFC 2616, section 14.18 (exception 3).
+ */
+ MHD_SUPPRESS_DATE_NO_CLOCK = 128,
+
+ /**
+ * Run without a listen socket. This option only makes sense if
+ * #MHD_add_connection is to be used exclusively to connect HTTP
+ * clients to the HTTP server. This option is incompatible with
+ * using a thread pool; if it is used, #MHD_OPTION_THREAD_POOL_SIZE
+ * is ignored.
+ */
+ MHD_USE_NO_LISTEN_SOCKET = 256,
+
+ /**
+ * Use `epoll()` instead of `select()` or `poll()` for the event loop.
+ * This option is only available on Linux; using the option on
+ * non-Linux systems will cause #MHD_start_daemon to fail.
+ */
+ MHD_USE_EPOLL_LINUX_ONLY = 512,
+
+ /**
+ * Run using an internal thread (or thread pool) doing `epoll()`.
+ * This option is only available on Linux; using the option on
+ * non-Linux systems will cause #MHD_start_daemon to fail.
+ */
+ MHD_USE_EPOLL_INTERNALLY_LINUX_ONLY = MHD_USE_SELECT_INTERNALLY | MHD_USE_EPOLL_LINUX_ONLY,
+
+ /**
+ * Force MHD to use a signal pipe to notify the event loop (of
+ * threads) of our shutdown. This is required if an appliction uses
+ * #MHD_USE_SELECT_INTERNALLY or #MHD_USE_THREAD_PER_CONNECTION and
+ * then performs #MHD_quiesce_daemon (which eliminates our ability
+ * to signal termination via the listen socket). In these modes,
+ * #MHD_quiesce_daemon will fail if this option was not set. Also,
+ * use of this option is automatic (as in, you do not even have to
+ * specify it), if #MHD_USE_NO_LISTEN_SOCKET is specified. In
+ * "external" `select()` mode, this option is always simply ignored.
+ * MHD can be build for use a pair of sockets instead of a pipe.
+ * Pair of sockets is forced on W32.
+ *
+ * You must also use this option if you use internal select mode
+ * or a thread pool in conjunction with #MHD_add_connection.
+ */
+ MHD_USE_PIPE_FOR_SHUTDOWN = 1024,
+
+ /**
+ * Use a single socket for IPv4 and IPv6.
+ */
+ MHD_USE_DUAL_STACK = MHD_USE_IPv6 | 2048,
+
+ /**
+ * Enable `epoll()` turbo. Disables certain calls to `shutdown()`
+ * and enables aggressive non-blocking optimisitc reads.
+ * Most effects only happen with #MHD_USE_EPOLL_LINUX_ONLY.
+ * Enalbed always on W32 as winsock does not properly behave
+ * with `shutdown()` and this then fixes potential problems.
+ */
+ MHD_USE_EPOLL_TURBO = 4096,
+
+ /**
+ * Enable suspend/resume functions, which also implies setting up
+ * pipes to signal resume.
+ */
+ MHD_USE_SUSPEND_RESUME = 8192 | MHD_USE_PIPE_FOR_SHUTDOWN,
+
+ /**
+ * Enable TCP_FASTOPEN option. This option is only available on Linux with a
+ * kernel >= 3.6. On other systems, using this option cases #MHD_start_daemon
+ * to fail.
+ */
+ MHD_USE_TCP_FASTOPEN = 16384
+
+};
+
+
+/**
+ * Type of a callback function used for logging by MHD.
+ *
+ * @param cls closure
+ * @param fm format string (`printf()`-style)
+ * @param ap arguments to @a fm
+ * @ingroup logging
+ */
+typedef void (*MHD_LogCallback)(void *cls, const char *fm, va_list ap);
+
+
+/**
+ * @brief MHD options.
+ *
+ * Passed in the varargs portion of #MHD_start_daemon.
+ */
+enum MHD_OPTION
+{
+
+ /**
+ * No more options / last option. This is used
+ * to terminate the VARARGs list.
+ */
+ MHD_OPTION_END = 0,
+
+ /**
+ * Maximum memory size per connection (followed by a `size_t`).
+ * Default is 32 kb (#MHD_POOL_SIZE_DEFAULT).
+ * Values above 128k are unlikely to result in much benefit, as half
+ * of the memory will be typically used for IO, and TCP buffers are
+ * unlikely to support window sizes above 64k on most systems.
+ */
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT = 1,
+
+ /**
+ * Maximum number of concurrent connections to
+ * accept (followed by an `unsigned int`).
+ */
+ MHD_OPTION_CONNECTION_LIMIT = 2,
+
+ /**
+ * After how many seconds of inactivity should a
+ * connection automatically be timed out? (followed
+ * by an `unsigned int`; use zero for no timeout).
+ */
+ MHD_OPTION_CONNECTION_TIMEOUT = 3,
+
+ /**
+ * Register a function that should be called whenever a request has
+ * been completed (this can be used for application-specific clean
+ * up). Requests that have never been presented to the application
+ * (via #MHD_AccessHandlerCallback) will not result in
+ * notifications.
+ *
+ * This option should be followed by TWO pointers. First a pointer
+ * to a function of type #MHD_RequestCompletedCallback and second a
+ * pointer to a closure to pass to the request completed callback.
+ * The second pointer maybe NULL.
+ */
+ MHD_OPTION_NOTIFY_COMPLETED = 4,
+
+ /**
+ * Limit on the number of (concurrent) connections made to the
+ * server from the same IP address. Can be used to prevent one
+ * IP from taking over all of the allowed connections. If the
+ * same IP tries to establish more than the specified number of
+ * connections, they will be immediately rejected. The option
+ * should be followed by an `unsigned int`. The default is
+ * zero, which means no limit on the number of connections
+ * from the same IP address.
+ */
+ MHD_OPTION_PER_IP_CONNECTION_LIMIT = 5,
+
+ /**
+ * Bind daemon to the supplied `struct sockaddr`. This option should
+ * be followed by a `struct sockaddr *`. If #MHD_USE_IPv6 is
+ * specified, the `struct sockaddr*` should point to a `struct
+ * sockaddr_in6`, otherwise to a `struct sockaddr_in`.
+ */
+ MHD_OPTION_SOCK_ADDR = 6,
+
+ /**
+ * Specify a function that should be called before parsing the URI from
+ * the client. The specified callback function can be used for processing
+ * the URI (including the options) before it is parsed. The URI after
+ * parsing will no longer contain the options, which maybe inconvenient for
+ * logging. This option should be followed by two arguments, the first
+ * one must be of the form
+ *
+ * void * my_logger(void *cls, const char *uri, struct MHD_Connection *con)
+ *
+ * where the return value will be passed as
+ * (`* con_cls`) in calls to the #MHD_AccessHandlerCallback
+ * when this request is processed later; returning a
+ * value of NULL has no special significance (however,
+ * note that if you return non-NULL, you can no longer
+ * rely on the first call to the access handler having
+ * `NULL == *con_cls` on entry;)
+ * "cls" will be set to the second argument following
+ * #MHD_OPTION_URI_LOG_CALLBACK. Finally, uri will
+ * be the 0-terminated URI of the request.
+ *
+ * Note that during the time of this call, most of the connection's
+ * state is not initialized (as we have not yet parsed he headers).
+ * However, information about the connecting client (IP, socket)
+ * is available.
+ */
+ MHD_OPTION_URI_LOG_CALLBACK = 7,
+
+ /**
+ * Memory pointer for the private key (key.pem) to be used by the
+ * HTTPS daemon. This option should be followed by a
+ * `const char *` argument.
+ * This should be used in conjunction with #MHD_OPTION_HTTPS_MEM_CERT.
+ */
+ MHD_OPTION_HTTPS_MEM_KEY = 8,
+
+ /**
+ * Memory pointer for the certificate (cert.pem) to be used by the
+ * HTTPS daemon. This option should be followed by a
+ * `const char *` argument.
+ * This should be used in conjunction with #MHD_OPTION_HTTPS_MEM_KEY.
+ */
+ MHD_OPTION_HTTPS_MEM_CERT = 9,
+
+ /**
+ * Daemon credentials type.
+ * Followed by an argument of type
+ * `gnutls_credentials_type_t`.
+ */
+ MHD_OPTION_HTTPS_CRED_TYPE = 10,
+
+ /**
+ * Memory pointer to a `const char *` specifying the
+ * cipher algorithm (default: "NORMAL").
+ */
+ MHD_OPTION_HTTPS_PRIORITIES = 11,
+
+ /**
+ * Pass a listen socket for MHD to use (systemd-style). If this
+ * option is used, MHD will not open its own listen socket(s). The
+ * argument passed must be of type `int` and refer to an
+ * existing socket that has been bound to a port and is listening.
+ */
+ MHD_OPTION_LISTEN_SOCKET = 12,
+
+ /**
+ * Use the given function for logging error messages. This option
+ * must be followed by two arguments; the first must be a pointer to
+ * a function of type #MHD_LogCallback and the second a pointer
+ * `void *` which will be passed as the first argument to the log
+ * callback.
+ *
+ * Note that MHD will not generate any log messages
+ * if it was compiled without the "--enable-messages"
+ * flag being set.
+ */
+ MHD_OPTION_EXTERNAL_LOGGER = 13,
+
+ /**
+ * Number (`unsigned int`) of threads in thread pool. Enable
+ * thread pooling by setting this value to to something
+ * greater than 1. Currently, thread model must be
+ * #MHD_USE_SELECT_INTERNALLY if thread pooling is enabled
+ * (#MHD_start_daemon returns NULL for an unsupported thread
+ * model).
+ */
+ MHD_OPTION_THREAD_POOL_SIZE = 14,
+
+ /**
+ * Additional options given in an array of `struct MHD_OptionItem`.
+ * The array must be terminated with an entry `{MHD_OPTION_END, 0, NULL}`.
+ * An example for code using #MHD_OPTION_ARRAY is:
+ *
+ * struct MHD_OptionItem ops[] = {
+ * { MHD_OPTION_CONNECTION_LIMIT, 100, NULL },
+ * { MHD_OPTION_CONNECTION_TIMEOUT, 10, NULL },
+ * { MHD_OPTION_END, 0, NULL }
+ * };
+ * d = MHD_start_daemon (0, 8080, NULL, NULL, dh, NULL,
+ * MHD_OPTION_ARRAY, ops,
+ * MHD_OPTION_END);
+ *
+ * For options that expect a single pointer argument, the
+ * second member of the `struct MHD_OptionItem` is ignored.
+ * For options that expect two pointer arguments, the first
+ * argument must be cast to `intptr_t`.
+ */
+ MHD_OPTION_ARRAY = 15,
+
+ /**
+ * Specify a function that should be called for unescaping escape
+ * sequences in URIs and URI arguments. Note that this function
+ * will NOT be used by the `struct MHD_PostProcessor`. If this
+ * option is not specified, the default method will be used which
+ * decodes escape sequences of the form "%HH". This option should
+ * be followed by two arguments, the first one must be of the form
+ *
+ * size_t my_unescaper(void *cls,
+ * struct MHD_Connection *c,
+ * char *s)
+ *
+ * where the return value must be "strlen(s)" and "s" should be
+ * updated. Note that the unescape function must not lengthen "s"
+ * (the result must be shorter than the input and still be
+ * 0-terminated). "cls" will be set to the second argument
+ * following #MHD_OPTION_UNESCAPE_CALLBACK.
+ */
+ MHD_OPTION_UNESCAPE_CALLBACK = 16,
+
+ /**
+ * Memory pointer for the random values to be used by the Digest
+ * Auth module. This option should be followed by two arguments.
+ * First an integer of type `size_t` which specifies the size
+ * of the buffer pointed to by the second argument in bytes.
+ * Note that the application must ensure that the buffer of the
+ * second argument remains allocated and unmodified while the
+ * deamon is running.
+ */
+ MHD_OPTION_DIGEST_AUTH_RANDOM = 17,
+
+ /**
+ * Size of the internal array holding the map of the nonce and
+ * the nonce counter. This option should be followed by an `unsigend int`
+ * argument.
+ */
+ MHD_OPTION_NONCE_NC_SIZE = 18,
+
+ /**
+ * Desired size of the stack for threads created by MHD. Followed
+ * by an argument of type `size_t`. Use 0 for system default.
+ */
+ MHD_OPTION_THREAD_STACK_SIZE = 19,
+
+ /**
+ * Memory pointer for the certificate (ca.pem) to be used by the
+ * HTTPS daemon for client authentification.
+ * This option should be followed by a `const char *` argument.
+ */
+ MHD_OPTION_HTTPS_MEM_TRUST = 20,
+
+ /**
+ * Increment to use for growing the read buffer (followed by a
+ * `size_t`). Must fit within #MHD_OPTION_CONNECTION_MEMORY_LIMIT.
+ */
+ MHD_OPTION_CONNECTION_MEMORY_INCREMENT = 21,
+
+ /**
+ * Use a callback to determine which X.509 certificate should be
+ * used for a given HTTPS connection. This option should be
+ * followed by a argument of type `gnutls_certificate_retrieve_function2 *`.
+ * This option provides an
+ * alternative to #MHD_OPTION_HTTPS_MEM_KEY,
+ * #MHD_OPTION_HTTPS_MEM_CERT. You must use this version if
+ * multiple domains are to be hosted at the same IP address using
+ * TLS's Server Name Indication (SNI) extension. In this case,
+ * the callback is expected to select the correct certificate
+ * based on the SNI information provided. The callback is expected
+ * to access the SNI data using `gnutls_server_name_get()`.
+ * Using this option requires GnuTLS 3.0 or higher.
+ */
+ MHD_OPTION_HTTPS_CERT_CALLBACK = 22,
+
+ /**
+ * When using #MHD_USE_TCP_FASTOPEN, this option changes the default TCP
+ * fastopen queue length of 50. Note that having a larger queue size can
+ * cause resource exhaustion attack as the TCP stack has to now allocate
+ * resources for the SYN packet along with its DATA. This option should be
+ * followed by an `unsigned int` argument.
+ */
+ MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE = 23,
+
+ /**
+ * Memory pointer for the Diffie-Hellman parameters (dh.pem) to be used by the
+ * HTTPS daemon for key exchange.
+ * This option must be followed by a `const char *` argument.
+ */
+ MHD_OPTION_HTTPS_MEM_DHPARAMS = 24,
+
+ /**
+ * If present and set to true, allow reusing address:port socket
+ * (by using SO_REUSEPORT on most platform, or platform-specific ways).
+ * If present and set to false, disallow reusing address:port socket
+ * (does nothing on most plaform, but uses SO_EXCLUSIVEADDRUSE on Windows).
+ * This option must be followed by a `unsigned int` argument.
+ */
+ MHD_OPTION_LISTENING_ADDRESS_REUSE = 25,
+
+ /**
+ * Memory pointer for a password that decrypts the private key (key.pem)
+ * to be used by the HTTPS daemon. This option should be followed by a
+ * `const char *` argument.
+ * This should be used in conjunction with #MHD_OPTION_HTTPS_MEM_KEY.
+ * @sa ::MHD_FEATURE_HTTPS_KEY_PASSWORD
+ */
+ MHD_OPTION_HTTPS_KEY_PASSWORD = 26,
+
+ /**
+ * Register a function that should be called whenever a connection is
+ * started or closed.
+ *
+ * This option should be followed by TWO pointers. First a pointer
+ * to a function of type #MHD_NotifyConnectionCallback and second a
+ * pointer to a closure to pass to the request completed callback.
+ * The second pointer maybe NULL.
+ */
+ MHD_OPTION_NOTIFY_CONNECTION = 27
+
+};
+
+
+/**
+ * Entry in an #MHD_OPTION_ARRAY.
+ */
+struct MHD_OptionItem
+{
+ /**
+ * Which option is being given. Use #MHD_OPTION_END
+ * to terminate the array.
+ */
+ enum MHD_OPTION option;
+
+ /**
+ * Option value (for integer arguments, and for options requiring
+ * two pointer arguments); should be 0 for options that take no
+ * arguments or only a single pointer argument.
+ */
+ intptr_t value;
+
+ /**
+ * Pointer option value (use NULL for options taking no arguments
+ * or only an integer option).
+ */
+ void *ptr_value;
+
+};
+
+
+/**
+ * The `enum MHD_ValueKind` specifies the source of
+ * the key-value pairs in the HTTP protocol.
+ */
+enum MHD_ValueKind
+{
+
+ /**
+ * Response header
+ */
+ MHD_RESPONSE_HEADER_KIND = 0,
+
+ /**
+ * HTTP header.
+ */
+ MHD_HEADER_KIND = 1,
+
+ /**
+ * Cookies. Note that the original HTTP header containing
+ * the cookie(s) will still be available and intact.
+ */
+ MHD_COOKIE_KIND = 2,
+
+ /**
+ * POST data. This is available only if a content encoding
+ * supported by MHD is used (currently only URL encoding),
+ * and only if the posted content fits within the available
+ * memory pool. Note that in that case, the upload data
+ * given to the #MHD_AccessHandlerCallback will be
+ * empty (since it has already been processed).
+ */
+ MHD_POSTDATA_KIND = 4,
+
+ /**
+ * GET (URI) arguments.
+ */
+ MHD_GET_ARGUMENT_KIND = 8,
+
+ /**
+ * HTTP footer (only for HTTP 1.1 chunked encodings).
+ */
+ MHD_FOOTER_KIND = 16
+};
+
+
+/**
+ * The `enum MHD_RequestTerminationCode` specifies reasons
+ * why a request has been terminated (or completed).
+ * @ingroup request
+ */
+enum MHD_RequestTerminationCode
+{
+
+ /**
+ * We finished sending the response.
+ * @ingroup request
+ */
+ MHD_REQUEST_TERMINATED_COMPLETED_OK = 0,
+
+ /**
+ * Error handling the connection (resources
+ * exhausted, other side closed connection,
+ * application error accepting request, etc.)
+ * @ingroup request
+ */
+ MHD_REQUEST_TERMINATED_WITH_ERROR = 1,
+
+ /**
+ * No activity on the connection for the number
+ * of seconds specified using
+ * #MHD_OPTION_CONNECTION_TIMEOUT.
+ * @ingroup request
+ */
+ MHD_REQUEST_TERMINATED_TIMEOUT_REACHED = 2,
+
+ /**
+ * We had to close the session since MHD was being
+ * shut down.
+ * @ingroup request
+ */
+ MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN = 3,
+
+ /**
+ * We tried to read additional data, but the other side closed the
+ * connection. This error is similar to
+ * #MHD_REQUEST_TERMINATED_WITH_ERROR, but specific to the case where
+ * the connection died because the other side did not send expected
+ * data.
+ * @ingroup request
+ */
+ MHD_REQUEST_TERMINATED_READ_ERROR = 4,
+
+ /**
+ * The client terminated the connection by closing the socket
+ * for writing (TCP half-closed); MHD aborted sending the
+ * response according to RFC 2616, section 8.1.4.
+ * @ingroup request
+ */
+ MHD_REQUEST_TERMINATED_CLIENT_ABORT = 5
+
+};
+
+
+/**
+ * The `enum MHD_ConnectionNotificationCode` specifies types
+ * of connection notifications.
+ * @ingroup request
+ */
+enum MHD_ConnectionNotificationCode
+{
+
+ /**
+ * A new connection has been started.
+ * @ingroup request
+ */
+ MHD_CONNECTION_NOTIFY_STARTED = 0,
+
+ /**
+ * A connection is closed.
+ * @ingroup request
+ */
+ MHD_CONNECTION_NOTIFY_CLOSED = 1
+
+};
+
+
+/**
+ * Information about a connection.
+ */
+union MHD_ConnectionInfo
+{
+
+ /**
+ * Cipher algorithm used, of type "enum gnutls_cipher_algorithm".
+ */
+ int /* enum gnutls_cipher_algorithm */ cipher_algorithm;
+
+ /**
+ * Protocol used, of type "enum gnutls_protocol".
+ */
+ int /* enum gnutls_protocol */ protocol;
+
+ /**
+ * Connect socket
+ */
+ MHD_socket connect_fd;
+
+ /**
+ * GNUtls session handle, of type "gnutls_session_t".
+ */
+ void * /* gnutls_session_t */ tls_session;
+
+ /**
+ * GNUtls client certificate handle, of type "gnutls_x509_crt_t".
+ */
+ void * /* gnutls_x509_crt_t */ client_cert;
+
+ /**
+ * Address information for the client.
+ */
+ struct sockaddr *client_addr;
+
+ /**
+ * Which daemon manages this connection (useful in case there are many
+ * daemons running).
+ */
+ struct MHD_Daemon *daemon;
+
+ /**
+ * Socket-specific client context. Points to the same address as
+ * the "socket_context" of the #MHD_NotifyConnectionCallback.
+ */
+ void **socket_context;
+};
+
+
+/**
+ * Values of this enum are used to specify what
+ * information about a connection is desired.
+ * @ingroup request
+ */
+enum MHD_ConnectionInfoType
+{
+ /**
+ * What cipher algorithm is being used.
+ * Takes no extra arguments.
+ * @ingroup request
+ */
+ MHD_CONNECTION_INFO_CIPHER_ALGO,
+
+ /**
+ *
+ * Takes no extra arguments.
+ * @ingroup request
+ */
+ MHD_CONNECTION_INFO_PROTOCOL,
+
+ /**
+ * Obtain IP address of the client. Takes no extra arguments.
+ * Returns essentially a `struct sockaddr **` (since the API returns
+ * a `union MHD_ConnectionInfo *` and that union contains a `struct
+ * sockaddr *`).
+ * @ingroup request
+ */
+ MHD_CONNECTION_INFO_CLIENT_ADDRESS,
+
+ /**
+ * Get the gnuTLS session handle.
+ * @ingroup request
+ */
+ MHD_CONNECTION_INFO_GNUTLS_SESSION,
+
+ /**
+ * Get the gnuTLS client certificate handle. Dysfunctional (never
+ * implemented, deprecated). Use #MHD_CONNECTION_INFO_GNUTLS_SESSION
+ * to get the `gnutls_session_t` and then call
+ * gnutls_certificate_get_peers().
+ */
+ MHD_CONNECTION_INFO_GNUTLS_CLIENT_CERT,
+
+ /**
+ * Get the `struct MHD_Daemon *` responsible for managing this connection.
+ * @ingroup request
+ */
+ MHD_CONNECTION_INFO_DAEMON,
+
+ /**
+ * Request the file descriptor for the listening socket.
+ * No extra arguments should be passed.
+ * @ingroup request
+ */
+ MHD_CONNECTION_INFO_CONNECTION_FD,
+
+ /**
+ * Returns the client-specific pointer to a `void *` that was (possibly)
+ * set during a #MHD_NotifyConnectionCallback when the socket was
+ * first accepted. Note that this is NOT the same as the "con_cls"
+ * argument of the #MHD_AccessHandlerCallback. The "con_cls" is
+ * fresh for each HTTP request, while the "socket_context" is fresh
+ * for each socket.
+ */
+ MHD_CONNECTION_INFO_SOCKET_CONTEXT
+
+};
+
+
+/**
+ * Values of this enum are used to specify what
+ * information about a deamon is desired.
+ */
+enum MHD_DaemonInfoType
+{
+ /**
+ * No longer supported (will return NULL).
+ */
+ MHD_DAEMON_INFO_KEY_SIZE,
+
+ /**
+ * No longer supported (will return NULL).
+ */
+ MHD_DAEMON_INFO_MAC_KEY_SIZE,
+
+ /**
+ * Request the file descriptor for the listening socket.
+ * No extra arguments should be passed.
+ */
+ MHD_DAEMON_INFO_LISTEN_FD,
+
+ /**
+ * Request the file descriptor for the external epoll.
+ * No extra arguments should be passed.
+ */
+ MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY,
+
+ /**
+ * Request the number of current connections handled by the daemon.
+ * No extra arguments should be passed.
+ */
+ MHD_DAEMON_INFO_CURRENT_CONNECTIONS
+};
+
+
+/**
+ * Callback for serious error condition. The default action is to print
+ * an error message and `abort()`.
+ *
+ * @param cls user specified value
+ * @param file where the error occured
+ * @param line where the error occured
+ * @param reason error detail, may be NULL
+ * @ingroup logging
+ */
+typedef void
+(*MHD_PanicCallback) (void *cls,
+ const char *file,
+ unsigned int line,
+ const char *reason);
+
+/**
+ * Allow or deny a client to connect.
+ *
+ * @param cls closure
+ * @param addr address information from the client
+ * @param addrlen length of @a addr
+ * @return #MHD_YES if connection is allowed, #MHD_NO if not
+ */
+typedef int
+(*MHD_AcceptPolicyCallback) (void *cls,
+ const struct sockaddr *addr,
+ socklen_t addrlen);
+
+
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
+ * must call MHD callbacks to provide content to give back to the
+ * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
+ * #MHD_HTTP_NOT_FOUND, etc.).
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param url the requested url
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param version the HTTP version string (i.e.
+ * #MHD_HTTP_VERSION_1_1)
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of #MHD_get_connection_values; very large POST
+ * data *will* be made available incrementally in
+ * @a upload_data)
+ * @param upload_data_size set initially to the size of the
+ * @a upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param con_cls pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global #MHD_RequestCompletedCallback (which
+ * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, `*con_cls` will be NULL.
+ * @return #MHD_YES if the connection was handled successfully,
+ * #MHD_NO if the socket must be closed due to a serios
+ * error while handling the request
+ */
+typedef int
+(*MHD_AccessHandlerCallback) (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls);
+
+
+/**
+ * Signature of the callback used by MHD to notify the
+ * application about completed requests.
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the #MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see #MHD_OPTION_NOTIFY_COMPLETED
+ * @ingroup request
+ */
+typedef void
+(*MHD_RequestCompletedCallback) (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe);
+
+/**
+ * Signature of the callback used by MHD to notify the
+ * application about started/stopped connections
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param socket_context socket-specific pointer where the
+ * client can associate some state specific
+ * to the TCP connection; note that this is
+ * different from the "con_cls" which is per
+ * HTTP request. The client can initialize
+ * during #MHD_CONNECTION_NOTIFY_STARTED and
+ * cleanup during #MHD_CONNECTION_NOTIFY_CLOSED
+ * and access in the meantime using
+ * #MHD_CONNECTION_INFO_SOCKET_CONTEXT.
+ * @param toe reason for connection notification
+ * @see #MHD_OPTION_NOTIFY_CONNECTION
+ * @ingroup request
+ */
+typedef void
+(*MHD_NotifyConnectionCallback) (void *cls,
+ struct MHD_Connection *connection,
+ void **socket_context,
+ enum MHD_ConnectionNotificationCode toe);
+
+
+/**
+ * Iterator over key-value pairs. This iterator
+ * can be used to iterate over all of the cookies,
+ * headers, or POST-data fields of a request, and
+ * also to iterate over the headers that have been
+ * added to a response.
+ *
+ * @param cls closure
+ * @param kind kind of the header we are looking at
+ * @param key key for the value, can be an empty string
+ * @param value corresponding value, can be NULL
+ * @return #MHD_YES to continue iterating,
+ * #MHD_NO to abort the iteration
+ * @ingroup request
+ */
+typedef int
+(*MHD_KeyValueIterator) (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *value);
+
+
+/**
+ * Callback used by libmicrohttpd in order to obtain content. The
+ * callback is to copy at most @a max bytes of content into @a buf. The
+ * total number of bytes that has been placed into @a buf should be
+ * returned.
+ *
+ * Note that returning zero will cause libmicrohttpd to try again.
+ * Thus, returning zero should only be used in conjunction
+ * with MHD_suspend_connection() to avoid busy waiting.
+ *
+ * @param cls extra argument to the callback
+ * @param pos position in the datastream to access;
+ * note that if a `struct MHD_Response` object is re-used,
+ * it is possible for the same content reader to
+ * be queried multiple times for the same data;
+ * however, if a `struct MHD_Response` is not re-used,
+ * libmicrohttpd guarantees that "pos" will be
+ * the sum of all non-negative return values
+ * obtained from the content reader so far.
+ * @param buf where to copy the data
+ * @param max maximum number of bytes to copy to @a buf (size of @a buf)
+ * @return number of bytes written to @a buf;
+ * 0 is legal unless we are running in internal select mode (since
+ * this would cause busy-waiting); 0 in external select mode
+ * will cause this function to be called again once the external
+ * select calls MHD again;
+ * #MHD_CONTENT_READER_END_OF_STREAM (-1) for the regular
+ * end of transmission (with chunked encoding, MHD will then
+ * terminate the chunk and send any HTTP footers that might be
+ * present; without chunked encoding and given an unknown
+ * response size, MHD will simply close the connection; note
+ * that while returning #MHD_CONTENT_READER_END_OF_STREAM is not technically
+ * legal if a response size was specified, MHD accepts this
+ * and treats it just as #MHD_CONTENT_READER_END_WITH_ERROR;
+ * #MHD_CONTENT_READER_END_WITH_ERROR (-2) to indicate a server
+ * error generating the response; this will cause MHD to simply
+ * close the connection immediately. If a response size was
+ * given or if chunked encoding is in use, this will indicate
+ * an error to the client. Note, however, that if the client
+ * does not know a response size and chunked encoding is not in
+ * use, then clients will not be able to tell the difference between
+ * #MHD_CONTENT_READER_END_WITH_ERROR and #MHD_CONTENT_READER_END_OF_STREAM.
+ * This is not a limitation of MHD but rather of the HTTP protocol.
+ */
+typedef ssize_t
+(*MHD_ContentReaderCallback) (void *cls,
+ uint64_t pos,
+ char *buf,
+ size_t max);
+
+
+/**
+ * This method is called by libmicrohttpd if we
+ * are done with a content reader. It should
+ * be used to free resources associated with the
+ * content reader.
+ *
+ * @param cls closure
+ * @ingroup response
+ */
+typedef void
+(*MHD_ContentReaderFreeCallback) (void *cls);
+
+
+/**
+ * Iterator over key-value pairs where the value
+ * maybe made available in increments and/or may
+ * not be zero-terminated. Used for processing
+ * POST data.
+ *
+ * @param cls user-specified closure
+ * @param kind type of the value, always #MHD_POSTDATA_KIND when called from MHD
+ * @param key 0-terminated key for the value
+ * @param filename name of the uploaded file, NULL if not known
+ * @param content_type mime-type of the data, NULL if not known
+ * @param transfer_encoding encoding of the data, NULL if not known
+ * @param data pointer to @a size bytes of data at the
+ * specified offset
+ * @param off offset of data in the overall value
+ * @param size number of bytes in @a data available
+ * @return #MHD_YES to continue iterating,
+ * #MHD_NO to abort the iteration
+ */
+typedef int
+(*MHD_PostDataIterator) (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *data,
+ uint64_t off,
+ size_t size);
+
+/* **************** Daemon handling functions ***************** */
+
+/**
+ * Start a webserver on the given port.
+ *
+ * @param flags combination of `enum MHD_FLAG` values
+ * @param port port to bind to (in host byte order)
+ * @param apc callback to call to check which clients
+ * will be allowed to connect; you can pass NULL
+ * in which case connections from any IP will be
+ * accepted
+ * @param apc_cls extra argument to apc
+ * @param dh handler called for all requests (repeatedly)
+ * @param dh_cls extra argument to @a dh
+ * @param ap list of options (type-value pairs,
+ * terminated with #MHD_OPTION_END).
+ * @return NULL on error, handle to daemon on success
+ * @ingroup event
+ */
+_MHD_EXTERN struct MHD_Daemon *
+MHD_start_daemon_va (unsigned int flags,
+ uint16_t port,
+ MHD_AcceptPolicyCallback apc, void *apc_cls,
+ MHD_AccessHandlerCallback dh, void *dh_cls,
+ va_list ap);
+
+
+/**
+ * Start a webserver on the given port. Variadic version of
+ * #MHD_start_daemon_va.
+ *
+ * @param flags combination of `enum MHD_FLAG` values
+ * @param port port to bind to
+ * @param apc callback to call to check which clients
+ * will be allowed to connect; you can pass NULL
+ * in which case connections from any IP will be
+ * accepted
+ * @param apc_cls extra argument to apc
+ * @param dh handler called for all requests (repeatedly)
+ * @param dh_cls extra argument to @a dh
+ * @return NULL on error, handle to daemon on success
+ * @ingroup event
+ */
+_MHD_EXTERN struct MHD_Daemon *
+MHD_start_daemon (unsigned int flags,
+ uint16_t port,
+ MHD_AcceptPolicyCallback apc, void *apc_cls,
+ MHD_AccessHandlerCallback dh, void *dh_cls,
+ ...);
+
+
+/**
+ * Stop accepting connections from the listening socket. Allows
+ * clients to continue processing, but stops accepting new
+ * connections. Note that the caller is responsible for closing the
+ * returned socket; however, if MHD is run using threads (anything but
+ * external select mode), it must not be closed until AFTER
+ * #MHD_stop_daemon has been called (as it is theoretically possible
+ * that an existing thread is still using it).
+ *
+ * Note that some thread modes require the caller to have passed
+ * #MHD_USE_PIPE_FOR_SHUTDOWN when using this API. If this daemon is
+ * in one of those modes and this option was not given to
+ * #MHD_start_daemon, this function will return #MHD_INVALID_SOCKET.
+ *
+ * @param daemon daemon to stop accepting new connections for
+ * @return old listen socket on success, #MHD_INVALID_SOCKET if
+ * the daemon was already not listening anymore
+ * @ingroup specialized
+ */
+_MHD_EXTERN MHD_socket
+MHD_quiesce_daemon (struct MHD_Daemon *daemon);
+
+
+/**
+ * Shutdown an HTTP daemon.
+ *
+ * @param daemon daemon to stop
+ * @ingroup event
+ */
+_MHD_EXTERN void
+MHD_stop_daemon (struct MHD_Daemon *daemon);
+
+
+/**
+ * Add another client connection to the set of connections managed by
+ * MHD. This API is usually not needed (since MHD will accept inbound
+ * connections on the server socket). Use this API in special cases,
+ * for example if your HTTP server is behind NAT and needs to connect
+ * out to the HTTP client, or if you are building a proxy.
+ *
+ * If you use this API in conjunction with a internal select or a
+ * thread pool, you must set the option
+ * #MHD_USE_PIPE_FOR_SHUTDOWN to ensure that the freshly added
+ * connection is immediately processed by MHD.
+ *
+ * The given client socket will be managed (and closed!) by MHD after
+ * this call and must no longer be used directly by the application
+ * afterwards.
+ *
+ * Per-IP connection limits are ignored when using this API.
+ *
+ * @param daemon daemon that manages the connection
+ * @param client_socket socket to manage (MHD will expect
+ * to receive an HTTP request from this socket next).
+ * @param addr IP address of the client
+ * @param addrlen number of bytes in @a addr
+ * @return #MHD_YES on success, #MHD_NO if this daemon could
+ * not handle the connection (i.e. `malloc()` failed, etc).
+ * The socket will be closed in any case; `errno` is
+ * set to indicate further details about the error.
+ * @ingroup specialized
+ */
+_MHD_EXTERN int
+MHD_add_connection (struct MHD_Daemon *daemon,
+ MHD_socket client_socket,
+ const struct sockaddr *addr,
+ socklen_t addrlen);
+
+
+/**
+ * Obtain the `select()` sets for this daemon.
+ * Daemon's FDs will be added to fd_sets. To get only
+ * daemon FDs in fd_sets, call FD_ZERO for each fd_set
+ * before calling this function. FD_SETSIZE is assumed
+ * to be platform's default.
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param max_fd increased to largest FD added (if larger
+ * than existing value); can be NULL
+ * @return #MHD_YES on success, #MHD_NO if this
+ * daemon was not started with the right
+ * options for this call or any FD didn't
+ * fit fd_set.
+ * @ingroup event
+ */
+_MHD_EXTERN int
+MHD_get_fdset (struct MHD_Daemon *daemon,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ MHD_socket *max_fd);
+
+
+/**
+ * Obtain the `select()` sets for this daemon.
+ * Daemon's FDs will be added to fd_sets. To get only
+ * daemon FDs in fd_sets, call FD_ZERO for each fd_set
+ * before calling this function. Passing custom FD_SETSIZE
+ * as @a fd_setsize allow usage of larger/smaller than
+ * platform's default fd_sets.
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param max_fd increased to largest FD added (if larger
+ * than existing value); can be NULL
+ * @param fd_setsize value of FD_SETSIZE
+ * @return #MHD_YES on success, #MHD_NO if this
+ * daemon was not started with the right
+ * options for this call or any FD didn't
+ * fit fd_set.
+ * @ingroup event
+ */
+_MHD_EXTERN int
+MHD_get_fdset2 (struct MHD_Daemon *daemon,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ MHD_socket *max_fd,
+ unsigned int fd_setsize);
+
+
+/**
+ * Obtain the `select()` sets for this daemon.
+ * Daemon's FDs will be added to fd_sets. To get only
+ * daemon FDs in fd_sets, call FD_ZERO for each fd_set
+ * before calling this function. Size of fd_set is
+ * determined by current value of FD_SETSIZE.
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param max_fd increased to largest FD added (if larger
+ * than existing value); can be NULL
+ * @return #MHD_YES on success, #MHD_NO if this
+ * daemon was not started with the right
+ * options for this call or any FD didn't
+ * fit fd_set.
+ * @ingroup event
+ */
+#define MHD_get_fdset(daemon,read_fd_set,write_fd_set,except_fd_set,max_fd) \
+ MHD_get_fdset2((daemon),(read_fd_set),(write_fd_set),(except_fd_set),(max_fd),FD_SETSIZE)
+
+
+/**
+ * Obtain timeout value for `select()` for this daemon (only needed if
+ * connection timeout is used). The returned value is how many milliseconds
+ * `select()` or `poll()` should at most block, not the timeout value set for
+ * connections. This function MUST NOT be called if MHD is running with
+ * #MHD_USE_THREAD_PER_CONNECTION.
+ *
+ * @param daemon daemon to query for timeout
+ * @param timeout set to the timeout (in milliseconds)
+ * @return #MHD_YES on success, #MHD_NO if timeouts are
+ * not used (or no connections exist that would
+ * necessiate the use of a timeout right now).
+ * @ingroup event
+ */
+_MHD_EXTERN int
+MHD_get_timeout (struct MHD_Daemon *daemon,
+ MHD_UNSIGNED_LONG_LONG *timeout);
+
+
+/**
+ * Run webserver operations (without blocking unless in client
+ * callbacks). This method should be called by clients in combination
+ * with #MHD_get_fdset if the client-controlled select method is used.
+ *
+ * This function is a convenience method, which is useful if the
+ * fd_sets from #MHD_get_fdset were not directly passed to `select()`;
+ * with this function, MHD will internally do the appropriate `select()`
+ * call itself again. While it is always safe to call #MHD_run (in
+ * external select mode), you should call #MHD_run_from_select if
+ * performance is important (as it saves an expensive call to
+ * `select()`).
+ *
+ * @param daemon daemon to run
+ * @return #MHD_YES on success, #MHD_NO if this
+ * daemon was not started with the right
+ * options for this call.
+ * @ingroup event
+ */
+_MHD_EXTERN int
+MHD_run (struct MHD_Daemon *daemon);
+
+
+/**
+ * Run webserver operations. This method should be called by clients
+ * in combination with #MHD_get_fdset if the client-controlled select
+ * method is used.
+ *
+ * You can use this function instead of #MHD_run if you called
+ * `select()` on the result from #MHD_get_fdset. File descriptors in
+ * the sets that are not controlled by MHD will be ignored. Calling
+ * this function instead of #MHD_run is more efficient as MHD will
+ * not have to call `select()` again to determine which operations are
+ * ready.
+ *
+ * @param daemon daemon to run select loop for
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set (not used, can be NULL)
+ * @return #MHD_NO on serious errors, #MHD_YES on success
+ * @ingroup event
+ */
+_MHD_EXTERN int
+MHD_run_from_select (struct MHD_Daemon *daemon,
+ const fd_set *read_fd_set,
+ const fd_set *write_fd_set,
+ const fd_set *except_fd_set);
+
+
+
+
+/* **************** Connection handling functions ***************** */
+
+/**
+ * Get all of the headers from the request.
+ *
+ * @param connection connection to get values from
+ * @param kind types of values to iterate over
+ * @param iterator callback to call on each header;
+ * maybe NULL (then just count headers)
+ * @param iterator_cls extra argument to @a iterator
+ * @return number of entries iterated over
+ * @ingroup request
+ */
+_MHD_EXTERN int
+MHD_get_connection_values (struct MHD_Connection *connection,
+ enum MHD_ValueKind kind,
+ MHD_KeyValueIterator iterator, void *iterator_cls);
+
+
+/**
+ * This function can be used to add an entry to the HTTP headers of a
+ * connection (so that the #MHD_get_connection_values function will
+ * return them -- and the `struct MHD_PostProcessor` will also see
+ * them). This maybe required in certain situations (see Mantis
+ * #1399) where (broken) HTTP implementations fail to supply values
+ * needed by the post processor (or other parts of the application).
+ *
+ * This function MUST only be called from within the
+ * #MHD_AccessHandlerCallback (otherwise, access maybe improperly
+ * synchronized). Furthermore, the client must guarantee that the key
+ * and value arguments are 0-terminated strings that are NOT freed
+ * until the connection is closed. (The easiest way to do this is by
+ * passing only arguments to permanently allocated strings.).
+ *
+ * @param connection the connection for which a
+ * value should be set
+ * @param kind kind of the value
+ * @param key key for the value
+ * @param value the value itself
+ * @return #MHD_NO if the operation could not be
+ * performed due to insufficient memory;
+ * #MHD_YES on success
+ * @ingroup request
+ */
+_MHD_EXTERN int
+MHD_set_connection_value (struct MHD_Connection *connection,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *value);
+
+
+/**
+ * Sets the global error handler to a different implementation. @a cb
+ * will only be called in the case of typically fatal, serious
+ * internal consistency issues. These issues should only arise in the
+ * case of serious memory corruption or similar problems with the
+ * architecture. While @a cb is allowed to return and MHD will then
+ * try to continue, this is never safe.
+ *
+ * The default implementation that is used if no panic function is set
+ * simply prints an error message and calls `abort()`. Alternative
+ * implementations might call `exit()` or other similar functions.
+ *
+ * @param cb new error handler
+ * @param cls passed to @a cb
+ * @ingroup logging
+ */
+_MHD_EXTERN void
+MHD_set_panic_func (MHD_PanicCallback cb, void *cls);
+
+
+/**
+ * Process escape sequences ('%HH') Updates val in place; the
+ * result should be UTF-8 encoded and cannot be larger than the input.
+ * The result must also still be 0-terminated.
+ *
+ * @param val value to unescape (modified in the process)
+ * @return length of the resulting val (`strlen(val)` may be
+ * shorter afterwards due to elimination of escape sequences)
+ */
+_MHD_EXTERN size_t
+MHD_http_unescape (char *val);
+
+
+/**
+ * Get a particular header value. If multiple
+ * values match the kind, return any one of them.
+ *
+ * @param connection connection to get values from
+ * @param kind what kind of value are we looking for
+ * @param key the header to look for, NULL to lookup 'trailing' value without a key
+ * @return NULL if no such item was found
+ * @ingroup request
+ */
+_MHD_EXTERN const char *
+MHD_lookup_connection_value (struct MHD_Connection *connection,
+ enum MHD_ValueKind kind,
+ const char *key);
+
+
+/**
+ * Queue a response to be transmitted to the client (as soon as
+ * possible but after #MHD_AccessHandlerCallback returns).
+ *
+ * @param connection the connection identifying the client
+ * @param status_code HTTP status code (i.e. #MHD_HTTP_OK)
+ * @param response response to transmit
+ * @return #MHD_NO on error (i.e. reply already sent),
+ * #MHD_YES on success or if message has been queued
+ * @ingroup response
+ */
+_MHD_EXTERN int
+MHD_queue_response (struct MHD_Connection *connection,
+ unsigned int status_code,
+ struct MHD_Response *response);
+
+
+/**
+ * Suspend handling of network data for a given connection. This can
+ * be used to dequeue a connection from MHD's event loop (external
+ * select, internal select or thread pool; not applicable to
+ * thread-per-connection!) for a while.
+ *
+ * If you use this API in conjunction with a internal select or a
+ * thread pool, you must set the option #MHD_USE_PIPE_FOR_SHUTDOWN to
+ * ensure that a resumed connection is immediately processed by MHD.
+ *
+ * Suspended connections continue to count against the total number of
+ * connections allowed (per daemon, as well as per IP, if such limits
+ * are set). Suspended connections will NOT time out; timeouts will
+ * restart when the connection handling is resumed. While a
+ * connection is suspended, MHD will not detect disconnects by the
+ * client.
+ *
+ * The only safe time to suspend a connection is from the
+ * #MHD_AccessHandlerCallback.
+ *
+ * Finally, it is an API violation to call #MHD_stop_daemon while
+ * having suspended connections (this will at least create memory and
+ * socket leaks or lead to undefined behavior). You must explicitly
+ * resume all connections before stopping the daemon.
+ *
+ * @param connection the connection to suspend
+ */
+_MHD_EXTERN void
+MHD_suspend_connection (struct MHD_Connection *connection);
+
+
+/**
+ * Resume handling of network data for suspended connection. It is
+ * safe to resume a suspended connection at any time. Calling this
+ * function on a connection that was not previously suspended will
+ * result in undefined behavior.
+ *
+ * @param connection the connection to resume
+ */
+_MHD_EXTERN void
+MHD_resume_connection (struct MHD_Connection *connection);
+
+
+/* **************** Response manipulation functions ***************** */
+
+
+/**
+ * Flags for special handling of responses.
+ */
+enum MHD_ResponseFlags
+{
+ /**
+ * Default: no special flags.
+ */
+ MHD_RF_NONE = 0,
+
+ /**
+ * Only respond in conservative HTTP 1.0-mode. In particular,
+ * do not (automatically) sent "Connection" headers and always
+ * close the connection after generating the response.
+ */
+ MHD_RF_HTTP_VERSION_1_0_ONLY = 1
+
+};
+
+
+/**
+ * MHD options (for future extensions).
+ */
+enum MHD_ResponseOptions
+{
+ /**
+ * End of the list of options.
+ */
+ MHD_RO_END = 0
+};
+
+
+/**
+ * Set special flags and options for a response.
+ *
+ * @param response the response to modify
+ * @param flags to set for the response
+ * @param ... #MHD_RO_END terminated list of options
+ * @return #MHD_YES on success, #MHD_NO on error
+ */
+_MHD_EXTERN int
+MHD_set_response_options (struct MHD_Response *response,
+ enum MHD_ResponseFlags flags,
+ ...);
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the data portion of the response, #MHD_SIZE_UNKNOWN for unknown
+ * @param block_size preferred block size for querying crc (advisory only,
+ * MHD may still call @a crc using smaller chunks); this
+ * is essentially the buffer size used for IO, clients
+ * should pick a value that is appropriate for IO and
+ * memory performance requirements
+ * @param crc callback to use to obtain response data
+ * @param crc_cls extra argument to @a crc
+ * @param crfc callback to call to free @a crc_cls resources
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @ingroup response
+ */
+_MHD_EXTERN struct MHD_Response *
+MHD_create_response_from_callback (uint64_t size,
+ size_t block_size,
+ MHD_ContentReaderCallback crc, void *crc_cls,
+ MHD_ContentReaderFreeCallback crfc);
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the @a data portion of the response
+ * @param data the data itself
+ * @param must_free libmicrohttpd should free data when done
+ * @param must_copy libmicrohttpd must make a copy of @a data
+ * right away, the data maybe released anytime after
+ * this call returns
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @deprecated use #MHD_create_response_from_buffer instead
+ * @ingroup response
+ */
+_MHD_EXTERN struct MHD_Response *
+MHD_create_response_from_data (size_t size,
+ void *data,
+ int must_free,
+ int must_copy);
+
+
+/**
+ * Specification for how MHD should treat the memory buffer
+ * given for the response.
+ * @ingroup response
+ */
+enum MHD_ResponseMemoryMode
+{
+
+ /**
+ * Buffer is a persistent (static/global) buffer that won't change
+ * for at least the lifetime of the response, MHD should just use
+ * it, not free it, not copy it, just keep an alias to it.
+ * @ingroup response
+ */
+ MHD_RESPMEM_PERSISTENT,
+
+ /**
+ * Buffer is heap-allocated with `malloc()` (or equivalent) and
+ * should be freed by MHD after processing the response has
+ * concluded (response reference counter reaches zero).
+ * @ingroup response
+ */
+ MHD_RESPMEM_MUST_FREE,
+
+ /**
+ * Buffer is in transient memory, but not on the heap (for example,
+ * on the stack or non-`malloc()` allocated) and only valid during the
+ * call to #MHD_create_response_from_buffer. MHD must make its
+ * own private copy of the data for processing.
+ * @ingroup response
+ */
+ MHD_RESPMEM_MUST_COPY
+
+};
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the data portion of the response
+ * @param buffer size bytes containing the response's data portion
+ * @param mode flags for buffer management
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @ingroup response
+ */
+_MHD_EXTERN struct MHD_Response *
+MHD_create_response_from_buffer (size_t size,
+ void *buffer,
+ enum MHD_ResponseMemoryMode mode);
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the data portion of the response
+ * @param fd file descriptor referring to a file on disk with the
+ * data; will be closed when response is destroyed;
+ * fd should be in 'blocking' mode
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @ingroup response
+ */
+_MHD_EXTERN struct MHD_Response *
+MHD_create_response_from_fd (size_t size,
+ int fd);
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the data portion of the response
+ * @param fd file descriptor referring to a file on disk with the
+ * data; will be closed when response is destroyed;
+ * fd should be in 'blocking' mode
+ * @param offset offset to start reading from in the file;
+ * Be careful! `off_t` may have been compiled to be a
+ * 64-bit variable for MHD, in which case your application
+ * also has to be compiled using the same options! Read
+ * the MHD manual for more details.
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @ingroup response
+ */
+_MHD_EXTERN struct MHD_Response *
+MHD_create_response_from_fd_at_offset (size_t size,
+ int fd,
+ off_t offset);
+
+
+#if 0
+/**
+ * Enumeration for actions MHD should perform on the underlying socket
+ * of the upgrade. This API is not finalized, and in particular
+ * the final set of actions is yet to be decided. This is just an
+ * idea for what we might want.
+ */
+enum MHD_UpgradeAction
+{
+
+ /**
+ * Close the socket, the application is done with it.
+ *
+ * Takes no extra arguments.
+ *
+ * NOTE: it is unclear if we want to have this in the
+ * "final" API, this is all just ideas.
+ */
+ MHD_UPGRADE_ACTION_CLOSE = 0,
+
+ /**
+ * Uncork the TCP write buffer (that is, tell the OS to transmit all
+ * bytes in the buffer now, and to not use TCP-CORKing).
+ *
+ * Takes no extra arguments.
+ *
+ * NOTE: it is unclear if we want to have this in the
+ * "final" API, this is all just ideas.
+ */
+ MHD_UPGRADE_ACTION_CORK
+
+};
+
+
+/**
+ * This connection-specific callback is provided by MHD to
+ * applications (unusual) during the #MHD_UpgradeHandler.
+ * It allows applications to perform 'special' actions on
+ * the underlying socket from the upgrade.
+ *
+ * @param cls the closure (from `upgrade_action_cls`)
+ * @param action which action should be performed
+ * @param ... arguments to the action (depends on the action)
+ * @return #MHD_NO on error, #MHD_YES on success
+ */
+typedef int
+(*MHD_UpgradeActionCallback)(void *cls,
+ enum MHD_UpgradeAction action,
+ ...);
+
+/**
+ * Function called after a protocol "upgrade" response was sent
+ * successfully and the socket should now be controlled by some
+ * protocol other than HTTP.
+ *
+ * Any data received on the socket will be made available in
+ * 'data_in'. The function should update 'data_in_size' to
+ * reflect the number of bytes consumed from 'data_in' (the remaining
+ * bytes will be made available in the next call to the handler).
+ *
+ * Any data that should be transmitted on the socket should be
+ * stored in 'data_out'. '*data_out_size' is initially set to
+ * the available buffer space in 'data_out'. It should be set to
+ * the number of bytes stored in 'data_out' (which can be zero).
+ *
+ * The return value is a BITMASK that indicates how the function
+ * intends to interact with the event loop. It can request to be
+ * notified for reading, writing, request to UNCORK the send buffer
+ * (which MHD is allowed to ignore, if it is not possible to uncork on
+ * the local platform), to wait for the 'external' select loop to
+ * trigger another round. It is also possible to specify "no events"
+ * to terminate the connection; in this case, the
+ * #MHD_RequestCompletedCallback will be called and all resources of
+ * the connection will be released.
+ *
+ * Except when in 'thread-per-connection' mode, implementations
+ * of this function should never block (as it will still be called
+ * from within the main event loop).
+ *
+ * @param cls closure
+ * @param connection original HTTP connection handle,
+ * giving the function a last chance
+ * to inspect the original HTTP request
+ * @param sock socket to use for bi-directional communication
+ * with the client. For HTTPS, this may not be a socket
+ * that is directly connected to the client and thus certain
+ * operations (TCP-specific setsockopt(), getsockopt(), etc.)
+ * may not work as expected (as the socket could be from a
+ * socketpair() or a TCP-loopback)
+ * @param upgrade_action function that can be used to perform actions
+ * on the @a sock (like those that cannot be done explicitly).
+ * Applications must use this callback to perform the
+ * close() action on the @a sock.
+ * @param upgrade_action_cls closure that must be passed to @a upgrade_action
+ */
+typedef void
+(*MHD_UpgradeHandler)(void *cls,
+ struct MHD_Connection *connection,
+ MHD_SOCKET sock,
+ MHD_UpgradeActionCallback upgrade_action,
+ void *upgrade_action_cls);
+
+
+/**
+ * Create a response object that can be used for 101 UPGRADE
+ * responses, for example to implement WebSockets. After sending the
+ * response, control over the data stream is given to the callback (which
+ * can then, for example, start some bi-directional communication).
+ * If the response is queued for multiple connections, the callback
+ * will be called for each connection. The callback
+ * will ONLY be called after the response header was successfully passed
+ * to the OS; if there are communication errors before, the usual MHD
+ * connection error handling code will be performed.
+ *
+ * Setting the correct HTTP code (i.e. MHD_HTTP_SWITCHING_PROTOCOLS)
+ * and setting correct HTTP headers for the upgrade must be done
+ * manually (this way, it is possible to implement most existing
+ * WebSocket versions using this API; in fact, this API might be useful
+ * for any protocol switch, not just WebSockets). Note that
+ * draft-ietf-hybi-thewebsocketprotocol-00 cannot be implemented this
+ * way as the header "HTTP/1.1 101 WebSocket Protocol Handshake"
+ * cannot be generated; instead, MHD will always produce "HTTP/1.1 101
+ * Switching Protocols" (if the response code 101 is used).
+ *
+ * As usual, the response object can be extended with header
+ * information and then be used any number of times (as long as the
+ * header information is not connection-specific).
+ *
+ * @param upgrade_handler function to call with the 'upgraded' socket
+ * @param upgrade_handler_cls closure for @a upgrade_handler
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ */
+struct MHD_Response *
+MHD_create_response_for_upgrade (MHD_UpgradeHandler upgrade_handler,
+ void *upgrade_handler_cls);
+#endif
+
+/**
+ * Destroy a response object and associated resources. Note that
+ * libmicrohttpd may keep some of the resources around if the response
+ * is still in the queue for some clients, so the memory may not
+ * necessarily be freed immediatley.
+ *
+ * @param response response to destroy
+ * @ingroup response
+ */
+_MHD_EXTERN void
+MHD_destroy_response (struct MHD_Response *response);
+
+
+/**
+ * Add a header line to the response.
+ *
+ * @param response response to add a header to
+ * @param header the header to add
+ * @param content value to add
+ * @return #MHD_NO on error (i.e. invalid header or content format),
+ * or out of memory
+ * @ingroup response
+ */
+_MHD_EXTERN int
+MHD_add_response_header (struct MHD_Response *response,
+ const char *header,
+ const char *content);
+
+
+/**
+ * Add a footer line to the response.
+ *
+ * @param response response to remove a header from
+ * @param footer the footer to delete
+ * @param content value to delete
+ * @return #MHD_NO on error (i.e. invalid footer or content format).
+ * @ingroup response
+ */
+_MHD_EXTERN int
+MHD_add_response_footer (struct MHD_Response *response,
+ const char *footer,
+ const char *content);
+
+
+/**
+ * Delete a header (or footer) line from the response.
+ *
+ * @param response response to remove a header from
+ * @param header the header to delete
+ * @param content value to delete
+ * @return #MHD_NO on error (no such header known)
+ * @ingroup response
+ */
+_MHD_EXTERN int
+MHD_del_response_header (struct MHD_Response *response,
+ const char *header,
+ const char *content);
+
+
+/**
+ * Get all of the headers (and footers) added to a response.
+ *
+ * @param response response to query
+ * @param iterator callback to call on each header;
+ * maybe NULL (then just count headers)
+ * @param iterator_cls extra argument to @a iterator
+ * @return number of entries iterated over
+ * @ingroup response
+ */
+_MHD_EXTERN int
+MHD_get_response_headers (struct MHD_Response *response,
+ MHD_KeyValueIterator iterator, void *iterator_cls);
+
+
+/**
+ * Get a particular header (or footer) from the response.
+ *
+ * @param response response to query
+ * @param key which header to get
+ * @return NULL if header does not exist
+ * @ingroup response
+ */
+_MHD_EXTERN const char *
+MHD_get_response_header (struct MHD_Response *response,
+ const char *key);
+
+
+/* ********************** PostProcessor functions ********************** */
+
+/**
+ * Create a `struct MHD_PostProcessor`.
+ *
+ * A `struct MHD_PostProcessor` can be used to (incrementally) parse
+ * the data portion of a POST request. Note that some buggy browsers
+ * fail to set the encoding type. If you want to support those, you
+ * may have to call #MHD_set_connection_value with the proper encoding
+ * type before creating a post processor (if no supported encoding
+ * type is set, this function will fail).
+ *
+ * @param connection the connection on which the POST is
+ * happening (used to determine the POST format)
+ * @param buffer_size maximum number of bytes to use for
+ * internal buffering (used only for the parsing,
+ * specifically the parsing of the keys). A
+ * tiny value (256-1024) should be sufficient.
+ * Do NOT use a value smaller than 256. For good
+ * performance, use 32 or 64k (i.e. 65536).
+ * @param iter iterator to be called with the parsed data,
+ * Must NOT be NULL.
+ * @param iter_cls first argument to @a iter
+ * @return NULL on error (out of memory, unsupported encoding),
+ * otherwise a PP handle
+ * @ingroup request
+ */
+_MHD_EXTERN struct MHD_PostProcessor *
+MHD_create_post_processor (struct MHD_Connection *connection,
+ size_t buffer_size,
+ MHD_PostDataIterator iter, void *iter_cls);
+
+
+/**
+ * Parse and process POST data. Call this function when POST data is
+ * available (usually during an #MHD_AccessHandlerCallback) with the
+ * "upload_data" and "upload_data_size". Whenever possible, this will
+ * then cause calls to the #MHD_PostDataIterator.
+ *
+ * @param pp the post processor
+ * @param post_data @a post_data_len bytes of POST data
+ * @param post_data_len length of @a post_data
+ * @return #MHD_YES on success, #MHD_NO on error
+ * (out-of-memory, iterator aborted, parse error)
+ * @ingroup request
+ */
+_MHD_EXTERN int
+MHD_post_process (struct MHD_PostProcessor *pp,
+ const char *post_data, size_t post_data_len);
+
+
+/**
+ * Release PostProcessor resources.
+ *
+ * @param pp the PostProcessor to destroy
+ * @return #MHD_YES if processing completed nicely,
+ * #MHD_NO if there were spurious characters / formatting
+ * problems; it is common to ignore the return
+ * value of this function
+ * @ingroup request
+ */
+_MHD_EXTERN int
+MHD_destroy_post_processor (struct MHD_PostProcessor *pp);
+
+
+/* ********************* Digest Authentication functions *************** */
+
+
+/**
+ * Constant to indicate that the nonce of the provided
+ * authentication code was wrong.
+ * @ingroup authentication
+ */
+#define MHD_INVALID_NONCE -1
+
+
+/**
+ * Get the username from the authorization header sent by the client
+ *
+ * @param connection The MHD connection structure
+ * @return NULL if no username could be found, a pointer
+ * to the username if found
+ * @ingroup authentication
+ */
+_MHD_EXTERN char *
+MHD_digest_auth_get_username (struct MHD_Connection *connection);
+
+
+/**
+ * Authenticates the authorization header sent by the client
+ *
+ * @param connection The MHD connection structure
+ * @param realm The realm presented to the client
+ * @param username The username needs to be authenticated
+ * @param password The password used in the authentication
+ * @param nonce_timeout The amount of time for a nonce to be
+ * invalid in seconds
+ * @return #MHD_YES if authenticated, #MHD_NO if not,
+ * #MHD_INVALID_NONCE if nonce is invalid
+ * @ingroup authentication
+ */
+_MHD_EXTERN int
+MHD_digest_auth_check (struct MHD_Connection *connection,
+ const char *realm,
+ const char *username,
+ const char *password,
+ unsigned int nonce_timeout);
+
+
+/**
+ * Queues a response to request authentication from the client
+ *
+ * @param connection The MHD connection structure
+ * @param realm The realm presented to the client
+ * @param opaque string to user for opaque value
+ * @param response reply to send; should contain the "access denied"
+ * body; note that this function will set the "WWW Authenticate"
+ * header and that the caller should not do this
+ * @param signal_stale #MHD_YES if the nonce is invalid to add
+ * 'stale=true' to the authentication header
+ * @return #MHD_YES on success, #MHD_NO otherwise
+ * @ingroup authentication
+ */
+_MHD_EXTERN int
+MHD_queue_auth_fail_response (struct MHD_Connection *connection,
+ const char *realm,
+ const char *opaque,
+ struct MHD_Response *response,
+ int signal_stale);
+
+
+/**
+ * Get the username and password from the basic authorization header sent by the client
+ *
+ * @param connection The MHD connection structure
+ * @param password a pointer for the password
+ * @return NULL if no username could be found, a pointer
+ * to the username if found
+ * @ingroup authentication
+ */
+_MHD_EXTERN char *
+MHD_basic_auth_get_username_password (struct MHD_Connection *connection,
+ char** password);
+
+
+/**
+ * Queues a response to request basic authentication from the client
+ * The given response object is expected to include the payload for
+ * the response; the "WWW-Authenticate" header will be added and the
+ * response queued with the 'UNAUTHORIZED' status code.
+ *
+ * @param connection The MHD connection structure
+ * @param realm the realm presented to the client
+ * @param response response object to modify and queue
+ * @return #MHD_YES on success, #MHD_NO otherwise
+ * @ingroup authentication
+ */
+_MHD_EXTERN int
+MHD_queue_basic_auth_fail_response (struct MHD_Connection *connection,
+ const char *realm,
+ struct MHD_Response *response);
+
+/* ********************** generic query functions ********************** */
+
+
+/**
+ * Obtain information about the given connection.
+ *
+ * @param connection what connection to get information about
+ * @param info_type what information is desired?
+ * @param ... depends on @a info_type
+ * @return NULL if this information is not available
+ * (or if the @a info_type is unknown)
+ * @ingroup specialized
+ */
+_MHD_EXTERN const union MHD_ConnectionInfo *
+MHD_get_connection_info (struct MHD_Connection *connection,
+ enum MHD_ConnectionInfoType info_type,
+ ...);
+
+
+/**
+ * MHD connection options. Given to #MHD_set_connection_option to
+ * set custom options for a particular connection.
+ */
+enum MHD_CONNECTION_OPTION
+{
+
+ /**
+ * Set a custom timeout for the given connection. Specified
+ * as the number of seconds, given as an `unsigned int`. Use
+ * zero for no timeout.
+ */
+ MHD_CONNECTION_OPTION_TIMEOUT
+
+};
+
+
+/**
+ * Set a custom option for the given connection, overriding defaults.
+ *
+ * @param connection connection to modify
+ * @param option option to set
+ * @param ... arguments to the option, depending on the option type
+ * @return #MHD_YES on success, #MHD_NO if setting the option failed
+ * @ingroup specialized
+ */
+_MHD_EXTERN int
+MHD_set_connection_option (struct MHD_Connection *connection,
+ enum MHD_CONNECTION_OPTION option,
+ ...);
+
+
+/**
+ * Information about an MHD daemon.
+ */
+union MHD_DaemonInfo
+{
+ /**
+ * Size of the key, no longer supported.
+ * @deprecated
+ */
+ size_t key_size;
+
+ /**
+ * Size of the mac key, no longer supported.
+ * @deprecated
+ */
+ size_t mac_key_size;
+
+ /**
+ * Listen socket file descriptor, for #MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY
+ * and #MHD_DAEMON_INFO_LISTEN_FD.
+ */
+ MHD_socket listen_fd;
+
+ /**
+ * Number of active connections, for #MHD_DAEMON_INFO_CURRENT_CONNECTIONS.
+ */
+ unsigned int num_connections;
+};
+
+
+/**
+ * Obtain information about the given daemon
+ * (not fully implemented!).
+ *
+ * @param daemon what daemon to get information about
+ * @param info_type what information is desired?
+ * @param ... depends on @a info_type
+ * @return NULL if this information is not available
+ * (or if the @a info_type is unknown)
+ * @ingroup specialized
+ */
+_MHD_EXTERN const union MHD_DaemonInfo *
+MHD_get_daemon_info (struct MHD_Daemon *daemon,
+ enum MHD_DaemonInfoType info_type,
+ ...);
+
+
+/**
+ * Obtain the version of this library
+ *
+ * @return static version string, e.g. "0.9.9"
+ * @ingroup specialized
+ */
+_MHD_EXTERN const char*
+MHD_get_version (void);
+
+
+/**
+ * Types of information about MHD features,
+ * used by #MHD_is_feature_supported().
+ */
+enum MHD_FEATURE
+{
+ /**
+ * Get whether messages are supported. If supported then in debug
+ * mode messages can be printed to stderr or to external logger.
+ */
+ MHD_FEATURE_MESSGES = 1,
+
+ /**
+ * Get whether HTTPS is supported. If supported then flag
+ * #MHD_USE_SSL and options #MHD_OPTION_HTTPS_MEM_KEY,
+ * #MHD_OPTION_HTTPS_MEM_CERT, #MHD_OPTION_HTTPS_MEM_TRUST,
+ * #MHD_OPTION_HTTPS_MEM_DHPARAMS, #MHD_OPTION_HTTPS_CRED_TYPE,
+ * #MHD_OPTION_HTTPS_PRIORITIES can be used.
+ */
+ MHD_FEATURE_SSL = 2,
+
+ /**
+ * Get whether option #MHD_OPTION_HTTPS_CERT_CALLBACK is
+ * supported.
+ */
+ MHD_FEATURE_HTTPS_CERT_CALLBACK = 3,
+
+ /**
+ * Get whether IPv6 is supported. If supported then flag
+ * #MHD_USE_IPv6 can be used.
+ */
+ MHD_FEATURE_IPv6 = 4,
+
+ /**
+ * Get whether IPv6 without IPv4 is supported. If not supported
+ * then IPv4 is always enabled in IPv6 sockets and
+ * flag #MHD_USE_DUAL_STACK if always used when #MHD_USE_IPv6 is
+ * specified.
+ */
+ MHD_FEATURE_IPv6_ONLY = 5,
+
+ /**
+ * Get whether `poll()` is supported. If supported then flag
+ * #MHD_USE_POLL can be used.
+ */
+ MHD_FEATURE_POLL = 6,
+
+ /**
+ * Get whether `epoll()` is supported. If supported then Flags
+ * #MHD_USE_EPOLL_LINUX_ONLY and
+ * #MHD_USE_EPOLL_INTERNALLY_LINUX_ONLY can be used.
+ */
+ MHD_FEATURE_EPOLL = 7,
+
+ /**
+ * Get whether shutdown on listen socket to signal other
+ * threads is supported. If not supported flag
+ * #MHD_USE_PIPE_FOR_SHUTDOWN is automatically forced.
+ */
+ MHD_FEATURE_SHUTDOWN_LISTEN_SOCKET = 8,
+
+ /**
+ * Get whether socketpair is used internally instead of pipe to
+ * signal other threads.
+ */
+ MHD_FEATURE_SOCKETPAIR = 9,
+
+ /**
+ * Get whether TCP Fast Open is supported. If supported then
+ * flag #MHD_USE_TCP_FASTOPEN and option
+ * #MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE can be used.
+ */
+ MHD_FEATURE_TCP_FASTOPEN = 10,
+
+ /**
+ * Get whether HTTP Basic authorization is supported. If supported
+ * then functions #MHD_basic_auth_get_username_password and
+ * #MHD_queue_basic_auth_fail_response can be used.
+ */
+ MHD_FEATURE_BASIC_AUTH = 11,
+
+ /**
+ * Get whether HTTP Digest authorization is supported. If
+ * supported then options #MHD_OPTION_DIGEST_AUTH_RANDOM,
+ * #MHD_OPTION_NONCE_NC_SIZE and
+ * #MHD_digest_auth_check() can be used.
+ */
+ MHD_FEATURE_DIGEST_AUTH = 12,
+
+ /**
+ * Get whether postprocessor is supported. If supported then
+ * functions #MHD_create_post_processor(), #MHD_post_process() and
+ * #MHD_destroy_post_processor() can
+ * be used.
+ */
+ MHD_FEATURE_POSTPROCESSOR = 13,
+
+ /**
+ * Get whether password encrypted private key for HTTPS daemon is
+ * supported. If supported then option
+ * ::MHD_OPTION_HTTPS_KEY_PASSWORD can be used.
+ */
+ MHD_FEATURE_HTTPS_KEY_PASSWORD = 14
+};
+
+
+/**
+ * Get information about supported MHD features.
+ * Indicate that MHD was compiled with or without support for
+ * particular feature. Some features require additional support
+ * by kernel. Kernel support is not checked by this function.
+ *
+ * @param feature type of requested information
+ * @return #MHD_YES if feature is supported by MHD, #MHD_NO if
+ * feature is not supported or feature is unknown.
+ * @ingroup specialized
+ */
+_MHD_EXTERN int
+MHD_is_feature_supported(enum MHD_FEATURE feature);
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/include/microspdy.h b/src/include/microspdy.h
new file mode 100644
index 0000000..7da5fbe
--- /dev/null
+++ b/src/include/microspdy.h
@@ -0,0 +1,1380 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012, 2013 Christian Grothoff
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file microspdy.h
+ * @brief public interface to libmicrospdy
+ * @author Andrey Uzunov
+ * @author Christian Grothoff
+ *
+ * All symbols defined in this header start with SPDY_. libmisrospdy is a small
+ * SPDY daemon library. The application can start multiple daemons
+ * and they are independent.<p>
+ *
+ * The header file defines various constants used by the SPDY and the HTTP protocol.
+ * This does not mean that the lib actually interprets all of these
+ * values. Not everything is implemented. The provided constants are exported as a convenience
+ * for users of the library. The lib does not verify that provided
+ * HTTP headers and if their values conform to the SPDY protocol,
+ * it only checks if the required headers for the SPDY requests and
+ * responses are provided.<p>
+ *
+ * The library uses just a single thread.<p>
+ *
+ * Before including "microspdy.h" you should add the necessary
+ * includes to define the types used in this file (which headers are needed may
+ * depend on your platform; for possible suggestions consult
+ * "platform.h" in the libmicrospdy distribution).<p>
+ *
+ * All of the functions returning SPDY_YES/SPDY_NO return
+ * SPDY_INPUT_ERROR when any of the parameters are invalid, e.g.
+ * required parameter is NULL.<p>
+ *
+ * The library does not check if anything at the application layer --
+ * requests and responses -- is correct. For example, it
+ * is up to the user to check if a client is sending HTTP body but the
+ * method is GET.<p>
+ *
+ * The SPDY flow control is just partially implemented: the receiving
+ * window is updated, and the client is notified, to prevent a client
+ * from stop sending POST body data, for example.
+ */
+#ifndef SPDY_MICROSPDY_H
+#define SPDY_MICROSPDY_H
+
+#include <zlib.h>
+#include <stdbool.h>
+
+/* While we generally would like users to use a configure-driven
+ build process which detects which headers are present and
+ hence works on any platform, we use "standard" includes here
+ to build out-of-the-box for beginning users on common systems.
+
+ Once you have a proper build system and go for more exotic
+ platforms, you should define MHD_PLATFORM_H in some header that
+ you always include *before* "microhttpd.h". Then the following
+ "standard" includes won't be used (which might be a good
+ idea, especially on platforms where they do not exist). */
+#ifndef MHD_PLATFORM_H
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdint.h>
+#ifdef __MINGW32__
+#include <ws2tcpip.h>
+#else
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+#endif
+
+#ifndef _MHD_EXTERN
+#define _MHD_EXTERN extern
+#endif
+
+/**
+ * return code for "YES".
+ */
+#define SPDY_YES 1
+
+/**
+ * return code for "NO".
+ */
+#define SPDY_NO 0
+
+/**
+ * return code for error when input parameters are wrong. To be returned
+ * only by functions which return int. The others will return NULL on
+ * input error.
+ */
+#define SPDY_INPUT_ERROR -1
+
+/**
+ * SPDY version supported by the lib.
+ */
+#define SPDY_VERSION 3
+
+/**
+ * The maximum allowed size (without 8 byte headers) of
+ * SPDY frames (value length) is 8192. The lib will accept and
+ * send frames with length at most this value here.
+ */
+#define SPDY_MAX_SUPPORTED_FRAME_SIZE 8192
+
+/**
+ * HTTP response codes.
+ */
+#define SPDY_HTTP_CONTINUE 100
+#define SPDY_HTTP_SWITCHING_PROTOCOLS 101
+#define SPDY_HTTP_PROCESSING 102
+
+#define SPDY_HTTP_OK 200
+#define SPDY_HTTP_CREATED 201
+#define SPDY_HTTP_ACCEPTED 202
+#define SPDY_HTTP_NON_AUTHORITATIVE_INFORMATION 203
+#define SPDY_HTTP_NO_CONTENT 204
+#define SPDY_HTTP_RESET_CONTENT 205
+#define SPDY_HTTP_PARTIAL_CONTENT 206
+#define SPDY_HTTP_MULTI_STATUS 207
+
+#define SPDY_HTTP_MULTIPLE_CHOICES 300
+#define SPDY_HTTP_MOVED_PERMANENTLY 301
+#define SPDY_HTTP_FOUND 302
+#define SPDY_HTTP_SEE_OTHER 303
+#define SPDY_HTTP_NOT_MODIFIED 304
+#define SPDY_HTTP_USE_PROXY 305
+#define SPDY_HTTP_SWITCH_PROXY 306
+#define SPDY_HTTP_TEMPORARY_REDIRECT 307
+
+#define SPDY_HTTP_BAD_REQUEST 400
+#define SPDY_HTTP_UNAUTHORIZED 401
+#define SPDY_HTTP_PAYMENT_REQUIRED 402
+#define SPDY_HTTP_FORBIDDEN 403
+#define SPDY_HTTP_NOT_FOUND 404
+#define SPDY_HTTP_METHOD_NOT_ALLOWED 405
+#define SPDY_HTTP_METHOD_NOT_ACCEPTABLE 406
+#define SPDY_HTTP_PROXY_AUTHENTICATION_REQUIRED 407
+#define SPDY_HTTP_REQUEST_TIMEOUT 408
+#define SPDY_HTTP_CONFLICT 409
+#define SPDY_HTTP_GONE 410
+#define SPDY_HTTP_LENGTH_REQUIRED 411
+#define SPDY_HTTP_PRECONDITION_FAILED 412
+#define SPDY_HTTP_REQUEST_ENTITY_TOO_LARGE 413
+#define SPDY_HTTP_REQUEST_URI_TOO_LONG 414
+#define SPDY_HTTP_UNSUPPORTED_MEDIA_TYPE 415
+#define SPDY_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE 416
+#define SPDY_HTTP_EXPECTATION_FAILED 417
+#define SPDY_HTTP_UNPROCESSABLE_ENTITY 422
+#define SPDY_HTTP_LOCKED 423
+#define SPDY_HTTP_FAILED_DEPENDENCY 424
+#define SPDY_HTTP_UNORDERED_COLLECTION 425
+#define SPDY_HTTP_UPGRADE_REQUIRED 426
+#define SPDY_HTTP_NO_RESPONSE 444
+#define SPDY_HTTP_RETRY_WITH 449
+#define SPDY_HTTP_BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS 450
+#define SPDY_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS 451
+
+#define SPDY_HTTP_INTERNAL_SERVER_ERROR 500
+#define SPDY_HTTP_NOT_IMPLEMENTED 501
+#define SPDY_HTTP_BAD_GATEWAY 502
+#define SPDY_HTTP_SERVICE_UNAVAILABLE 503
+#define SPDY_HTTP_GATEWAY_TIMEOUT 504
+#define SPDY_HTTP_HTTP_VERSION_NOT_SUPPORTED 505
+#define SPDY_HTTP_VARIANT_ALSO_NEGOTIATES 506
+#define SPDY_HTTP_INSUFFICIENT_STORAGE 507
+#define SPDY_HTTP_BANDWIDTH_LIMIT_EXCEEDED 509
+#define SPDY_HTTP_NOT_EXTENDED 510
+
+/**
+ * HTTP headers are used in SPDY, but all of them MUST be lowercase.
+ * Some are not valid in SPDY and MUST not be used
+ */
+#define SPDY_HTTP_HEADER_ACCEPT "accept"
+#define SPDY_HTTP_HEADER_ACCEPT_CHARSET "accept-charset"
+#define SPDY_HTTP_HEADER_ACCEPT_ENCODING "accept-encoding"
+#define SPDY_HTTP_HEADER_ACCEPT_LANGUAGE "accept-language"
+#define SPDY_HTTP_HEADER_ACCEPT_RANGES "accept-ranges"
+#define SPDY_HTTP_HEADER_AGE "age"
+#define SPDY_HTTP_HEADER_ALLOW "allow"
+#define SPDY_HTTP_HEADER_AUTHORIZATION "authorization"
+#define SPDY_HTTP_HEADER_CACHE_CONTROL "cache-control"
+/* Connection header is forbidden in SPDY */
+#define SPDY_HTTP_HEADER_CONNECTION "connection"
+#define SPDY_HTTP_HEADER_CONTENT_ENCODING "content-encoding"
+#define SPDY_HTTP_HEADER_CONTENT_LANGUAGE "content-language"
+#define SPDY_HTTP_HEADER_CONTENT_LENGTH "content-length"
+#define SPDY_HTTP_HEADER_CONTENT_LOCATION "content-location"
+#define SPDY_HTTP_HEADER_CONTENT_MD5 "content-md5"
+#define SPDY_HTTP_HEADER_CONTENT_RANGE "content-range"
+#define SPDY_HTTP_HEADER_CONTENT_TYPE "content-type"
+#define SPDY_HTTP_HEADER_COOKIE "cookie"
+#define SPDY_HTTP_HEADER_DATE "date"
+#define SPDY_HTTP_HEADER_ETAG "etag"
+#define SPDY_HTTP_HEADER_EXPECT "expect"
+#define SPDY_HTTP_HEADER_EXPIRES "expires"
+#define SPDY_HTTP_HEADER_FROM "from"
+/* Host header is forbidden in SPDY */
+#define SPDY_HTTP_HEADER_HOST "host"
+#define SPDY_HTTP_HEADER_IF_MATCH "if-match"
+#define SPDY_HTTP_HEADER_IF_MODIFIED_SINCE "if-modified-since"
+#define SPDY_HTTP_HEADER_IF_NONE_MATCH "if-none-match"
+#define SPDY_HTTP_HEADER_IF_RANGE "if-range"
+#define SPDY_HTTP_HEADER_IF_UNMODIFIED_SINCE "if-unmodified-since"
+/* Keep-Alive header is forbidden in SPDY */
+#define SPDY_HTTP_HEADER_KEEP_ALIVE "keep-alive"
+#define SPDY_HTTP_HEADER_LAST_MODIFIED "last-modified"
+#define SPDY_HTTP_HEADER_LOCATION "location"
+#define SPDY_HTTP_HEADER_MAX_FORWARDS "max-forwards"
+#define SPDY_HTTP_HEADER_PRAGMA "pragma"
+#define SPDY_HTTP_HEADER_PROXY_AUTHENTICATE "proxy-authenticate"
+#define SPDY_HTTP_HEADER_PROXY_AUTHORIZATION "proxy-authorization"
+/* Proxy-Connection header is forbidden in SPDY */
+#define SPDY_HTTP_HEADER_PROXY_CONNECTION "proxy-connection"
+#define SPDY_HTTP_HEADER_RANGE "range"
+#define SPDY_HTTP_HEADER_REFERER "referer"
+#define SPDY_HTTP_HEADER_RETRY_AFTER "retry-after"
+#define SPDY_HTTP_HEADER_SERVER "server"
+#define SPDY_HTTP_HEADER_SET_COOKIE "set-cookie"
+#define SPDY_HTTP_HEADER_SET_COOKIE2 "set-cookie2"
+#define SPDY_HTTP_HEADER_TE "te"
+#define SPDY_HTTP_HEADER_TRAILER "trailer"
+/* Transfer-Encoding header is forbidden in SPDY */
+#define SPDY_HTTP_HEADER_TRANSFER_ENCODING "transfer-encoding"
+#define SPDY_HTTP_HEADER_UPGRADE "upgrade"
+#define SPDY_HTTP_HEADER_USER_AGENT "user-agent"
+#define SPDY_HTTP_HEADER_VARY "vary"
+#define SPDY_HTTP_HEADER_VIA "via"
+#define SPDY_HTTP_HEADER_WARNING "warning"
+#define SPDY_HTTP_HEADER_WWW_AUTHENTICATE "www-authenticate"
+
+/**
+ * HTTP versions (a value must be provided in SPDY requests/responses).
+ */
+#define SPDY_HTTP_VERSION_1_0 "HTTP/1.0"
+#define SPDY_HTTP_VERSION_1_1 "HTTP/1.1"
+
+/**
+ * HTTP methods
+ */
+#define SPDY_HTTP_METHOD_CONNECT "CONNECT"
+#define SPDY_HTTP_METHOD_DELETE "DELETE"
+#define SPDY_HTTP_METHOD_GET "GET"
+#define SPDY_HTTP_METHOD_HEAD "HEAD"
+#define SPDY_HTTP_METHOD_OPTIONS "OPTIONS"
+#define SPDY_HTTP_METHOD_POST "POST"
+#define SPDY_HTTP_METHOD_PUT "PUT"
+#define SPDY_HTTP_METHOD_TRACE "TRACE"
+
+/**
+ * HTTP POST encodings, see also
+ * http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
+ */
+#define SPDY_HTTP_POST_ENCODING_FORM_URLENCODED "application/x-www-form-urlencoded"
+#define SPDY_HTTP_POST_ENCODING_MULTIPART_FORMDATA "multipart/form-data"
+
+
+/**
+ * Handle for the daemon (listening on a socket).
+ */
+struct SPDY_Daemon;
+
+
+/**
+ * Handle for a SPDY session/connection.
+ */
+struct SPDY_Session;
+
+
+/**
+ * Handle for a SPDY request sent by a client. The structure has pointer
+ * to the session's handler
+ */
+struct SPDY_Request;
+
+
+/**
+ * Handle for a response containing HTTP headers and data to be sent.
+ * The structure has pointer to the session's handler
+ * for this response.
+ */
+struct SPDY_Response;
+
+
+/**
+ * Collection of tuples of an HTTP header and values used in requests
+ * and responses.
+ */
+struct SPDY_NameValue;
+
+
+/**
+ * Collection of tuples of a SPDY setting ID, value
+ * and flags used to control the sessions.
+ */
+struct SPDY_Settings;
+
+
+/**
+ * SPDY IO sybsystem flags used by SPDY_init() and SPDY_deinit().<p>
+ *
+ * The values are used internally as flags, that is why they must be
+ * powers of 2.
+ */
+enum SPDY_IO_SUBSYSTEM
+{
+
+ /**
+ * No subsystem. For internal use.
+ */
+ SPDY_IO_SUBSYSTEM_NONE = 0,
+
+ /**
+ * Default TLS implementation provided by openSSL/libssl.
+ */
+ SPDY_IO_SUBSYSTEM_OPENSSL = 1,
+
+ /**
+ * No TLS is used.
+ */
+ SPDY_IO_SUBSYSTEM_RAW = 2
+};
+
+
+/**
+ * SPDY daemon options. Passed in the varargs portion of
+ * SPDY_start_daemon to customize the daemon. Each option must
+ * be followed by a value of a specific type.<p>
+ *
+ * The values are used internally as flags, that is why they must be
+ * powers of 2.
+ */
+enum SPDY_DAEMON_OPTION
+{
+
+ /**
+ * No more options / last option. This is used
+ * to terminate the VARARGs list.
+ */
+ SPDY_DAEMON_OPTION_END = 0,
+
+ /**
+ * Set a custom timeout for all connections. Must be followed by
+ * a number of seconds, given as an 'unsigned int'. Use
+ * zero for no timeout.
+ */
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT = 1,
+
+ /**
+ * Bind daemon to the supplied sockaddr. This option must be
+ * followed by a 'struct sockaddr *'. The 'struct sockaddr*'
+ * should point to a 'struct sockaddr_in6' or to a
+ * 'struct sockaddr_in'.
+ */
+ SPDY_DAEMON_OPTION_SOCK_ADDR = 2,
+
+ /**
+ * Flags for the daemon. Must be followed by a SPDY_DAEMON_FLAG value
+ * which is the result of bitwise OR of desired flags.
+ */
+ SPDY_DAEMON_OPTION_FLAGS = 4,
+
+ /**
+ * IO subsystem type used by daemon and all its sessions. If not set,
+ * TLS provided by openssl is used. Must be followed by a
+ * SPDY_IO_SUBSYSTEM value.
+ */
+ SPDY_DAEMON_OPTION_IO_SUBSYSTEM = 8,
+
+ /**
+ * Maximum number of frames to be written to the socket at once. The
+ * library tries to send max_num_frames in a single call to SPDY_run
+ * for a single session. This means no requests can be received nor
+ * other sessions can send data as long the current one has enough
+ * frames to send and there is no error on writing. Thus, a big value
+ * will affect the performance. Small value gives fairnes for sessions.
+ * Must be followed by a positive integer (uin32_t). If not set, the
+ * default value 10 will be used.
+ */
+ SPDY_DAEMON_OPTION_MAX_NUM_FRAMES = 16
+};
+
+
+/**
+ * Flags for starting SPDY daemon. They are used to set some settings
+ * for the daemon, which do not require values.
+ */
+enum SPDY_DAEMON_FLAG
+{
+ /**
+ * No flags selected.
+ */
+ SPDY_DAEMON_FLAG_NO = 0,
+
+ /**
+ * The server will bind only on IPv6 addresses. If the flag is set and
+ * the daemon is provided with IPv4 address or IPv6 is not supported,
+ * starting daemon will fail.
+ */
+ SPDY_DAEMON_FLAG_ONLY_IPV6 = 1,
+
+ /**
+ * All sessions' sockets will be set with TCP_NODELAY if the flag is
+ * used. Option considered only by SPDY_IO_SUBSYSTEM_RAW.
+ */
+ SPDY_DAEMON_FLAG_NO_DELAY = 2
+};
+
+
+/**
+ * SPDY settings IDs sent by both client and server in SPDY SETTINGS frame.
+ * They affect the whole SPDY session. Defined in SPDY Protocol - Draft 3.
+ */
+enum SPDY_SETTINGS
+{
+
+ /**
+ * Allows the sender to send its expected upload bandwidth on this
+ * channel. This number is an estimate. The value should be the
+ * integral number of kilobytes per second that the sender predicts
+ * as an expected maximum upload channel capacity.
+ */
+ SPDY_SETTINGS_UPLOAD_BANDWIDTH = 1,
+
+ /**
+ * Allows the sender to send its expected download bandwidth on this
+ * channel. This number is an estimate. The value should be the
+ * integral number of kilobytes per second that the sender predicts as
+ * an expected maximum download channel capacity.
+ */
+ SPDY_SETTINGS_DOWNLOAD_BANDWIDTH = 2,
+
+ /**
+ * Allows the sender to send its expected round-trip-time on this
+ * channel. The round trip time is defined as the minimum amount of
+ * time to send a control frame from this client to the remote and
+ * receive a response. The value is represented in milliseconds.
+ */
+ SPDY_SETTINGS_ROUND_TRIP_TIME = 3,
+
+ /**
+ * Allows the sender to inform the remote endpoint the maximum number
+ * of concurrent streams which it will allow. By default there is no
+ * limit. For implementors it is recommended that this value be no
+ * smaller than 100.
+ */
+ SPDY_SETTINGS_MAX_CONCURRENT_STREAMS = 4,
+
+ /**
+ * Allows the sender to inform the remote endpoint of the current TCP
+ * CWND value.
+ */
+ SPDY_SETTINGS_CURRENT_CWND = 5,
+
+ /**
+ * Allows the sender to inform the remote endpoint the retransmission
+ * rate (bytes retransmitted / total bytes transmitted).
+ */
+ SPDY_SETTINGS_DOWNLOAD_RETRANS_RATE = 6,
+
+ /**
+ * Allows the sender to inform the remote endpoint the initial window
+ * size (in bytes) for new streams.
+ */
+ SPDY_SETTINGS_INITIAL_WINDOW_SIZE = 7,
+
+ /**
+ * Allows the server to inform the client if the new size of the
+ * client certificate vector.
+ */
+ SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8
+};
+
+
+/**
+ * Flags for each individual SPDY setting in the SPDY SETTINGS frame.
+ * They affect only one setting to which they are set.
+ * Defined in SPDY Protocol - Draft 3.
+ */
+enum SPDY_FLAG_SETTINGS
+{
+
+ /**
+ * When set, the sender of this SETTINGS frame is requesting that the
+ * recipient persist the ID/Value and return it in future SETTINGS
+ * frames sent from the sender to this recipient. Because persistence
+ * is only implemented on the client, this flag is only sent by the
+ * server.
+ */
+ SPDY_FLAG_SETTINGS_PERSIST_VALUE = 1,
+
+ /**
+ * When set, the sender is notifying the recipient that this ID/Value
+ * pair was previously sent to the sender by the recipient with the
+ * #SPDY_FLAG_SETTINGS_PERSIST_VALUE, and the sender is returning it.
+ * Because persistence is only implemented on the client, this flag is
+ * only sent by the client.
+ */
+ SPDY_FLAG_SETTINGS_PERSISTED = 2
+};
+
+
+/**
+ * Flag associated with a whole SPDY SETTINGS frame. Affect all the
+ * settings in the frame. Defined in SPDY Protocol - Draft 3.
+ */
+enum SPDY_FLAG_SETTINGS_FRAME
+{
+
+ /**
+ * When set, the client should clear any previously persisted SETTINGS
+ * ID/Value pairs. If this frame contains ID/Value pairs with the
+ * #SPDY_FLAG_SETTINGS_PERSIST_VALUE set, then the client will first
+ * clear its existing, persisted settings, and then persist the values
+ * with the flag set which are contained within this frame. Because
+ * persistence is only implemented on the client, this flag can only
+ * be used when the sender is the server.
+ */
+ SPDY_FLAG_SETTINGS_CLEAR_SETTINGS = 1
+};
+
+
+/**
+ * SPDY settings function options. Passed in the varargs portion of
+ * SPDY_SettingsReceivedCallback and SPDY_send_settings to customize
+ * more the settings handling. Each option must
+ * be followed by a value of a specific type.<p>
+ *
+ * The values are used internally as flags, that is why they must be
+ * powers of 2.
+ */
+enum SPDY_SETTINGS_OPTION
+{
+
+ /**
+ * No more options / last option. This is used
+ * to terminate the VARARGs list.
+ */
+ SPDY_SETTINGS_OPTION_END = 0
+};
+
+
+/**
+ * Used as a parameter for SPDY_ResponseResultCallback and shows if the
+ * response was actually written to the TLS socket or discarded by the
+ * lib for any reason (and respectively the reason).
+ */
+enum SPDY_RESPONSE_RESULT
+{
+
+ /**
+ * The lib has written the full response to the TLS socket.
+ */
+ SPDY_RESPONSE_RESULT_SUCCESS = 0,
+
+ /**
+ * The session is being closed, so the data is being discarded
+ */
+ SPDY_RESPONSE_RESULT_SESSION_CLOSED = 1,
+
+ /**
+ * The stream for this response has been closed. May happen when the
+ * sender had sent first SYN_STREAM and after that RST_STREAM.
+ */
+ SPDY_RESPONSE_RESULT_STREAM_CLOSED = 2
+};
+
+
+/**
+ * Callback for serious error condition. The default action is to print
+ * an error message and abort().
+ *
+ * @param cls user specified value
+ * @param file where the error occured
+ * @param line where the error occured
+ * @param reason error details message, may be NULL
+ */
+typedef void
+(*SPDY_PanicCallback) (void * cls,
+ const char *file,
+ unsigned int line,
+ const char *reason);
+
+
+/**
+ * Callback for new SPDY session established by a client. Called
+ * immediately after the TCP connection was established.
+ *
+ * @param cls client-defined closure
+ * @param session handler for the new SPDY session
+ */
+typedef void
+(*SPDY_NewSessionCallback) (void * cls,
+ struct SPDY_Session * session);
+
+
+/**
+ * Callback for closed session. Called after the TCP connection was
+ * closed. In this callback function the user has the last
+ * chance to access the SPDY_Session structure. After that the latter
+ * will be cleaned!
+ *
+ * @param cls client-defined closure
+ * @param session handler for the closed SPDY session
+ * @param by_client #SPDY_YES if the session close was initiated by the
+ * client;
+ * #SPDY_NO if closed by the server
+ */
+typedef void
+(*SPDY_SessionClosedCallback) (void *cls,
+ struct SPDY_Session *session,
+ int by_client);
+
+
+/**
+ * Iterator over name-value pairs.
+ *
+ * @param cls client-defined closure
+ * @param name of the pair
+ * @param value of the pair
+ * @return #SPDY_YES to continue iterating,
+ * #SPDY_NO to abort the iteration
+ */
+typedef int
+(*SPDY_NameValueIterator) (void *cls,
+ const char *name,
+ const char * const * value,
+ int num_values);
+
+
+/**
+ * Callback for received SPDY request. The functions is called whenever
+ * a reqest comes, but will also be called if more headers/trailers are
+ * received.
+ *
+ * @param cls client-defined closure
+ * @param request handler. The request object is required for
+ * sending responses.
+ * @param priority of the SPDY stream which the request was
+ * sent over
+ * @param method HTTP method
+ * @param path HTTP path
+ * @param version HTTP version just like in HTTP request/response:
+ * "HTTP/1.0" or "HTTP/1.1" currently
+ * @param host called host as in HTTP
+ * @param scheme used ("http" or "https"). In SPDY 3 it is only "https".
+ * @param headers other HTTP headers from the request
+ * @param more a flag saying if more data related to the request is
+ * expected to be received. HTTP body may arrive (e.g. POST data);
+ * then SPDY_NewDataCallback will be called for the connection.
+ * It is also possible that more headers/trailers arrive;
+ * then the same callback will be invoked. The user should detect
+ * that it is not the first invocation of the function for that
+ * request.
+ */
+typedef void
+(*SPDY_NewRequestCallback) (void *cls,
+ struct SPDY_Request *request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue *headers,
+ bool more);
+
+
+/**
+ * Callback for received new data chunk (HTTP body) from a given
+ * request (e.g. POST data).
+ *
+ * @param cls client-defined closure
+ * @param request handler
+ * @param buf data chunk from the POST data
+ * @param size the size of the data chunk 'buf' in bytes. Note that it
+ * may be 0.
+ * @param more false if this is the last chunk from the data. Note:
+ * true does not mean that more data will come, exceptional
+ * situation is possible
+ * @return #SPDY_YES to continue calling the function,
+ * #SPDY_NO to stop calling the function for this request
+ */
+typedef int
+(*SPDY_NewDataCallback) (void *cls,
+ struct SPDY_Request *request,
+ const void *buf,
+ size_t size,
+ bool more);
+// How about passing POST encoding information
+// here as well?
+//TODO
+
+
+/**
+ * Callback to be used with SPDY_build_response_with_callback. The
+ * callback will be called when the lib wants to write to the TLS socket.
+ * The application should provide the data to be sent.
+ *
+ * @param cls client-defined closure
+ * @param max maximum number of bytes that are allowed to be written
+ * to the buffer.
+ * @param more true if more data will be sent (i.e. the function must
+ * be calleed again),
+ * false if this is the last chunk, the lib will close
+ * the stream
+ * @return number of bytes written to buffer. On error the call MUST
+ * return value less than 0 to indicate the library.
+ */
+typedef ssize_t
+(*SPDY_ResponseCallback) (void *cls,
+ void *buffer,
+ size_t max,
+ bool *more);
+
+
+/**
+ * Callback to be called when the last bytes from the response was sent
+ * to the client or when the response was discarded from the lib. This
+ * callback is a very good place to discard the request and the response
+ * objects, if they will not be reused (e.g., sending the same response
+ * again). If the stream is closed it is safe to discard the request
+ * object.
+ *
+ * @param cls client-defined closure
+ * @param response handler to the response that was just sent
+ * @param request handler to the request for which the response was sent
+ * @param status shows if actually the response was sent or it was
+ * discarded by the lib for any reason (e.g., closing session,
+ * closing stream, stopping daemon, etc.). It is possible that
+ * status indicates an error but parts of the response headers
+ * and/or body (in one
+ * or several frames) were already sent to the client.
+ * @param streamopened indicates if the the stream for this request/
+ * response pair is still opened. If yes, the server may want
+ * to use SPDY push to send something additional to the client
+ * and/or close the stream.
+ */
+typedef void
+(*SPDY_ResponseResultCallback) (void * cls,
+ struct SPDY_Response *response,
+ struct SPDY_Request *request,
+ enum SPDY_RESPONSE_RESULT status,
+ bool streamopened);
+
+
+/**
+ * Callback to notify when SPDY ping response is received.
+ *
+ * @param session handler for which the ping request was sent
+ * @param rtt the timespan between sending ping request and receiving it
+ * from the library
+ */
+typedef void
+(*SPDY_PingCallback) (void * cls,
+ struct SPDY_Session *session,
+ struct timeval *rtt);
+
+
+/**
+ * Iterator over settings ID/Value/Flags tuples.
+ *
+ * @param cls client-defined closure
+ * @param id SPDY settings ID
+ * @param value value for this setting
+ * @param flags flags for this tuple; use
+ * `enum SPDY_FLAG_SETTINGS`
+ * @return #SPDY_YES to continue iterating,
+ * #SPDY_NO to abort the iteration
+ */
+typedef int
+(*SPDY_SettingsIterator) (void *cls,
+ enum SPDY_SETTINGS id,
+ int32_t value,
+ uint8_t flags);
+
+
+/**
+ * Callback to notify when SPDY SETTINGS are received from the client.
+ *
+ * @param session handler for which settings are received
+ * @param settings ID/value/flags tuples of the settings
+ * @param flags for the whole settings frame; use
+ * enum SPDY_FLAG_SETTINGS_FRAME
+ * @param ... list of options (type-value pairs,
+ * terminated with #SPDY_SETTINGS_OPTION_END).
+ */
+typedef void
+(*SPDY_SettingsReceivedCallback) (struct SPDY_Session *session,
+ struct SPDY_Settings *settings,
+ uint8_t flags,
+ ...);
+
+
+/* Global functions for the library */
+
+
+/**
+ * Init function for the whole library. It MUST be called before any
+ * other function of the library to initialize things like TLS context
+ * and possibly other stuff needed by the lib. Currently the call
+ * always returns #SPDY_YES.
+ *
+ * @param io_subsystem the IO subsystem that will
+ * be initialized. Several can be used with bitwise OR. If no
+ * parameter is set, the default openssl subsystem will be used.
+ * @return #SPDY_YES if the library was correctly initialized and its
+ * functions can be used now;
+ * #SPDY_NO on error
+ */
+_MHD_EXTERN int
+(SPDY_init) (enum SPDY_IO_SUBSYSTEM io_subsystem, ...);
+#define SPDY_init() SPDY_init (SPDY_IO_SUBSYSTEM_OPENSSL)
+
+
+/**
+ * Deinit function for the whole lib. It can be called after finishing
+ * using the library. It frees and cleans up resources allocated in
+ * SPDY_init. Currently the function does not do anything.
+ */
+_MHD_EXTERN void
+SPDY_deinit (void);
+
+
+/**
+ * Sets the global error handler to a different implementation. "cb"
+ * will only be called in the case of typically fatal, serious
+ * internal consistency issues. These issues should only arise in the
+ * case of serious memory corruption or similar problems with the
+ * architecture as well as failed assertions. While "cb" is allowed to
+ * return and the lib will then try to continue, this is never safe.
+ *
+ * The default implementation that is used if no panic function is set
+ * simply prints an error message and calls "abort". Alternative
+ * implementations might call "exit" or other similar functions.
+ *
+ * @param cb new error handler
+ * @param cls passed to error handler
+ */
+_MHD_EXTERN void
+SPDY_set_panic_func (SPDY_PanicCallback cb,
+ void *cls);
+
+
+/* Daemon functions */
+
+
+/**
+ * Start a SPDY webserver on the given port.
+ *
+ * @param port to bind to. The value is ignored if address structure
+ * is passed as daemon option
+ * @param certfile path to the certificate that will be used by server
+ * @param keyfile path to the keyfile for the certificate
+ * @param nscb callback called when a new SPDY session is
+ * established by a client
+ * @param sccb callback called when a session is closed
+ * @param nrcb callback called when a client sends request
+ * @param npdcb callback called when HTTP body (POST data) is received
+ * after request
+ * @param cls common extra argument to all of the callbacks
+ * @param ... list of options (type-value pairs,
+ * terminated with #SPDY_DAEMON_OPTION_END).
+ * @return NULL on error, handle to daemon on success
+ */
+_MHD_EXTERN struct SPDY_Daemon *
+SPDY_start_daemon (uint16_t port,
+ const char *certfile,
+ const char *keyfile,
+ SPDY_NewSessionCallback nscb,
+ SPDY_SessionClosedCallback sccb,
+ SPDY_NewRequestCallback nrcb,
+ SPDY_NewDataCallback npdcb,
+ void *cls,
+ ...);
+
+
+/**
+ * Shutdown the daemon. First all sessions are closed. It is NOT safe
+ * to call this function in user callbacks.
+ *
+ * @param daemon to stop
+ */
+_MHD_EXTERN void
+SPDY_stop_daemon (struct SPDY_Daemon *daemon);
+
+
+/**
+ * Obtain the select sets for this daemon. Only those are retrieved,
+ * which some processing should be done for, i.e. not all sockets are
+ * added to write_fd_set.<p>
+ *
+ * It is possible that there is
+ * nothing to be read from a socket but there is data either in the
+ * TLS subsystem's read buffers or in libmicrospdy's read buffers, which
+ * waits for being processed. In such case the file descriptor will be
+ * added to write_fd_set. Since it is very likely for the socket to be
+ * ready for writing, the select used in the application's event loop
+ * will return with success, SPDY_run will be called, the data will be
+ * processed and maybe something will be written to the socket. Without
+ * this behaviour, considering a proper event loop, data may stay in the
+ * buffers, but run is never called.
+ *
+ * @param daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @return largest FD added to any of the sets
+ */
+_MHD_EXTERN int
+SPDY_get_fdset (struct SPDY_Daemon *daemon,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set);
+
+
+/**
+ * Obtain timeout value for select for this daemon. The returned value
+ * is how long select
+ * should at most block, not the timeout value set for connections.
+ *
+ * @param daemon to query for timeout
+ * @param timeout will be set to the timeout value (in milliseconds)
+ * @return #SPDY_YES on success
+ * #SPDY_NO if no connections exist that
+ * would necessiate the use of a timeout right now
+ */
+_MHD_EXTERN int
+SPDY_get_timeout (struct SPDY_Daemon *daemon,
+ unsigned long long *timeout);
+
+
+/**
+ * Run webserver operations. This method must be called in
+ * the client event loop.
+ *
+ * @param daemon to run
+ */
+_MHD_EXTERN void
+SPDY_run (struct SPDY_Daemon *daemon);
+
+
+/* SPDY Session handling functions */
+
+
+/**
+ * Closes a SPDY session. SPDY clients and servers are expected to keep
+ * sessions opened as long as possible. However, the server may want to
+ * close some connections, e.g. if there are too many, to free some
+ * resources. The function can also be used to close a specific session
+ * if the client is not desired.
+ *
+ * @param session handler to be closed
+ */
+_MHD_EXTERN void
+SPDY_close_session (struct SPDY_Session * session);
+
+
+/**
+ * Associate a void pointer with a session. The data accessible by the
+ * pointer can later be used wherever the session handler is available.
+ *
+ * @param session handler
+ * @param cls any data pointed by a pointer to be accessible later
+ */
+_MHD_EXTERN void
+SPDY_set_cls_to_session (struct SPDY_Session *session,
+ void *cls);
+
+
+/**
+ * Retrieves the pointer associated with SPDY_set_cls_to_session().
+ *
+ * @param session handler to get its cls
+ * @return same pointer added by SPDY_set_cls_to_session() or
+ * NULL when nothing was associated
+ */
+_MHD_EXTERN void *
+SPDY_get_cls_from_session (struct SPDY_Session *session);
+
+
+/**
+ * Retrieves the remote address of a given session.
+ *
+ * @param session handler to get its remote address
+ * @param addr out parameter; pointing to remote address
+ * @return length of the address structure
+ */
+_MHD_EXTERN socklen_t
+SPDY_get_remote_addr (struct SPDY_Session *session,
+ struct sockaddr **addr);
+
+
+/* SPDY name/value data structure handling functions */
+
+
+/**
+ * Create a new NameValue structure. It is needed for putting inside the
+ * HTTP headers and their values for a response. The user should later
+ * destroy alone the structure.
+ *
+ * @return handler to the new empty structure or NULL on error
+ */
+_MHD_EXTERN struct SPDY_NameValue *
+SPDY_name_value_create (void);
+
+
+/**
+ * Add name/value pair to a NameValue structure. SPDY_NO will be returned
+ * if the name/value pair is already in the structure. It is legal to
+ * add different values for the same name.
+ *
+ * @param container structure to which the new pair is added
+ * @param name for the value. Null-terminated string.
+ * @param value the value itself. Null-terminated string.
+ * @return #SPDY_NO on error or #SPDY_YES on success
+ */
+_MHD_EXTERN int
+SPDY_name_value_add (struct SPDY_NameValue *container,
+ const char *name,
+ const char *value);
+
+
+/**
+ * Lookup value for a name in a name/value structure.
+ *
+ * @param container structure in which to lookup
+ * @param name the name to look for
+ * @param num_values length of the returned array with values
+ * @return NULL if no such item was found, or an array containing the
+ * values
+ */
+_MHD_EXTERN const char * const *
+SPDY_name_value_lookup (struct SPDY_NameValue *container,
+ const char *name,
+ int *num_values);
+
+
+/**
+ * Iterate over name/value structure.
+ *
+ * @param container structure which to iterate over
+ * @param iterator callback to call on each name/value pair;
+ * maybe NULL (then just count headers)
+ * @param iterator_cls extra argument to @a iterator
+ * @return number of entries iterated over
+ */
+_MHD_EXTERN int
+SPDY_name_value_iterate (struct SPDY_NameValue *container,
+ SPDY_NameValueIterator iterator,
+ void *iterator_cls);
+
+
+/**
+ * Destroy a NameValue structure. Use this function to destroy only
+ * objects which, after passed to, will not be destroied by other
+ * functions.
+ *
+ */
+_MHD_EXTERN void
+SPDY_name_value_destroy (struct SPDY_NameValue *container);
+
+
+/* SPDY request handling functions */
+
+
+/**
+ * Gets the session responsible for the given
+ * request.
+ *
+ * @param request for which the session is wanted
+ * @return session handler for the request
+ */
+_MHD_EXTERN struct SPDY_Session *
+SPDY_get_session_for_request (const struct SPDY_Request *request);
+
+
+/**
+ * Associate a void pointer with a request. The data accessible by the
+ * pointer can later be used wherever the request handler is available.
+ *
+ * @param request with which to associate a pointer
+ * @param cls any data pointed by a pointer to be accessible later
+ */
+_MHD_EXTERN void
+SPDY_set_cls_to_request (struct SPDY_Request *request,
+ void *cls);
+
+
+/**
+ * Retrieves the pointer associated with the request by
+ * SPDY_set_cls_to_request().
+ *
+ * @param request to get its cls
+ * @return same pointer added by SPDY_set_cls_to_request() or
+ * NULL when nothing was associated
+ */
+_MHD_EXTERN void *
+SPDY_get_cls_from_request (struct SPDY_Request *request);
+
+
+/* SPDY response handling functions */
+
+
+/**
+ * Create response object containing all needed headers and data. The
+ * response object is not bound to a request, so it can be used multiple
+ * times with SPDY_queue_response() and schould be
+ * destroied by calling the SPDY_destroy_response().<p>
+ *
+ * Currently the library does not provide compression of the body data.
+ * It is up to the user to pass already compressed data and the
+ * appropriate headers to this function when desired.
+ *
+ * @param status HTTP status code for the response (e.g. 404)
+ * @param statustext HTTP status message for the response, which will
+ * be appended to the status code (e.g. "OK"). Can be NULL
+ * @param version HTTP version for the response (e.g. "http/1.1")
+ * @param headers name/value structure containing additional HTTP headers.
+ * Can be NULL. Can be used multiple times, it is up to
+ * the user to destoy the object when not needed anymore.
+ * @param data the body of the response. The lib will make a copy of it,
+ * so it is up to the user to take care of the memory
+ * pointed by data
+ * @param size length of @a data. It can be 0, then the lib will send only
+ * headers
+ * @return NULL on error, handle to response object on success
+ */
+_MHD_EXTERN struct SPDY_Response *
+SPDY_build_response (int status,
+ const char *statustext,
+ const char *version,
+ struct SPDY_NameValue *headers,
+ const void *data,
+ size_t size);
+
+
+/**
+ * Create response object containing all needed headers. The data will
+ * be provided later when the lib calls the callback function (just
+ * before writing it to the TLS socket). The
+ * response object is not bound to a request, so it can be used multiple
+ * times with SPDY_queue_response() and schould be
+ * destroied by calling the SPDY_destroy_response().<p>
+ *
+ * Currently the library does not provide compression of the body data.
+ * It is up to the user to pass already compressed data and the
+ * appropriate headers to this function and the callback when desired.
+ *
+ * @param status HTTP status code for the response (e.g. 404)
+ * @param statustext HTTP status message for the response, which will
+ * be appended to the status code (e.g. "OK"). Can be NULL
+ * @param version HTTP version for the response (e.g. "http/1.1")
+ * @param headers name/value structure containing additional HTTP headers.
+ * Can be NULL. Can be used multiple times, it is up to
+ * the user to destoy the object when not needed anymore.
+ * @param rcb callback to use to obtain response data
+ * @param rcb_cls extra argument to @a rcb
+ * @param block_size preferred block size for querying rcb (advisory only,
+ * the lib will call rcb specifying the block size); clients
+ * should pick a value that is appropriate for IO and
+ * memory performance requirements. The function will
+ * fail if the value is bigger than the maximum
+ * supported value (SPDY_MAX_SUPPORTED_FRAME_SIZE).
+ * Can be 0, then the lib will use
+ * #SPDY_MAX_SUPPORTED_FRAME_SIZE instead.
+ * @return NULL on error, handle to response object on success
+ */
+_MHD_EXTERN struct SPDY_Response *
+SPDY_build_response_with_callback(int status,
+ const char *statustext,
+ const char *version,
+ struct SPDY_NameValue *headers,
+ SPDY_ResponseCallback rcb,
+ void *rcb_cls,
+ uint32_t block_size);
+
+
+/**
+ * Queue response object to be sent to the client. A successfully queued
+ * response may never be sent, e.g. when the stream gets closed. The
+ * data will be added to the output queue. The call will fail, if the
+ * output for this session
+ * is closed (i.e. the session is closed, half or full) or the output
+ * channel for the stream, on which the request was received, is closed
+ * (i.e. the stream is closed, half or full).
+ *
+ * @param request object identifying the request to which the
+ * response is returned
+ * @param response object containg headers and data to be sent
+ * @param closestream TRUE if the server does NOT intend to PUSH
+ * something more associated to this request/response later,
+ * FALSE otherwise
+ * @param consider_priority if FALSE, the response will be added to the
+ * end of the queue. If TRUE, the response will be added after
+ * the last previously added response with priority of the
+ * request grater or equal to that of the current one. This
+ * means that the function should be called with TRUE each time
+ * if one wants to be sure that the output queue behaves like
+ * a priority queue
+ * @param rrcb callback called when all the data was sent (last frame
+ * from response) or when that frame was discarded (e.g. the
+ * stream has been closed meanwhile)
+ * @param rrcb_cls extra argument to @a rrcb
+ * @return #SPDY_NO on error or #SPDY_YES on success
+ */
+_MHD_EXTERN int
+SPDY_queue_response (struct SPDY_Request *request,
+ struct SPDY_Response *response,
+ bool closestream,
+ bool consider_priority,
+ SPDY_ResponseResultCallback rrcb,
+ void *rrcb_cls);
+
+
+/**
+ * Destroy a response structure. It should be called for all objects
+ * returned by SPDY_build_response*() functions to free the memory
+ * associated with the prepared response. It is safe to call this
+ * function not before being sure that the response will not be used by
+ * the lib anymore, this means after SPDY_ResponseResultCallback
+ * callbacks were called for all calls to SPDY_queue_response() passing
+ * this response.
+ *
+ * @param response to destroy
+ */
+_MHD_EXTERN void
+SPDY_destroy_response (struct SPDY_Response *response);
+
+
+/* SPDY settings ID/value data structure handling functions */
+
+
+/**
+ * Create a new SettingsIDValue structure. It is needed for putting
+ * inside tuples of SPDY option, flags and value for sending to the
+ * client.
+ *
+ * @return hendler to the new empty structure or NULL on error
+ */
+_MHD_EXTERN const struct SPDY_Settings *
+SPDY_settings_create (void);
+
+
+/**
+ * Add or update a tuple to a SettingsIDValue structure.
+ *
+ * @param container structure to which the new tuple is added
+ * @param id SPDY settings ID that will be sent. If this ID already in
+ * container, the tupple for it will be updated (value and/or
+ * flags). If it is not in the container, a new tupple will be
+ * added.
+ * @param flags SPDY settings flags applied only to this setting
+ * @param value of the setting
+ * @return #SPDY_NO on error
+ * or #SPDY_YES if a new setting was added
+ */
+_MHD_EXTERN int
+SPDY_settings_add (struct SPDY_Settings *container,
+ enum SPDY_SETTINGS id,
+ enum SPDY_FLAG_SETTINGS flags,
+ int32_t value);
+
+
+/**
+ * Lookup value and flags for an ID in a settings ID/value structure.
+ *
+ * @param container structure in which to lookup
+ * @param id SPDY settings ID to search for
+ * @param flags out param for SPDY settings flags for this setting;
+ * check it against the flags in enum SPDY_FLAG_SETTINGS
+ * @param value out param for the value of this setting
+ * @return #SPDY_NO if the setting is not into the structure
+ * or #SPDY_YES if it is into it
+ */
+_MHD_EXTERN int
+SPDY_settings_lookup (const struct SPDY_Settings *container,
+ enum SPDY_SETTINGS id,
+ enum SPDY_FLAG_SETTINGS *flags,
+ int32_t *value);
+
+
+/**
+ * Iterate over settings ID/value structure.
+ *
+ * @param container structure which to iterate over
+ * @param iterator callback to call on each ID/value pair;
+ * maybe NULL (then just count number of settings)
+ * @param iterator_cls extra argument to iterator
+ * @return number of entries iterated over
+ */
+_MHD_EXTERN int
+SPDY_settings_iterate (const struct SPDY_Settings *container,
+ SPDY_SettingsIterator iterator,
+ void *iterator_cls);
+
+
+/**
+ * Destroy a settings ID/value structure. Use this function to destroy
+ * only objects which, after passed to, will not be destroied by other
+ * functions.
+ *
+ * @param container structure which to detroy
+ */
+_MHD_EXTERN void
+SPDY_settings_destroy (struct SPDY_Settings * container);
+
+
+/* SPDY SETTINGS handling functions */
+
+
+/**
+ * Send SPDY SETTINGS to the client. The call will return fail if there
+ * in invald setting into the settings container (e.g. invalid setting
+ * ID).
+ *
+ * @param session SPDY_Session handler for which settings are being sent
+ * @param settings ID/value pairs of the settings to be sent.
+ * Can be used multiple times, it is up to the user to destoy
+ * the object when not needed anymore.
+ * @param flags for the whole settings frame. They are valid for all tuples
+ * @param ... list of options (type-value pairs,
+ * terminated with #SPDY_SETTINGS_OPTION_END).
+ * @return SPDY_NO on error or SPDY_YES on
+ * success
+ */
+_MHD_EXTERN int
+SPDY_send_settings (struct SPDY_Session *session,
+ struct SPDY_Settings *settings,
+ enum SPDY_FLAG_SETTINGS_FRAME flags,
+ ...);
+
+
+/* SPDY misc functions */
+
+
+/**
+ * Destroy a request structure. It should be called for all objects
+ * received as a parameter in SPDY_NewRequestCallback to free the memory
+ * associated with the request. It is safe to call this
+ * function not before being sure that the request will not be used by
+ * the lib anymore, this means after the stream, on which this request
+ * had been sent, was closed and all SPDY_ResponseResultCallback
+ * callbacks were called for all calls to SPDY_queue_response() passing
+ * this request object.
+ *
+ * @param request to destroy
+ */
+_MHD_EXTERN void
+SPDY_destroy_request (struct SPDY_Request * request);
+
+
+/**
+ * Send SPDY ping to the client
+ *
+ * @param session handler for which the ping request is sent
+ * @param rttcb callback called when ping response to the request is
+ * received
+ * @param rttcb_cls extra argument to @a rttcb
+ * @return #SPDY_NO on error or #SPDY_YES on success
+ */
+_MHD_EXTERN int
+SPDY_send_ping (struct SPDY_Session *session,
+ SPDY_PingCallback rttcb,
+ void *rttcb_cls);
+
+#endif
diff --git a/src/include/platform.h b/src/include/platform.h
new file mode 100644
index 0000000..22ddddd
--- /dev/null
+++ b/src/include/platform.h
@@ -0,0 +1,205 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2008 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file platform.h
+ * @brief platform-specific includes for libmicrohttpd
+ * @author Christian Grothoff
+ *
+ * This file is included by the libmicrohttpd code
+ * before "microhttpd.h"; it provides the required
+ * standard headers (which are platform-specific).<p>
+ *
+ * Note that this file depends on our configure.ac
+ * build process and the generated config.h file.
+ * Hence you cannot include it directly in applications
+ * that use libmicrohttpd.
+ */
+#ifndef MHD_PLATFORM_H
+#define MHD_PLATFORM_H
+
+#include "MHD_config.h"
+
+#ifndef BUILDING_MHD_LIB
+#ifdef _MHD_EXTERN
+#undef _MHD_EXTERN
+#endif /* _MHD_EXTERN */
+#if defined(_WIN32) && defined(MHD_W32LIB)
+#define _MHD_EXTERN extern
+#elif defined (_WIN32) && defined(MHD_W32DLL)
+#define _MHD_EXTERN __declspec(dllimport)
+#else
+#define _MHD_EXTERN extern
+#endif
+#elif !defined(_MHD_EXTERN) /* && BUILDING_MHD_LIB */
+#if defined(_WIN32) && defined(MHD_W32LIB)
+#define _MHD_EXTERN extern
+#elif defined (_WIN32) && defined(MHD_W32DLL)
+#define _MHD_EXTERN extern __declspec(dllexport)
+#else
+#define _MHD_EXTERN extern
+#endif
+#endif /* BUILDING_MHD_LIB */
+
+#define _XOPEN_SOURCE_EXTENDED 1
+#if OS390
+#define _OPEN_THREADS
+#define _OPEN_SYS_SOCK_IPV6
+#define _OPEN_MSGQ_EXT
+#define _LP64
+#endif
+
+#if defined(_WIN32)
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501
+#else // _WIN32_WINNT
+#if _WIN32_WINNT < 0x0501
+#error "Headers for Windows XP or later are required"
+#endif // _WIN32_WINNT < 0x0501
+#endif // _WIN32_WINNT
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#endif // _WIN32
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdarg.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stddef.h>
+#ifdef MHD_USE_POSIX_THREADS
+#undef HAVE_CONFIG_H
+#include <pthread.h>
+#define HAVE_CONFIG_H 1
+#endif // MHD_USE_POSIX_THREADS
+
+/* different OSes have fd_set in
+ a broad range of header files;
+ we just include most of them (if they
+ are available) */
+
+
+#ifdef OS_VXWORKS
+#include <sockLib.h>
+#include <netinet/in.h>
+#include <stdarg.h>
+#include <sys/mman.h>
+#define RESTRICT __restrict__
+#endif
+#if HAVE_MEMORY_H
+#include <memory.h>
+#endif
+
+#if HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_MSG_H
+#include <sys/msg.h>
+#endif
+#if HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+#if HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#if HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if HAVE_TIME_H
+#include <time.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#if HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include <ws2tcpip.h>
+#define sleep(seconds) (SleepEx((seconds)*1000, 1)/1000)
+#define usleep(useconds) (void)SleepEx((useconds)/1000, 1)
+#endif
+
+#if !defined(SHUT_WR) && defined(SD_SEND)
+#define SHUT_WR SD_SEND
+#endif
+#if !defined(SHUT_RD) && defined(SD_RECEIVE)
+#define SHUT_RD SD_RECEIVE
+#endif
+#if !defined(SHUT_RDWR) && defined(SD_BOTH)
+#define SHUT_RDWR SD_BOTH
+#endif
+
+#if defined(_MSC_FULL_VER) && !defined (_SSIZE_T_DEFINED)
+#define _SSIZE_T_DEFINED
+typedef intptr_t ssize_t;
+#endif // !_SSIZE_T_DEFINED */
+#ifndef MHD_SOCKET_DEFINED
+/**
+ * MHD_socket is type for socket FDs
+ */
+#if !defined(_WIN32) || defined(_SYS_TYPES_FD_SET)
+#define MHD_POSIX_SOCKETS 1
+typedef int MHD_socket;
+#define MHD_INVALID_SOCKET (-1)
+#else /* !defined(_WIN32) || defined(_SYS_TYPES_FD_SET) */
+#define MHD_WINSOCK_SOCKETS 1
+#include <winsock2.h>
+typedef SOCKET MHD_socket;
+#define MHD_INVALID_SOCKET (INVALID_SOCKET)
+#endif /* !defined(_WIN32) || defined(_SYS_TYPES_FD_SET) */
+#define MHD_SOCKET_DEFINED 1
+#endif /* MHD_SOCKET_DEFINED */
+
+/* Force don't use pipes on W32 */
+#if defined(_WIN32) && !defined(MHD_DONT_USE_PIPES)
+#define MHD_DONT_USE_PIPES 1
+#endif /* defined(_WIN32) && !defined(MHD_DONT_USE_PIPES) */
+
+/* MHD_pipe is type for pipe FDs*/
+#ifndef MHD_DONT_USE_PIPES
+typedef int MHD_pipe;
+#else /* ! MHD_DONT_USE_PIPES */
+typedef MHD_socket MHD_pipe;
+#endif /* ! MHD_DONT_USE_PIPES */
+
+#if !defined(IPPROTO_IPV6) && defined(_MSC_FULL_VER) && _WIN32_WINNT >= 0x0501
+/* VC use IPPROTO_IPV6 as part of enum */
+#define IPPROTO_IPV6 IPPROTO_IPV6
+#endif
+
+#endif
diff --git a/src/include/platform_interface.h b/src/include/platform_interface.h
new file mode 100644
index 0000000..9e63f7a
--- /dev/null
+++ b/src/include/platform_interface.h
@@ -0,0 +1,344 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2014 Karlson2k (Evgeny Grin)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library.
+ If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file include/platform_interface.h
+ * @brief internal platform abstraction functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_PLATFORM_INTERFACE_H
+#define MHD_PLATFORM_INTERFACE_H
+
+#include "platform.h"
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include "w32functions.h"
+#endif
+
+/* *****************************
+ General function mapping
+ *****************************/
+#if !defined(_WIN32) || defined(__CYGWIN__)
+/**
+ * Check two strings case-insensitive equality
+ * @param a first string to check
+ * @param b second string to check
+ * @return boolean true if strings are equal, boolean false if strings are unequal
+ */
+#define MHD_str_equal_caseless_(a,b) (0==strcasecmp((a),(b)))
+#else
+/**
+ * Check two strings case-insensitive equality
+ * @param a first string to check
+ * @param b second string to check
+ * @return boolean true if strings are equal, boolean false if strings are unequal
+ */
+#define MHD_str_equal_caseless_(a,b) (0==_stricmp((a),(b)))
+#endif
+
+#if !defined(_WIN32) || defined(__CYGWIN__)
+/**
+ * Check not more than n chars in two strings case-insensitive equality
+ * @param a first string to check
+ * @param b second string to check
+ * @param n maximum number of chars to check
+ * @return boolean true if strings are equal, boolean false if strings are unequal
+ */
+#define MHD_str_equal_caseless_n_(a,b,n) (0==strncasecmp((a),(b),(n)))
+#else
+/**
+ * Check not more than n chars in two strings case-insensitive equality
+ * @param a first string to check
+ * @param b second string to check
+ * @param n maximum number of chars to check
+ * @return boolean true if strings are equal, boolean false if strings are unequal
+ */
+#define MHD_str_equal_caseless_n_(a,b,n) (0==_strnicmp((a),(b),(n)))
+#endif
+
+/* Platform-independent snprintf name */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define MHD_snprintf_ snprintf
+#else
+#define MHD_snprintf_ W32_snprintf
+#endif
+
+
+
+/* MHD_socket_close_(fd) close any FDs (non-W32) / close only socket FDs (W32) */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define MHD_socket_close_(fd) close((fd))
+#else
+#define MHD_socket_close_(fd) closesocket((fd))
+#endif
+
+/* MHD_socket_errno_ is errno of last function (non-W32) / errno of last socket function (W32) */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define MHD_socket_errno_ errno
+#else
+#define MHD_socket_errno_ MHD_W32_errno_from_winsock_()
+#endif
+
+/* MHD_socket_last_strerr_ is description string of last errno (non-W32) /
+ * description string of last socket error (W32) */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define MHD_socket_last_strerr_() strerror(errno)
+#else
+#define MHD_socket_last_strerr_() MHD_W32_strerror_last_winsock_()
+#endif
+
+/* MHD_strerror_ is strerror (both non-W32/W32) */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define MHD_strerror_(errnum) strerror((errnum))
+#else
+#define MHD_strerror_(errnum) MHD_W32_strerror_((errnum))
+#endif
+
+/* MHD_set_socket_errno_ set errno to errnum (non-W32) / set socket last error to errnum (W32) */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define MHD_set_socket_errno_(errnum) errno=(errnum)
+#else
+#define MHD_set_socket_errno_(errnum) MHD_W32_set_last_winsock_error_((errnum))
+#endif
+
+/* MHD_SYS_select_ is wrapper macro for system select() function */
+#if !defined(MHD_WINSOCK_SOCKETS)
+#define MHD_SYS_select_(n,r,w,e,t) select((n),(r),(w),(e),(t))
+#else
+#define MHD_SYS_select_(n,r,w,e,t) select((int)0,(r),(w),(e),(t))
+#endif
+
+#if defined(HAVE_POLL)
+/* MHD_sys_poll_ is wrapper macro for system poll() function */
+#if !defined(MHD_WINSOCK_SOCKETS)
+#define MHD_sys_poll_ poll
+#else /* MHD_WINSOCK_SOCKETS */
+#define MHD_sys_poll_ WSAPoll
+#endif /* MHD_WINSOCK_SOCKETS */
+#endif /* HAVE_POLL */
+
+/* MHD_pipe_ create pipe (!MHD_DONT_USE_PIPES) /
+ * create two connected sockets (MHD_DONT_USE_PIPES) */
+#ifndef MHD_DONT_USE_PIPES
+#define MHD_pipe_(fdarr) pipe((fdarr))
+#else /* MHD_DONT_USE_PIPES */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define MHD_pipe_(fdarr) socketpair(AF_LOCAL, SOCK_STREAM, 0, (fdarr))
+#else /* !defined(_WIN32) || defined(__CYGWIN__) */
+#define MHD_pipe_(fdarr) MHD_W32_pair_of_sockets_((fdarr))
+#endif /* !defined(_WIN32) || defined(__CYGWIN__) */
+#endif /* MHD_DONT_USE_PIPES */
+
+/* MHD_pipe_errno_ is errno of last function (!MHD_DONT_USE_PIPES) /
+ * errno of last emulated pipe function (MHD_DONT_USE_PIPES) */
+#ifndef MHD_DONT_USE_PIPES
+#define MHD_pipe_errno_ errno
+#else
+#define MHD_pipe_errno_ MHD_socket_errno_
+#endif
+
+/* MHD_pipe_last_strerror_ is description string of last errno (!MHD_DONT_USE_PIPES) /
+ * description string of last pipe error (MHD_DONT_USE_PIPES) */
+#ifndef MHD_DONT_USE_PIPES
+#define MHD_pipe_last_strerror_() strerror(errno)
+#else
+#define MHD_pipe_last_strerror_() MHD_socket_last_strerr_()
+#endif
+
+/* MHD_pipe_write_ write data to real pipe (!MHD_DONT_USE_PIPES) /
+ * write data to emulated pipe (MHD_DONT_USE_PIPES) */
+#ifndef MHD_DONT_USE_PIPES
+#define MHD_pipe_write_(fd, ptr, sz) write((fd), (const void*)(ptr), (sz))
+#else
+#define MHD_pipe_write_(fd, ptr, sz) send((fd), (const char*)(ptr), (sz), 0)
+#endif
+
+/* MHD_pipe_read_ read data from real pipe (!MHD_DONT_USE_PIPES) /
+ * read data from emulated pipe (MHD_DONT_USE_PIPES) */
+#ifndef MHD_DONT_USE_PIPES
+#define MHD_pipe_read_(fd, ptr, sz) read((fd), (void*)(ptr), (sz))
+#else
+#define MHD_pipe_read_(fd, ptr, sz) recv((fd), (char*)(ptr), (sz), 0)
+#endif
+
+/* MHD_pipe_close_(fd) close any FDs (non-W32) /
+ * close emulated pipe FDs (W32) */
+#ifndef MHD_DONT_USE_PIPES
+#define MHD_pipe_close_(fd) close((fd))
+#else
+#define MHD_pipe_close_(fd) MHD_socket_close_((fd))
+#endif
+
+/* MHD_INVALID_PIPE_ is a value of bad pipe FD */
+#ifndef MHD_DONT_USE_PIPES
+#define MHD_INVALID_PIPE_ (-1)
+#else
+#define MHD_INVALID_PIPE_ MHD_INVALID_SOCKET
+#endif
+
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define MHD_random_() random()
+#else
+#define MHD_random_() MHD_W32_random_()
+#endif
+
+#if defined(MHD_USE_POSIX_THREADS)
+typedef pthread_t MHD_thread_handle_;
+#elif defined(MHD_USE_W32_THREADS)
+#include <windows.h>
+typedef HANDLE MHD_thread_handle_;
+#else
+#error "No threading API is available."
+#endif
+
+#if defined(MHD_USE_POSIX_THREADS)
+#define MHD_THRD_RTRN_TYPE_ void*
+#define MHD_THRD_CALL_SPEC_
+#elif defined(MHD_USE_W32_THREADS)
+#define MHD_THRD_RTRN_TYPE_ unsigned
+#define MHD_THRD_CALL_SPEC_ __stdcall
+#endif
+
+#if defined(MHD_USE_POSIX_THREADS)
+/**
+ * Wait until specified thread is ended
+ * @param thread ID to watch
+ * @return zero on success, nonzero on failure
+ */
+#define MHD_join_thread_(thread) pthread_join((thread), NULL)
+#elif defined(MHD_USE_W32_THREADS)
+/**
+ * Wait until specified thread is ended
+ * Close thread handle on success
+ * @param thread handle to watch
+ * @return zero on success, nonzero on failure
+ */
+#define MHD_join_thread_(thread) (WAIT_OBJECT_0 == WaitForSingleObject((thread), INFINITE) ? (CloseHandle((thread)), 0) : 1 )
+#endif
+
+#if defined(MHD_USE_W32_THREADS)
+#define MHD_W32_MUTEX_ 1
+#include <windows.h>
+typedef CRITICAL_SECTION MHD_mutex_;
+#elif defined(HAVE_PTHREAD_H) && defined(MHD_USE_POSIX_THREADS)
+#define MHD_PTHREAD_MUTEX_ 1
+typedef pthread_mutex_t MHD_mutex_;
+#else
+#error "No base mutex API is available."
+#endif
+
+#if defined(MHD_PTHREAD_MUTEX_)
+/**
+ * Create new mutex.
+ * @param mutex pointer to the mutex
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+#define MHD_mutex_create_(mutex) \
+ ((0 == pthread_mutex_init ((mutex), NULL)) ? MHD_YES : MHD_NO)
+#elif defined(MHD_W32_MUTEX_)
+/**
+ * Create new mutex.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+#define MHD_mutex_create_(mutex) \
+ ((NULL != (mutex) && 0 != InitializeCriticalSectionAndSpinCount((mutex),2000)) ? MHD_YES : MHD_NO)
+#endif
+
+#if defined(MHD_PTHREAD_MUTEX_)
+/**
+ * Destroy previously created mutex.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+#define MHD_mutex_destroy_(mutex) \
+ ((0 == pthread_mutex_destroy ((mutex))) ? MHD_YES : MHD_NO)
+#elif defined(MHD_W32_MUTEX_)
+/**
+ * Destroy previously created mutex.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+#define MHD_mutex_destroy_(mutex) \
+ ((NULL != (mutex)) ? (DeleteCriticalSection(mutex), MHD_YES) : MHD_NO)
+#endif
+
+#if defined(MHD_PTHREAD_MUTEX_)
+/**
+ * Acquire lock on previously created mutex.
+ * If mutex was already locked by other thread, function
+ * blocks until mutex becomes available.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+#define MHD_mutex_lock_(mutex) \
+ ((0 == pthread_mutex_lock((mutex))) ? MHD_YES : MHD_NO)
+#elif defined(MHD_W32_MUTEX_)
+/**
+ * Acquire lock on previously created mutex.
+ * If mutex was already locked by other thread, function
+ * blocks until mutex becomes available.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+#define MHD_mutex_lock_(mutex) \
+ ((NULL != (mutex)) ? (EnterCriticalSection((mutex)), MHD_YES) : MHD_NO)
+#endif
+
+#if defined(MHD_PTHREAD_MUTEX_)
+/**
+ * Try to acquire lock on previously created mutex.
+ * Function returns immediately.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES if mutex is locked, #MHD_NO if
+ * mutex was not locked.
+ */
+#define MHD_mutex_trylock_(mutex) \
+ ((0 == pthread_mutex_trylock((mutex))) ? MHD_YES : MHD_NO)
+#elif defined(MHD_W32_MUTEX_)
+/**
+ * Try to acquire lock on previously created mutex.
+ * Function returns immediately.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES if mutex is locked, #MHD_NO if
+ * mutex was not locked.
+ */
+#define MHD_mutex_trylock_(mutex) \
+ ((NULL != (mutex) && 0 != TryEnterCriticalSection ((mutex))) ? MHD_YES : MHD_NO)
+#endif
+
+#if defined(MHD_PTHREAD_MUTEX_)
+/**
+ * Unlock previously created and locked mutex.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+#define MHD_mutex_unlock_(mutex) \
+ ((0 == pthread_mutex_unlock((mutex))) ? MHD_YES : MHD_NO)
+#elif defined(MHD_W32_MUTEX_)
+/**
+ * Unlock previously created and locked mutex.
+ * @param mutex pointer to mutex
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+#define MHD_mutex_unlock_(mutex) \
+ ((NULL != (mutex)) ? (LeaveCriticalSection((mutex)), MHD_YES) : MHD_NO)
+#endif
+
+#endif // MHD_PLATFORM_INTERFACE_H
diff --git a/src/include/w32functions.h b/src/include/w32functions.h
new file mode 100644
index 0000000..a86c4b2
--- /dev/null
+++ b/src/include/w32functions.h
@@ -0,0 +1,213 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2014 Karlson2k (Evgeny Grin)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library.
+ If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file include/w32functions.h
+ * @brief internal functions for W32 systems
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_W32FUNCTIONS_H
+#define MHD_W32FUNCTIONS_H
+#ifndef _WIN32
+#error w32functions.h is designed only for W32 systems
+#endif
+
+#include "platform.h"
+#include <errno.h>
+#include <winsock2.h>
+#include "platform_interface.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define MHDW32ERRBASE 3300
+
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK (MHDW32ERRBASE+1)
+#endif
+#ifndef EINPROGRESS
+#define EINPROGRESS (MHDW32ERRBASE+2)
+#endif
+#ifndef EALREADY
+#define EALREADY (MHDW32ERRBASE+3)
+#endif
+#ifndef ENOTSOCK
+#define ENOTSOCK (MHDW32ERRBASE+4)
+#endif
+#ifndef EDESTADDRREQ
+#define EDESTADDRREQ (MHDW32ERRBASE+5)
+#endif
+#ifndef EMSGSIZE
+#define EMSGSIZE (MHDW32ERRBASE+6)
+#endif
+#ifndef EPROTOTYPE
+#define EPROTOTYPE (MHDW32ERRBASE+7)
+#endif
+#ifndef ENOPROTOOPT
+#define ENOPROTOOPT (MHDW32ERRBASE+8)
+#endif
+#ifndef EPROTONOSUPPORT
+#define EPROTONOSUPPORT (MHDW32ERRBASE+9)
+#endif
+#ifndef EOPNOTSUPP
+#define EOPNOTSUPP (MHDW32ERRBASE+10)
+#endif
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT (MHDW32ERRBASE+11)
+#endif
+#ifndef EADDRINUSE
+#define EADDRINUSE (MHDW32ERRBASE+12)
+#endif
+#ifndef EADDRNOTAVAIL
+#define EADDRNOTAVAIL (MHDW32ERRBASE+13)
+#endif
+#ifndef ENETDOWN
+#define ENETDOWN (MHDW32ERRBASE+14)
+#endif
+#ifndef ENETUNREACH
+#define ENETUNREACH (MHDW32ERRBASE+15)
+#endif
+#ifndef ENETRESET
+#define ENETRESET (MHDW32ERRBASE+16)
+#endif
+#ifndef ECONNABORTED
+#define ECONNABORTED (MHDW32ERRBASE+17)
+#endif
+#ifndef ECONNRESET
+#define ECONNRESET (MHDW32ERRBASE+18)
+#endif
+#ifndef ENOBUFS
+#define ENOBUFS (MHDW32ERRBASE+19)
+#endif
+#ifndef EISCONN
+#define EISCONN (MHDW32ERRBASE+20)
+#endif
+#ifndef ENOTCONN
+#define ENOTCONN (MHDW32ERRBASE+21)
+#endif
+#ifndef ETOOMANYREFS
+#define ETOOMANYREFS (MHDW32ERRBASE+22)
+#endif
+#ifndef ECONNREFUSED
+#define ECONNREFUSED (MHDW32ERRBASE+23)
+#endif
+#ifndef ELOOP
+#define ELOOP (MHDW32ERRBASE+24)
+#endif
+#ifndef EHOSTDOWN
+#define EHOSTDOWN (MHDW32ERRBASE+25)
+#endif
+#ifndef EHOSTUNREACH
+#define EHOSTUNREACH (MHDW32ERRBASE+26)
+#endif
+#ifndef EPROCLIM
+#define EPROCLIM (MHDW32ERRBASE+27)
+#endif
+#ifndef EUSERS
+#define EUSERS (MHDW32ERRBASE+28)
+#endif
+#ifndef EDQUOT
+#define EDQUOT (MHDW32ERRBASE+29)
+#endif
+#ifndef ESTALE
+#define ESTALE (MHDW32ERRBASE+30)
+#endif
+#ifndef EREMOTE
+#define EREMOTE (MHDW32ERRBASE+31)
+#endif
+#ifndef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT (MHDW32ERRBASE+32)
+#endif
+#ifndef EPFNOSUPPORT
+#define EPFNOSUPPORT (MHDW32ERRBASE+33)
+#endif
+#ifndef ESHUTDOWN
+#define ESHUTDOWN (MHDW32ERRBASE+34)
+#endif
+#ifndef ENODATA
+#define ENODATA (MHDW32ERRBASE+35)
+#endif
+#ifndef ETIMEDOUT
+#define ETIMEDOUT (MHDW32ERRBASE+36)
+#endif
+
+/**
+ * Return errno equivalent of last winsock error
+ * @return errno equivalent of last winsock error
+ */
+int MHD_W32_errno_from_winsock_(void);
+
+/**
+ * Return pointer to string description of errnum error
+ * Works fine with both standard errno errnums
+ * and errnums from MHD_W32_errno_from_winsock_
+ * @param errnum the errno or value from MHD_W32_errno_from_winsock_()
+ * @return pointer to string description of error
+ */
+const char* MHD_W32_strerror_(int errnum);
+
+/**
+ * Return pointer to string description of last winsock error
+ * @return pointer to string description of last winsock error
+ */
+const char* MHD_W32_strerror_last_winsock_(void);
+
+/**
+ * Set last winsock error to equivalent of given errno value
+ * @param errnum the errno value to set
+ */
+void MHD_W32_set_last_winsock_error_(int errnum);
+
+/**
+ * Create pair of mutually connected TCP/IP sockets on loopback address
+ * @param sockets_pair array to receive resulted sockets
+ * @return zero on success, -1 otherwise
+ */
+int MHD_W32_pair_of_sockets_(SOCKET sockets_pair[2]);
+
+/**
+ * Generate 31-bit pseudo random number.
+ * Function initialize itself at first call to current time.
+ * @return 31-bit pseudo random number.
+ */
+int MHD_W32_random_(void);
+
+/* Emulate snprintf function on W32 */
+int W32_snprintf(char *__restrict s, size_t n, const char *__restrict format, ...);
+
+#ifndef _MSC_FULL_VER
+/* Thread name available only for VC-compiler */
+static void W32_SetThreadName(const DWORD thread_id, const char *thread_name)
+{ }
+#else /* _MSC_FULL_VER */
+/**
+ * Set thread name
+ * @param thread_id ID of thread, -1 for current thread
+ * @param thread_name name to set
+ */
+void W32_SetThreadName(const DWORD thread_id, const char *thread_name);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif //MHD_W32FUNCTIONS_H
diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am
new file mode 100644
index 0000000..dbf3bac
--- /dev/null
+++ b/src/microhttpd/Makefile.am
@@ -0,0 +1,176 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/microhttpd
+
+AM_CFLAGS = $(HIDDEN_VISIBILITY_CFLAGS)
+
+if HAVE_W32
+MHD_W32_LIB = $(top_builddir)/src/platform/libplatform_interface.la
+endif
+
+lib_LTLIBRARIES = \
+ libmicrohttpd.la
+
+noinst_DATA =
+MOSTLYCLEANFILES =
+
+if W32_SHARED_LIB_EXP
+W32_MHD_LIB_LDFLAGS = -Wl,--output-def,$(lt_cv_objdir)/libmicrohttpd.def -XCClinker -static-libgcc
+noinst_DATA += $(lt_cv_objdir)/libmicrohttpd.lib $(lt_cv_objdir)/libmicrohttpd.def $(lt_cv_objdir)/libmicrohttpd.exp
+MOSTLYCLEANFILES += $(lt_cv_objdir)/libmicrohttpd.lib $(lt_cv_objdir)/libmicrohttpd.def $(lt_cv_objdir)/libmicrohttpd.exp
+
+$(lt_cv_objdir)/libmicrohttpd.def: libmicrohttpd.la
+
+$(lt_cv_objdir)/libmicrohttpd.exp: $(lt_cv_objdir)/libmicrohttpd.lib
+
+$(lt_cv_objdir)/libmicrohttpd.lib: $(lt_cv_objdir)/libmicrohttpd.def libmicrohttpd.la $(libmicrohttpd_la_OBJECTS)
+if USE_MS_LIB_TOOL
+ @echo Creating $@ and libmicrohttpd.exp by $(MS_LIB_TOOL)... && \
+ dll_name=`$(EGREP) -o dlname=\'.+\' libmicrohttpd.la` && \
+ dll_name=$${dll_name#*\'} && dll_name=$${dll_name%\'} && test -n "$$dll_name" && \
+ echo Creating $$dll_name by $(MS_LIB_TOOL).. && cd "$(lt_cv_objdir)" && \
+ $(MS_LIB_TOOL) -def:libmicrohttpd.def -name:$$dll_name -out:libmicrohttpd.lib $(libmicrohttpd_la_OBJECTS:.lo=.o) && cd ..
+else
+ @echo Creating $@ and libmicrohttpd.exp by $(DLLTOOL)... && \
+ dll_name=`$(EGREP) -o dlname=\'.+\' libmicrohttpd.la` && \
+ dll_name=$${dll_name#*\'} && dll_name=$${dll_name%\'} && test -n "$$dll_name" && \
+ echo Creating $$dll_name by $(DLLTOOL).. && cd "$(lt_cv_objdir)" && \
+ $(DLLTOOL) -d ./libmicrohttpd.def -D $$dll_name -l libmicrohttpd.lib $(libmicrohttpd_la_OBJECTS:.lo=.o) -e ./libmicrohttpd.exp && cd .. &&\
+ echo Created libmicrohttpd.exp and libmicrohttpd.lib.
+endif
+else
+ W32_MHD_LIB_LDFLAGS =
+endif
+
+if W32_STATIC_LIB
+noinst_DATA += $(lt_cv_objdir)/libmicrohttpd-static.lib
+MOSTLYCLEANFILES += $(lt_cv_objdir)/libmicrohttpd-static.lib
+
+$(lt_cv_objdir)/libmicrohttpd-static.lib: libmicrohttpd.la $(libmicrohttpd_la_OBJECTS)
+if USE_MS_LIB_TOOL
+ $(MS_LIB_TOOL) -out:$@ $(libmicrohttpd_la_OBJECTS:.lo=.o)
+else
+ cp $(lt_cv_objdir)/libmicrohttpd.a $@
+endif
+endif
+
+
+libmicrohttpd_la_SOURCES = \
+ connection.c connection.h \
+ reason_phrase.c reason_phrase.h \
+ daemon.c \
+ internal.c internal.h \
+ memorypool.c memorypool.h \
+ response.c response.h
+libmicrohttpd_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(MHD_LIB_CPPFLAGS) \
+ -DBUILDING_MHD_LIB=1
+libmicrohttpd_la_CFLAGS = \
+ $(AM_CFLAGS) $(MHD_LIB_CFLAGS)
+libmicrohttpd_la_LDFLAGS = \
+ $(MHD_LIB_LDFLAGS) \
+ $(W32_MHD_LIB_LDFLAGS) \
+ -version-info @LIB_VERSION_CURRENT@:@LIB_VERSION_REVISION@:@LIB_VERSION_AGE@
+libmicrohttpd_la_LIBADD = \
+ $(MHD_W32_LIB) $(MHD_LIBDEPS)
+libmicrohttpd_la_DEPENDENCIES = \
+ $(MHD_W32_LIB)
+
+if HAVE_W32
+MHD_DLL_RES_SRC = microhttpd_dll_res.rc
+MHD_DLL_RES_LO = libmicrohttpd_la-$(MHD_DLL_RES_SRC:.rc=.lo)
+
+EXTRA_libmicrohttpd_la_DEPENDENCIES = $(MHD_DLL_RES_LO)
+libmicrohttpd_la_LIBADD += $(MHD_DLL_RES_LO)
+
+# General rule is not required, but keep it just in case
+.rc.lo:
+ $(LIBTOOL) $(AM_V_lt) --tag=RC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(RC) $(RCFLAGS) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $< -o $@
+
+# To add dll resource only to .dll file and exclude it form static
+# lib, a little trick was used. Allow libtool to create file.lo,
+# file.o and .libs/file.lo, .libs/file.o files, then overwrite file.o
+# by empty object generated from empty c-file. Later libtool will
+# use .libs/file.o for shared lib and empty file.o for static lib.
+# This implementation is based on trick found in liblzma.
+# Note: windres does not understand '-isystem' flag, so all
+# possible '-isystem' flags are replaced by simple '-I' flags.
+$(MHD_DLL_RES_LO): $(MHD_DLL_RES_SRC)
+ RC_CPP_FLAGS=" $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) " && \
+ $(LIBTOOL) $(AM_V_lt) --tag=RC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(RC) $(RCFLAGS) $(DEFS) $${RC_CPP_FLAGS// -isystem / -I } $< -o $@ && \
+ echo > $@-empty.c && $(CC) $(AM_CFLAGS) $(CFLAGS) -c $@-empty.c -o $(@:.lo=.o) && rm -f $@-empty.c
+endif
+
+if USE_COVERAGE
+ AM_CFLAGS += --coverage
+endif
+
+if !HAVE_TSEARCH
+libmicrohttpd_la_SOURCES += \
+ tsearch.c tsearch.h
+endif
+
+if HAVE_POSTPROCESSOR
+libmicrohttpd_la_SOURCES += \
+ postprocessor.c
+endif
+
+if ENABLE_DAUTH
+libmicrohttpd_la_SOURCES += \
+ digestauth.c \
+ md5.c md5.h
+endif
+
+if ENABLE_BAUTH
+libmicrohttpd_la_SOURCES += \
+ basicauth.c \
+ base64.c base64.h
+endif
+
+if ENABLE_HTTPS
+libmicrohttpd_la_SOURCES += \
+ connection_https.c connection_https.h
+endif
+
+
+
+check_PROGRAMS = \
+ test_daemon
+
+if HAVE_POSTPROCESSOR
+check_PROGRAMS += \
+ test_postprocessor \
+ test_postprocessor_large \
+ test_postprocessor_amp
+endif
+
+TESTS = $(check_PROGRAMS)
+
+test_daemon_SOURCES = \
+ test_daemon.c
+test_daemon_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_postprocessor_SOURCES = \
+ test_postprocessor.c
+test_postprocessor_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+test_postprocessor_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(MHD_W32_LIB)
+
+test_postprocessor_amp_SOURCES = \
+ test_postprocessor_amp.c
+test_postprocessor_amp_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+test_postprocessor_amp_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_postprocessor_large_SOURCES = \
+ test_postprocessor_large.c
+test_postprocessor_large_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+test_postprocessor_large_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(MHD_W32_LIB)
diff --git a/src/microhttpd/Makefile.in b/src/microhttpd/Makefile.in
new file mode 100644
index 0000000..f6aa7ab
--- /dev/null
+++ b/src/microhttpd/Makefile.in
@@ -0,0 +1,1427 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@W32_SHARED_LIB_EXP_TRUE@am__append_1 = $(lt_cv_objdir)/libmicrohttpd.lib $(lt_cv_objdir)/libmicrohttpd.def $(lt_cv_objdir)/libmicrohttpd.exp
+@W32_SHARED_LIB_EXP_TRUE@am__append_2 = $(lt_cv_objdir)/libmicrohttpd.lib $(lt_cv_objdir)/libmicrohttpd.def $(lt_cv_objdir)/libmicrohttpd.exp
+@W32_STATIC_LIB_TRUE@am__append_3 = $(lt_cv_objdir)/libmicrohttpd-static.lib
+@W32_STATIC_LIB_TRUE@am__append_4 = $(lt_cv_objdir)/libmicrohttpd-static.lib
+@HAVE_W32_TRUE@am__append_5 = $(MHD_DLL_RES_LO)
+@USE_COVERAGE_TRUE@am__append_6 = --coverage
+@HAVE_TSEARCH_FALSE@am__append_7 = \
+@HAVE_TSEARCH_FALSE@ tsearch.c tsearch.h
+
+@HAVE_POSTPROCESSOR_TRUE@am__append_8 = \
+@HAVE_POSTPROCESSOR_TRUE@ postprocessor.c
+
+@ENABLE_DAUTH_TRUE@am__append_9 = \
+@ENABLE_DAUTH_TRUE@ digestauth.c \
+@ENABLE_DAUTH_TRUE@ md5.c md5.h
+
+@ENABLE_BAUTH_TRUE@am__append_10 = \
+@ENABLE_BAUTH_TRUE@ basicauth.c \
+@ENABLE_BAUTH_TRUE@ base64.c base64.h
+
+@ENABLE_HTTPS_TRUE@am__append_11 = \
+@ENABLE_HTTPS_TRUE@ connection_https.c connection_https.h
+
+check_PROGRAMS = test_daemon$(EXEEXT) $(am__EXEEXT_1)
+@HAVE_POSTPROCESSOR_TRUE@am__append_12 = \
+@HAVE_POSTPROCESSOR_TRUE@ test_postprocessor \
+@HAVE_POSTPROCESSOR_TRUE@ test_postprocessor_large \
+@HAVE_POSTPROCESSOR_TRUE@ test_postprocessor_amp
+
+subdir = src/microhttpd
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(srcdir)/microhttpd_dll_res.rc.in $(top_srcdir)/depcomp \
+ $(top_srcdir)/test-driver
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES = microhttpd_dll_res.rc
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+am__libmicrohttpd_la_SOURCES_DIST = connection.c connection.h \
+ reason_phrase.c reason_phrase.h daemon.c internal.c internal.h \
+ memorypool.c memorypool.h response.c response.h tsearch.c \
+ tsearch.h postprocessor.c digestauth.c md5.c md5.h basicauth.c \
+ base64.c base64.h connection_https.c connection_https.h
+@HAVE_TSEARCH_FALSE@am__objects_1 = libmicrohttpd_la-tsearch.lo
+@HAVE_POSTPROCESSOR_TRUE@am__objects_2 = \
+@HAVE_POSTPROCESSOR_TRUE@ libmicrohttpd_la-postprocessor.lo
+@ENABLE_DAUTH_TRUE@am__objects_3 = libmicrohttpd_la-digestauth.lo \
+@ENABLE_DAUTH_TRUE@ libmicrohttpd_la-md5.lo
+@ENABLE_BAUTH_TRUE@am__objects_4 = libmicrohttpd_la-basicauth.lo \
+@ENABLE_BAUTH_TRUE@ libmicrohttpd_la-base64.lo
+@ENABLE_HTTPS_TRUE@am__objects_5 = \
+@ENABLE_HTTPS_TRUE@ libmicrohttpd_la-connection_https.lo
+am_libmicrohttpd_la_OBJECTS = libmicrohttpd_la-connection.lo \
+ libmicrohttpd_la-reason_phrase.lo libmicrohttpd_la-daemon.lo \
+ libmicrohttpd_la-internal.lo libmicrohttpd_la-memorypool.lo \
+ libmicrohttpd_la-response.lo $(am__objects_1) $(am__objects_2) \
+ $(am__objects_3) $(am__objects_4) $(am__objects_5)
+libmicrohttpd_la_OBJECTS = $(am_libmicrohttpd_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libmicrohttpd_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(libmicrohttpd_la_CFLAGS) $(CFLAGS) \
+ $(libmicrohttpd_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_POSTPROCESSOR_TRUE@am__EXEEXT_1 = test_postprocessor$(EXEEXT) \
+@HAVE_POSTPROCESSOR_TRUE@ test_postprocessor_large$(EXEEXT) \
+@HAVE_POSTPROCESSOR_TRUE@ test_postprocessor_amp$(EXEEXT)
+am_test_daemon_OBJECTS = test_daemon.$(OBJEXT)
+test_daemon_OBJECTS = $(am_test_daemon_OBJECTS)
+test_daemon_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_postprocessor_OBJECTS = \
+ test_postprocessor-test_postprocessor.$(OBJEXT)
+test_postprocessor_OBJECTS = $(am_test_postprocessor_OBJECTS)
+test_postprocessor_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la $(MHD_W32_LIB)
+am_test_postprocessor_amp_OBJECTS = \
+ test_postprocessor_amp-test_postprocessor_amp.$(OBJEXT)
+test_postprocessor_amp_OBJECTS = $(am_test_postprocessor_amp_OBJECTS)
+test_postprocessor_amp_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_postprocessor_large_OBJECTS = \
+ test_postprocessor_large-test_postprocessor_large.$(OBJEXT)
+test_postprocessor_large_OBJECTS = \
+ $(am_test_postprocessor_large_OBJECTS)
+test_postprocessor_large_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la $(MHD_W32_LIB)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmicrohttpd_la_SOURCES) $(test_daemon_SOURCES) \
+ $(test_postprocessor_SOURCES) \
+ $(test_postprocessor_amp_SOURCES) \
+ $(test_postprocessor_large_SOURCES)
+DIST_SOURCES = $(am__libmicrohttpd_la_SOURCES_DIST) \
+ $(test_daemon_SOURCES) $(test_postprocessor_SOURCES) \
+ $(test_postprocessor_amp_SOURCES) \
+ $(test_postprocessor_large_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(noinst_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red='[0;31m'; \
+ grn='[0;32m'; \
+ lgn='[1;32m'; \
+ blu='[1;34m'; \
+ mgn='[0;35m'; \
+ brg='[1m'; \
+ std='[m'; \
+ fi; \
+}
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/microhttpd
+
+AM_CFLAGS = $(HIDDEN_VISIBILITY_CFLAGS) $(am__append_6)
+@HAVE_W32_TRUE@MHD_W32_LIB = $(top_builddir)/src/platform/libplatform_interface.la
+lib_LTLIBRARIES = \
+ libmicrohttpd.la
+
+noinst_DATA = $(am__append_1) $(am__append_3)
+MOSTLYCLEANFILES = $(am__append_2) $(am__append_4)
+@W32_SHARED_LIB_EXP_FALSE@W32_MHD_LIB_LDFLAGS =
+@W32_SHARED_LIB_EXP_TRUE@W32_MHD_LIB_LDFLAGS = -Wl,--output-def,$(lt_cv_objdir)/libmicrohttpd.def -XCClinker -static-libgcc
+libmicrohttpd_la_SOURCES = connection.c connection.h reason_phrase.c \
+ reason_phrase.h daemon.c internal.c internal.h memorypool.c \
+ memorypool.h response.c response.h $(am__append_7) \
+ $(am__append_8) $(am__append_9) $(am__append_10) \
+ $(am__append_11)
+libmicrohttpd_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(MHD_LIB_CPPFLAGS) \
+ -DBUILDING_MHD_LIB=1
+
+libmicrohttpd_la_CFLAGS = \
+ $(AM_CFLAGS) $(MHD_LIB_CFLAGS)
+
+libmicrohttpd_la_LDFLAGS = \
+ $(MHD_LIB_LDFLAGS) \
+ $(W32_MHD_LIB_LDFLAGS) \
+ -version-info @LIB_VERSION_CURRENT@:@LIB_VERSION_REVISION@:@LIB_VERSION_AGE@
+
+libmicrohttpd_la_LIBADD = $(MHD_W32_LIB) $(MHD_LIBDEPS) \
+ $(am__append_5)
+libmicrohttpd_la_DEPENDENCIES = \
+ $(MHD_W32_LIB)
+
+@HAVE_W32_TRUE@MHD_DLL_RES_SRC = microhttpd_dll_res.rc
+@HAVE_W32_TRUE@MHD_DLL_RES_LO = libmicrohttpd_la-$(MHD_DLL_RES_SRC:.rc=.lo)
+@HAVE_W32_TRUE@EXTRA_libmicrohttpd_la_DEPENDENCIES = $(MHD_DLL_RES_LO)
+TESTS = $(check_PROGRAMS)
+test_daemon_SOURCES = \
+ test_daemon.c
+
+test_daemon_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_postprocessor_SOURCES = \
+ test_postprocessor.c
+
+test_postprocessor_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+
+test_postprocessor_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(MHD_W32_LIB)
+
+test_postprocessor_amp_SOURCES = \
+ test_postprocessor_amp.c
+
+test_postprocessor_amp_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+
+test_postprocessor_amp_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_postprocessor_large_SOURCES = \
+ test_postprocessor_large.c
+
+test_postprocessor_large_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+
+test_postprocessor_large_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(MHD_W32_LIB)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .log .o .obj .rc .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/microhttpd/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/microhttpd/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+microhttpd_dll_res.rc: $(top_builddir)/config.status $(srcdir)/microhttpd_dll_res.rc.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmicrohttpd.la: $(libmicrohttpd_la_OBJECTS) $(libmicrohttpd_la_DEPENDENCIES) $(EXTRA_libmicrohttpd_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libmicrohttpd_la_LINK) -rpath $(libdir) $(libmicrohttpd_la_OBJECTS) $(libmicrohttpd_la_LIBADD) $(LIBS)
+
+clean-checkPROGRAMS:
+ @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+test_daemon$(EXEEXT): $(test_daemon_OBJECTS) $(test_daemon_DEPENDENCIES) $(EXTRA_test_daemon_DEPENDENCIES)
+ @rm -f test_daemon$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_daemon_OBJECTS) $(test_daemon_LDADD) $(LIBS)
+
+test_postprocessor$(EXEEXT): $(test_postprocessor_OBJECTS) $(test_postprocessor_DEPENDENCIES) $(EXTRA_test_postprocessor_DEPENDENCIES)
+ @rm -f test_postprocessor$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_postprocessor_OBJECTS) $(test_postprocessor_LDADD) $(LIBS)
+
+test_postprocessor_amp$(EXEEXT): $(test_postprocessor_amp_OBJECTS) $(test_postprocessor_amp_DEPENDENCIES) $(EXTRA_test_postprocessor_amp_DEPENDENCIES)
+ @rm -f test_postprocessor_amp$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_postprocessor_amp_OBJECTS) $(test_postprocessor_amp_LDADD) $(LIBS)
+
+test_postprocessor_large$(EXEEXT): $(test_postprocessor_large_OBJECTS) $(test_postprocessor_large_DEPENDENCIES) $(EXTRA_test_postprocessor_large_DEPENDENCIES)
+ @rm -f test_postprocessor_large$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_postprocessor_large_OBJECTS) $(test_postprocessor_large_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-base64.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-basicauth.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-connection.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-connection_https.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-daemon.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-digestauth.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-internal.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-md5.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-memorypool.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-postprocessor.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-reason_phrase.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-response.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrohttpd_la-tsearch.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_daemon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_postprocessor-test_postprocessor.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_postprocessor_amp-test_postprocessor_amp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_postprocessor_large-test_postprocessor_large.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+libmicrohttpd_la-connection.lo: connection.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-connection.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-connection.Tpo -c -o libmicrohttpd_la-connection.lo `test -f 'connection.c' || echo '$(srcdir)/'`connection.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-connection.Tpo $(DEPDIR)/libmicrohttpd_la-connection.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='connection.c' object='libmicrohttpd_la-connection.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-connection.lo `test -f 'connection.c' || echo '$(srcdir)/'`connection.c
+
+libmicrohttpd_la-reason_phrase.lo: reason_phrase.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-reason_phrase.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-reason_phrase.Tpo -c -o libmicrohttpd_la-reason_phrase.lo `test -f 'reason_phrase.c' || echo '$(srcdir)/'`reason_phrase.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-reason_phrase.Tpo $(DEPDIR)/libmicrohttpd_la-reason_phrase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='reason_phrase.c' object='libmicrohttpd_la-reason_phrase.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-reason_phrase.lo `test -f 'reason_phrase.c' || echo '$(srcdir)/'`reason_phrase.c
+
+libmicrohttpd_la-daemon.lo: daemon.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-daemon.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-daemon.Tpo -c -o libmicrohttpd_la-daemon.lo `test -f 'daemon.c' || echo '$(srcdir)/'`daemon.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-daemon.Tpo $(DEPDIR)/libmicrohttpd_la-daemon.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='daemon.c' object='libmicrohttpd_la-daemon.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-daemon.lo `test -f 'daemon.c' || echo '$(srcdir)/'`daemon.c
+
+libmicrohttpd_la-internal.lo: internal.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-internal.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-internal.Tpo -c -o libmicrohttpd_la-internal.lo `test -f 'internal.c' || echo '$(srcdir)/'`internal.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-internal.Tpo $(DEPDIR)/libmicrohttpd_la-internal.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='internal.c' object='libmicrohttpd_la-internal.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-internal.lo `test -f 'internal.c' || echo '$(srcdir)/'`internal.c
+
+libmicrohttpd_la-memorypool.lo: memorypool.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-memorypool.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-memorypool.Tpo -c -o libmicrohttpd_la-memorypool.lo `test -f 'memorypool.c' || echo '$(srcdir)/'`memorypool.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-memorypool.Tpo $(DEPDIR)/libmicrohttpd_la-memorypool.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='memorypool.c' object='libmicrohttpd_la-memorypool.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-memorypool.lo `test -f 'memorypool.c' || echo '$(srcdir)/'`memorypool.c
+
+libmicrohttpd_la-response.lo: response.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-response.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-response.Tpo -c -o libmicrohttpd_la-response.lo `test -f 'response.c' || echo '$(srcdir)/'`response.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-response.Tpo $(DEPDIR)/libmicrohttpd_la-response.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='response.c' object='libmicrohttpd_la-response.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-response.lo `test -f 'response.c' || echo '$(srcdir)/'`response.c
+
+libmicrohttpd_la-tsearch.lo: tsearch.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-tsearch.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-tsearch.Tpo -c -o libmicrohttpd_la-tsearch.lo `test -f 'tsearch.c' || echo '$(srcdir)/'`tsearch.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-tsearch.Tpo $(DEPDIR)/libmicrohttpd_la-tsearch.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tsearch.c' object='libmicrohttpd_la-tsearch.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-tsearch.lo `test -f 'tsearch.c' || echo '$(srcdir)/'`tsearch.c
+
+libmicrohttpd_la-postprocessor.lo: postprocessor.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-postprocessor.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-postprocessor.Tpo -c -o libmicrohttpd_la-postprocessor.lo `test -f 'postprocessor.c' || echo '$(srcdir)/'`postprocessor.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-postprocessor.Tpo $(DEPDIR)/libmicrohttpd_la-postprocessor.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='postprocessor.c' object='libmicrohttpd_la-postprocessor.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-postprocessor.lo `test -f 'postprocessor.c' || echo '$(srcdir)/'`postprocessor.c
+
+libmicrohttpd_la-digestauth.lo: digestauth.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-digestauth.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-digestauth.Tpo -c -o libmicrohttpd_la-digestauth.lo `test -f 'digestauth.c' || echo '$(srcdir)/'`digestauth.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-digestauth.Tpo $(DEPDIR)/libmicrohttpd_la-digestauth.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='digestauth.c' object='libmicrohttpd_la-digestauth.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-digestauth.lo `test -f 'digestauth.c' || echo '$(srcdir)/'`digestauth.c
+
+libmicrohttpd_la-md5.lo: md5.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-md5.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-md5.Tpo -c -o libmicrohttpd_la-md5.lo `test -f 'md5.c' || echo '$(srcdir)/'`md5.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-md5.Tpo $(DEPDIR)/libmicrohttpd_la-md5.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='md5.c' object='libmicrohttpd_la-md5.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-md5.lo `test -f 'md5.c' || echo '$(srcdir)/'`md5.c
+
+libmicrohttpd_la-basicauth.lo: basicauth.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-basicauth.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-basicauth.Tpo -c -o libmicrohttpd_la-basicauth.lo `test -f 'basicauth.c' || echo '$(srcdir)/'`basicauth.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-basicauth.Tpo $(DEPDIR)/libmicrohttpd_la-basicauth.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='basicauth.c' object='libmicrohttpd_la-basicauth.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-basicauth.lo `test -f 'basicauth.c' || echo '$(srcdir)/'`basicauth.c
+
+libmicrohttpd_la-base64.lo: base64.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-base64.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-base64.Tpo -c -o libmicrohttpd_la-base64.lo `test -f 'base64.c' || echo '$(srcdir)/'`base64.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-base64.Tpo $(DEPDIR)/libmicrohttpd_la-base64.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='base64.c' object='libmicrohttpd_la-base64.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-base64.lo `test -f 'base64.c' || echo '$(srcdir)/'`base64.c
+
+libmicrohttpd_la-connection_https.lo: connection_https.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -MT libmicrohttpd_la-connection_https.lo -MD -MP -MF $(DEPDIR)/libmicrohttpd_la-connection_https.Tpo -c -o libmicrohttpd_la-connection_https.lo `test -f 'connection_https.c' || echo '$(srcdir)/'`connection_https.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrohttpd_la-connection_https.Tpo $(DEPDIR)/libmicrohttpd_la-connection_https.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='connection_https.c' object='libmicrohttpd_la-connection_https.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) $(libmicrohttpd_la_CFLAGS) $(CFLAGS) -c -o libmicrohttpd_la-connection_https.lo `test -f 'connection_https.c' || echo '$(srcdir)/'`connection_https.c
+
+test_postprocessor-test_postprocessor.o: test_postprocessor.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_postprocessor-test_postprocessor.o -MD -MP -MF $(DEPDIR)/test_postprocessor-test_postprocessor.Tpo -c -o test_postprocessor-test_postprocessor.o `test -f 'test_postprocessor.c' || echo '$(srcdir)/'`test_postprocessor.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_postprocessor-test_postprocessor.Tpo $(DEPDIR)/test_postprocessor-test_postprocessor.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_postprocessor.c' object='test_postprocessor-test_postprocessor.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_postprocessor-test_postprocessor.o `test -f 'test_postprocessor.c' || echo '$(srcdir)/'`test_postprocessor.c
+
+test_postprocessor-test_postprocessor.obj: test_postprocessor.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_postprocessor-test_postprocessor.obj -MD -MP -MF $(DEPDIR)/test_postprocessor-test_postprocessor.Tpo -c -o test_postprocessor-test_postprocessor.obj `if test -f 'test_postprocessor.c'; then $(CYGPATH_W) 'test_postprocessor.c'; else $(CYGPATH_W) '$(srcdir)/test_postprocessor.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_postprocessor-test_postprocessor.Tpo $(DEPDIR)/test_postprocessor-test_postprocessor.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_postprocessor.c' object='test_postprocessor-test_postprocessor.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_postprocessor-test_postprocessor.obj `if test -f 'test_postprocessor.c'; then $(CYGPATH_W) 'test_postprocessor.c'; else $(CYGPATH_W) '$(srcdir)/test_postprocessor.c'; fi`
+
+test_postprocessor_amp-test_postprocessor_amp.o: test_postprocessor_amp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_amp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_postprocessor_amp-test_postprocessor_amp.o -MD -MP -MF $(DEPDIR)/test_postprocessor_amp-test_postprocessor_amp.Tpo -c -o test_postprocessor_amp-test_postprocessor_amp.o `test -f 'test_postprocessor_amp.c' || echo '$(srcdir)/'`test_postprocessor_amp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_postprocessor_amp-test_postprocessor_amp.Tpo $(DEPDIR)/test_postprocessor_amp-test_postprocessor_amp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_postprocessor_amp.c' object='test_postprocessor_amp-test_postprocessor_amp.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_amp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_postprocessor_amp-test_postprocessor_amp.o `test -f 'test_postprocessor_amp.c' || echo '$(srcdir)/'`test_postprocessor_amp.c
+
+test_postprocessor_amp-test_postprocessor_amp.obj: test_postprocessor_amp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_amp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_postprocessor_amp-test_postprocessor_amp.obj -MD -MP -MF $(DEPDIR)/test_postprocessor_amp-test_postprocessor_amp.Tpo -c -o test_postprocessor_amp-test_postprocessor_amp.obj `if test -f 'test_postprocessor_amp.c'; then $(CYGPATH_W) 'test_postprocessor_amp.c'; else $(CYGPATH_W) '$(srcdir)/test_postprocessor_amp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_postprocessor_amp-test_postprocessor_amp.Tpo $(DEPDIR)/test_postprocessor_amp-test_postprocessor_amp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_postprocessor_amp.c' object='test_postprocessor_amp-test_postprocessor_amp.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_amp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_postprocessor_amp-test_postprocessor_amp.obj `if test -f 'test_postprocessor_amp.c'; then $(CYGPATH_W) 'test_postprocessor_amp.c'; else $(CYGPATH_W) '$(srcdir)/test_postprocessor_amp.c'; fi`
+
+test_postprocessor_large-test_postprocessor_large.o: test_postprocessor_large.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_large_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_postprocessor_large-test_postprocessor_large.o -MD -MP -MF $(DEPDIR)/test_postprocessor_large-test_postprocessor_large.Tpo -c -o test_postprocessor_large-test_postprocessor_large.o `test -f 'test_postprocessor_large.c' || echo '$(srcdir)/'`test_postprocessor_large.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_postprocessor_large-test_postprocessor_large.Tpo $(DEPDIR)/test_postprocessor_large-test_postprocessor_large.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_postprocessor_large.c' object='test_postprocessor_large-test_postprocessor_large.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_large_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_postprocessor_large-test_postprocessor_large.o `test -f 'test_postprocessor_large.c' || echo '$(srcdir)/'`test_postprocessor_large.c
+
+test_postprocessor_large-test_postprocessor_large.obj: test_postprocessor_large.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_large_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_postprocessor_large-test_postprocessor_large.obj -MD -MP -MF $(DEPDIR)/test_postprocessor_large-test_postprocessor_large.Tpo -c -o test_postprocessor_large-test_postprocessor_large.obj `if test -f 'test_postprocessor_large.c'; then $(CYGPATH_W) 'test_postprocessor_large.c'; else $(CYGPATH_W) '$(srcdir)/test_postprocessor_large.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_postprocessor_large-test_postprocessor_large.Tpo $(DEPDIR)/test_postprocessor_large-test_postprocessor_large.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_postprocessor_large.c' object='test_postprocessor_large-test_postprocessor_large.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_postprocessor_large_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_postprocessor_large-test_postprocessor_large.obj `if test -f 'test_postprocessor_large.c'; then $(CYGPATH_W) 'test_postprocessor_large.c'; else $(CYGPATH_W) '$(srcdir)/test_postprocessor_large.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ else \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all $(check_PROGRAMS)
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+test_daemon.log: test_daemon$(EXEEXT)
+ @p='test_daemon$(EXEEXT)'; \
+ b='test_daemon'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_postprocessor.log: test_postprocessor$(EXEEXT)
+ @p='test_postprocessor$(EXEEXT)'; \
+ b='test_postprocessor'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_postprocessor_large.log: test_postprocessor_large$(EXEEXT)
+ @p='test_postprocessor_large$(EXEEXT)'; \
+ b='test_postprocessor_large'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_postprocessor_amp.log: test_postprocessor_amp$(EXEEXT)
+ @p='test_postprocessor_amp$(EXEEXT)'; \
+ b='test_postprocessor_amp'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(MOSTLYCLEANFILES)" || rm -f $(MOSTLYCLEANFILES)
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-TESTS check-am clean \
+ clean-checkPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am \
+ install-libLTLIBRARIES install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ recheck tags tags-am uninstall uninstall-am \
+ uninstall-libLTLIBRARIES
+
+
+@W32_SHARED_LIB_EXP_TRUE@$(lt_cv_objdir)/libmicrohttpd.def: libmicrohttpd.la
+
+@W32_SHARED_LIB_EXP_TRUE@$(lt_cv_objdir)/libmicrohttpd.exp: $(lt_cv_objdir)/libmicrohttpd.lib
+
+@W32_SHARED_LIB_EXP_TRUE@$(lt_cv_objdir)/libmicrohttpd.lib: $(lt_cv_objdir)/libmicrohttpd.def libmicrohttpd.la $(libmicrohttpd_la_OBJECTS)
+@USE_MS_LIB_TOOL_TRUE@@W32_SHARED_LIB_EXP_TRUE@ @echo Creating $@ and libmicrohttpd.exp by $(MS_LIB_TOOL)... && \
+@USE_MS_LIB_TOOL_TRUE@@W32_SHARED_LIB_EXP_TRUE@ dll_name=`$(EGREP) -o dlname=\'.+\' libmicrohttpd.la` && \
+@USE_MS_LIB_TOOL_TRUE@@W32_SHARED_LIB_EXP_TRUE@ dll_name=$${dll_name#*\'} && dll_name=$${dll_name%\'} && test -n "$$dll_name" && \
+@USE_MS_LIB_TOOL_TRUE@@W32_SHARED_LIB_EXP_TRUE@ echo Creating $$dll_name by $(MS_LIB_TOOL).. && cd "$(lt_cv_objdir)" && \
+@USE_MS_LIB_TOOL_TRUE@@W32_SHARED_LIB_EXP_TRUE@ $(MS_LIB_TOOL) -def:libmicrohttpd.def -name:$$dll_name -out:libmicrohttpd.lib $(libmicrohttpd_la_OBJECTS:.lo=.o) && cd ..
+@USE_MS_LIB_TOOL_FALSE@@W32_SHARED_LIB_EXP_TRUE@ @echo Creating $@ and libmicrohttpd.exp by $(DLLTOOL)... && \
+@USE_MS_LIB_TOOL_FALSE@@W32_SHARED_LIB_EXP_TRUE@ dll_name=`$(EGREP) -o dlname=\'.+\' libmicrohttpd.la` && \
+@USE_MS_LIB_TOOL_FALSE@@W32_SHARED_LIB_EXP_TRUE@ dll_name=$${dll_name#*\'} && dll_name=$${dll_name%\'} && test -n "$$dll_name" && \
+@USE_MS_LIB_TOOL_FALSE@@W32_SHARED_LIB_EXP_TRUE@ echo Creating $$dll_name by $(DLLTOOL).. && cd "$(lt_cv_objdir)" && \
+@USE_MS_LIB_TOOL_FALSE@@W32_SHARED_LIB_EXP_TRUE@ $(DLLTOOL) -d ./libmicrohttpd.def -D $$dll_name -l libmicrohttpd.lib $(libmicrohttpd_la_OBJECTS:.lo=.o) -e ./libmicrohttpd.exp && cd .. &&\
+@USE_MS_LIB_TOOL_FALSE@@W32_SHARED_LIB_EXP_TRUE@ echo Created libmicrohttpd.exp and libmicrohttpd.lib.
+
+@W32_STATIC_LIB_TRUE@$(lt_cv_objdir)/libmicrohttpd-static.lib: libmicrohttpd.la $(libmicrohttpd_la_OBJECTS)
+@USE_MS_LIB_TOOL_TRUE@@W32_STATIC_LIB_TRUE@ $(MS_LIB_TOOL) -out:$@ $(libmicrohttpd_la_OBJECTS:.lo=.o)
+@USE_MS_LIB_TOOL_FALSE@@W32_STATIC_LIB_TRUE@ cp $(lt_cv_objdir)/libmicrohttpd.a $@
+
+# General rule is not required, but keep it just in case
+@HAVE_W32_TRUE@.rc.lo:
+@HAVE_W32_TRUE@ $(LIBTOOL) $(AM_V_lt) --tag=RC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(RC) $(RCFLAGS) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $< -o $@
+
+# To add dll resource only to .dll file and exclude it form static
+# lib, a little trick was used. Allow libtool to create file.lo,
+# file.o and .libs/file.lo, .libs/file.o files, then overwrite file.o
+# by empty object generated from empty c-file. Later libtool will
+# use .libs/file.o for shared lib and empty file.o for static lib.
+# This implementation is based on trick found in liblzma.
+# Note: windres does not understand '-isystem' flag, so all
+# possible '-isystem' flags are replaced by simple '-I' flags.
+@HAVE_W32_TRUE@$(MHD_DLL_RES_LO): $(MHD_DLL_RES_SRC)
+@HAVE_W32_TRUE@ RC_CPP_FLAGS=" $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) " && \
+@HAVE_W32_TRUE@ $(LIBTOOL) $(AM_V_lt) --tag=RC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(RC) $(RCFLAGS) $(DEFS) $${RC_CPP_FLAGS// -isystem / -I } $< -o $@ && \
+@HAVE_W32_TRUE@ echo > $@-empty.c && $(CC) $(AM_CFLAGS) $(CFLAGS) -c $@-empty.c -o $(@:.lo=.o) && rm -f $@-empty.c
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/microhttpd/base64.c b/src/microhttpd/base64.c
new file mode 100644
index 0000000..4fb4755
--- /dev/null
+++ b/src/microhttpd/base64.c
@@ -0,0 +1,62 @@
+/*
+ * This code implements the BASE64 algorithm.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * @file base64.c
+ * @brief This code implements the BASE64 algorithm
+ * @author Matthieu Speder
+ */
+#include "base64.h"
+
+static const char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static const char base64_digits[] =
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+ 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26,
+ 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+
+char *
+BASE64Decode(const char* src)
+{
+ size_t in_len = strlen (src);
+ char* dest;
+ char* result;
+
+ if (in_len % 4)
+ {
+ /* Wrong base64 string length */
+ return NULL;
+ }
+ result = dest = malloc(in_len / 4 * 3 + 1);
+ if (result == NULL)
+ return NULL; /* out of memory */
+ while (*src) {
+ char a = base64_digits[(unsigned char)*(src++)];
+ char b = base64_digits[(unsigned char)*(src++)];
+ char c = base64_digits[(unsigned char)*(src++)];
+ char d = base64_digits[(unsigned char)*(src++)];
+ *(dest++) = (a << 2) | ((b & 0x30) >> 4);
+ if (c == (char)-1)
+ break;
+ *(dest++) = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
+ if (d == (char)-1)
+ break;
+ *(dest++) = ((c & 0x03) << 6) | d;
+ }
+ *dest = 0;
+ return result;
+}
+
+
+/* end of base64.c */
diff --git a/src/microhttpd/base64.h b/src/microhttpd/base64.h
new file mode 100644
index 0000000..ba96ca0
--- /dev/null
+++ b/src/microhttpd/base64.h
@@ -0,0 +1,17 @@
+/*
+ * This code implements the BASE64 algorithm.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * @file base64.c
+ * @brief This code implements the BASE64 algorithm
+ * @author Matthieu Speder
+ */
+#ifndef BASE64_H
+#define BASE64_H
+
+#include "platform.h"
+
+char *
+BASE64Decode(const char* src);
+
+#endif /* !BASE64_H */
diff --git a/src/microhttpd/basicauth.c b/src/microhttpd/basicauth.c
new file mode 100644
index 0000000..cbe0fc7
--- /dev/null
+++ b/src/microhttpd/basicauth.c
@@ -0,0 +1,148 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2010, 2011, 2012 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file basicauth.c
+ * @brief Implements HTTP basic authentication methods
+ * @author Amr Ali
+ * @author Matthieu Speder
+ */
+#include "platform.h"
+#include <limits.h>
+#include "internal.h"
+#include "base64.h"
+
+/**
+ * Beginning string for any valid Basic authentication header.
+ */
+#define _BASIC_BASE "Basic "
+
+
+/**
+ * Get the username and password from the basic authorization header sent by the client
+ *
+ * @param connection The MHD connection structure
+ * @param password a pointer for the password
+ * @return NULL if no username could be found, a pointer
+ * to the username if found
+ * @ingroup authentication
+ */
+char *
+MHD_basic_auth_get_username_password (struct MHD_Connection *connection,
+ char** password)
+{
+ const char *header;
+ char *decode;
+ const char *separator;
+ char *user;
+
+ if ( (NULL == (header = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION))) ||
+ (0 != strncmp (header, _BASIC_BASE, strlen(_BASIC_BASE))) )
+ return NULL;
+ header += strlen (_BASIC_BASE);
+ if (NULL == (decode = BASE64Decode (header)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Error decoding basic authentication\n");
+#endif
+ return NULL;
+ }
+ /* Find user:password pattern */
+ if (NULL == (separator = strchr (decode, ':')))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(connection->daemon,
+ "Basic authentication doesn't contain ':' separator\n");
+#endif
+ free (decode);
+ return NULL;
+ }
+ if (NULL == (user = strdup (decode)))
+ {
+ free (decode);
+ return NULL;
+ }
+ user[separator - decode] = '\0'; /* cut off at ':' */
+ if (NULL != password)
+ {
+ *password = strdup (separator + 1);
+ if (NULL == *password)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(connection->daemon,
+ "Failed to allocate memory for password\n");
+#endif
+ free (decode);
+ free (user);
+ return NULL;
+ }
+ }
+ free (decode);
+ return user;
+}
+
+
+/**
+ * Queues a response to request basic authentication from the client.
+ * The given response object is expected to include the payload for
+ * the response; the "WWW-Authenticate" header will be added and the
+ * response queued with the 'UNAUTHORIZED' status code.
+ *
+ * @param connection The MHD connection structure
+ * @param realm the realm presented to the client
+ * @param response response object to modify and queue
+ * @return #MHD_YES on success, #MHD_NO otherwise
+ * @ingroup authentication
+ */
+int
+MHD_queue_basic_auth_fail_response (struct MHD_Connection *connection,
+ const char *realm,
+ struct MHD_Response *response)
+{
+ int ret;
+ size_t hlen = strlen(realm) + strlen("Basic realm=\"\"") + 1;
+ char *header;
+
+ header = (char*)malloc(hlen);
+ if (NULL == header)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(connection->daemon,
+ "Failed to allocate memory for auth header\n");
+#endif /* HAVE_MESSAGES */
+ return MHD_NO;
+ }
+ MHD_snprintf_ (header,
+ hlen,
+ "Basic realm=\"%s\"",
+ realm);
+ ret = MHD_add_response_header (response,
+ MHD_HTTP_HEADER_WWW_AUTHENTICATE,
+ header);
+ free(header);
+ if (MHD_YES == ret)
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_UNAUTHORIZED,
+ response);
+ return ret;
+}
+
+/* end of basicauth.c */
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c
new file mode 100644
index 0000000..785fafd
--- /dev/null
+++ b/src/microhttpd/connection.c
@@ -0,0 +1,2919 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007-2015 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+/**
+ * @file connection.c
+ * @brief Methods for managing connections
+ * @author Daniel Pittman
+ * @author Christian Grothoff
+ */
+
+#include "internal.h"
+#include <limits.h>
+#include "connection.h"
+#include "memorypool.h"
+#include "response.h"
+#include "reason_phrase.h"
+
+#if HAVE_NETINET_TCP_H
+/* for TCP_CORK */
+#include <netinet/tcp.h>
+#endif
+
+#if defined(_WIN32) && defined(MHD_W32_MUTEX_)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif /* _WIN32 && MHD_W32_MUTEX_ */
+
+
+/**
+ * Message to transmit when http 1.1 request is received
+ */
+#define HTTP_100_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n"
+
+/**
+ * Response text used when the request (http header) is too big to
+ * be processed.
+ *
+ * Intentionally empty here to keep our memory footprint
+ * minimal.
+ */
+#if HAVE_MESSAGES
+#define REQUEST_TOO_BIG "<html><head><title>Request too big</title></head><body>Your HTTP header was too big for the memory constraints of this webserver.</body></html>"
+#else
+#define REQUEST_TOO_BIG ""
+#endif
+
+/**
+ * Response text used when the request (http header) does not
+ * contain a "Host:" header and still claims to be HTTP 1.1.
+ *
+ * Intentionally empty here to keep our memory footprint
+ * minimal.
+ */
+#if HAVE_MESSAGES
+#define REQUEST_LACKS_HOST "<html><head><title>"Host:" header required</title></head><body>In HTTP 1.1, requests must include a "Host:" header, and your HTTP 1.1 request lacked such a header.</body></html>"
+#else
+#define REQUEST_LACKS_HOST ""
+#endif
+
+/**
+ * Response text used when the request (http header) is
+ * malformed.
+ *
+ * Intentionally empty here to keep our memory footprint
+ * minimal.
+ */
+#if HAVE_MESSAGES
+#define REQUEST_MALFORMED "<html><head><title>Request malformed</title></head><body>Your HTTP request was syntactically incorrect.</body></html>"
+#else
+#define REQUEST_MALFORMED ""
+#endif
+
+/**
+ * Response text used when there is an internal server error.
+ *
+ * Intentionally empty here to keep our memory footprint
+ * minimal.
+ */
+#if HAVE_MESSAGES
+#define INTERNAL_ERROR "<html><head><title>Internal server error</title></head><body>Some programmer needs to study the manual more carefully.</body></html>"
+#else
+#define INTERNAL_ERROR ""
+#endif
+
+/**
+ * Add extra debug messages with reasons for closing connections
+ * (non-error reasons).
+ */
+#define DEBUG_CLOSE MHD_NO
+
+/**
+ * Should all data send be printed to stderr?
+ */
+#define DEBUG_SEND_DATA MHD_NO
+
+
+/**
+ * Get all of the headers from the request.
+ *
+ * @param connection connection to get values from
+ * @param kind types of values to iterate over
+ * @param iterator callback to call on each header;
+ * maybe NULL (then just count headers)
+ * @param iterator_cls extra argument to @a iterator
+ * @return number of entries iterated over
+ * @ingroup request
+ */
+int
+MHD_get_connection_values (struct MHD_Connection *connection,
+ enum MHD_ValueKind kind,
+ MHD_KeyValueIterator iterator, void *iterator_cls)
+{
+ int ret;
+ struct MHD_HTTP_Header *pos;
+
+ if (NULL == connection)
+ return -1;
+ ret = 0;
+ for (pos = connection->headers_received; NULL != pos; pos = pos->next)
+ if (0 != (pos->kind & kind))
+ {
+ ret++;
+ if ((NULL != iterator) &&
+ (MHD_YES != iterator (iterator_cls,
+ kind, pos->header, pos->value)))
+ return ret;
+ }
+ return ret;
+}
+
+
+/**
+ * This function can be used to add an entry to the HTTP headers of a
+ * connection (so that the #MHD_get_connection_values function will
+ * return them -- and the `struct MHD_PostProcessor` will also see
+ * them). This maybe required in certain situations (see Mantis
+ * #1399) where (broken) HTTP implementations fail to supply values
+ * needed by the post processor (or other parts of the application).
+ *
+ * This function MUST only be called from within the
+ * #MHD_AccessHandlerCallback (otherwise, access maybe improperly
+ * synchronized). Furthermore, the client must guarantee that the key
+ * and value arguments are 0-terminated strings that are NOT freed
+ * until the connection is closed. (The easiest way to do this is by
+ * passing only arguments to permanently allocated strings.).
+ *
+ * @param connection the connection for which a
+ * value should be set
+ * @param kind kind of the value
+ * @param key key for the value
+ * @param value the value itself
+ * @return #MHD_NO if the operation could not be
+ * performed due to insufficient memory;
+ * #MHD_YES on success
+ * @ingroup request
+ */
+int
+MHD_set_connection_value (struct MHD_Connection *connection,
+ enum MHD_ValueKind kind,
+ const char *key, const char *value)
+{
+ struct MHD_HTTP_Header *pos;
+
+ pos = MHD_pool_allocate (connection->pool,
+ sizeof (struct MHD_HTTP_Header), MHD_YES);
+ if (NULL == pos)
+ return MHD_NO;
+ pos->header = (char *) key;
+ pos->value = (char *) value;
+ pos->kind = kind;
+ pos->next = NULL;
+ /* append 'pos' to the linked list of headers */
+ if (NULL == connection->headers_received_tail)
+ {
+ connection->headers_received = pos;
+ connection->headers_received_tail = pos;
+ }
+ else
+ {
+ connection->headers_received_tail->next = pos;
+ connection->headers_received_tail = pos;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Get a particular header value. If multiple
+ * values match the kind, return any one of them.
+ *
+ * @param connection connection to get values from
+ * @param kind what kind of value are we looking for
+ * @param key the header to look for, NULL to lookup 'trailing' value without a key
+ * @return NULL if no such item was found
+ * @ingroup request
+ */
+const char *
+MHD_lookup_connection_value (struct MHD_Connection *connection,
+ enum MHD_ValueKind kind, const char *key)
+{
+ struct MHD_HTTP_Header *pos;
+
+ if (NULL == connection)
+ return NULL;
+ for (pos = connection->headers_received; NULL != pos; pos = pos->next)
+ if ((0 != (pos->kind & kind)) &&
+ ( (key == pos->header) ||
+ ( (NULL != pos->header) &&
+ (NULL != key) &&
+ (MHD_str_equal_caseless_(key, pos->header)))))
+ return pos->value;
+ return NULL;
+}
+
+
+/**
+ * Do we (still) need to send a 100 continue
+ * message for this connection?
+ *
+ * @param connection connection to test
+ * @return 0 if we don't need 100 CONTINUE, 1 if we do
+ */
+static int
+need_100_continue (struct MHD_Connection *connection)
+{
+ const char *expect;
+
+ return ( (NULL == connection->response) &&
+ (NULL != connection->version) &&
+ (MHD_str_equal_caseless_(connection->version,
+ MHD_HTTP_VERSION_1_1)) &&
+ (NULL != (expect = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_EXPECT))) &&
+ (MHD_str_equal_caseless_(expect, "100-continue")) &&
+ (connection->continue_message_write_offset <
+ strlen (HTTP_100_CONTINUE)) );
+}
+
+
+/**
+ * Close the given connection and give the
+ * specified termination code to the user.
+ *
+ * @param connection connection to close
+ * @param termination_code termination reason to give
+ */
+void
+MHD_connection_close (struct MHD_Connection *connection,
+ enum MHD_RequestTerminationCode termination_code)
+{
+ struct MHD_Daemon *daemon;
+
+ daemon = connection->daemon;
+ if (0 == (connection->daemon->options & MHD_USE_EPOLL_TURBO))
+ shutdown (connection->socket_fd,
+ (MHD_YES == connection->read_closed) ? SHUT_WR : SHUT_RDWR);
+ connection->state = MHD_CONNECTION_CLOSED;
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP;
+ if ( (NULL != daemon->notify_completed) &&
+ (MHD_YES == connection->client_aware) )
+ daemon->notify_completed (daemon->notify_completed_cls,
+ connection,
+ &connection->client_context,
+ termination_code);
+ connection->client_aware = MHD_NO;
+}
+
+
+/**
+ * A serious error occured, close the
+ * connection (and notify the application).
+ *
+ * @param connection connection to close with error
+ * @param emsg error message (can be NULL)
+ */
+static void
+connection_close_error (struct MHD_Connection *connection,
+ const char *emsg)
+{
+#if HAVE_MESSAGES
+ if (NULL != emsg)
+ MHD_DLOG (connection->daemon, emsg);
+#endif
+ MHD_connection_close (connection, MHD_REQUEST_TERMINATED_WITH_ERROR);
+}
+
+
+/**
+ * Macro to only include error message in call to
+ * "connection_close_error" if we have HAVE_MESSAGES.
+ */
+#if HAVE_MESSAGES
+#define CONNECTION_CLOSE_ERROR(c, emsg) connection_close_error (c, emsg)
+#else
+#define CONNECTION_CLOSE_ERROR(c, emsg) connection_close_error (c, NULL)
+#endif
+
+
+/**
+ * Prepare the response buffer of this connection for
+ * sending. Assumes that the response mutex is
+ * already held. If the transmission is complete,
+ * this function may close the socket (and return
+ * #MHD_NO).
+ *
+ * @param connection the connection
+ * @return #MHD_NO if readying the response failed (the
+ * lock on the response will have been released already
+ * in this case).
+ */
+static int
+try_ready_normal_body (struct MHD_Connection *connection)
+{
+ ssize_t ret;
+ struct MHD_Response *response;
+
+ response = connection->response;
+ if (NULL == response->crc)
+ return MHD_YES;
+ if (0 == response->total_size)
+ return MHD_YES; /* 0-byte response is always ready */
+ if ( (response->data_start <=
+ connection->response_write_position) &&
+ (response->data_size + response->data_start >
+ connection->response_write_position) )
+ return MHD_YES; /* response already ready */
+#if LINUX
+ if ( (MHD_INVALID_SOCKET != response->fd) &&
+ (0 == (connection->daemon->options & MHD_USE_SSL)) )
+ {
+ /* will use sendfile, no need to bother response crc */
+ return MHD_YES;
+ }
+#endif
+
+ ret = response->crc (response->crc_cls,
+ connection->response_write_position,
+ response->data,
+ MHD_MIN (response->data_buffer_size,
+ response->total_size -
+ connection->response_write_position));
+ if ( (((ssize_t) MHD_CONTENT_READER_END_OF_STREAM) == ret) ||
+ (((ssize_t) MHD_CONTENT_READER_END_WITH_ERROR) == ret) )
+ {
+ /* either error or http 1.0 transfer, close socket! */
+ response->total_size = connection->response_write_position;
+ if (NULL != response->crc)
+ (void) MHD_mutex_unlock_ (&response->mutex);
+ if ( ((ssize_t)MHD_CONTENT_READER_END_OF_STREAM) == ret)
+ MHD_connection_close (connection, MHD_REQUEST_TERMINATED_COMPLETED_OK);
+ else
+ CONNECTION_CLOSE_ERROR (connection,
+ "Closing connection (stream error)\n");
+ return MHD_NO;
+ }
+ response->data_start = connection->response_write_position;
+ response->data_size = ret;
+ if (0 == ret)
+ {
+ connection->state = MHD_CONNECTION_NORMAL_BODY_UNREADY;
+ if (NULL != response->crc)
+ (void) MHD_mutex_unlock_ (&response->mutex);
+ return MHD_NO;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Prepare the response buffer of this connection for sending.
+ * Assumes that the response mutex is already held. If the
+ * transmission is complete, this function may close the socket (and
+ * return MHD_NO).
+ *
+ * @param connection the connection
+ * @return #MHD_NO if readying the response failed
+ */
+static int
+try_ready_chunked_body (struct MHD_Connection *connection)
+{
+ ssize_t ret;
+ char *buf;
+ struct MHD_Response *response;
+ size_t size;
+ char cbuf[10]; /* 10: max strlen of "%x\r\n" */
+ size_t cblen;
+
+ response = connection->response;
+ if (0 == connection->write_buffer_size)
+ {
+ size = connection->daemon->pool_size;
+ do
+ {
+ size /= 2;
+ if (size < 128)
+ {
+ /* not enough memory */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Closing connection (out of memory)\n");
+ return MHD_NO;
+ }
+ buf = MHD_pool_allocate (connection->pool, size, MHD_NO);
+ }
+ while (NULL == buf);
+ connection->write_buffer_size = size;
+ connection->write_buffer = buf;
+ }
+
+ if ( (response->data_start <=
+ connection->response_write_position) &&
+ (response->data_size + response->data_start >
+ connection->response_write_position) )
+ {
+ /* buffer already ready, use what is there for the chunk */
+ ret = response->data_size + response->data_start - connection->response_write_position;
+ if ( (ret > 0) &&
+ (((size_t) ret) > connection->write_buffer_size - sizeof (cbuf) - 2) )
+ ret = connection->write_buffer_size - sizeof (cbuf) - 2;
+ memcpy (&connection->write_buffer[sizeof (cbuf)],
+ &response->data[connection->response_write_position - response->data_start],
+ ret);
+ }
+ else
+ {
+ /* buffer not in range, try to fill it */
+ if (0 == response->total_size)
+ ret = 0; /* response must be empty, don't bother calling crc */
+ else
+ ret = response->crc (response->crc_cls,
+ connection->response_write_position,
+ &connection->write_buffer[sizeof (cbuf)],
+ connection->write_buffer_size - sizeof (cbuf) - 2);
+ }
+ if ( ((ssize_t) MHD_CONTENT_READER_END_WITH_ERROR) == ret)
+ {
+ /* error, close socket! */
+ response->total_size = connection->response_write_position;
+ CONNECTION_CLOSE_ERROR (connection,
+ "Closing connection (error generating response)\n");
+ return MHD_NO;
+ }
+ if ( (((ssize_t) MHD_CONTENT_READER_END_OF_STREAM) == ret) ||
+ (0 == response->total_size) )
+ {
+ /* end of message, signal other side! */
+ strcpy (connection->write_buffer, "0\r\n");
+ connection->write_buffer_append_offset = 3;
+ connection->write_buffer_send_offset = 0;
+ response->total_size = connection->response_write_position;
+ return MHD_YES;
+ }
+ if (0 == ret)
+ {
+ connection->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY;
+ return MHD_NO;
+ }
+ if (ret > 0xFFFFFF)
+ ret = 0xFFFFFF;
+ MHD_snprintf_ (cbuf,
+ sizeof (cbuf),
+ "%X\r\n", (unsigned int) ret);
+ cblen = strlen (cbuf);
+ EXTRA_CHECK (cblen <= sizeof (cbuf));
+ memcpy (&connection->write_buffer[sizeof (cbuf) - cblen], cbuf, cblen);
+ memcpy (&connection->write_buffer[sizeof (cbuf) + ret], "\r\n", 2);
+ connection->response_write_position += ret;
+ connection->write_buffer_send_offset = sizeof (cbuf) - cblen;
+ connection->write_buffer_append_offset = sizeof (cbuf) + ret + 2;
+ return MHD_YES;
+}
+
+
+/**
+ * Are we allowed to keep the given connection alive? We can use the
+ * TCP stream for a second request if the connection is HTTP 1.1 and
+ * the "Connection" header either does not exist or is not set to
+ * "close", or if the connection is HTTP 1.0 and the "Connection"
+ * header is explicitly set to "keep-alive". If no HTTP version is
+ * specified (or if it is not 1.0 or 1.1), we definitively close the
+ * connection. If the "Connection" header is not exactly "close" or
+ * "keep-alive", we proceed to use the default for the respective HTTP
+ * version (which is conservative for HTTP 1.0, but might be a bit
+ * optimistic for HTTP 1.1).
+ *
+ * @param connection the connection to check for keepalive
+ * @return #MHD_YES if (based on the request), a keepalive is
+ * legal
+ */
+static int
+keepalive_possible (struct MHD_Connection *connection)
+{
+ const char *end;
+
+ if (NULL == connection->version)
+ return MHD_NO;
+ if ( (NULL != connection->response) &&
+ (0 != (connection->response->flags & MHD_RF_HTTP_VERSION_1_0_ONLY) ) )
+ return MHD_NO;
+ end = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONNECTION);
+ if (MHD_str_equal_caseless_(connection->version,
+ MHD_HTTP_VERSION_1_1))
+ {
+ if (NULL == end)
+ return MHD_YES;
+ if ( (MHD_str_equal_caseless_ (end, "close")) ||
+ (MHD_str_equal_caseless_ (end, "upgrade")) )
+ return MHD_NO;
+ return MHD_YES;
+ }
+ if (MHD_str_equal_caseless_(connection->version,
+ MHD_HTTP_VERSION_1_0))
+ {
+ if (NULL == end)
+ return MHD_NO;
+ if (MHD_str_equal_caseless_(end, "Keep-Alive"))
+ return MHD_YES;
+ return MHD_NO;
+ }
+ return MHD_NO;
+}
+
+
+/**
+ * Produce HTTP "Date:" header.
+ *
+ * @param date where to write the header, with
+ * at least 128 bytes available space.
+ */
+static void
+get_date_string (char *date)
+{
+ static const char *const days[] =
+ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+ static const char *const mons[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
+ "Nov", "Dec"
+ };
+ struct tm now;
+ time_t t;
+#if defined(_WIN32) && !defined(HAVE_GMTIME_S) && !defined(__CYGWIN__)
+ struct tm* pNow;
+#endif
+
+ date[0] = 0;
+ time (&t);
+#if !defined(_WIN32)
+ if (NULL != gmtime_r (&t, &now))
+ {
+#elif defined(HAVE_GMTIME_S)
+ if (0 == gmtime_s (&now, &t))
+ {
+#elif defined(__CYGWIN__)
+ if (NULL != gmtime_r (&t, &now))
+ {
+#else
+ pNow = gmtime(&t);
+ if (NULL != pNow)
+ {
+ now = *pNow;
+#endif
+ sprintf (date,
+ "Date: %3s, %02u %3s %04u %02u:%02u:%02u GMT\r\n",
+ days[now.tm_wday % 7],
+ (unsigned int) now.tm_mday,
+ mons[now.tm_mon % 12],
+ (unsigned int) (1900 + now.tm_year),
+ (unsigned int) now.tm_hour,
+ (unsigned int) now.tm_min,
+ (unsigned int) now.tm_sec);
+ }
+}
+
+
+/**
+ * Try growing the read buffer. We initially claim half the
+ * available buffer space for the read buffer (the other half
+ * being left for management data structures; the write
+ * buffer can in the end take virtually everything as the
+ * read buffer can be reduced to the minimum necessary at that
+ * point.
+ *
+ * @param connection the connection
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+static int
+try_grow_read_buffer (struct MHD_Connection *connection)
+{
+ void *buf;
+ size_t new_size;
+
+ if (0 == connection->read_buffer_size)
+ new_size = connection->daemon->pool_size / 2;
+ else
+ new_size = connection->read_buffer_size + MHD_BUF_INC_SIZE;
+ buf = MHD_pool_reallocate (connection->pool,
+ connection->read_buffer,
+ connection->read_buffer_size,
+ new_size);
+ if (NULL == buf)
+ return MHD_NO;
+ /* we can actually grow the buffer, do it! */
+ connection->read_buffer = buf;
+ connection->read_buffer_size = new_size;
+ return MHD_YES;
+}
+
+
+/**
+ * Allocate the connection's write buffer and fill it with all of the
+ * headers (or footers, if we have already sent the body) from the
+ * HTTPd's response. If headers are missing in the response supplied
+ * by the application, additional headers may be added here.
+ *
+ * @param connection the connection
+ * @return #MHD_YES on success, #MHD_NO on failure (out of memory)
+ */
+static int
+build_header_response (struct MHD_Connection *connection)
+{
+ size_t size;
+ size_t off;
+ struct MHD_HTTP_Header *pos;
+ char code[256];
+ char date[128];
+ char content_length_buf[128];
+ size_t content_length_len;
+ char *data;
+ enum MHD_ValueKind kind;
+ const char *reason_phrase;
+ uint32_t rc;
+ const char *client_requested_close;
+ const char *response_has_close;
+ const char *response_has_keepalive;
+ const char *have_encoding;
+ const char *have_content_length;
+ int must_add_close;
+ int must_add_chunked_encoding;
+ int must_add_keep_alive;
+ int must_add_content_length;
+
+ EXTRA_CHECK (NULL != connection->version);
+ if (0 == strlen (connection->version))
+ {
+ data = MHD_pool_allocate (connection->pool, 0, MHD_YES);
+ connection->write_buffer = data;
+ connection->write_buffer_append_offset = 0;
+ connection->write_buffer_send_offset = 0;
+ connection->write_buffer_size = 0;
+ return MHD_YES;
+ }
+ if (MHD_CONNECTION_FOOTERS_RECEIVED == connection->state)
+ {
+ rc = connection->responseCode & (~MHD_ICY_FLAG);
+ reason_phrase = MHD_get_reason_phrase_for (rc);
+ sprintf (code,
+ "%s %u %s\r\n",
+ (0 != (connection->responseCode & MHD_ICY_FLAG))
+ ? "ICY"
+ : ( (MHD_str_equal_caseless_ (MHD_HTTP_VERSION_1_0,
+ connection->version))
+ ? MHD_HTTP_VERSION_1_0
+ : MHD_HTTP_VERSION_1_1),
+ rc,
+ reason_phrase);
+ off = strlen (code);
+ /* estimate size */
+ size = off + 2; /* +2 for extra "\r\n" at the end */
+ kind = MHD_HEADER_KIND;
+ if ( (0 == (connection->daemon->options & MHD_SUPPRESS_DATE_NO_CLOCK)) &&
+ (NULL == MHD_get_response_header (connection->response,
+ MHD_HTTP_HEADER_DATE)) )
+ get_date_string (date);
+ else
+ date[0] = '\0';
+ size += strlen (date);
+ }
+ else
+ {
+ /* 2 bytes for final CRLF of a Chunked-Body */
+ size = 2;
+ kind = MHD_FOOTER_KIND;
+ off = 0;
+ }
+
+ /* calculate extra headers we need to add, such as 'Connection: close',
+ first see what was explicitly requested by the application */
+ must_add_close = MHD_NO;
+ must_add_chunked_encoding = MHD_NO;
+ must_add_keep_alive = MHD_NO;
+ must_add_content_length = MHD_NO;
+ switch (connection->state)
+ {
+ case MHD_CONNECTION_FOOTERS_RECEIVED:
+ response_has_close = MHD_get_response_header (connection->response,
+ MHD_HTTP_HEADER_CONNECTION);
+ response_has_keepalive = response_has_close;
+ if ( (NULL != response_has_close) &&
+ (!MHD_str_equal_caseless_ (response_has_close, "close")) )
+ response_has_close = NULL;
+ if ( (NULL != response_has_keepalive) &&
+ (!MHD_str_equal_caseless_ (response_has_keepalive, "Keep-Alive")) )
+ response_has_keepalive = NULL;
+ client_requested_close = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONNECTION);
+ if ( (NULL != client_requested_close) &&
+ (!MHD_str_equal_caseless_ (client_requested_close, "close")) )
+ client_requested_close = NULL;
+
+ /* now analyze chunked encoding situation */
+ connection->have_chunked_upload = MHD_NO;
+
+ if ( (MHD_SIZE_UNKNOWN == connection->response->total_size) &&
+ (NULL == response_has_close) &&
+ (NULL == client_requested_close) )
+ {
+ /* size is unknown, and close was not explicitly requested;
+ need to either to HTTP 1.1 chunked encoding or
+ close the connection */
+ /* 'close' header doesn't exist yet, see if we need to add one;
+ if the client asked for a close, no need to start chunk'ing */
+ if ( (MHD_YES == keepalive_possible (connection)) &&
+ (MHD_str_equal_caseless_ (MHD_HTTP_VERSION_1_1,
+ connection->version) ) )
+ {
+ have_encoding = MHD_get_response_header (connection->response,
+ MHD_HTTP_HEADER_TRANSFER_ENCODING);
+ if (NULL == have_encoding)
+ {
+ must_add_chunked_encoding = MHD_YES;
+ connection->have_chunked_upload = MHD_YES;
+ }
+ else if (MHD_str_equal_caseless_(have_encoding, "identity"))
+ {
+ /* application forced identity encoding, can't do 'chunked' */
+ must_add_close = MHD_YES;
+ }
+ else
+ {
+ connection->have_chunked_upload = MHD_YES;
+ }
+ }
+ else
+ {
+ /* Keep alive or chunking not possible
+ => set close header if not present */
+ if (NULL == response_has_close)
+ must_add_close = MHD_YES;
+ }
+ }
+
+ /* check for other reasons to add 'close' header */
+ if ( ( (NULL != client_requested_close) ||
+ (MHD_YES == connection->read_closed) ) &&
+ (NULL == response_has_close) &&
+ (0 == (connection->response->flags & MHD_RF_HTTP_VERSION_1_0_ONLY) ) )
+ must_add_close = MHD_YES;
+
+ /* check if we should add a 'content length' header */
+ have_content_length = MHD_get_response_header (connection->response,
+ MHD_HTTP_HEADER_CONTENT_LENGTH);
+
+ if ( (MHD_SIZE_UNKNOWN != connection->response->total_size) &&
+ (NULL == have_content_length) &&
+ ( (NULL == connection->method) ||
+ (! MHD_str_equal_caseless_ (connection->method,
+ MHD_HTTP_METHOD_CONNECT)) ) )
+ {
+ /*
+ Here we add a content-length if one is missing; however,
+ for 'connect' methods, the responses MUST NOT include a
+ content-length header *if* the response code is 2xx (in
+ which case we expect there to be no body). Still,
+ as we don't know the response code here in some cases, we
+ simply only force adding a content-length header if this
+ is not a 'connect' or if the response is not empty
+ (which is kind of more sane, because if some crazy
+ application did return content with a 2xx status code,
+ then having a content-length might again be a good idea).
+
+ Note that the change from 'SHOULD NOT' to 'MUST NOT' is
+ a recent development of the HTTP 1.1 specification.
+ */
+ content_length_len
+ = sprintf (content_length_buf,
+ MHD_HTTP_HEADER_CONTENT_LENGTH ": " MHD_UNSIGNED_LONG_LONG_PRINTF "\r\n",
+ (MHD_UNSIGNED_LONG_LONG) connection->response->total_size);
+ must_add_content_length = MHD_YES;
+ }
+
+ /* check for adding keep alive */
+ if ( (NULL == response_has_keepalive) &&
+ (NULL == response_has_close) &&
+ (MHD_NO == must_add_close) &&
+ (0 == (connection->response->flags & MHD_RF_HTTP_VERSION_1_0_ONLY) ) &&
+ (MHD_YES == keepalive_possible (connection)) )
+ must_add_keep_alive = MHD_YES;
+ break;
+ case MHD_CONNECTION_BODY_SENT:
+ break;
+ default:
+ EXTRA_CHECK (0);
+ }
+
+ if (must_add_close)
+ size += strlen ("Connection: close\r\n");
+ if (must_add_keep_alive)
+ size += strlen ("Connection: Keep-Alive\r\n");
+ if (must_add_chunked_encoding)
+ size += strlen ("Transfer-Encoding: chunked\r\n");
+ if (must_add_content_length)
+ size += content_length_len;
+ EXTRA_CHECK (! (must_add_close && must_add_keep_alive) );
+ EXTRA_CHECK (! (must_add_chunked_encoding && must_add_content_length) );
+
+ for (pos = connection->response->first_header; NULL != pos; pos = pos->next)
+ if ( (pos->kind == kind) &&
+ (! ( (MHD_YES == must_add_close) &&
+ (pos->value == response_has_keepalive) &&
+ (MHD_str_equal_caseless_(pos->header,
+ MHD_HTTP_HEADER_CONNECTION) ) ) ) )
+ size += strlen (pos->header) + strlen (pos->value) + 4; /* colon, space, linefeeds */
+ /* produce data */
+ data = MHD_pool_allocate (connection->pool, size + 1, MHD_NO);
+ if (NULL == data)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Not enough memory for write!\n");
+#endif
+ return MHD_NO;
+ }
+ if (MHD_CONNECTION_FOOTERS_RECEIVED == connection->state)
+ {
+ memcpy (data, code, off);
+ }
+ if (must_add_close)
+ {
+ /* we must add the 'Connection: close' header */
+ memcpy (&data[off],
+ "Connection: close\r\n",
+ strlen ("Connection: close\r\n"));
+ off += strlen ("Connection: close\r\n");
+ }
+ if (must_add_keep_alive)
+ {
+ /* we must add the 'Connection: Keep-Alive' header */
+ memcpy (&data[off],
+ "Connection: Keep-Alive\r\n",
+ strlen ("Connection: Keep-Alive\r\n"));
+ off += strlen ("Connection: Keep-Alive\r\n");
+ }
+ if (must_add_chunked_encoding)
+ {
+ /* we must add the 'Transfer-Encoding: chunked' header */
+ memcpy (&data[off],
+ "Transfer-Encoding: chunked\r\n",
+ strlen ("Transfer-Encoding: chunked\r\n"));
+ off += strlen ("Transfer-Encoding: chunked\r\n");
+ }
+ if (must_add_content_length)
+ {
+ /* we must add the 'Content-Length' header */
+ memcpy (&data[off],
+ content_length_buf,
+ content_length_len);
+ off += content_length_len;
+ }
+ for (pos = connection->response->first_header; NULL != pos; pos = pos->next)
+ if ( (pos->kind == kind) &&
+ (! ( (pos->value == response_has_keepalive) &&
+ (MHD_YES == must_add_close) &&
+ (MHD_str_equal_caseless_(pos->header,
+ MHD_HTTP_HEADER_CONNECTION) ) ) ) )
+ off += sprintf (&data[off],
+ "%s: %s\r\n",
+ pos->header,
+ pos->value);
+ if (MHD_CONNECTION_FOOTERS_RECEIVED == connection->state)
+ {
+ strcpy (&data[off], date);
+ off += strlen (date);
+ }
+ memcpy (&data[off], "\r\n", 2);
+ off += 2;
+
+ if (off != size)
+ mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL);
+ connection->write_buffer = data;
+ connection->write_buffer_append_offset = size;
+ connection->write_buffer_send_offset = 0;
+ connection->write_buffer_size = size + 1;
+ return MHD_YES;
+}
+
+
+/**
+ * We encountered an error processing the request.
+ * Handle it properly by stopping to read data
+ * and sending the indicated response code and message.
+ *
+ * @param connection the connection
+ * @param status_code the response code to send (400, 413 or 414)
+ * @param message the error message to send
+ */
+static void
+transmit_error_response (struct MHD_Connection *connection,
+ unsigned int status_code,
+ const char *message)
+{
+ struct MHD_Response *response;
+
+ if (NULL == connection->version)
+ {
+ /* we were unable to process the full header line, so we don't
+ really know what version the client speaks; assume 1.0 */
+ connection->version = MHD_HTTP_VERSION_1_0;
+ }
+ connection->state = MHD_CONNECTION_FOOTERS_RECEIVED;
+ connection->read_closed = MHD_YES;
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Error %u (`%s') processing request, closing connection.\n",
+ status_code, message);
+#endif
+ EXTRA_CHECK (NULL == connection->response);
+ response = MHD_create_response_from_buffer (strlen (message),
+ (void *) message,
+ MHD_RESPMEM_PERSISTENT);
+ MHD_queue_response (connection, status_code, response);
+ EXTRA_CHECK (NULL != connection->response);
+ MHD_destroy_response (response);
+ if (MHD_NO == build_header_response (connection))
+ {
+ /* oops - close! */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Closing connection (failed to create response header)\n");
+ }
+ else
+ {
+ connection->state = MHD_CONNECTION_HEADERS_SENDING;
+ }
+}
+
+
+/**
+ * Update the 'event_loop_info' field of this connection based on the state
+ * that the connection is now in. May also close the connection or
+ * perform other updates to the connection if needed to prepare for
+ * the next round of the event loop.
+ *
+ * @param connection connetion to get poll set for
+ */
+static void
+MHD_connection_update_event_loop_info (struct MHD_Connection *connection)
+{
+ while (1)
+ {
+#if DEBUG_STATES
+ MHD_DLOG (connection->daemon,
+ "%s: state: %s\n",
+ __FUNCTION__,
+ MHD_state_to_string (connection->state));
+#endif
+ switch (connection->state)
+ {
+#if HTTPS_SUPPORT
+ case MHD_TLS_CONNECTION_INIT:
+ if (0 == gnutls_record_get_direction (connection->tls_session))
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
+ else
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
+ break;
+#endif
+ case MHD_CONNECTION_INIT:
+ case MHD_CONNECTION_URL_RECEIVED:
+ case MHD_CONNECTION_HEADER_PART_RECEIVED:
+ /* while reading headers, we always grow the
+ read buffer if needed, no size-check required */
+ if ( (connection->read_buffer_offset == connection->read_buffer_size) &&
+ (MHD_NO == try_grow_read_buffer (connection)) )
+ {
+ transmit_error_response (connection,
+ (connection->url != NULL)
+ ? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE
+ : MHD_HTTP_REQUEST_URI_TOO_LONG,
+ REQUEST_TOO_BIG);
+ continue;
+ }
+ if (MHD_NO == connection->read_closed)
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
+ else
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK;
+ break;
+ case MHD_CONNECTION_HEADERS_RECEIVED:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_HEADERS_PROCESSED:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_CONTINUE_SENDING:
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
+ break;
+ case MHD_CONNECTION_CONTINUE_SENT:
+ if (connection->read_buffer_offset == connection->read_buffer_size)
+ {
+ if ((MHD_YES != try_grow_read_buffer (connection)) &&
+ (0 != (connection->daemon->options &
+ (MHD_USE_SELECT_INTERNALLY |
+ MHD_USE_THREAD_PER_CONNECTION))))
+ {
+ /* failed to grow the read buffer, and the
+ client which is supposed to handle the
+ received data in a *blocking* fashion
+ (in this mode) did not handle the data as
+ it was supposed to!
+ => we would either have to do busy-waiting
+ (on the client, which would likely fail),
+ or if we do nothing, we would just timeout
+ on the connection (if a timeout is even
+ set!).
+ Solution: we kill the connection with an error */
+ transmit_error_response (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ INTERNAL_ERROR);
+ continue;
+ }
+ }
+ if ( (connection->read_buffer_offset < connection->read_buffer_size) &&
+ (MHD_NO == connection->read_closed) )
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
+ else
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK;
+ break;
+ case MHD_CONNECTION_BODY_RECEIVED:
+ case MHD_CONNECTION_FOOTER_PART_RECEIVED:
+ /* while reading footers, we always grow the
+ read buffer if needed, no size-check required */
+ if (MHD_YES == connection->read_closed)
+ {
+ CONNECTION_CLOSE_ERROR (connection,
+ NULL);
+ continue;
+ }
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
+ /* transition to FOOTERS_RECEIVED
+ happens in read handler */
+ break;
+ case MHD_CONNECTION_FOOTERS_RECEIVED:
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK;
+ break;
+ case MHD_CONNECTION_HEADERS_SENDING:
+ /* headers in buffer, keep writing */
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
+ break;
+ case MHD_CONNECTION_HEADERS_SENT:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_NORMAL_BODY_READY:
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
+ break;
+ case MHD_CONNECTION_NORMAL_BODY_UNREADY:
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK;
+ break;
+ case MHD_CONNECTION_CHUNKED_BODY_READY:
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
+ break;
+ case MHD_CONNECTION_CHUNKED_BODY_UNREADY:
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK;
+ break;
+ case MHD_CONNECTION_BODY_SENT:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_FOOTERS_SENDING:
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
+ break;
+ case MHD_CONNECTION_FOOTERS_SENT:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_CLOSED:
+ connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP;
+ return; /* do nothing, not even reading */
+ default:
+ EXTRA_CHECK (0);
+ }
+ break;
+ }
+}
+
+
+/**
+ * Parse a single line of the HTTP header. Advance
+ * read_buffer (!) appropriately. If the current line does not
+ * fit, consider growing the buffer. If the line is
+ * far too long, close the connection. If no line is
+ * found (incomplete, buffer too small, line too long),
+ * return NULL. Otherwise return a pointer to the line.
+ *
+ * @param connection connection we're processing
+ * @return NULL if no full line is available
+ */
+static char *
+get_next_header_line (struct MHD_Connection *connection)
+{
+ char *rbuf;
+ size_t pos;
+
+ if (0 == connection->read_buffer_offset)
+ return NULL;
+ pos = 0;
+ rbuf = connection->read_buffer;
+ while ((pos < connection->read_buffer_offset - 1) &&
+ ('\r' != rbuf[pos]) && ('\n' != rbuf[pos]))
+ pos++;
+ if ( (pos == connection->read_buffer_offset - 1) &&
+ ('\n' != rbuf[pos]) )
+ {
+ /* not found, consider growing... */
+ if ( (connection->read_buffer_offset == connection->read_buffer_size) &&
+ (MHD_NO ==
+ try_grow_read_buffer (connection)) )
+ {
+ transmit_error_response (connection,
+ (NULL != connection->url)
+ ? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE
+ : MHD_HTTP_REQUEST_URI_TOO_LONG,
+ REQUEST_TOO_BIG);
+ }
+ return NULL;
+ }
+ /* found, check if we have proper LFCR */
+ if (('\r' == rbuf[pos]) && ('\n' == rbuf[pos + 1]))
+ rbuf[pos++] = '\0'; /* skip both r and n */
+ rbuf[pos++] = '\0';
+ connection->read_buffer += pos;
+ connection->read_buffer_size -= pos;
+ connection->read_buffer_offset -= pos;
+ return rbuf;
+}
+
+
+/**
+ * Add an entry to the HTTP headers of a connection. If this fails,
+ * transmit an error response (request too big).
+ *
+ * @param connection the connection for which a
+ * value should be set
+ * @param kind kind of the value
+ * @param key key for the value
+ * @param value the value itself
+ * @return #MHD_NO on failure (out of memory), #MHD_YES for success
+ */
+static int
+connection_add_header (struct MHD_Connection *connection,
+ char *key, char *value, enum MHD_ValueKind kind)
+{
+ if (MHD_NO == MHD_set_connection_value (connection,
+ kind,
+ key, value))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Not enough memory to allocate header record!\n");
+#endif
+ transmit_error_response (connection, MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
+ REQUEST_TOO_BIG);
+ return MHD_NO;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Parse and unescape the arguments given by the client as part
+ * of the HTTP request URI.
+ *
+ * @param kind header kind to use for adding to the connection
+ * @param connection connection to add headers to
+ * @param args argument URI string (after "?" in URI)
+ * @return #MHD_NO on failure (out of memory), #MHD_YES for success
+ */
+static int
+parse_arguments (enum MHD_ValueKind kind,
+ struct MHD_Connection *connection,
+ char *args)
+{
+ char *equals;
+ char *amper;
+
+ while (NULL != args)
+ {
+ equals = strchr (args, '=');
+ amper = strchr (args, '&');
+ if (NULL == amper)
+ {
+ /* last argument */
+ if (NULL == equals)
+ {
+ /* got 'foo', add key 'foo' with NULL for value */
+ MHD_unescape_plus (args);
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ args);
+ return connection_add_header (connection,
+ args,
+ NULL,
+ kind);
+ }
+ /* got 'foo=bar' */
+ equals[0] = '\0';
+ equals++;
+ MHD_unescape_plus (args);
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ args);
+ MHD_unescape_plus (equals);
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ equals);
+ return connection_add_header (connection, args, equals, kind);
+ }
+ /* amper is non-NULL here */
+ amper[0] = '\0';
+ amper++;
+ if ( (NULL == equals) ||
+ (equals >= amper) )
+ {
+ /* got 'foo&bar' or 'foo&bar=val', add key 'foo' with NULL for value */
+ MHD_unescape_plus (args);
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ args);
+ if (MHD_NO ==
+ connection_add_header (connection,
+ args,
+ NULL,
+ kind))
+ return MHD_NO;
+ /* continue with 'bar' */
+ args = amper;
+ continue;
+
+ }
+ /* equals and amper are non-NULL here, and equals < amper,
+ so we got regular 'foo=value&bar...'-kind of argument */
+ equals[0] = '\0';
+ equals++;
+ MHD_unescape_plus (args);
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ args);
+ MHD_unescape_plus (equals);
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ equals);
+ if (MHD_NO == connection_add_header (connection, args, equals, kind))
+ return MHD_NO;
+ args = amper;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Parse the cookie header (see RFC 2109).
+ *
+ * @return #MHD_YES for success, #MHD_NO for failure (malformed, out of memory)
+ */
+static int
+parse_cookie_header (struct MHD_Connection *connection)
+{
+ const char *hdr;
+ char *cpy;
+ char *pos;
+ char *sce;
+ char *semicolon;
+ char *equals;
+ char *ekill;
+ char old;
+ int quotes;
+
+ hdr = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_COOKIE);
+ if (NULL == hdr)
+ return MHD_YES;
+ cpy = MHD_pool_allocate (connection->pool, strlen (hdr) + 1, MHD_YES);
+ if (NULL == cpy)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Not enough memory to parse cookies!\n");
+#endif
+ transmit_error_response (connection, MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
+ REQUEST_TOO_BIG);
+ return MHD_NO;
+ }
+ memcpy (cpy, hdr, strlen (hdr) + 1);
+ pos = cpy;
+ while (NULL != pos)
+ {
+ while (' ' == *pos)
+ pos++; /* skip spaces */
+
+ sce = pos;
+ while (((*sce) != '\0') &&
+ ((*sce) != ',') && ((*sce) != ';') && ((*sce) != '='))
+ sce++;
+ /* remove tailing whitespace (if any) from key */
+ ekill = sce - 1;
+ while ((*ekill == ' ') && (ekill >= pos))
+ *(ekill--) = '\0';
+ old = *sce;
+ *sce = '\0';
+ if (old != '=')
+ {
+ /* value part omitted, use empty string... */
+ if (MHD_NO ==
+ connection_add_header (connection, pos, "", MHD_COOKIE_KIND))
+ return MHD_NO;
+ if (old == '\0')
+ break;
+ pos = sce + 1;
+ continue;
+ }
+ equals = sce + 1;
+ quotes = 0;
+ semicolon = equals;
+ while ( ('\0' != semicolon[0]) &&
+ ( (0 != quotes) ||
+ ( (';' != semicolon[0]) &&
+ (',' != semicolon[0]) ) ) )
+ {
+ if ('"' == semicolon[0])
+ quotes = (quotes + 1) & 1;
+ semicolon++;
+ }
+ if ('\0' == semicolon[0])
+ semicolon = NULL;
+ if (NULL != semicolon)
+ {
+ semicolon[0] = '\0';
+ semicolon++;
+ }
+ /* remove quotes */
+ if ( ('"' == equals[0]) &&
+ ('"' == equals[strlen (equals) - 1]) )
+ {
+ equals[strlen (equals) - 1] = '\0';
+ equals++;
+ }
+ if (MHD_NO == connection_add_header (connection,
+ pos, equals, MHD_COOKIE_KIND))
+ return MHD_NO;
+ pos = semicolon;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Parse the first line of the HTTP HEADER.
+ *
+ * @param connection the connection (updated)
+ * @param line the first line
+ * @return #MHD_YES if the line is ok, #MHD_NO if it is malformed
+ */
+static int
+parse_initial_message_line (struct MHD_Connection *connection,
+ char *line)
+{
+ char *uri;
+ char *http_version;
+ char *args;
+
+ if (NULL == (uri = strchr (line, ' ')))
+ return MHD_NO; /* serious error */
+ uri[0] = '\0';
+ connection->method = line;
+ uri++;
+ while (' ' == uri[0])
+ uri++;
+ http_version = strchr (uri, ' ');
+ if (NULL != http_version)
+ {
+ http_version[0] = '\0';
+ http_version++;
+ }
+ if (NULL != connection->daemon->uri_log_callback)
+ connection->client_context
+ = connection->daemon->uri_log_callback (connection->daemon->uri_log_callback_cls,
+ uri,
+ connection);
+ args = strchr (uri, '?');
+ if (NULL != args)
+ {
+ args[0] = '\0';
+ args++;
+ parse_arguments (MHD_GET_ARGUMENT_KIND, connection, args);
+ }
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ uri);
+ connection->url = uri;
+ if (NULL == http_version)
+ connection->version = "";
+ else
+ connection->version = http_version;
+ return MHD_YES;
+}
+
+
+/**
+ * Call the handler of the application for this
+ * connection. Handles chunking of the upload
+ * as well as normal uploads.
+ *
+ * @param connection connection we're processing
+ */
+static void
+call_connection_handler (struct MHD_Connection *connection)
+{
+ size_t processed;
+
+ if (NULL != connection->response)
+ return; /* already queued a response */
+ processed = 0;
+ connection->client_aware = MHD_YES;
+ if (MHD_NO ==
+ connection->daemon->default_handler (connection->daemon-> default_handler_cls,
+ connection,
+ connection->url,
+ connection->method,
+ connection->version,
+ NULL, &processed,
+ &connection->client_context))
+ {
+ /* serious internal error, close connection */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Internal application error, closing connection.\n");
+ return;
+ }
+}
+
+
+
+/**
+ * Call the handler of the application for this
+ * connection. Handles chunking of the upload
+ * as well as normal uploads.
+ *
+ * @param connection connection we're processing
+ */
+static void
+process_request_body (struct MHD_Connection *connection)
+{
+ size_t processed;
+ size_t available;
+ size_t used;
+ size_t i;
+ int instant_retry;
+ int malformed;
+ char *buffer_head;
+ char *end;
+
+ if (NULL != connection->response)
+ return; /* already queued a response */
+
+ buffer_head = connection->read_buffer;
+ available = connection->read_buffer_offset;
+ do
+ {
+ instant_retry = MHD_NO;
+ if ( (MHD_YES == connection->have_chunked_upload) &&
+ (MHD_SIZE_UNKNOWN == connection->remaining_upload_size) )
+ {
+ if ( (connection->current_chunk_offset == connection->current_chunk_size) &&
+ (0 != connection->current_chunk_offset) &&
+ (available >= 2) )
+ {
+ /* skip new line at the *end* of a chunk */
+ i = 0;
+ if ((buffer_head[i] == '\r') || (buffer_head[i] == '\n'))
+ i++; /* skip 1st part of line feed */
+ if ((buffer_head[i] == '\r') || (buffer_head[i] == '\n'))
+ i++; /* skip 2nd part of line feed */
+ if (i == 0)
+ {
+ /* malformed encoding */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Received malformed HTTP request (bad chunked encoding), closing connection.\n");
+ return;
+ }
+ available -= i;
+ buffer_head += i;
+ connection->current_chunk_offset = 0;
+ connection->current_chunk_size = 0;
+ }
+ if (connection->current_chunk_offset <
+ connection->current_chunk_size)
+ {
+ /* we are in the middle of a chunk, give
+ as much as possible to the client (without
+ crossing chunk boundaries) */
+ processed =
+ connection->current_chunk_size -
+ connection->current_chunk_offset;
+ if (processed > available)
+ processed = available;
+ if (available > processed)
+ instant_retry = MHD_YES;
+ }
+ else
+ {
+ /* we need to read chunk boundaries */
+ i = 0;
+ while (i < available)
+ {
+ if ((buffer_head[i] == '\r') || (buffer_head[i] == '\n'))
+ break;
+ i++;
+ if (i >= 6)
+ break;
+ }
+ /* take '\n' into account; if '\n'
+ is the unavailable character, we
+ will need to wait until we have it
+ before going further */
+ if ((i + 1 >= available) &&
+ !((i == 1) && (available == 2) && (buffer_head[0] == '0')))
+ break; /* need more data... */
+ malformed = (i >= 6);
+ if (!malformed)
+ {
+ buffer_head[i] = '\0';
+ connection->current_chunk_size = strtoul (buffer_head, &end, 16);
+ malformed = ('\0' != *end);
+ }
+ if (malformed)
+ {
+ /* malformed encoding */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Received malformed HTTP request (bad chunked encoding), closing connection.\n");
+ return;
+ }
+ i++;
+ if ((i < available) &&
+ ((buffer_head[i] == '\r') || (buffer_head[i] == '\n')))
+ i++; /* skip 2nd part of line feed */
+
+ buffer_head += i;
+ available -= i;
+ connection->current_chunk_offset = 0;
+
+ if (available > 0)
+ instant_retry = MHD_YES;
+ if (0 == connection->current_chunk_size)
+ {
+ connection->remaining_upload_size = 0;
+ break;
+ }
+ continue;
+ }
+ }
+ else
+ {
+ /* no chunked encoding, give all to the client */
+ if ( (0 != connection->remaining_upload_size) &&
+ (MHD_SIZE_UNKNOWN != connection->remaining_upload_size) &&
+ (connection->remaining_upload_size < available) )
+ {
+ processed = connection->remaining_upload_size;
+ }
+ else
+ {
+ /**
+ * 1. no chunked encoding, give all to the client
+ * 2. client may send large chunked data, but only a smaller part is available at one time.
+ */
+ processed = available;
+ }
+ }
+ used = processed;
+ connection->client_aware = MHD_YES;
+ if (MHD_NO ==
+ connection->daemon->default_handler (connection->daemon->default_handler_cls,
+ connection,
+ connection->url,
+ connection->method,
+ connection->version,
+ buffer_head,
+ &processed,
+ &connection->client_context))
+ {
+ /* serious internal error, close connection */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Internal application error, closing connection.\n");
+ return;
+ }
+ if (processed > used)
+ mhd_panic (mhd_panic_cls, __FILE__, __LINE__
+#if HAVE_MESSAGES
+ , "API violation"
+#else
+ , NULL
+#endif
+ );
+ if (0 != processed)
+ instant_retry = MHD_NO; /* client did not process everything */
+ used -= processed;
+ if (connection->have_chunked_upload == MHD_YES)
+ connection->current_chunk_offset += used;
+ /* dh left "processed" bytes in buffer for next time... */
+ buffer_head += used;
+ available -= used;
+ if (connection->remaining_upload_size != MHD_SIZE_UNKNOWN)
+ connection->remaining_upload_size -= used;
+ }
+ while (MHD_YES == instant_retry);
+ if (available > 0)
+ memmove (connection->read_buffer, buffer_head, available);
+ connection->read_buffer_offset = available;
+}
+
+
+/**
+ * Try reading data from the socket into the
+ * read buffer of the connection.
+ *
+ * @param connection connection we're processing
+ * @return #MHD_YES if something changed,
+ * #MHD_NO if we were interrupted or if
+ * no space was available
+ */
+static int
+do_read (struct MHD_Connection *connection)
+{
+ int bytes_read;
+
+ if (connection->read_buffer_size == connection->read_buffer_offset)
+ return MHD_NO;
+ bytes_read = connection->recv_cls (connection,
+ &connection->read_buffer
+ [connection->read_buffer_offset],
+ connection->read_buffer_size -
+ connection->read_buffer_offset);
+ if (bytes_read < 0)
+ {
+ const int err = MHD_socket_errno_;
+ if ((EINTR == err) || (EAGAIN == err) || (EWOULDBLOCK == err))
+ return MHD_NO;
+ if (ECONNRESET == err)
+ {
+ CONNECTION_CLOSE_ERROR (connection, NULL);
+ return MHD_NO;
+ }
+ CONNECTION_CLOSE_ERROR (connection, NULL);
+ return MHD_YES;
+ }
+ if (0 == bytes_read)
+ {
+ /* other side closed connection; RFC 2616, section 8.1.4 suggests
+ we should then shutdown ourselves as well. */
+ connection->read_closed = MHD_YES;
+ MHD_connection_close (connection,
+ MHD_REQUEST_TERMINATED_CLIENT_ABORT);
+ return MHD_YES;
+ }
+ connection->read_buffer_offset += bytes_read;
+ return MHD_YES;
+}
+
+
+/**
+ * Try writing data to the socket from the
+ * write buffer of the connection.
+ *
+ * @param connection connection we're processing
+ * @return #MHD_YES if something changed,
+ * #MHD_NO if we were interrupted
+ */
+static int
+do_write (struct MHD_Connection *connection)
+{
+ ssize_t ret;
+ size_t max;
+
+ max = connection->write_buffer_append_offset - connection->write_buffer_send_offset;
+ ret = connection->send_cls (connection,
+ &connection->write_buffer
+ [connection->write_buffer_send_offset],
+ max);
+
+ if (ret < 0)
+ {
+ const int err = MHD_socket_errno_;
+ if ((EINTR == err) || (EAGAIN == err) || (EWOULDBLOCK == err))
+ return MHD_NO;
+ CONNECTION_CLOSE_ERROR (connection, NULL);
+ return MHD_YES;
+ }
+#if DEBUG_SEND_DATA
+ fprintf (stderr,
+ "Sent response: `%.*s'\n",
+ ret,
+ &connection->write_buffer[connection->write_buffer_send_offset]);
+#endif
+ /* only increment if this wasn't a "sendfile" transmission without
+ buffer involvement! */
+ if (0 != max)
+ connection->write_buffer_send_offset += ret;
+ return MHD_YES;
+}
+
+
+/**
+ * Check if we are done sending the write-buffer.
+ * If so, transition into "next_state".
+ *
+ * @param connection connection to check write status for
+ * @param next_state the next state to transition to
+ * @return #MHD_NO if we are not done, #MHD_YES if we are
+ */
+static int
+check_write_done (struct MHD_Connection *connection,
+ enum MHD_CONNECTION_STATE next_state)
+{
+ if (connection->write_buffer_append_offset !=
+ connection->write_buffer_send_offset)
+ return MHD_NO;
+ connection->write_buffer_append_offset = 0;
+ connection->write_buffer_send_offset = 0;
+ connection->state = next_state;
+ MHD_pool_reallocate (connection->pool,
+ connection->write_buffer,
+ connection->write_buffer_size, 0);
+ connection->write_buffer = NULL;
+ connection->write_buffer_size = 0;
+ return MHD_YES;
+}
+
+
+/**
+ * We have received (possibly the beginning of) a line in the
+ * header (or footer). Validate (check for ":") and prepare
+ * to process.
+ *
+ * @param connection connection we're processing
+ * @param line line from the header to process
+ * @return #MHD_YES on success, #MHD_NO on error (malformed @a line)
+ */
+static int
+process_header_line (struct MHD_Connection *connection, char *line)
+{
+ char *colon;
+
+ /* line should be normal header line, find colon */
+ colon = strchr (line, ':');
+ if (NULL == colon)
+ {
+ /* error in header line, die hard */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Received malformed line (no colon), closing connection.\n");
+ return MHD_NO;
+ }
+ /* zero-terminate header */
+ colon[0] = '\0';
+ colon++; /* advance to value */
+ while ((colon[0] != '\0') && ((colon[0] == ' ') || (colon[0] == '\t')))
+ colon++;
+ /* we do the actual adding of the connection
+ header at the beginning of the while
+ loop since we need to be able to inspect
+ the *next* header line (in case it starts
+ with a space...) */
+ connection->last = line;
+ connection->colon = colon;
+ return MHD_YES;
+}
+
+
+/**
+ * Process a header value that spans multiple lines.
+ * The previous line(s) are in connection->last.
+ *
+ * @param connection connection we're processing
+ * @param line the current input line
+ * @param kind if the line is complete, add a header
+ * of the given kind
+ * @return #MHD_YES if the line was processed successfully
+ */
+static int
+process_broken_line (struct MHD_Connection *connection,
+ char *line, enum MHD_ValueKind kind)
+{
+ char *last;
+ char *tmp;
+ size_t last_len;
+ size_t tmp_len;
+
+ last = connection->last;
+ if ((line[0] == ' ') || (line[0] == '\t'))
+ {
+ /* value was continued on the next line, see
+ http://www.jmarshall.com/easy/http/ */
+ last_len = strlen (last);
+ /* skip whitespace at start of 2nd line */
+ tmp = line;
+ while ((tmp[0] == ' ') || (tmp[0] == '\t'))
+ tmp++;
+ tmp_len = strlen (tmp);
+ /* FIXME: we might be able to do this better (faster!), as most
+ likely 'last' and 'line' should already be adjacent in
+ memory; however, doing this right gets tricky if we have a
+ value continued over multiple lines (in which case we need to
+ record how often we have done this so we can check for
+ adjaency); also, in the case where these are not adjacent
+ (not sure how it can happen!), we would want to allocate from
+ the end of the pool, so as to not destroy the read-buffer's
+ ability to grow nicely. */
+ last = MHD_pool_reallocate (connection->pool,
+ last,
+ last_len + 1,
+ last_len + tmp_len + 1);
+ if (NULL == last)
+ {
+ transmit_error_response (connection,
+ MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
+ REQUEST_TOO_BIG);
+ return MHD_NO;
+ }
+ memcpy (&last[last_len], tmp, tmp_len + 1);
+ connection->last = last;
+ return MHD_YES; /* possibly more than 2 lines... */
+ }
+ EXTRA_CHECK ((NULL != last) && (NULL != connection->colon));
+ if ((MHD_NO == connection_add_header (connection,
+ last, connection->colon, kind)))
+ {
+ transmit_error_response (connection, MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
+ REQUEST_TOO_BIG);
+ return MHD_NO;
+ }
+ /* we still have the current line to deal with... */
+ if (0 != strlen (line))
+ {
+ if (MHD_NO == process_header_line (connection, line))
+ {
+ transmit_error_response (connection,
+ MHD_HTTP_BAD_REQUEST, REQUEST_MALFORMED);
+ return MHD_NO;
+ }
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Parse the various headers; figure out the size
+ * of the upload and make sure the headers follow
+ * the protocol. Advance to the appropriate state.
+ *
+ * @param connection connection we're processing
+ */
+static void
+parse_connection_headers (struct MHD_Connection *connection)
+{
+ const char *clen;
+ MHD_UNSIGNED_LONG_LONG cval;
+ struct MHD_Response *response;
+ const char *enc;
+ char *end;
+
+ parse_cookie_header (connection);
+ if ( (0 != (MHD_USE_PEDANTIC_CHECKS & connection->daemon->options)) &&
+ (NULL != connection->version) &&
+ (MHD_str_equal_caseless_(MHD_HTTP_VERSION_1_1, connection->version)) &&
+ (NULL ==
+ MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_HOST)) )
+ {
+ /* die, http 1.1 request without host and we are pedantic */
+ connection->state = MHD_CONNECTION_FOOTERS_RECEIVED;
+ connection->read_closed = MHD_YES;
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Received `%s' request without `%s' header.\n",
+ MHD_HTTP_VERSION_1_1, MHD_HTTP_HEADER_HOST);
+#endif
+ EXTRA_CHECK (NULL == connection->response);
+ response =
+ MHD_create_response_from_buffer (strlen (REQUEST_LACKS_HOST),
+ REQUEST_LACKS_HOST,
+ MHD_RESPMEM_PERSISTENT);
+ MHD_queue_response (connection, MHD_HTTP_BAD_REQUEST, response);
+ MHD_destroy_response (response);
+ return;
+ }
+
+ connection->remaining_upload_size = 0;
+ enc = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_TRANSFER_ENCODING);
+ if (NULL != enc)
+ {
+ connection->remaining_upload_size = MHD_SIZE_UNKNOWN;
+ if (MHD_str_equal_caseless_(enc, "chunked"))
+ connection->have_chunked_upload = MHD_YES;
+ }
+ else
+ {
+ clen = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONTENT_LENGTH);
+ if (NULL != clen)
+ {
+ cval = strtoul (clen, &end, 10);
+ if ( ('\0' != *end) ||
+ ( (LONG_MAX == cval) && (errno == ERANGE) ) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Failed to parse `%s' header `%s', closing connection.\n",
+ MHD_HTTP_HEADER_CONTENT_LENGTH,
+ clen);
+#endif
+ CONNECTION_CLOSE_ERROR (connection, NULL);
+ return;
+ }
+ connection->remaining_upload_size = cval;
+ }
+ }
+}
+
+
+/**
+ * Update the 'last_activity' field of the connection to the current time
+ * and move the connection to the head of the 'normal_timeout' list if
+ * the timeout for the connection uses the default value.
+ *
+ * @param connection the connection that saw some activity
+ */
+static void
+update_last_activity (struct MHD_Connection *connection)
+{
+ struct MHD_Daemon *daemon = connection->daemon;
+
+ connection->last_activity = MHD_monotonic_time();
+ if (connection->connection_timeout != daemon->connection_timeout)
+ return; /* custom timeout, no need to move it in "normal" DLL */
+
+ /* move connection to head of timeout list (by remove + add operation) */
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ XDLL_remove (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ connection);
+ XDLL_insert (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ connection);
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+}
+
+
+/**
+ * This function handles a particular connection when it has been
+ * determined that there is data to be read off a socket.
+ *
+ * @param connection connection to handle
+ * @return always #MHD_YES (we should continue to process the
+ * connection)
+ */
+int
+MHD_connection_handle_read (struct MHD_Connection *connection)
+{
+ update_last_activity (connection);
+ if (MHD_CONNECTION_CLOSED == connection->state)
+ return MHD_YES;
+ /* make sure "read" has a reasonable number of bytes
+ in buffer to use per system call (if possible) */
+ if (connection->read_buffer_offset + connection->daemon->pool_increment >
+ connection->read_buffer_size)
+ try_grow_read_buffer (connection);
+ if (MHD_NO == do_read (connection))
+ return MHD_YES;
+ while (1)
+ {
+#if DEBUG_STATES
+ MHD_DLOG (connection->daemon, "%s: state: %s\n",
+ __FUNCTION__,
+ MHD_state_to_string (connection->state));
+#endif
+ switch (connection->state)
+ {
+ case MHD_CONNECTION_INIT:
+ case MHD_CONNECTION_URL_RECEIVED:
+ case MHD_CONNECTION_HEADER_PART_RECEIVED:
+ case MHD_CONNECTION_HEADERS_RECEIVED:
+ case MHD_CONNECTION_HEADERS_PROCESSED:
+ case MHD_CONNECTION_CONTINUE_SENDING:
+ case MHD_CONNECTION_CONTINUE_SENT:
+ case MHD_CONNECTION_BODY_RECEIVED:
+ case MHD_CONNECTION_FOOTER_PART_RECEIVED:
+ /* nothing to do but default action */
+ if (MHD_YES == connection->read_closed)
+ {
+ MHD_connection_close (connection,
+ MHD_REQUEST_TERMINATED_READ_ERROR);
+ continue;
+ }
+ break;
+ case MHD_CONNECTION_CLOSED:
+ return MHD_YES;
+ default:
+ /* shrink read buffer to how much is actually used */
+ MHD_pool_reallocate (connection->pool,
+ connection->read_buffer,
+ connection->read_buffer_size + 1,
+ connection->read_buffer_offset);
+ break;
+ }
+ break;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * This function was created to handle writes to sockets when it has
+ * been determined that the socket can be written to.
+ *
+ * @param connection connection to handle
+ * @return always #MHD_YES (we should continue to process the
+ * connection)
+ */
+int
+MHD_connection_handle_write (struct MHD_Connection *connection)
+{
+ struct MHD_Response *response;
+ ssize_t ret;
+
+ update_last_activity (connection);
+ while (1)
+ {
+#if DEBUG_STATES
+ MHD_DLOG (connection->daemon, "%s: state: %s\n",
+ __FUNCTION__,
+ MHD_state_to_string (connection->state));
+#endif
+ switch (connection->state)
+ {
+ case MHD_CONNECTION_INIT:
+ case MHD_CONNECTION_URL_RECEIVED:
+ case MHD_CONNECTION_HEADER_PART_RECEIVED:
+ case MHD_CONNECTION_HEADERS_RECEIVED:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_HEADERS_PROCESSED:
+ break;
+ case MHD_CONNECTION_CONTINUE_SENDING:
+ ret = connection->send_cls (connection,
+ &HTTP_100_CONTINUE
+ [connection->continue_message_write_offset],
+ strlen (HTTP_100_CONTINUE) -
+ connection->continue_message_write_offset);
+ if (ret < 0)
+ {
+ const int err = MHD_socket_errno_;
+ if ((err == EINTR) || (err == EAGAIN) || (EWOULDBLOCK == err))
+ break;
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Failed to send data: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ CONNECTION_CLOSE_ERROR (connection, NULL);
+ return MHD_YES;
+ }
+#if DEBUG_SEND_DATA
+ fprintf (stderr,
+ "Sent 100 continue response: `%.*s'\n",
+ (int) ret,
+ &HTTP_100_CONTINUE[connection->continue_message_write_offset]);
+#endif
+ connection->continue_message_write_offset += ret;
+ break;
+ case MHD_CONNECTION_CONTINUE_SENT:
+ case MHD_CONNECTION_BODY_RECEIVED:
+ case MHD_CONNECTION_FOOTER_PART_RECEIVED:
+ case MHD_CONNECTION_FOOTERS_RECEIVED:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_HEADERS_SENDING:
+ do_write (connection);
+ if (connection->state != MHD_CONNECTION_HEADERS_SENDING)
+ break;
+ check_write_done (connection, MHD_CONNECTION_HEADERS_SENT);
+ break;
+ case MHD_CONNECTION_HEADERS_SENT:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_NORMAL_BODY_READY:
+ response = connection->response;
+ if (NULL != response->crc)
+ (void) MHD_mutex_lock_ (&response->mutex);
+ if (MHD_YES != try_ready_normal_body (connection))
+ break;
+ ret = connection->send_cls (connection,
+ &response->data
+ [connection->response_write_position
+ - response->data_start],
+ response->data_size -
+ (connection->response_write_position
+ - response->data_start));
+ const int err = MHD_socket_errno_;
+#if DEBUG_SEND_DATA
+ if (ret > 0)
+ fprintf (stderr,
+ "Sent DATA response: `%.*s'\n",
+ (int) ret,
+ &response->data[connection->response_write_position -
+ response->data_start]);
+#endif
+ if (NULL != response->crc)
+ (void) MHD_mutex_unlock_ (&response->mutex);
+ if (ret < 0)
+ {
+ if ((err == EINTR) || (err == EAGAIN) || (EWOULDBLOCK == err))
+ return MHD_YES;
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Failed to send data: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ CONNECTION_CLOSE_ERROR (connection, NULL);
+ return MHD_YES;
+ }
+ connection->response_write_position += ret;
+ if (connection->response_write_position ==
+ connection->response->total_size)
+ connection->state = MHD_CONNECTION_FOOTERS_SENT; /* have no footers */
+ break;
+ case MHD_CONNECTION_NORMAL_BODY_UNREADY:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_CHUNKED_BODY_READY:
+ do_write (connection);
+ if (MHD_CONNECTION_CHUNKED_BODY_READY != connection->state)
+ break;
+ check_write_done (connection,
+ (connection->response->total_size ==
+ connection->response_write_position) ?
+ MHD_CONNECTION_BODY_SENT :
+ MHD_CONNECTION_CHUNKED_BODY_UNREADY);
+ break;
+ case MHD_CONNECTION_CHUNKED_BODY_UNREADY:
+ case MHD_CONNECTION_BODY_SENT:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_FOOTERS_SENDING:
+ do_write (connection);
+ if (connection->state != MHD_CONNECTION_FOOTERS_SENDING)
+ break;
+ check_write_done (connection, MHD_CONNECTION_FOOTERS_SENT);
+ break;
+ case MHD_CONNECTION_FOOTERS_SENT:
+ EXTRA_CHECK (0);
+ break;
+ case MHD_CONNECTION_CLOSED:
+ return MHD_YES;
+ case MHD_TLS_CONNECTION_INIT:
+ EXTRA_CHECK (0);
+ break;
+ default:
+ EXTRA_CHECK (0);
+ CONNECTION_CLOSE_ERROR (connection,
+ "Internal error\n");
+ return MHD_YES;
+ }
+ break;
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Clean up the state of the given connection and move it into the
+ * clean up queue for final disposal.
+ *
+ * @param connection handle for the connection to clean up
+ */
+static void
+cleanup_connection (struct MHD_Connection *connection)
+{
+ struct MHD_Daemon *daemon = connection->daemon;
+
+ if (NULL != connection->response)
+ {
+ MHD_destroy_response (connection->response);
+ connection->response = NULL;
+ }
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ if (connection->connection_timeout == daemon->connection_timeout)
+ XDLL_remove (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ connection);
+ else
+ XDLL_remove (daemon->manual_timeout_head,
+ daemon->manual_timeout_tail,
+ connection);
+ if (MHD_YES == connection->suspended)
+ DLL_remove (daemon->suspended_connections_head,
+ daemon->suspended_connections_tail,
+ connection);
+ else
+ DLL_remove (daemon->connections_head,
+ daemon->connections_tail,
+ connection);
+ DLL_insert (daemon->cleanup_head,
+ daemon->cleanup_tail,
+ connection);
+ connection->suspended = MHD_NO;
+ connection->resuming = MHD_NO;
+ connection->in_idle = MHD_NO;
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_(&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+}
+
+
+/**
+ * This function was created to handle per-connection processing that
+ * has to happen even if the socket cannot be read or written to.
+ *
+ * @param connection connection to handle
+ * @return #MHD_YES if we should continue to process the
+ * connection (not dead yet), #MHD_NO if it died
+ */
+int
+MHD_connection_handle_idle (struct MHD_Connection *connection)
+{
+ struct MHD_Daemon *daemon = connection->daemon;
+ unsigned int timeout;
+ const char *end;
+ char *line;
+ int client_close;
+
+ connection->in_idle = MHD_YES;
+ while (1)
+ {
+#if DEBUG_STATES
+ MHD_DLOG (daemon,
+ "%s: state: %s\n",
+ __FUNCTION__,
+ MHD_state_to_string (connection->state));
+#endif
+ switch (connection->state)
+ {
+ case MHD_CONNECTION_INIT:
+ line = get_next_header_line (connection);
+ if (NULL == line)
+ {
+ if (MHD_CONNECTION_INIT != connection->state)
+ continue;
+ if (MHD_YES == connection->read_closed)
+ {
+ CONNECTION_CLOSE_ERROR (connection,
+ NULL);
+ continue;
+ }
+ break;
+ }
+ if (MHD_NO == parse_initial_message_line (connection, line))
+ CONNECTION_CLOSE_ERROR (connection, NULL);
+ else
+ connection->state = MHD_CONNECTION_URL_RECEIVED;
+ continue;
+ case MHD_CONNECTION_URL_RECEIVED:
+ line = get_next_header_line (connection);
+ if (NULL == line)
+ {
+ if (MHD_CONNECTION_URL_RECEIVED != connection->state)
+ continue;
+ if (MHD_YES == connection->read_closed)
+ {
+ CONNECTION_CLOSE_ERROR (connection,
+ NULL);
+ continue;
+ }
+ break;
+ }
+ if (strlen (line) == 0)
+ {
+ connection->state = MHD_CONNECTION_HEADERS_RECEIVED;
+ continue;
+ }
+ if (MHD_NO == process_header_line (connection, line))
+ {
+ transmit_error_response (connection,
+ MHD_HTTP_BAD_REQUEST,
+ REQUEST_MALFORMED);
+ break;
+ }
+ connection->state = MHD_CONNECTION_HEADER_PART_RECEIVED;
+ continue;
+ case MHD_CONNECTION_HEADER_PART_RECEIVED:
+ line = get_next_header_line (connection);
+ if (NULL == line)
+ {
+ if (connection->state != MHD_CONNECTION_HEADER_PART_RECEIVED)
+ continue;
+ if (MHD_YES == connection->read_closed)
+ {
+ CONNECTION_CLOSE_ERROR (connection,
+ NULL);
+ continue;
+ }
+ break;
+ }
+ if (MHD_NO ==
+ process_broken_line (connection, line, MHD_HEADER_KIND))
+ continue;
+ if (0 == strlen (line))
+ {
+ connection->state = MHD_CONNECTION_HEADERS_RECEIVED;
+ continue;
+ }
+ continue;
+ case MHD_CONNECTION_HEADERS_RECEIVED:
+ parse_connection_headers (connection);
+ if (MHD_CONNECTION_CLOSED == connection->state)
+ continue;
+ connection->state = MHD_CONNECTION_HEADERS_PROCESSED;
+ continue;
+ case MHD_CONNECTION_HEADERS_PROCESSED:
+ call_connection_handler (connection); /* first call */
+ if (MHD_CONNECTION_CLOSED == connection->state)
+ continue;
+ if (need_100_continue (connection))
+ {
+ connection->state = MHD_CONNECTION_CONTINUE_SENDING;
+ break;
+ }
+ if ( (NULL != connection->response) &&
+ ( (MHD_str_equal_caseless_ (connection->method,
+ MHD_HTTP_METHOD_POST)) ||
+ (MHD_str_equal_caseless_ (connection->method,
+ MHD_HTTP_METHOD_PUT))) )
+ {
+ /* we refused (no upload allowed!) */
+ connection->remaining_upload_size = 0;
+ /* force close, in case client still tries to upload... */
+ connection->read_closed = MHD_YES;
+ }
+ connection->state = (0 == connection->remaining_upload_size)
+ ? MHD_CONNECTION_FOOTERS_RECEIVED : MHD_CONNECTION_CONTINUE_SENT;
+ continue;
+ case MHD_CONNECTION_CONTINUE_SENDING:
+ if (connection->continue_message_write_offset ==
+ strlen (HTTP_100_CONTINUE))
+ {
+ connection->state = MHD_CONNECTION_CONTINUE_SENT;
+ continue;
+ }
+ break;
+ case MHD_CONNECTION_CONTINUE_SENT:
+ if (0 != connection->read_buffer_offset)
+ {
+ process_request_body (connection); /* loop call */
+ if (MHD_CONNECTION_CLOSED == connection->state)
+ continue;
+ }
+ if ((0 == connection->remaining_upload_size) ||
+ ((connection->remaining_upload_size == MHD_SIZE_UNKNOWN) &&
+ (0 == connection->read_buffer_offset) &&
+ (MHD_YES == connection->read_closed)))
+ {
+ if ((MHD_YES == connection->have_chunked_upload) &&
+ (MHD_NO == connection->read_closed))
+ connection->state = MHD_CONNECTION_BODY_RECEIVED;
+ else
+ connection->state = MHD_CONNECTION_FOOTERS_RECEIVED;
+ continue;
+ }
+ break;
+ case MHD_CONNECTION_BODY_RECEIVED:
+ line = get_next_header_line (connection);
+ if (NULL == line)
+ {
+ if (connection->state != MHD_CONNECTION_BODY_RECEIVED)
+ continue;
+ if (MHD_YES == connection->read_closed)
+ {
+ CONNECTION_CLOSE_ERROR (connection,
+ NULL);
+ continue;
+ }
+ break;
+ }
+ if (0 == strlen (line))
+ {
+ connection->state = MHD_CONNECTION_FOOTERS_RECEIVED;
+ continue;
+ }
+ if (MHD_NO == process_header_line (connection, line))
+ {
+ transmit_error_response (connection,
+ MHD_HTTP_BAD_REQUEST,
+ REQUEST_MALFORMED);
+ break;
+ }
+ connection->state = MHD_CONNECTION_FOOTER_PART_RECEIVED;
+ continue;
+ case MHD_CONNECTION_FOOTER_PART_RECEIVED:
+ line = get_next_header_line (connection);
+ if (NULL == line)
+ {
+ if (connection->state != MHD_CONNECTION_FOOTER_PART_RECEIVED)
+ continue;
+ if (MHD_YES == connection->read_closed)
+ {
+ CONNECTION_CLOSE_ERROR (connection,
+ NULL);
+ continue;
+ }
+ break;
+ }
+ if (MHD_NO ==
+ process_broken_line (connection, line, MHD_FOOTER_KIND))
+ continue;
+ if (0 == strlen (line))
+ {
+ connection->state = MHD_CONNECTION_FOOTERS_RECEIVED;
+ continue;
+ }
+ continue;
+ case MHD_CONNECTION_FOOTERS_RECEIVED:
+ call_connection_handler (connection); /* "final" call */
+ if (connection->state == MHD_CONNECTION_CLOSED)
+ continue;
+ if (NULL == connection->response)
+ break; /* try again next time */
+ if (MHD_NO == build_header_response (connection))
+ {
+ /* oops - close! */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Closing connection (failed to create response header)\n");
+ continue;
+ }
+ connection->state = MHD_CONNECTION_HEADERS_SENDING;
+
+#if HAVE_DECL_TCP_CORK
+ /* starting header send, set TCP cork */
+ {
+ const int val = 1;
+ setsockopt (connection->socket_fd, IPPROTO_TCP, TCP_CORK, &val,
+ sizeof (val));
+ }
+#endif
+ break;
+ case MHD_CONNECTION_HEADERS_SENDING:
+ /* no default action */
+ break;
+ case MHD_CONNECTION_HEADERS_SENT:
+ if (connection->have_chunked_upload)
+ connection->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY;
+ else
+ connection->state = MHD_CONNECTION_NORMAL_BODY_UNREADY;
+ continue;
+ case MHD_CONNECTION_NORMAL_BODY_READY:
+ /* nothing to do here */
+ break;
+ case MHD_CONNECTION_NORMAL_BODY_UNREADY:
+ if (NULL != connection->response->crc)
+ (void) MHD_mutex_lock_ (&connection->response->mutex);
+ if (0 == connection->response->total_size)
+ {
+ if (NULL != connection->response->crc)
+ (void) MHD_mutex_unlock_ (&connection->response->mutex);
+ connection->state = MHD_CONNECTION_BODY_SENT;
+ continue;
+ }
+ if (MHD_YES == try_ready_normal_body (connection))
+ {
+ if (NULL != connection->response->crc)
+ (void) MHD_mutex_unlock_ (&connection->response->mutex);
+ connection->state = MHD_CONNECTION_NORMAL_BODY_READY;
+ break;
+ }
+ /* not ready, no socket action */
+ break;
+ case MHD_CONNECTION_CHUNKED_BODY_READY:
+ /* nothing to do here */
+ break;
+ case MHD_CONNECTION_CHUNKED_BODY_UNREADY:
+ if (NULL != connection->response->crc)
+ (void) MHD_mutex_lock_ (&connection->response->mutex);
+ if (0 == connection->response->total_size)
+ {
+ if (NULL != connection->response->crc)
+ (void) MHD_mutex_unlock_ (&connection->response->mutex);
+ connection->state = MHD_CONNECTION_BODY_SENT;
+ continue;
+ }
+ if (MHD_YES == try_ready_chunked_body (connection))
+ {
+ if (NULL != connection->response->crc)
+ (void) MHD_mutex_unlock_ (&connection->response->mutex);
+ connection->state = MHD_CONNECTION_CHUNKED_BODY_READY;
+ continue;
+ }
+ if (NULL != connection->response->crc)
+ (void) MHD_mutex_unlock_ (&connection->response->mutex);
+ break;
+ case MHD_CONNECTION_BODY_SENT:
+ if (MHD_NO == build_header_response (connection))
+ {
+ /* oops - close! */
+ CONNECTION_CLOSE_ERROR (connection,
+ "Closing connection (failed to create response header)\n");
+ continue;
+ }
+ if ( (MHD_NO == connection->have_chunked_upload) ||
+ (connection->write_buffer_send_offset ==
+ connection->write_buffer_append_offset) )
+ connection->state = MHD_CONNECTION_FOOTERS_SENT;
+ else
+ connection->state = MHD_CONNECTION_FOOTERS_SENDING;
+ continue;
+ case MHD_CONNECTION_FOOTERS_SENDING:
+ /* no default action */
+ break;
+ case MHD_CONNECTION_FOOTERS_SENT:
+#if HAVE_DECL_TCP_CORK
+ /* done sending, uncork */
+ {
+ const int val = 0;
+ setsockopt (connection->socket_fd, IPPROTO_TCP, TCP_CORK, &val,
+ sizeof (val));
+ }
+#endif
+ end =
+ MHD_get_response_header (connection->response,
+ MHD_HTTP_HEADER_CONNECTION);
+ client_close = ((NULL != end) && (MHD_str_equal_caseless_(end, "close")));
+ MHD_destroy_response (connection->response);
+ connection->response = NULL;
+ if ( (NULL != daemon->notify_completed) &&
+ (MHD_YES == connection->client_aware) )
+ {
+ daemon->notify_completed (daemon->notify_completed_cls,
+ connection,
+ &connection->client_context,
+ MHD_REQUEST_TERMINATED_COMPLETED_OK);
+ connection->client_aware = MHD_NO;
+ }
+ end =
+ MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONNECTION);
+ if ( (MHD_YES == connection->read_closed) ||
+ (client_close) ||
+ ((NULL != end) && (MHD_str_equal_caseless_ (end, "close"))) )
+ {
+ connection->read_closed = MHD_YES;
+ connection->read_buffer_offset = 0;
+ }
+ if (((MHD_YES == connection->read_closed) &&
+ (0 == connection->read_buffer_offset)) ||
+ (MHD_NO == keepalive_possible (connection)))
+ {
+ /* have to close for some reason */
+ MHD_connection_close (connection,
+ MHD_REQUEST_TERMINATED_COMPLETED_OK);
+ MHD_pool_destroy (connection->pool);
+ connection->pool = NULL;
+ connection->read_buffer = NULL;
+ connection->read_buffer_size = 0;
+ connection->read_buffer_offset = 0;
+ }
+ else
+ {
+ /* can try to keep-alive */
+ connection->version = NULL;
+ connection->state = MHD_CONNECTION_INIT;
+ connection->read_buffer
+ = MHD_pool_reset (connection->pool,
+ connection->read_buffer,
+ connection->read_buffer_size);
+ }
+ connection->client_aware = MHD_NO;
+ connection->client_context = NULL;
+ connection->continue_message_write_offset = 0;
+ connection->responseCode = 0;
+ connection->headers_received = NULL;
+ connection->headers_received_tail = NULL;
+ connection->response_write_position = 0;
+ connection->have_chunked_upload = MHD_NO;
+ connection->method = NULL;
+ connection->url = NULL;
+ connection->write_buffer = NULL;
+ connection->write_buffer_size = 0;
+ connection->write_buffer_send_offset = 0;
+ connection->write_buffer_append_offset = 0;
+ continue;
+ case MHD_CONNECTION_CLOSED:
+ cleanup_connection (connection);
+ return MHD_NO;
+ default:
+ EXTRA_CHECK (0);
+ break;
+ }
+ break;
+ }
+ timeout = connection->connection_timeout;
+ if ( (0 != timeout) &&
+ (timeout <= (MHD_monotonic_time() - connection->last_activity)) )
+ {
+ MHD_connection_close (connection, MHD_REQUEST_TERMINATED_TIMEOUT_REACHED);
+ connection->in_idle = MHD_NO;
+ return MHD_YES;
+ }
+ MHD_connection_update_event_loop_info (connection);
+#if EPOLL_SUPPORT
+ switch (connection->event_loop_info)
+ {
+ case MHD_EVENT_LOOP_INFO_READ:
+ if ( (0 != (connection->epoll_state & MHD_EPOLL_STATE_READ_READY)) &&
+ (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED)) &&
+ (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL)) )
+ {
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ connection);
+ connection->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ break;
+ case MHD_EVENT_LOOP_INFO_WRITE:
+ if ( (0 != (connection->epoll_state & MHD_EPOLL_STATE_WRITE_READY)) &&
+ (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED)) &&
+ (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL)) )
+ {
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ connection);
+ connection->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ break;
+ case MHD_EVENT_LOOP_INFO_BLOCK:
+ /* we should look at this connection again in the next iteration
+ of the event loop, as we're waiting on the application */
+ if ( (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) &&
+ (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED))) )
+ {
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ connection);
+ connection->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ break;
+ case MHD_EVENT_LOOP_INFO_CLEANUP:
+ /* This connection is finished, nothing left to do */
+ break;
+ }
+ return MHD_connection_epoll_update_ (connection);
+#else
+ return MHD_YES;
+#endif
+}
+
+
+#if EPOLL_SUPPORT
+/**
+ * Perform epoll() processing, possibly moving the connection back into
+ * the epoll() set if needed.
+ *
+ * @param connection connection to process
+ * @return #MHD_YES if we should continue to process the
+ * connection (not dead yet), #MHD_NO if it died
+ */
+int
+MHD_connection_epoll_update_ (struct MHD_Connection *connection)
+{
+ struct MHD_Daemon *daemon = connection->daemon;
+
+ if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EPOLL_SET)) &&
+ (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED)) &&
+ ( (0 == (connection->epoll_state & MHD_EPOLL_STATE_WRITE_READY)) ||
+ ( (0 == (connection->epoll_state & MHD_EPOLL_STATE_READ_READY)) &&
+ ( (MHD_EVENT_LOOP_INFO_READ == connection->event_loop_info) ||
+ (connection->read_buffer_size > connection->read_buffer_offset) ) &&
+ (MHD_NO == connection->read_closed) ) ) )
+ {
+ /* add to epoll set */
+ struct epoll_event event;
+
+ event.events = EPOLLIN | EPOLLOUT | EPOLLET;
+ event.data.ptr = connection;
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_ADD,
+ connection->socket_fd,
+ &event))
+ {
+#if HAVE_MESSAGES
+ if (0 != (daemon->options & MHD_USE_DEBUG))
+ MHD_DLOG (daemon,
+ "Call to epoll_ctl failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ connection->state = MHD_CONNECTION_CLOSED;
+ cleanup_connection (connection);
+ return MHD_NO;
+ }
+ connection->epoll_state |= MHD_EPOLL_STATE_IN_EPOLL_SET;
+ }
+ connection->in_idle = MHD_NO;
+ return MHD_YES;
+}
+#endif
+
+
+/**
+ * Set callbacks for this connection to those for HTTP.
+ *
+ * @param connection connection to initialize
+ */
+void
+MHD_set_http_callbacks_ (struct MHD_Connection *connection)
+{
+ connection->read_handler = &MHD_connection_handle_read;
+ connection->write_handler = &MHD_connection_handle_write;
+ connection->idle_handler = &MHD_connection_handle_idle;
+}
+
+
+/**
+ * Obtain information about the given connection.
+ *
+ * @param connection what connection to get information about
+ * @param info_type what information is desired?
+ * @param ... depends on @a info_type
+ * @return NULL if this information is not available
+ * (or if the @a info_type is unknown)
+ * @ingroup specialized
+ */
+const union MHD_ConnectionInfo *
+MHD_get_connection_info (struct MHD_Connection *connection,
+ enum MHD_ConnectionInfoType info_type, ...)
+{
+ switch (info_type)
+ {
+#if HTTPS_SUPPORT
+ case MHD_CONNECTION_INFO_CIPHER_ALGO:
+ if (connection->tls_session == NULL)
+ return NULL;
+ connection->cipher = gnutls_cipher_get (connection->tls_session);
+ return (const union MHD_ConnectionInfo *) &connection->cipher;
+ case MHD_CONNECTION_INFO_PROTOCOL:
+ if (connection->tls_session == NULL)
+ return NULL;
+ connection->protocol = gnutls_protocol_get_version (connection->tls_session);
+ return (const union MHD_ConnectionInfo *) &connection->protocol;
+ case MHD_CONNECTION_INFO_GNUTLS_SESSION:
+ if (connection->tls_session == NULL)
+ return NULL;
+ return (const union MHD_ConnectionInfo *) &connection->tls_session;
+#endif
+ case MHD_CONNECTION_INFO_CLIENT_ADDRESS:
+ return (const union MHD_ConnectionInfo *) &connection->addr;
+ case MHD_CONNECTION_INFO_DAEMON:
+ return (const union MHD_ConnectionInfo *) &connection->daemon;
+ case MHD_CONNECTION_INFO_CONNECTION_FD:
+ return (const union MHD_ConnectionInfo *) &connection->socket_fd;
+ case MHD_CONNECTION_INFO_SOCKET_CONTEXT:
+ return (const union MHD_ConnectionInfo *) &connection->socket_context;
+ default:
+ return NULL;
+ };
+}
+
+
+/**
+ * Set a custom option for the given connection, overriding defaults.
+ *
+ * @param connection connection to modify
+ * @param option option to set
+ * @param ... arguments to the option, depending on the option type
+ * @return #MHD_YES on success, #MHD_NO if setting the option failed
+ * @ingroup specialized
+ */
+int
+MHD_set_connection_option (struct MHD_Connection *connection,
+ enum MHD_CONNECTION_OPTION option,
+ ...)
+{
+ va_list ap;
+ struct MHD_Daemon *daemon;
+
+ daemon = connection->daemon;
+ switch (option)
+ {
+ case MHD_CONNECTION_OPTION_TIMEOUT:
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ if (MHD_YES != connection->suspended)
+ {
+ if (connection->connection_timeout == daemon->connection_timeout)
+ XDLL_remove (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ connection);
+ else
+ XDLL_remove (daemon->manual_timeout_head,
+ daemon->manual_timeout_tail,
+ connection);
+ }
+ va_start (ap, option);
+ connection->connection_timeout = va_arg (ap, unsigned int);
+ va_end (ap);
+ if (MHD_YES != connection->suspended)
+ {
+ if (connection->connection_timeout == daemon->connection_timeout)
+ XDLL_insert (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ connection);
+ else
+ XDLL_insert (daemon->manual_timeout_head,
+ daemon->manual_timeout_tail,
+ connection);
+ }
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+ return MHD_YES;
+ default:
+ return MHD_NO;
+ }
+}
+
+
+/**
+ * Queue a response to be transmitted to the client (as soon as
+ * possible but after #MHD_AccessHandlerCallback returns).
+ *
+ * @param connection the connection identifying the client
+ * @param status_code HTTP status code (i.e. #MHD_HTTP_OK)
+ * @param response response to transmit
+ * @return #MHD_NO on error (i.e. reply already sent),
+ * #MHD_YES on success or if message has been queued
+ * @ingroup response
+ */
+int
+MHD_queue_response (struct MHD_Connection *connection,
+ unsigned int status_code,
+ struct MHD_Response *response)
+{
+ if ( (NULL == connection) ||
+ (NULL == response) ||
+ (NULL != connection->response) ||
+ ( (MHD_CONNECTION_HEADERS_PROCESSED != connection->state) &&
+ (MHD_CONNECTION_FOOTERS_RECEIVED != connection->state) ) )
+ return MHD_NO;
+ MHD_increment_response_rc (response);
+ connection->response = response;
+ connection->responseCode = status_code;
+ if ( (NULL != connection->method) &&
+ (MHD_str_equal_caseless_ (connection->method, MHD_HTTP_METHOD_HEAD)) )
+ {
+ /* if this is a "HEAD" request, pretend that we
+ have already sent the full message body */
+ connection->response_write_position = response->total_size;
+ }
+ if ( (MHD_CONNECTION_HEADERS_PROCESSED == connection->state) &&
+ (NULL != connection->method) &&
+ ( (MHD_str_equal_caseless_ (connection->method,
+ MHD_HTTP_METHOD_POST)) ||
+ (MHD_str_equal_caseless_ (connection->method,
+ MHD_HTTP_METHOD_PUT))) )
+ {
+ /* response was queued "early", refuse to read body / footers or
+ further requests! */
+ connection->read_closed = MHD_YES;
+ connection->state = MHD_CONNECTION_FOOTERS_RECEIVED;
+ }
+ if (MHD_NO == connection->in_idle)
+ (void) MHD_connection_handle_idle (connection);
+ return MHD_YES;
+}
+
+
+/* end of connection.c */
diff --git a/src/microhttpd/connection.h b/src/microhttpd/connection.h
new file mode 100644
index 0000000..07feec2
--- /dev/null
+++ b/src/microhttpd/connection.h
@@ -0,0 +1,110 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file connection.h
+ * @brief Methods for managing connections
+ * @author Daniel Pittman
+ * @author Christian Grothoff
+ */
+
+#ifndef CONNECTION_H
+#define CONNECTION_H
+
+#include "internal.h"
+
+
+/**
+ * Set callbacks for this connection to those for HTTP.
+ *
+ * @param connection connection to initialize
+ */
+void
+MHD_set_http_callbacks_ (struct MHD_Connection *connection);
+
+
+/**
+ * This function handles a particular connection when it has been
+ * determined that there is data to be read off a socket. All
+ * implementations (multithreaded, external select, internal select)
+ * call this function to handle reads.
+ *
+ * @param connection connection to handle
+ * @return always MHD_YES (we should continue to process the
+ * connection)
+ */
+int
+MHD_connection_handle_read (struct MHD_Connection *connection);
+
+
+/**
+ * This function was created to handle writes to sockets when it has
+ * been determined that the socket can be written to. All
+ * implementations (multithreaded, external select, internal select)
+ * call this function
+ *
+ * @param connection connection to handle
+ * @return always MHD_YES (we should continue to process the
+ * connection)
+ */
+int
+MHD_connection_handle_write (struct MHD_Connection *connection);
+
+
+/**
+ * This function was created to handle per-connection processing that
+ * has to happen even if the socket cannot be read or written to. All
+ * implementations (multithreaded, external select, internal select)
+ * call this function.
+ *
+ * @param connection connection to handle
+ * @return MHD_YES if we should continue to process the
+ * connection (not dead yet), MHD_NO if it died
+ */
+int
+MHD_connection_handle_idle (struct MHD_Connection *connection);
+
+
+/**
+ * Close the given connection and give the
+ * specified termination code to the user.
+ *
+ * @param connection connection to close
+ * @param termination_code termination reason to give
+ */
+void
+MHD_connection_close (struct MHD_Connection *connection,
+ enum MHD_RequestTerminationCode termination_code);
+
+
+#if EPOLL_SUPPORT
+/**
+ * Perform epoll processing, possibly moving the connection back into
+ * the epoll set if needed.
+ *
+ * @param connection connection to process
+ * @return MHD_YES if we should continue to process the
+ * connection (not dead yet), MHD_NO if it died
+ */
+int
+MHD_connection_epoll_update_ (struct MHD_Connection *connection);
+#endif
+
+
+#endif
diff --git a/src/microhttpd/connection_https.c b/src/microhttpd/connection_https.c
new file mode 100644
index 0000000..834d52e
--- /dev/null
+++ b/src/microhttpd/connection_https.c
@@ -0,0 +1,183 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008, 2010 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+/**
+ * @file connection_https.c
+ * @brief Methods for managing SSL/TLS connections. This file is only
+ * compiled if ENABLE_HTTPS is set.
+ * @author Sagie Amir
+ * @author Christian Grothoff
+ */
+
+#include "internal.h"
+#include "connection.h"
+#include "memorypool.h"
+#include "response.h"
+#include "reason_phrase.h"
+#include <gnutls/gnutls.h>
+
+
+/**
+ * Give gnuTLS chance to work on the TLS handshake.
+ *
+ * @param connection connection to handshake on
+ * @return #MHD_YES on error or if the handshake is progressing
+ * #MHD_NO if the handshake has completed successfully
+ * and we should start to read/write data
+ */
+static int
+run_tls_handshake (struct MHD_Connection *connection)
+{
+ int ret;
+
+ connection->last_activity = MHD_monotonic_time();
+ if (connection->state == MHD_TLS_CONNECTION_INIT)
+ {
+ ret = gnutls_handshake (connection->tls_session);
+ if (ret == GNUTLS_E_SUCCESS)
+ {
+ /* set connection state to enable HTTP processing */
+ connection->state = MHD_CONNECTION_INIT;
+ return MHD_YES;
+ }
+ if ( (ret == GNUTLS_E_AGAIN) ||
+ (ret == GNUTLS_E_INTERRUPTED) )
+ {
+ /* handshake not done */
+ return MHD_YES;
+ }
+ /* handshake failed */
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Error: received handshake message out of context\n");
+#endif
+ MHD_connection_close (connection,
+ MHD_REQUEST_TERMINATED_WITH_ERROR);
+ return MHD_YES;
+ }
+ return MHD_NO;
+}
+
+
+/**
+ * This function handles a particular SSL/TLS connection when
+ * it has been determined that there is data to be read off a
+ * socket. Message processing is done by message type which is
+ * determined by peeking into the first message type byte of the
+ * stream.
+ *
+ * Error message handling: all fatal level messages cause the
+ * connection to be terminated.
+ *
+ * Application data is forwarded to the underlying daemon for
+ * processing.
+ *
+ * @param connection the source connection
+ * @return always #MHD_YES (we should continue to process the connection)
+ */
+static int
+MHD_tls_connection_handle_read (struct MHD_Connection *connection)
+{
+ if (MHD_YES == run_tls_handshake (connection))
+ return MHD_YES;
+ return MHD_connection_handle_read (connection);
+}
+
+
+/**
+ * This function was created to handle writes to sockets when it has
+ * been determined that the socket can be written to. This function
+ * will forward all write requests to the underlying daemon unless
+ * the connection has been marked for closing.
+ *
+ * @return always #MHD_YES (we should continue to process the connection)
+ */
+static int
+MHD_tls_connection_handle_write (struct MHD_Connection *connection)
+{
+ if (MHD_YES == run_tls_handshake (connection))
+ return MHD_YES;
+ return MHD_connection_handle_write (connection);
+}
+
+
+/**
+ * This function was created to handle per-connection processing that
+ * has to happen even if the socket cannot be read or written to. All
+ * implementations (multithreaded, external select, internal select)
+ * call this function.
+ *
+ * @param connection being handled
+ * @return #MHD_YES if we should continue to process the
+ * connection (not dead yet), #MHD_NO if it died
+ */
+static int
+MHD_tls_connection_handle_idle (struct MHD_Connection *connection)
+{
+ unsigned int timeout;
+
+#if DEBUG_STATES
+ MHD_DLOG (connection->daemon,
+ "%s: state: %s\n",
+ __FUNCTION__,
+ MHD_state_to_string (connection->state));
+#endif
+ timeout = connection->connection_timeout;
+ if ( (timeout != 0) && (timeout <= (MHD_monotonic_time() - connection->last_activity)))
+ MHD_connection_close (connection,
+ MHD_REQUEST_TERMINATED_TIMEOUT_REACHED);
+ switch (connection->state)
+ {
+ /* on newly created connections we might reach here before any reply has been received */
+ case MHD_TLS_CONNECTION_INIT:
+ break;
+ /* close connection if necessary */
+ case MHD_CONNECTION_CLOSED:
+ gnutls_bye (connection->tls_session, GNUTLS_SHUT_RDWR);
+ return MHD_connection_handle_idle (connection);
+ default:
+ if ( (0 != gnutls_record_check_pending (connection->tls_session)) &&
+ (MHD_YES != MHD_tls_connection_handle_read (connection)) )
+ return MHD_YES;
+ return MHD_connection_handle_idle (connection);
+ }
+#if EPOLL_SUPPORT
+ return MHD_connection_epoll_update_ (connection);
+#else
+ return MHD_YES;
+#endif
+}
+
+
+/**
+ * Set connection callback function to be used through out
+ * the processing of this secure connection.
+ *
+ * @param connection which callbacks should be modified
+ */
+void
+MHD_set_https_callbacks (struct MHD_Connection *connection)
+{
+ connection->read_handler = &MHD_tls_connection_handle_read;
+ connection->write_handler = &MHD_tls_connection_handle_write;
+ connection->idle_handler = &MHD_tls_connection_handle_idle;
+}
+
+/* end of connection_https.c */
diff --git a/src/microhttpd/connection_https.h b/src/microhttpd/connection_https.h
new file mode 100644
index 0000000..02ffb52
--- /dev/null
+++ b/src/microhttpd/connection_https.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2008 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file connection_https.h
+ * @brief Methods for managing connections
+ * @author Christian Grothoff
+ */
+
+#ifndef CONNECTION_HTTPS_H
+#define CONNECTION_HTTPS_H
+
+#include "internal.h"
+
+#if HTTPS_SUPPORT
+/**
+ * Set connection callback function to be used through out
+ * the processing of this secure connection.
+ *
+ * @param connection which callbacks should be modified
+ */
+void
+MHD_set_https_callbacks (struct MHD_Connection *connection);
+#endif
+
+#endif
diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c
new file mode 100644
index 0000000..1aa7d8d
--- /dev/null
+++ b/src/microhttpd/daemon.c
@@ -0,0 +1,4844 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007-2014 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+/**
+ * @file microhttpd/daemon.c
+ * @brief A minimal-HTTP server library
+ * @author Daniel Pittman
+ * @author Christian Grothoff
+ */
+#if defined(_WIN32) && !defined(__CYGWIN__)
+/* override small default value */
+#define FD_SETSIZE 1024
+#define MHD_DEFAULT_FD_SETSIZE 64
+#else
+#define MHD_DEFAULT_FD_SETSIZE FD_SETSIZE
+#endif
+#include "platform.h"
+#include "internal.h"
+#include "response.h"
+#include "connection.h"
+#include "memorypool.h"
+#include <limits.h>
+#include "autoinit_funcs.h"
+
+#if HAVE_SEARCH_H
+#include <search.h>
+#else
+#include "tsearch.h"
+#endif
+
+#if HTTPS_SUPPORT
+#include "connection_https.h"
+#include <gcrypt.h>
+#endif
+
+#if defined(HAVE_POLL_H) && defined(HAVE_POLL)
+#include <poll.h>
+#endif
+
+#ifdef LINUX
+#include <sys/sendfile.h>
+#endif
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#include <process.h>
+#endif
+
+#ifndef HAVE_ACCEPT4
+#define HAVE_ACCEPT4 0
+#endif
+
+/**
+ * Default connection limit.
+ */
+#ifndef WINDOWS
+#define MHD_MAX_CONNECTIONS_DEFAULT FD_SETSIZE - 4
+#else
+#define MHD_MAX_CONNECTIONS_DEFAULT FD_SETSIZE
+#endif
+
+/**
+ * Default memory allowed per connection.
+ */
+#define MHD_POOL_SIZE_DEFAULT (32 * 1024)
+
+#ifdef TCP_FASTOPEN
+/**
+ * Default TCP fastopen queue size.
+ */
+#define MHD_TCP_FASTOPEN_QUEUE_SIZE_DEFAULT 10
+#endif
+
+/**
+ * Print extra messages with reasons for closing
+ * sockets? (only adds non-error messages).
+ */
+#define DEBUG_CLOSE MHD_NO
+
+/**
+ * Print extra messages when establishing
+ * connections? (only adds non-error messages).
+ */
+#define DEBUG_CONNECT MHD_NO
+
+#ifndef LINUX
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+#endif
+
+#ifndef SOCK_CLOEXEC
+#define SOCK_CLOEXEC 0
+#endif
+
+#ifndef EPOLL_CLOEXEC
+#define EPOLL_CLOEXEC 0
+#endif
+
+
+/**
+ * Default implementation of the panic function,
+ * prints an error message and aborts.
+ *
+ * @param cls unused
+ * @param file name of the file with the problem
+ * @param line line number with the problem
+ * @param reason error message with details
+ */
+static void
+mhd_panic_std (void *cls,
+ const char *file,
+ unsigned int line,
+ const char *reason)
+{
+#if HAVE_MESSAGES
+ fprintf (stderr, "Fatal error in GNU libmicrohttpd %s:%u: %s\n",
+ file, line, reason);
+#endif
+ abort ();
+}
+
+
+/**
+ * Handler for fatal errors.
+ */
+MHD_PanicCallback mhd_panic;
+
+/**
+ * Closure argument for "mhd_panic".
+ */
+void *mhd_panic_cls;
+
+#ifdef _WIN32
+/**
+ * Track initialization of winsock
+ */
+static int mhd_winsock_inited_ = 0;
+#endif
+
+/**
+ * Trace up to and return master daemon. If the supplied daemon
+ * is a master, then return the daemon itself.
+ *
+ * @param daemon handle to a daemon
+ * @return master daemon handle
+ */
+static struct MHD_Daemon*
+MHD_get_master (struct MHD_Daemon *daemon)
+{
+ while (NULL != daemon->master)
+ daemon = daemon->master;
+ return daemon;
+}
+
+
+/**
+ * Maintain connection count for single address.
+ */
+struct MHD_IPCount
+{
+ /**
+ * Address family. AF_INET or AF_INET6 for now.
+ */
+ int family;
+
+ /**
+ * Actual address.
+ */
+ union
+ {
+ /**
+ * IPv4 address.
+ */
+ struct in_addr ipv4;
+#if HAVE_INET6
+ /**
+ * IPv6 address.
+ */
+ struct in6_addr ipv6;
+#endif
+ } addr;
+
+ /**
+ * Counter.
+ */
+ unsigned int count;
+};
+
+
+/**
+ * Lock shared structure for IP connection counts and connection DLLs.
+ *
+ * @param daemon handle to daemon where lock is
+ */
+static void
+MHD_ip_count_lock (struct MHD_Daemon *daemon)
+{
+ if (MHD_YES != MHD_mutex_lock_(&daemon->per_ip_connection_mutex))
+ {
+ MHD_PANIC ("Failed to acquire IP connection limit mutex\n");
+ }
+}
+
+
+/**
+ * Unlock shared structure for IP connection counts and connection DLLs.
+ *
+ * @param daemon handle to daemon where lock is
+ */
+static void
+MHD_ip_count_unlock (struct MHD_Daemon *daemon)
+{
+ if (MHD_YES != MHD_mutex_unlock_(&daemon->per_ip_connection_mutex))
+ {
+ MHD_PANIC ("Failed to release IP connection limit mutex\n");
+ }
+}
+
+
+/**
+ * Tree comparison function for IP addresses (supplied to tsearch() family).
+ * We compare everything in the struct up through the beginning of the
+ * 'count' field.
+ *
+ * @param a1 first address to compare
+ * @param a2 second address to compare
+ * @return -1, 0 or 1 depending on result of compare
+ */
+static int
+MHD_ip_addr_compare (const void *a1, const void *a2)
+{
+ return memcmp (a1, a2, offsetof (struct MHD_IPCount, count));
+}
+
+
+/**
+ * Parse address and initialize 'key' using the address.
+ *
+ * @param addr address to parse
+ * @param addrlen number of bytes in addr
+ * @param key where to store the parsed address
+ * @return #MHD_YES on success and #MHD_NO otherwise (e.g., invalid address type)
+ */
+static int
+MHD_ip_addr_to_key (const struct sockaddr *addr,
+ socklen_t addrlen,
+ struct MHD_IPCount *key)
+{
+ memset(key, 0, sizeof(*key));
+
+ /* IPv4 addresses */
+ if (sizeof (struct sockaddr_in) == addrlen)
+ {
+ const struct sockaddr_in *addr4 = (const struct sockaddr_in*) addr;
+ key->family = AF_INET;
+ memcpy (&key->addr.ipv4, &addr4->sin_addr, sizeof(addr4->sin_addr));
+ return MHD_YES;
+ }
+
+#if HAVE_INET6
+ /* IPv6 addresses */
+ if (sizeof (struct sockaddr_in6) == addrlen)
+ {
+ const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*) addr;
+ key->family = AF_INET6;
+ memcpy (&key->addr.ipv6, &addr6->sin6_addr, sizeof(addr6->sin6_addr));
+ return MHD_YES;
+ }
+#endif
+
+ /* Some other address */
+ return MHD_NO;
+}
+
+
+/**
+ * Check if IP address is over its limit.
+ *
+ * @param daemon handle to daemon where connection counts are tracked
+ * @param addr address to add (or increment counter)
+ * @param addrlen number of bytes in addr
+ * @return Return #MHD_YES if IP below limit, #MHD_NO if IP has surpassed limit.
+ * Also returns #MHD_NO if fails to allocate memory.
+ */
+static int
+MHD_ip_limit_add (struct MHD_Daemon *daemon,
+ const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ struct MHD_IPCount *key;
+ void **nodep;
+ void *node;
+ int result;
+
+ daemon = MHD_get_master (daemon);
+ /* Ignore if no connection limit assigned */
+ if (0 == daemon->per_ip_connection_limit)
+ return MHD_YES;
+
+ if (NULL == (key = malloc (sizeof(*key))))
+ return MHD_NO;
+
+ /* Initialize key */
+ if (MHD_NO == MHD_ip_addr_to_key (addr, addrlen, key))
+ {
+ /* Allow unhandled address types through */
+ free (key);
+ return MHD_YES;
+ }
+ MHD_ip_count_lock (daemon);
+
+ /* Search for the IP address */
+ if (NULL == (nodep = tsearch (key,
+ &daemon->per_ip_connection_count,
+ &MHD_ip_addr_compare)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to add IP connection count node\n");
+#endif
+ MHD_ip_count_unlock (daemon);
+ free (key);
+ return MHD_NO;
+ }
+ node = *nodep;
+ /* If we got an existing node back, free the one we created */
+ if (node != key)
+ free(key);
+ key = (struct MHD_IPCount *) node;
+ /* Test if there is room for another connection; if so,
+ * increment count */
+ result = (key->count < daemon->per_ip_connection_limit);
+ if (MHD_YES == result)
+ ++key->count;
+
+ MHD_ip_count_unlock (daemon);
+ return result;
+}
+
+
+/**
+ * Decrement connection count for IP address, removing from table
+ * count reaches 0.
+ *
+ * @param daemon handle to daemon where connection counts are tracked
+ * @param addr address to remove (or decrement counter)
+ * @param addrlen number of bytes in @a addr
+ */
+static void
+MHD_ip_limit_del (struct MHD_Daemon *daemon,
+ const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ struct MHD_IPCount search_key;
+ struct MHD_IPCount *found_key;
+ void **nodep;
+
+ daemon = MHD_get_master (daemon);
+ /* Ignore if no connection limit assigned */
+ if (0 == daemon->per_ip_connection_limit)
+ return;
+ /* Initialize search key */
+ if (MHD_NO == MHD_ip_addr_to_key (addr, addrlen, &search_key))
+ return;
+
+ MHD_ip_count_lock (daemon);
+
+ /* Search for the IP address */
+ if (NULL == (nodep = tfind (&search_key,
+ &daemon->per_ip_connection_count,
+ &MHD_ip_addr_compare)))
+ {
+ /* Something's wrong if we couldn't find an IP address
+ * that was previously added */
+ MHD_PANIC ("Failed to find previously-added IP address\n");
+ }
+ found_key = (struct MHD_IPCount *) *nodep;
+ /* Validate existing count for IP address */
+ if (0 == found_key->count)
+ {
+ MHD_PANIC ("Previously-added IP address had 0 count\n");
+ }
+ /* Remove the node entirely if count reduces to 0 */
+ if (0 == --found_key->count)
+ {
+ tdelete (found_key,
+ &daemon->per_ip_connection_count,
+ &MHD_ip_addr_compare);
+ free (found_key);
+ }
+
+ MHD_ip_count_unlock (daemon);
+}
+
+
+#if HTTPS_SUPPORT
+/**
+ * Callback for receiving data from the socket.
+ *
+ * @param connection the MHD_Connection structure
+ * @param other where to write received data to
+ * @param i maximum size of other (in bytes)
+ * @return number of bytes actually received
+ */
+static ssize_t
+recv_tls_adapter (struct MHD_Connection *connection, void *other, size_t i)
+{
+ int res;
+
+ if (MHD_YES == connection->tls_read_ready)
+ {
+ connection->daemon->num_tls_read_ready--;
+ connection->tls_read_ready = MHD_NO;
+ }
+ res = gnutls_record_recv (connection->tls_session, other, i);
+ if ( (GNUTLS_E_AGAIN == res) ||
+ (GNUTLS_E_INTERRUPTED == res) )
+ {
+ MHD_set_socket_errno_ (EINTR);
+#if EPOLL_SUPPORT
+ connection->epoll_state &= ~MHD_EPOLL_STATE_READ_READY;
+#endif
+ return -1;
+ }
+ if (res < 0)
+ {
+ /* Likely 'GNUTLS_E_INVALID_SESSION' (client communication
+ disrupted); set errno to something caller will interpret
+ correctly as a hard error */
+ MHD_set_socket_errno_ (ECONNRESET);
+ return res;
+ }
+ if (res == i)
+ {
+ connection->tls_read_ready = MHD_YES;
+ connection->daemon->num_tls_read_ready++;
+ }
+ return res;
+}
+
+
+/**
+ * Callback for writing data to the socket.
+ *
+ * @param connection the MHD connection structure
+ * @param other data to write
+ * @param i number of bytes to write
+ * @return actual number of bytes written
+ */
+static ssize_t
+send_tls_adapter (struct MHD_Connection *connection,
+ const void *other, size_t i)
+{
+ int res;
+
+ res = gnutls_record_send (connection->tls_session, other, i);
+ if ( (GNUTLS_E_AGAIN == res) ||
+ (GNUTLS_E_INTERRUPTED == res) )
+ {
+ MHD_set_socket_errno_ (EINTR);
+#if EPOLL_SUPPORT
+ connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
+#endif
+ return -1;
+ }
+ if (res < 0)
+ {
+ /* some other GNUTLS error, should set 'errno'; as we do not
+ really understand the error (not listed in GnuTLS
+ documentation explicitly), we set 'errno' to something that
+ will cause the connection to fail. */
+ MHD_set_socket_errno_ (ECONNRESET);
+ return -1;
+ }
+ return res;
+}
+
+
+/**
+ * Read and setup our certificate and key.
+ *
+ * @param daemon handle to daemon to initialize
+ * @return 0 on success
+ */
+static int
+MHD_init_daemon_certificate (struct MHD_Daemon *daemon)
+{
+ gnutls_datum_t key;
+ gnutls_datum_t cert;
+ int ret;
+
+#if GNUTLS_VERSION_MAJOR >= 3
+ if (NULL != daemon->cert_callback)
+ {
+ gnutls_certificate_set_retrieve_function2 (daemon->x509_cred,
+ daemon->cert_callback);
+ }
+#endif
+ if (NULL != daemon->https_mem_trust)
+ {
+ cert.data = (unsigned char *) daemon->https_mem_trust;
+ cert.size = strlen (daemon->https_mem_trust);
+ if (gnutls_certificate_set_x509_trust_mem (daemon->x509_cred, &cert,
+ GNUTLS_X509_FMT_PEM) < 0)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(daemon,
+ "Bad trust certificate format\n");
+#endif
+ return -1;
+ }
+ }
+
+ if (MHD_YES == daemon->have_dhparams)
+ {
+ gnutls_certificate_set_dh_params (daemon->x509_cred,
+ daemon->https_mem_dhparams);
+ }
+ /* certificate & key loaded from memory */
+ if ( (NULL != daemon->https_mem_cert) &&
+ (NULL != daemon->https_mem_key) )
+ {
+ key.data = (unsigned char *) daemon->https_mem_key;
+ key.size = strlen (daemon->https_mem_key);
+ cert.data = (unsigned char *) daemon->https_mem_cert;
+ cert.size = strlen (daemon->https_mem_cert);
+
+ if (NULL != daemon->https_key_password) {
+#if GNUTLS_VERSION_NUMBER >= 0x030111
+ ret = gnutls_certificate_set_x509_key_mem2 (daemon->x509_cred,
+ &cert, &key,
+ GNUTLS_X509_FMT_PEM,
+ daemon->https_key_password,
+ 0);
+#else
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to setup x509 certificate/key: pre 3.X.X version " \
+ "of GnuTLS does not support setting key password");
+#endif
+ return -1;
+#endif
+ }
+ else
+ ret = gnutls_certificate_set_x509_key_mem (daemon->x509_cred,
+ &cert, &key,
+ GNUTLS_X509_FMT_PEM);
+#if HAVE_MESSAGES
+ if (0 != ret)
+ MHD_DLOG (daemon,
+ "GnuTLS failed to setup x509 certificate/key: %s\n",
+ gnutls_strerror (ret));
+#endif
+ return ret;
+ }
+#if GNUTLS_VERSION_MAJOR >= 3
+ if (NULL != daemon->cert_callback)
+ return 0;
+#endif
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "You need to specify a certificate and key location\n");
+#endif
+ return -1;
+}
+
+
+/**
+ * Initialize security aspects of the HTTPS daemon
+ *
+ * @param daemon handle to daemon to initialize
+ * @return 0 on success
+ */
+static int
+MHD_TLS_init (struct MHD_Daemon *daemon)
+{
+ switch (daemon->cred_type)
+ {
+ case GNUTLS_CRD_CERTIFICATE:
+ if (0 !=
+ gnutls_certificate_allocate_credentials (&daemon->x509_cred))
+ return GNUTLS_E_MEMORY_ERROR;
+ return MHD_init_daemon_certificate (daemon);
+ default:
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Error: invalid credentials type %d specified.\n",
+ daemon->cred_type);
+#endif
+ return -1;
+ }
+}
+#endif
+
+
+/**
+ * Add @a fd to the @a set. If @a fd is
+ * greater than @a max_fd, set @a max_fd to @a fd.
+ *
+ * @param fd file descriptor to add to the @a set
+ * @param set set to modify
+ * @param max_fd maximum value to potentially update
+ * @param fd_setsize value of FD_SETSIZE
+ * @return #MHD_YES on success, #MHD_NO otherwise
+ */
+static int
+add_to_fd_set (MHD_socket fd,
+ fd_set *set,
+ MHD_socket *max_fd,
+ unsigned int fd_setsize)
+{
+ if (NULL == set)
+ return MHD_NO;
+#ifdef MHD_WINSOCK_SOCKETS
+ if (set->fd_count >= fd_setsize)
+ {
+ if (FD_ISSET(fd, set))
+ return MHD_YES;
+ else
+ return MHD_NO;
+ }
+#else // ! MHD_WINSOCK_SOCKETS
+ if (fd >= fd_setsize)
+ return MHD_NO;
+#endif // ! MHD_WINSOCK_SOCKETS
+ FD_SET (fd, set);
+ if ( (NULL != max_fd) && (MHD_INVALID_SOCKET != fd) &&
+ ((fd > *max_fd) || (MHD_INVALID_SOCKET == *max_fd)) )
+ *max_fd = fd;
+
+ return MHD_YES;
+}
+
+#undef MHD_get_fdset
+
+/**
+ * Obtain the `select()` sets for this daemon.
+ * Daemon's FDs will be added to fd_sets. To get only
+ * daemon FDs in fd_sets, call FD_ZERO for each fd_set
+ * before calling this function. FD_SETSIZE is assumed
+ * to be platform's default.
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param max_fd increased to largest FD added (if larger
+ * than existing value); can be NULL
+ * @return #MHD_YES on success, #MHD_NO if this
+ * daemon was not started with the right
+ * options for this call or any FD didn't
+ * fit fd_set.
+ * @ingroup event
+ */
+int
+MHD_get_fdset (struct MHD_Daemon *daemon,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ MHD_socket *max_fd)
+{
+ return MHD_get_fdset2(daemon, read_fd_set,
+ write_fd_set, except_fd_set,
+ max_fd, MHD_DEFAULT_FD_SETSIZE);
+}
+
+/**
+ * Obtain the `select()` sets for this daemon.
+ * Daemon's FDs will be added to fd_sets. To get only
+ * daemon FDs in fd_sets, call FD_ZERO for each fd_set
+ * before calling this function. Passing custom FD_SETSIZE
+ * as @a fd_setsize allow usage of larger/smaller than
+ * platform's default fd_sets.
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param max_fd increased to largest FD added (if larger
+ * than existing value); can be NULL
+ * @param fd_setsize value of FD_SETSIZE
+ * @return #MHD_YES on success, #MHD_NO if this
+ * daemon was not started with the right
+ * options for this call or any FD didn't
+ * fit fd_set.
+ * @ingroup event
+ */
+int
+MHD_get_fdset2 (struct MHD_Daemon *daemon,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ MHD_socket *max_fd,
+ unsigned int fd_setsize)
+{
+ struct MHD_Connection *pos;
+
+ if ( (NULL == daemon)
+ || (NULL == read_fd_set)
+ || (NULL == write_fd_set)
+ || (MHD_YES == daemon->shutdown)
+ || (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
+ || (0 != (daemon->options & MHD_USE_POLL)))
+ return MHD_NO;
+#if EPOLL_SUPPORT
+ if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY))
+ {
+ /* we're in epoll mode, use the epoll FD as a stand-in for
+ the entire event set */
+
+ return add_to_fd_set (daemon->epoll_fd, read_fd_set, max_fd, fd_setsize);
+ }
+#endif
+ if (MHD_INVALID_SOCKET != daemon->socket_fd &&
+ MHD_YES != add_to_fd_set (daemon->socket_fd, read_fd_set, max_fd, fd_setsize))
+ return MHD_NO;
+
+ for (pos = daemon->connections_head; NULL != pos; pos = pos->next)
+ {
+ switch (pos->event_loop_info)
+ {
+ case MHD_EVENT_LOOP_INFO_READ:
+ if (MHD_YES != add_to_fd_set (pos->socket_fd, read_fd_set, max_fd, fd_setsize))
+ return MHD_NO;
+ break;
+ case MHD_EVENT_LOOP_INFO_WRITE:
+ if (MHD_YES != add_to_fd_set (pos->socket_fd, write_fd_set, max_fd, fd_setsize))
+ return MHD_NO;
+ if (pos->read_buffer_size > pos->read_buffer_offset &&
+ MHD_YES != add_to_fd_set (pos->socket_fd, read_fd_set, max_fd, fd_setsize))
+ return MHD_NO;
+ break;
+ case MHD_EVENT_LOOP_INFO_BLOCK:
+ if (pos->read_buffer_size > pos->read_buffer_offset &&
+ MHD_YES != add_to_fd_set (pos->socket_fd, read_fd_set, max_fd, fd_setsize))
+ return MHD_NO;
+ break;
+ case MHD_EVENT_LOOP_INFO_CLEANUP:
+ /* this should never happen */
+ break;
+ }
+ }
+#if DEBUG_CONNECT
+#if HAVE_MESSAGES
+ if (NULL != max_fd)
+ MHD_DLOG (daemon,
+ "Maximum socket in select set: %d\n",
+ *max_fd);
+#endif
+#endif
+ return MHD_YES;
+}
+
+
+/**
+ * Main function of the thread that handles an individual
+ * connection when #MHD_USE_THREAD_PER_CONNECTION is set.
+ *
+ * @param data the `struct MHD_Connection` this thread will handle
+ * @return always 0
+ */
+static MHD_THRD_RTRN_TYPE_ MHD_THRD_CALL_SPEC_
+MHD_handle_connection (void *data)
+{
+ struct MHD_Connection *con = data;
+ int num_ready;
+ fd_set rs;
+ fd_set ws;
+ MHD_socket max;
+ struct timeval tv;
+ struct timeval *tvp;
+ unsigned int timeout;
+ time_t now;
+#if WINDOWS
+ MHD_pipe spipe = con->daemon->wpipe[0];
+ char tmp;
+#ifdef HAVE_POLL
+ int extra_slot;
+#endif /* HAVE_POLL */
+#define EXTRA_SLOTS 1
+#else /* !WINDOWS */
+#define EXTRA_SLOTS 0
+#endif /* !WINDOWS */
+#ifdef HAVE_POLL
+ struct pollfd p[1 + EXTRA_SLOTS];
+#endif
+
+ timeout = con->daemon->connection_timeout;
+ while ( (MHD_YES != con->daemon->shutdown) &&
+ (MHD_CONNECTION_CLOSED != con->state) )
+ {
+ tvp = NULL;
+ if (timeout > 0)
+ {
+ now = MHD_monotonic_time();
+ if (now - con->last_activity > timeout)
+ tv.tv_sec = 0;
+ else
+ tv.tv_sec = timeout - (now - con->last_activity);
+ tv.tv_usec = 0;
+ tvp = &tv;
+ }
+#if HTTPS_SUPPORT
+ if (MHD_YES == con->tls_read_ready)
+ {
+ /* do not block (more data may be inside of TLS buffers waiting for us) */
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ }
+#endif
+ if (0 == (con->daemon->options & MHD_USE_POLL))
+ {
+ /* use select */
+ int err_state = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ max = 0;
+ switch (con->event_loop_info)
+ {
+ case MHD_EVENT_LOOP_INFO_READ:
+ if (MHD_YES !=
+ add_to_fd_set (con->socket_fd, &rs, &max, FD_SETSIZE))
+ err_state = 1;
+ break;
+ case MHD_EVENT_LOOP_INFO_WRITE:
+ if (MHD_YES !=
+ add_to_fd_set (con->socket_fd, &ws, &max, FD_SETSIZE))
+ err_state = 1;
+ if ( (con->read_buffer_size > con->read_buffer_offset) &&
+ (MHD_YES !=
+ add_to_fd_set (con->socket_fd, &rs, &max, FD_SETSIZE)) )
+ err_state = 1;
+ break;
+ case MHD_EVENT_LOOP_INFO_BLOCK:
+ if ( (con->read_buffer_size > con->read_buffer_offset) &&
+ (MHD_YES !=
+ add_to_fd_set (con->socket_fd, &rs, &max, FD_SETSIZE)) )
+ err_state = 1;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ break;
+ case MHD_EVENT_LOOP_INFO_CLEANUP:
+ /* how did we get here!? */
+ goto exit;
+ }
+#if WINDOWS
+ if (MHD_INVALID_PIPE_ != spipe)
+ {
+ if (MHD_YES !=
+ add_to_fd_set (spipe, &rs, &max, FD_SETSIZE))
+ err_state = 1;
+ }
+#endif
+ if (0 != err_state)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (con->daemon,
+ "Can't add FD to fd_set\n");
+#endif
+ goto exit;
+ }
+
+ num_ready = MHD_SYS_select_ (max + 1, &rs, &ws, NULL, tvp);
+ if (num_ready < 0)
+ {
+ if (EINTR == MHD_socket_errno_)
+ continue;
+#if HAVE_MESSAGES
+ MHD_DLOG (con->daemon,
+ "Error during select (%d): `%s'\n",
+ MHD_socket_errno_,
+ MHD_socket_last_strerr_ ());
+#endif
+ break;
+ }
+#if WINDOWS
+ /* drain signaling pipe */
+ if ( (MHD_INVALID_PIPE_ != spipe) &&
+ (FD_ISSET (spipe, &rs)) )
+ (void) MHD_pipe_read_ (spipe, &tmp, sizeof (tmp));
+#endif
+ /* call appropriate connection handler if necessary */
+ if ( (FD_ISSET (con->socket_fd, &rs))
+#if HTTPS_SUPPORT
+ || (MHD_YES == con->tls_read_ready)
+#endif
+ )
+ con->read_handler (con);
+ if (FD_ISSET (con->socket_fd, &ws))
+ con->write_handler (con);
+ if (MHD_NO == con->idle_handler (con))
+ goto exit;
+ }
+#ifdef HAVE_POLL
+ else
+ {
+ /* use poll */
+ memset (&p, 0, sizeof (p));
+ p[0].fd = con->socket_fd;
+ switch (con->event_loop_info)
+ {
+ case MHD_EVENT_LOOP_INFO_READ:
+ p[0].events |= POLLIN;
+ break;
+ case MHD_EVENT_LOOP_INFO_WRITE:
+ p[0].events |= POLLOUT;
+ if (con->read_buffer_size > con->read_buffer_offset)
+ p[0].events |= POLLIN;
+ break;
+ case MHD_EVENT_LOOP_INFO_BLOCK:
+ if (con->read_buffer_size > con->read_buffer_offset)
+ p[0].events |= POLLIN;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ break;
+ case MHD_EVENT_LOOP_INFO_CLEANUP:
+ /* how did we get here!? */
+ goto exit;
+ }
+#if WINDOWS
+ extra_slot = 0;
+ if (MHD_INVALID_PIPE_ != spipe)
+ {
+ p[1].events |= POLLIN;
+ p[1].fd = spipe;
+ p[1].revents = 0;
+ extra_slot = 1;
+ }
+#endif
+ if (MHD_sys_poll_ (p,
+#if WINDOWS
+ 1 + extra_slot,
+#else
+ 1,
+#endif
+ (NULL == tvp) ? -1 : tv.tv_sec * 1000) < 0)
+ {
+ if (EINTR == MHD_socket_errno_)
+ continue;
+#if HAVE_MESSAGES
+ MHD_DLOG (con->daemon,
+ "Error during poll: `%s'\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ break;
+ }
+#if WINDOWS
+ /* drain signaling pipe */
+ if ( (MHD_INVALID_PIPE_ != spipe) &&
+ (0 != (p[1].revents & (POLLERR | POLLHUP))) )
+ (void) MHD_pipe_read_ (spipe, &tmp, sizeof (tmp));
+#endif
+ if ( (0 != (p[0].revents & POLLIN))
+#if HTTPS_SUPPORT
+ || (MHD_YES == con->tls_read_ready)
+#endif
+ )
+ con->read_handler (con);
+ if (0 != (p[0].revents & POLLOUT))
+ con->write_handler (con);
+ if (0 != (p[0].revents & (POLLERR | POLLHUP)))
+ MHD_connection_close (con, MHD_REQUEST_TERMINATED_WITH_ERROR);
+ if (MHD_NO == con->idle_handler (con))
+ goto exit;
+ }
+#endif
+ }
+ if (MHD_CONNECTION_IN_CLEANUP != con->state)
+ {
+#if DEBUG_CLOSE
+#if HAVE_MESSAGES
+ MHD_DLOG (con->daemon,
+ "Processing thread terminating, closing connection\n");
+#endif
+#endif
+ if (MHD_CONNECTION_CLOSED != con->state)
+ MHD_connection_close (con,
+ MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN);
+ con->idle_handler (con);
+ }
+exit:
+ if (NULL != con->response)
+ {
+ MHD_destroy_response (con->response);
+ con->response = NULL;
+ }
+
+ if (NULL != con->daemon->notify_connection)
+ con->daemon->notify_connection (con->daemon->notify_connection_cls,
+ con,
+ &con->socket_context,
+ MHD_CONNECTION_NOTIFY_CLOSED);
+
+ return (MHD_THRD_RTRN_TYPE_)0;
+}
+
+
+/**
+ * Callback for receiving data from the socket.
+ *
+ * @param connection the MHD connection structure
+ * @param other where to write received data to
+ * @param i maximum size of other (in bytes)
+ * @return number of bytes actually received
+ */
+static ssize_t
+recv_param_adapter (struct MHD_Connection *connection,
+ void *other,
+ size_t i)
+{
+ ssize_t ret;
+
+ if ( (MHD_INVALID_SOCKET == connection->socket_fd) ||
+ (MHD_CONNECTION_CLOSED == connection->state) )
+ {
+ MHD_set_socket_errno_ (ENOTCONN);
+ return -1;
+ }
+ ret = recv (connection->socket_fd, other, i, MSG_NOSIGNAL);
+#if EPOLL_SUPPORT
+ if (ret < (ssize_t) i)
+ {
+ /* partial read --- no longer read-ready */
+ connection->epoll_state &= ~MHD_EPOLL_STATE_READ_READY;
+ }
+#endif
+ return ret;
+}
+
+
+/**
+ * Callback for writing data to the socket.
+ *
+ * @param connection the MHD connection structure
+ * @param other data to write
+ * @param i number of bytes to write
+ * @return actual number of bytes written
+ */
+static ssize_t
+send_param_adapter (struct MHD_Connection *connection,
+ const void *other,
+ size_t i)
+{
+ ssize_t ret;
+#if LINUX
+ MHD_socket fd;
+ off_t offset;
+ off_t left;
+#endif
+
+ if ( (MHD_INVALID_SOCKET == connection->socket_fd) ||
+ (MHD_CONNECTION_CLOSED == connection->state) )
+ {
+ MHD_set_socket_errno_ (ENOTCONN);
+ return -1;
+ }
+ if (0 != (connection->daemon->options & MHD_USE_SSL))
+ return send (connection->socket_fd, other, i, MSG_NOSIGNAL);
+#if LINUX
+ if ( (connection->write_buffer_append_offset ==
+ connection->write_buffer_send_offset) &&
+ (NULL != connection->response) &&
+ (MHD_INVALID_SOCKET != (fd = connection->response->fd)) )
+ {
+ /* can use sendfile */
+ offset = (off_t) connection->response_write_position + connection->response->fd_off;
+ left = connection->response->total_size - connection->response_write_position;
+ if (left > SSIZE_MAX)
+ left = SSIZE_MAX; /* cap at return value limit */
+ if (-1 != (ret = sendfile (connection->socket_fd,
+ fd,
+ &offset,
+ (size_t) left)))
+ {
+#if EPOLL_SUPPORT
+ if (ret < left)
+ {
+ /* partial write --- no longer write-ready */
+ connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
+ }
+#endif
+ return ret;
+ }
+ const int err = MHD_socket_errno_;
+ if ( (EINTR == err) || (EAGAIN == err) || (EWOULDBLOCK == err) )
+ return 0;
+ if ( (EINVAL == err) || (EBADF == err) )
+ return -1;
+ /* None of the 'usual' sendfile errors occurred, so we should try
+ to fall back to 'SEND'; see also this thread for info on
+ odd libc/Linux behavior with sendfile:
+ http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */
+ }
+#endif
+ ret = send (connection->socket_fd, other, i, MSG_NOSIGNAL);
+#if EPOLL_SUPPORT
+ if (ret < (ssize_t) i)
+ {
+ /* partial write --- no longer write-ready */
+ connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
+ }
+#endif
+ /* Handle broken kernel / libc, returning -1 but not setting errno;
+ kill connection as that should be safe; reported on mailinglist here:
+ http://lists.gnu.org/archive/html/libmicrohttpd/2014-10/msg00023.html */
+ if ( (-1 == ret) && (0 == errno) )
+ errno = ECONNRESET;
+ return ret;
+}
+
+
+/**
+ * Signature of main function for a thread.
+ *
+ * @param cls closure argument for the function
+ * @return termination code from the thread
+ */
+typedef MHD_THRD_RTRN_TYPE_ (MHD_THRD_CALL_SPEC_ *ThreadStartRoutine)(void *cls);
+
+
+/**
+ * Create a thread and set the attributes according to our options.
+ *
+ * @param thread handle to initialize
+ * @param daemon daemon with options
+ * @param start_routine main function of thread
+ * @param arg argument for start_routine
+ * @return 0 on success
+ */
+static int
+create_thread (MHD_thread_handle_ *thread,
+ const struct MHD_Daemon *daemon,
+ ThreadStartRoutine start_routine,
+ void *arg)
+{
+#if defined(MHD_USE_POSIX_THREADS)
+ pthread_attr_t attr;
+ pthread_attr_t *pattr;
+ int ret;
+
+ if (0 != daemon->thread_stack_size)
+ {
+ if (0 != (ret = pthread_attr_init (&attr)))
+ goto ERR;
+ if (0 != (ret = pthread_attr_setstacksize (&attr, daemon->thread_stack_size)))
+ {
+ pthread_attr_destroy (&attr);
+ goto ERR;
+ }
+ pattr = &attr;
+ }
+ else
+ {
+ pattr = NULL;
+ }
+ ret = pthread_create (thread, pattr,
+ start_routine, arg);
+#ifdef HAVE_PTHREAD_SETNAME_NP
+ (void) pthread_setname_np (*thread, "libmicrohttpd");
+#endif /* HAVE_PTHREAD_SETNAME_NP */
+ if (0 != daemon->thread_stack_size)
+ pthread_attr_destroy (&attr);
+ return ret;
+ ERR:
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to set thread stack size\n");
+#endif
+ errno = EINVAL;
+ return ret;
+#elif defined(MHD_USE_W32_THREADS)
+ unsigned threadID;
+ *thread = (HANDLE)_beginthreadex(NULL, (unsigned)daemon->thread_stack_size, start_routine,
+ arg, 0, &threadID);
+ if (NULL == (*thread))
+ return errno;
+
+ W32_SetThreadName(threadID, "libmicrohttpd");
+
+ return 0;
+#endif
+}
+
+
+/**
+ * Add another client connection to the set of connections
+ * managed by MHD. This API is usually not needed (since
+ * MHD will accept inbound connections on the server socket).
+ * Use this API in special cases, for example if your HTTP
+ * server is behind NAT and needs to connect out to the
+ * HTTP client.
+ *
+ * The given client socket will be managed (and closed!) by MHD after
+ * this call and must no longer be used directly by the application
+ * afterwards.
+ *
+ * Per-IP connection limits are ignored when using this API.
+ *
+ * @param daemon daemon that manages the connection
+ * @param client_socket socket to manage (MHD will expect
+ * to receive an HTTP request from this socket next).
+ * @param addr IP address of the client
+ * @param addrlen number of bytes in @a addr
+ * @param external_add perform additional operations needed due
+ * to the application calling us directly
+ * @return #MHD_YES on success, #MHD_NO if this daemon could
+ * not handle the connection (i.e. malloc failed, etc).
+ * The socket will be closed in any case; 'errno' is
+ * set to indicate further details about the error.
+ */
+static int
+internal_add_connection (struct MHD_Daemon *daemon,
+ MHD_socket client_socket,
+ const struct sockaddr *addr,
+ socklen_t addrlen,
+ int external_add)
+{
+ struct MHD_Connection *connection;
+ int res_thread_create;
+ unsigned int i;
+ int eno;
+ struct MHD_Daemon *worker;
+#if OSX
+ static int on = 1;
+#endif
+
+ if (NULL != daemon->worker_pool)
+ {
+ /* have a pool, try to find a pool with capacity; we use the
+ socket as the initial offset into the pool for load
+ balancing */
+ for (i=0;i<daemon->worker_pool_size;i++)
+ {
+ worker = &daemon->worker_pool[(i + client_socket) % daemon->worker_pool_size];
+ if (worker->connections < worker->connection_limit)
+ return internal_add_connection (worker,
+ client_socket,
+ addr, addrlen,
+ external_add);
+ }
+ /* all pools are at their connection limit, must refuse */
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+#if ENFILE
+ errno = ENFILE;
+#endif
+ return MHD_NO;
+ }
+
+#ifndef WINDOWS
+ if ( (client_socket >= FD_SETSIZE) &&
+ (0 == (daemon->options & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY))) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Socket descriptor larger than FD_SETSIZE: %d > %d\n",
+ client_socket,
+ FD_SETSIZE);
+#endif
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+#if EINVAL
+ errno = EINVAL;
+#endif
+ return MHD_NO;
+ }
+#endif
+
+
+#if HAVE_MESSAGES
+#if DEBUG_CONNECT
+ MHD_DLOG (daemon,
+ "Accepted connection on socket %d\n",
+ client_socket);
+#endif
+#endif
+ if ( (daemon->connections == daemon->connection_limit) ||
+ (MHD_NO == MHD_ip_limit_add (daemon, addr, addrlen)) )
+ {
+ /* above connection limit - reject */
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Server reached connection limit (closing inbound connection)\n");
+#endif
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+#if ENFILE
+ errno = ENFILE;
+#endif
+ return MHD_NO;
+ }
+
+ /* apply connection acceptance policy if present */
+ if ( (NULL != daemon->apc) &&
+ (MHD_NO == daemon->apc (daemon->apc_cls,
+ addr, addrlen)) )
+ {
+#if DEBUG_CLOSE
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Connection rejected, closing connection\n");
+#endif
+#endif
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+ MHD_ip_limit_del (daemon, addr, addrlen);
+#if EACCESS
+ errno = EACCESS;
+#endif
+ return MHD_NO;
+ }
+
+#if OSX
+#ifdef SOL_SOCKET
+#ifdef SO_NOSIGPIPE
+ setsockopt (client_socket,
+ SOL_SOCKET, SO_NOSIGPIPE,
+ &on, sizeof (on));
+#endif
+#endif
+#endif
+
+ if (NULL == (connection = malloc (sizeof (struct MHD_Connection))))
+ {
+ eno = errno;
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Error allocating memory: %s\n",
+ MHD_strerror_ (errno));
+#endif
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+ MHD_ip_limit_del (daemon, addr, addrlen);
+ errno = eno;
+ return MHD_NO;
+ }
+ memset (connection,
+ 0,
+ sizeof (struct MHD_Connection));
+ connection->pool = MHD_pool_create (daemon->pool_size);
+ if (NULL == connection->pool)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Error allocating memory: %s\n",
+ MHD_strerror_ (errno));
+#endif
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+ MHD_ip_limit_del (daemon, addr, addrlen);
+ free (connection);
+#if ENOMEM
+ errno = ENOMEM;
+#endif
+ return MHD_NO;
+ }
+
+ connection->connection_timeout = daemon->connection_timeout;
+ if (NULL == (connection->addr = malloc (addrlen)))
+ {
+ eno = errno;
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Error allocating memory: %s\n",
+ MHD_strerror_ (errno));
+#endif
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+ MHD_ip_limit_del (daemon, addr, addrlen);
+ MHD_pool_destroy (connection->pool);
+ free (connection);
+ errno = eno;
+ return MHD_NO;
+ }
+ memcpy (connection->addr, addr, addrlen);
+ connection->addr_len = addrlen;
+ connection->socket_fd = client_socket;
+ connection->daemon = daemon;
+ connection->last_activity = MHD_monotonic_time();
+
+ /* set default connection handlers */
+ MHD_set_http_callbacks_ (connection);
+ connection->recv_cls = &recv_param_adapter;
+ connection->send_cls = &send_param_adapter;
+
+ if (0 == (connection->daemon->options & MHD_USE_EPOLL_TURBO))
+ {
+ /* non-blocking sockets are required on most systems and for GNUtls;
+ however, they somehow cause serious problems on CYGWIN (#1824);
+ in turbo mode, we assume that non-blocking was already set
+ by 'accept4' or whoever calls 'MHD_add_connection' */
+#ifdef CYGWIN
+ if (0 != (daemon->options & MHD_USE_SSL))
+#endif
+ {
+ /* make socket non-blocking */
+#if !defined(WINDOWS) || defined(CYGWIN)
+ int flags = fcntl (connection->socket_fd, F_GETFL);
+ if ( (-1 == flags) ||
+ (0 != fcntl (connection->socket_fd, F_SETFL, flags | O_NONBLOCK)) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to make socket non-blocking: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ }
+#else
+ unsigned long flags = 1;
+ if (0 != ioctlsocket (connection->socket_fd, FIONBIO, &flags))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to make socket non-blocking: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ }
+#endif
+ }
+ }
+
+#if HTTPS_SUPPORT
+ if (0 != (daemon->options & MHD_USE_SSL))
+ {
+ connection->recv_cls = &recv_tls_adapter;
+ connection->send_cls = &send_tls_adapter;
+ connection->state = MHD_TLS_CONNECTION_INIT;
+ MHD_set_https_callbacks (connection);
+ gnutls_init (&connection->tls_session, GNUTLS_SERVER);
+ gnutls_priority_set (connection->tls_session,
+ daemon->priority_cache);
+ switch (daemon->cred_type)
+ {
+ /* set needed credentials for certificate authentication. */
+ case GNUTLS_CRD_CERTIFICATE:
+ gnutls_credentials_set (connection->tls_session,
+ GNUTLS_CRD_CERTIFICATE,
+ daemon->x509_cred);
+ break;
+ default:
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Failed to setup TLS credentials: unknown credential type %d\n",
+ daemon->cred_type);
+#endif
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+ MHD_ip_limit_del (daemon, addr, addrlen);
+ free (connection->addr);
+ free (connection);
+ MHD_PANIC ("Unknown credential type");
+#if EINVAL
+ errno = EINVAL;
+#endif
+ return MHD_NO;
+ }
+ gnutls_transport_set_ptr (connection->tls_session,
+ (gnutls_transport_ptr_t) connection);
+ gnutls_transport_set_pull_function (connection->tls_session,
+ (gnutls_pull_func) &recv_param_adapter);
+ gnutls_transport_set_push_function (connection->tls_session,
+ (gnutls_push_func) &send_param_adapter);
+
+ if (daemon->https_mem_trust)
+ gnutls_certificate_server_set_request (connection->tls_session,
+ GNUTLS_CERT_REQUEST);
+ }
+#endif
+
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ XDLL_insert (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ connection);
+ DLL_insert (daemon->connections_head,
+ daemon->connections_tail,
+ connection);
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+
+ if (NULL != daemon->notify_connection)
+ daemon->notify_connection (daemon->notify_connection_cls,
+ connection,
+ &connection->socket_context,
+ MHD_CONNECTION_NOTIFY_STARTED);
+
+ /* attempt to create handler thread */
+ if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
+ {
+ res_thread_create = create_thread (&connection->pid,
+ daemon,
+ &MHD_handle_connection,
+ connection);
+ if (0 != res_thread_create)
+ {
+ eno = errno;
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to create a thread: %s\n",
+ MHD_strerror_ (res_thread_create));
+#endif
+ goto cleanup;
+ }
+ }
+ else
+ if ( (MHD_YES == external_add) &&
+ (MHD_INVALID_PIPE_ != daemon->wpipe[1]) &&
+ (1 != MHD_pipe_write_ (daemon->wpipe[1], "n", 1)) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "failed to signal new connection via pipe");
+#endif
+ }
+#if EPOLL_SUPPORT
+ if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY))
+ {
+ if (0 == (daemon->options & MHD_USE_EPOLL_TURBO))
+ {
+ struct epoll_event event;
+
+ event.events = EPOLLIN | EPOLLOUT | EPOLLET;
+ event.data.ptr = connection;
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_ADD,
+ client_socket,
+ &event))
+ {
+ eno = errno;
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_ctl failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ goto cleanup;
+ }
+ connection->epoll_state |= MHD_EPOLL_STATE_IN_EPOLL_SET;
+ }
+ else
+ {
+ connection->epoll_state |= MHD_EPOLL_STATE_READ_READY | MHD_EPOLL_STATE_WRITE_READY
+ | MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ connection);
+ }
+ }
+#endif
+ daemon->connections++;
+ return MHD_YES;
+ cleanup:
+ if (NULL != daemon->notify_connection)
+ daemon->notify_connection (daemon->notify_connection_cls,
+ connection,
+ &connection->socket_context,
+ MHD_CONNECTION_NOTIFY_CLOSED);
+ if (0 != MHD_socket_close_ (client_socket))
+ MHD_PANIC ("close failed\n");
+ MHD_ip_limit_del (daemon, addr, addrlen);
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ DLL_remove (daemon->connections_head,
+ daemon->connections_tail,
+ connection);
+ XDLL_remove (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ connection);
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+ MHD_pool_destroy (connection->pool);
+ free (connection->addr);
+ free (connection);
+#if EINVAL
+ errno = eno;
+#endif
+ return MHD_NO;
+}
+
+
+/**
+ * Suspend handling of network data for a given connection. This can
+ * be used to dequeue a connection from MHD's event loop (external
+ * select, internal select or thread pool; not applicable to
+ * thread-per-connection!) for a while.
+ *
+ * If you use this API in conjunction with a internal select or a
+ * thread pool, you must set the option #MHD_USE_PIPE_FOR_SHUTDOWN to
+ * ensure that a resumed connection is immediately processed by MHD.
+ *
+ * Suspended connections continue to count against the total number of
+ * connections allowed (per daemon, as well as per IP, if such limits
+ * are set). Suspended connections will NOT time out; timeouts will
+ * restart when the connection handling is resumed. While a
+ * connection is suspended, MHD will not detect disconnects by the
+ * client.
+ *
+ * The only safe time to suspend a connection is from the
+ * #MHD_AccessHandlerCallback.
+ *
+ * Finally, it is an API violation to call #MHD_stop_daemon while
+ * having suspended connections (this will at least create memory and
+ * socket leaks or lead to undefined behavior). You must explicitly
+ * resume all connections before stopping the daemon.
+ *
+ * @param connection the connection to suspend
+ */
+void
+MHD_suspend_connection (struct MHD_Connection *connection)
+{
+ struct MHD_Daemon *daemon;
+
+ daemon = connection->daemon;
+ if (MHD_USE_SUSPEND_RESUME != (daemon->options & MHD_USE_SUSPEND_RESUME))
+ MHD_PANIC ("Cannot suspend connections without enabling MHD_USE_SUSPEND_RESUME!\n");
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ DLL_remove (daemon->connections_head,
+ daemon->connections_tail,
+ connection);
+ DLL_insert (daemon->suspended_connections_head,
+ daemon->suspended_connections_tail,
+ connection);
+ if (connection->connection_timeout == daemon->connection_timeout)
+ XDLL_remove (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ connection);
+ else
+ XDLL_remove (daemon->manual_timeout_head,
+ daemon->manual_timeout_tail,
+ connection);
+#if EPOLL_SUPPORT
+ if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY))
+ {
+ if (0 != (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL))
+ {
+ EDLL_remove (daemon->eready_head,
+ daemon->eready_tail,
+ connection);
+ connection->epoll_state &= ~MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ if (0 != (connection->epoll_state & MHD_EPOLL_STATE_IN_EPOLL_SET))
+ {
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_DEL,
+ connection->socket_fd,
+ NULL))
+ MHD_PANIC ("Failed to remove FD from epoll set\n");
+ connection->epoll_state &= ~MHD_EPOLL_STATE_IN_EPOLL_SET;
+ }
+ connection->epoll_state |= MHD_EPOLL_STATE_SUSPENDED;
+ }
+#endif
+ connection->suspended = MHD_YES;
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+}
+
+
+/**
+ * Resume handling of network data for suspended connection. It is
+ * safe to resume a suspended connection at any time. Calling this function
+ * on a connection that was not previously suspended will result
+ * in undefined behavior.
+ *
+ * @param connection the connection to resume
+ */
+void
+MHD_resume_connection (struct MHD_Connection *connection)
+{
+ struct MHD_Daemon *daemon;
+
+ daemon = connection->daemon;
+ if (MHD_USE_SUSPEND_RESUME != (daemon->options & MHD_USE_SUSPEND_RESUME))
+ MHD_PANIC ("Cannot resume connections without enabling MHD_USE_SUSPEND_RESUME!\n");
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ connection->resuming = MHD_YES;
+ daemon->resuming = MHD_YES;
+ if ( (MHD_INVALID_PIPE_ != daemon->wpipe[1]) &&
+ (1 != MHD_pipe_write_ (daemon->wpipe[1], "r", 1)) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "failed to signal resume via pipe");
+#endif
+ }
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+}
+
+
+/**
+ * Run through the suspended connections and move any that are no
+ * longer suspended back to the active state.
+ *
+ * @param daemon daemon context
+ * @return #MHD_YES if a connection was actually resumed
+ */
+static int
+resume_suspended_connections (struct MHD_Daemon *daemon)
+{
+ struct MHD_Connection *pos;
+ struct MHD_Connection *next = NULL;
+ int ret;
+
+ ret = MHD_NO;
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ if (MHD_YES == daemon->resuming)
+ next = daemon->suspended_connections_head;
+
+ while (NULL != (pos = next))
+ {
+ next = pos->next;
+ if (MHD_NO == pos->resuming)
+ continue;
+ ret = MHD_YES;
+ DLL_remove (daemon->suspended_connections_head,
+ daemon->suspended_connections_tail,
+ pos);
+ DLL_insert (daemon->connections_head,
+ daemon->connections_tail,
+ pos);
+ if (pos->connection_timeout == daemon->connection_timeout)
+ XDLL_insert (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ pos);
+ else
+ XDLL_insert (daemon->manual_timeout_head,
+ daemon->manual_timeout_tail,
+ pos);
+#if EPOLL_SUPPORT
+ if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY))
+ {
+ if (0 != (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL))
+ MHD_PANIC ("Resumed connection was already in EREADY set\n");
+ /* we always mark resumed connections as ready, as we
+ might have missed the edge poll event during suspension */
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ pos);
+ pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ pos->epoll_state &= ~MHD_EPOLL_STATE_SUSPENDED;
+ }
+#endif
+ pos->suspended = MHD_NO;
+ pos->resuming = MHD_NO;
+ }
+ daemon->resuming = MHD_NO;
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+ return ret;
+}
+
+
+/**
+ * Change socket options to be non-blocking, non-inheritable.
+ *
+ * @param daemon daemon context
+ * @param sock socket to manipulate
+ */
+static void
+make_nonblocking_noninheritable (struct MHD_Daemon *daemon,
+ MHD_socket sock)
+{
+#ifdef WINDOWS
+ DWORD dwFlags;
+ unsigned long flags = 1;
+
+ if (0 != ioctlsocket (sock, FIONBIO, &flags))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to make socket non-blocking: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ }
+ if (!GetHandleInformation ((HANDLE) sock, &dwFlags) ||
+ ((dwFlags != (dwFlags & ~HANDLE_FLAG_INHERIT)) &&
+ !SetHandleInformation ((HANDLE) sock, HANDLE_FLAG_INHERIT, 0)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to make socket non-inheritable: %u\n",
+ (unsigned int) GetLastError ());
+#endif
+ }
+#else
+ int flags;
+ int nonblock;
+
+ nonblock = O_NONBLOCK;
+#ifdef CYGWIN
+ if (0 == (daemon->options & MHD_USE_SSL))
+ nonblock = 0;
+#endif
+ flags = fcntl (sock, F_GETFD);
+ if ( ( (-1 == flags) ||
+ ( (flags != (flags | FD_CLOEXEC)) &&
+ (0 != fcntl (sock, F_SETFD, flags | nonblock | FD_CLOEXEC)) ) ) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to make socket non-inheritable: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ }
+#endif
+}
+
+
+/**
+ * Add another client connection to the set of connections managed by
+ * MHD. This API is usually not needed (since MHD will accept inbound
+ * connections on the server socket). Use this API in special cases,
+ * for example if your HTTP server is behind NAT and needs to connect
+ * out to the HTTP client, or if you are building a proxy.
+ *
+ * If you use this API in conjunction with a internal select or a
+ * thread pool, you must set the option
+ * #MHD_USE_PIPE_FOR_SHUTDOWN to ensure that the freshly added
+ * connection is immediately processed by MHD.
+ *
+ * The given client socket will be managed (and closed!) by MHD after
+ * this call and must no longer be used directly by the application
+ * afterwards.
+ *
+ * Per-IP connection limits are ignored when using this API.
+ *
+ * @param daemon daemon that manages the connection
+ * @param client_socket socket to manage (MHD will expect
+ * to receive an HTTP request from this socket next).
+ * @param addr IP address of the client
+ * @param addrlen number of bytes in @a addr
+ * @return #MHD_YES on success, #MHD_NO if this daemon could
+ * not handle the connection (i.e. malloc() failed, etc).
+ * The socket will be closed in any case; `errno` is
+ * set to indicate further details about the error.
+ * @ingroup specialized
+ */
+int
+MHD_add_connection (struct MHD_Daemon *daemon,
+ MHD_socket client_socket,
+ const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ make_nonblocking_noninheritable (daemon,
+ client_socket);
+ return internal_add_connection (daemon,
+ client_socket,
+ addr, addrlen,
+ MHD_YES);
+}
+
+
+/**
+ * Accept an incoming connection and create the MHD_Connection object for
+ * it. This function also enforces policy by way of checking with the
+ * accept policy callback.
+ *
+ * @param daemon handle with the listen socket
+ * @return #MHD_YES on success (connections denied by policy or due
+ * to 'out of memory' and similar errors) are still considered
+ * successful as far as #MHD_accept_connection() is concerned);
+ * a return code of #MHD_NO only refers to the actual
+ * accept() system call.
+ */
+static int
+MHD_accept_connection (struct MHD_Daemon *daemon)
+{
+#if HAVE_INET6
+ struct sockaddr_in6 addrstorage;
+#else
+ struct sockaddr_in addrstorage;
+#endif
+ struct sockaddr *addr = (struct sockaddr *) &addrstorage;
+ socklen_t addrlen;
+ MHD_socket s;
+ MHD_socket fd;
+ int nonblock;
+
+ addrlen = sizeof (addrstorage);
+ memset (addr, 0, sizeof (addrstorage));
+ if (MHD_INVALID_SOCKET == (fd = daemon->socket_fd))
+ return MHD_NO;
+#ifdef HAVE_SOCK_NONBLOCK
+ nonblock = SOCK_NONBLOCK;
+#else
+ nonblock = 0;
+#endif
+#ifdef CYGWIN
+ if (0 == (daemon->options & MHD_USE_SSL))
+ nonblock = 0;
+#endif
+#if HAVE_ACCEPT4
+ s = accept4 (fd, addr, &addrlen, SOCK_CLOEXEC | nonblock);
+#else
+ s = accept (fd, addr, &addrlen);
+#endif
+ if ((MHD_INVALID_SOCKET == s) || (addrlen <= 0))
+ {
+#if HAVE_MESSAGES
+ const int err = MHD_socket_errno_;
+ /* This could be a common occurance with multiple worker threads */
+ if ( (EINVAL == err) &&
+ (MHD_INVALID_SOCKET == daemon->socket_fd) )
+ return MHD_NO; /* can happen during shutdown */
+ if ((EAGAIN != err) && (EWOULDBLOCK != err))
+ MHD_DLOG (daemon,
+ "Error accepting connection: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ if (MHD_INVALID_SOCKET != s)
+ {
+ if (0 != MHD_socket_close_ (s))
+ MHD_PANIC ("close failed\n");
+ /* just in case */
+ }
+ return MHD_NO;
+ }
+#if !defined(HAVE_ACCEPT4) || HAVE_ACCEPT4+0 == 0 || !defined(HAVE_SOCK_NONBLOCK) || SOCK_CLOEXEC+0 == 0
+ make_nonblocking_noninheritable (daemon, s);
+#endif
+#if HAVE_MESSAGES
+#if DEBUG_CONNECT
+ MHD_DLOG (daemon,
+ "Accepted connection on socket %d\n",
+ s);
+#endif
+#endif
+ (void) internal_add_connection (daemon, s,
+ addr, addrlen,
+ MHD_NO);
+ return MHD_YES;
+}
+
+
+/**
+ * Free resources associated with all closed connections.
+ * (destroy responses, free buffers, etc.). All closed
+ * connections are kept in the "cleanup" doubly-linked list.
+ *
+ * @param daemon daemon to clean up
+ */
+static void
+MHD_cleanup_connections (struct MHD_Daemon *daemon)
+{
+ struct MHD_Connection *pos;
+
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ while (NULL != (pos = daemon->cleanup_head))
+ {
+ DLL_remove (daemon->cleanup_head,
+ daemon->cleanup_tail,
+ pos);
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_NO == pos->thread_joined) )
+ {
+ if (0 != MHD_join_thread_ (pos->pid))
+ {
+ MHD_PANIC ("Failed to join a thread\n");
+ }
+ }
+ MHD_pool_destroy (pos->pool);
+#if HTTPS_SUPPORT
+ if (NULL != pos->tls_session)
+ gnutls_deinit (pos->tls_session);
+#endif
+ if (NULL != daemon->notify_connection)
+ daemon->notify_connection (daemon->notify_connection_cls,
+ pos,
+ &pos->socket_context,
+ MHD_CONNECTION_NOTIFY_CLOSED);
+ MHD_ip_limit_del (daemon, pos->addr, pos->addr_len);
+#if EPOLL_SUPPORT
+ if (0 != (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL))
+ {
+ EDLL_remove (daemon->eready_head,
+ daemon->eready_tail,
+ pos);
+ pos->epoll_state &= ~MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (MHD_INVALID_SOCKET != daemon->epoll_fd) &&
+ (0 != (pos->epoll_state & MHD_EPOLL_STATE_IN_EPOLL_SET)) )
+ {
+ /* epoll documentation suggests that closing a FD
+ automatically removes it from the epoll set; however,
+ this is not true as if we fail to do manually remove it,
+ we are still seeing an event for this fd in epoll,
+ causing grief (use-after-free...) --- at least on my
+ system. */
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_DEL,
+ pos->socket_fd,
+ NULL))
+ MHD_PANIC ("Failed to remove FD from epoll set\n");
+ pos->epoll_state &= ~MHD_EPOLL_STATE_IN_EPOLL_SET;
+ }
+#endif
+ if (NULL != pos->response)
+ {
+ MHD_destroy_response (pos->response);
+ pos->response = NULL;
+ }
+ if (MHD_INVALID_SOCKET != pos->socket_fd)
+ {
+#ifdef WINDOWS
+ shutdown (pos->socket_fd, SHUT_WR);
+#endif
+ if (0 != MHD_socket_close_ (pos->socket_fd))
+ MHD_PANIC ("close failed\n");
+ }
+ if (NULL != pos->addr)
+ free (pos->addr);
+ free (pos);
+ daemon->connections--;
+ }
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+}
+
+
+/**
+ * Obtain timeout value for `select()` for this daemon (only needed if
+ * connection timeout is used). The returned value is how long
+ * `select()` or `poll()` should at most block, not the timeout value set
+ * for connections. This function MUST NOT be called if MHD is
+ * running with #MHD_USE_THREAD_PER_CONNECTION.
+ *
+ * @param daemon daemon to query for timeout
+ * @param timeout set to the timeout (in milliseconds)
+ * @return #MHD_YES on success, #MHD_NO if timeouts are
+ * not used (or no connections exist that would
+ * necessiate the use of a timeout right now).
+ * @ingroup event
+ */
+int
+MHD_get_timeout (struct MHD_Daemon *daemon,
+ MHD_UNSIGNED_LONG_LONG *timeout)
+{
+ time_t earliest_deadline;
+ time_t now;
+ struct MHD_Connection *pos;
+ int have_timeout;
+
+ if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Illegal call to MHD_get_timeout\n");
+#endif
+ return MHD_NO;
+ }
+
+#if HTTPS_SUPPORT
+ if (0 != daemon->num_tls_read_ready)
+ {
+ /* if there is any TLS connection with data ready for
+ reading, we must not block in the event loop */
+ *timeout = 0;
+ return MHD_YES;
+ }
+#endif
+
+ have_timeout = MHD_NO;
+ earliest_deadline = 0; /* avoid compiler warnings */
+ for (pos = daemon->manual_timeout_head; NULL != pos; pos = pos->nextX)
+ {
+ if (0 != pos->connection_timeout)
+ {
+ if ( (! have_timeout) ||
+ (earliest_deadline > pos->last_activity + pos->connection_timeout) )
+ earliest_deadline = pos->last_activity + pos->connection_timeout;
+#if HTTPS_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_SSL)) &&
+ (0 != gnutls_record_check_pending (pos->tls_session)) )
+ earliest_deadline = 0;
+#endif
+ have_timeout = MHD_YES;
+ }
+ }
+ /* normal timeouts are sorted, so we only need to look at the 'head' */
+ pos = daemon->normal_timeout_head;
+ if ( (NULL != pos) &&
+ (0 != pos->connection_timeout) )
+ {
+ if ( (! have_timeout) ||
+ (earliest_deadline > pos->last_activity + pos->connection_timeout) )
+ earliest_deadline = pos->last_activity + pos->connection_timeout;
+#if HTTPS_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_SSL)) &&
+ (0 != gnutls_record_check_pending (pos->tls_session)) )
+ earliest_deadline = 0;
+#endif
+ have_timeout = MHD_YES;
+ }
+
+ if (MHD_NO == have_timeout)
+ return MHD_NO;
+ now = MHD_monotonic_time();
+ if (earliest_deadline < now)
+ *timeout = 0;
+ else
+ *timeout = 1000 * (1 + earliest_deadline - now);
+ return MHD_YES;
+}
+
+
+/**
+ * Run webserver operations. This method should be called by clients
+ * in combination with #MHD_get_fdset if the client-controlled select
+ * method is used.
+ *
+ * You can use this function instead of #MHD_run if you called
+ * `select()` on the result from #MHD_get_fdset. File descriptors in
+ * the sets that are not controlled by MHD will be ignored. Calling
+ * this function instead of #MHD_run is more efficient as MHD will
+ * not have to call `select()` again to determine which operations are
+ * ready.
+ *
+ * @param daemon daemon to run select loop for
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set (not used, can be NULL)
+ * @return #MHD_NO on serious errors, #MHD_YES on success
+ * @ingroup event
+ */
+int
+MHD_run_from_select (struct MHD_Daemon *daemon,
+ const fd_set *read_fd_set,
+ const fd_set *write_fd_set,
+ const fd_set *except_fd_set)
+{
+ MHD_socket ds;
+ char tmp;
+ struct MHD_Connection *pos;
+ struct MHD_Connection *next;
+
+#if EPOLL_SUPPORT
+ if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY))
+ {
+ /* we're in epoll mode, the epoll FD stands for
+ the entire event set! */
+ if (daemon->epoll_fd >= FD_SETSIZE)
+ return MHD_NO; /* poll fd too big, fail hard */
+ if (FD_ISSET (daemon->epoll_fd, read_fd_set))
+ return MHD_run (daemon);
+ return MHD_YES;
+ }
+#endif
+
+ /* select connection thread handling type */
+ if ( (MHD_INVALID_SOCKET != (ds = daemon->socket_fd)) &&
+ (FD_ISSET (ds, read_fd_set)) )
+ (void) MHD_accept_connection (daemon);
+ /* drain signaling pipe to avoid spinning select */
+ if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) &&
+ (FD_ISSET (daemon->wpipe[0], read_fd_set)) )
+ (void) MHD_pipe_read_ (daemon->wpipe[0], &tmp, sizeof (tmp));
+
+ if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
+ {
+ /* do not have a thread per connection, process all connections now */
+ next = daemon->connections_head;
+ while (NULL != (pos = next))
+ {
+ next = pos->next;
+ ds = pos->socket_fd;
+ if (MHD_INVALID_SOCKET == ds)
+ continue;
+ switch (pos->event_loop_info)
+ {
+ case MHD_EVENT_LOOP_INFO_READ:
+ if ( (FD_ISSET (ds, read_fd_set))
+#if HTTPS_SUPPORT
+ || (MHD_YES == pos->tls_read_ready)
+#endif
+ )
+ pos->read_handler (pos);
+ break;
+ case MHD_EVENT_LOOP_INFO_WRITE:
+ if ( (FD_ISSET (ds, read_fd_set)) &&
+ (pos->read_buffer_size > pos->read_buffer_offset) )
+ pos->read_handler (pos);
+ if (FD_ISSET (ds, write_fd_set))
+ pos->write_handler (pos);
+ break;
+ case MHD_EVENT_LOOP_INFO_BLOCK:
+ if ( (FD_ISSET (ds, read_fd_set)) &&
+ (pos->read_buffer_size > pos->read_buffer_offset) )
+ pos->read_handler (pos);
+ break;
+ case MHD_EVENT_LOOP_INFO_CLEANUP:
+ /* should never happen */
+ break;
+ }
+ pos->idle_handler (pos);
+ }
+ }
+ MHD_cleanup_connections (daemon);
+ return MHD_YES;
+}
+
+
+/**
+ * Main internal select() call. Will compute select sets, call select()
+ * and then #MHD_run_from_select with the result.
+ *
+ * @param daemon daemon to run select() loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_NO on serious errors, #MHD_YES on success
+ */
+static int
+MHD_select (struct MHD_Daemon *daemon,
+ int may_block)
+{
+ int num_ready;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ struct timeval timeout;
+ struct timeval *tv;
+ MHD_UNSIGNED_LONG_LONG ltimeout;
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ if (MHD_YES == daemon->shutdown)
+ return MHD_NO;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ max = MHD_INVALID_SOCKET;
+ if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
+ {
+ if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) &&
+ (MHD_YES == resume_suspended_connections (daemon)) )
+ may_block = MHD_NO;
+
+ /* single-threaded, go over everything */
+ if (MHD_NO == MHD_get_fdset2 (daemon, &rs, &ws, &es, &max, FD_SETSIZE))
+ return MHD_NO;
+
+ /* If we're at the connection limit, no need to
+ accept new connections; however, make sure
+ we do not miss the shutdown, so only do this
+ optimization if we have a shutdown signaling
+ pipe. */
+ if ( (MHD_INVALID_SOCKET != daemon->socket_fd) &&
+ (daemon->connections == daemon->connection_limit) &&
+ (0 != (daemon->options & MHD_USE_PIPE_FOR_SHUTDOWN)) )
+ FD_CLR (daemon->socket_fd, &rs);
+ }
+ else
+ {
+ /* accept only, have one thread per connection */
+ if ( (MHD_INVALID_SOCKET != daemon->socket_fd) &&
+ (MHD_YES != add_to_fd_set (daemon->socket_fd,
+ &rs,
+ &max,
+ FD_SETSIZE)) )
+ return MHD_NO;
+ }
+ if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) &&
+ (MHD_YES != add_to_fd_set (daemon->wpipe[0],
+ &rs,
+ &max,
+ FD_SETSIZE)) )
+ return MHD_NO;
+
+ tv = NULL;
+ if (MHD_NO == may_block)
+ {
+ timeout.tv_usec = 0;
+ timeout.tv_sec = 0;
+ tv = &timeout;
+ }
+ else if ( (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES == MHD_get_timeout (daemon, <imeout)) )
+ {
+ /* ltimeout is in ms */
+ timeout.tv_usec = (ltimeout % 1000) * 1000;
+ timeout.tv_sec = ltimeout / 1000;
+ tv = &timeout;
+ }
+ if (MHD_INVALID_SOCKET == max)
+ return MHD_YES;
+ num_ready = MHD_SYS_select_ (max + 1, &rs, &ws, &es, tv);
+ if (MHD_YES == daemon->shutdown)
+ return MHD_NO;
+ if (num_ready < 0)
+ {
+ if (EINTR == MHD_socket_errno_)
+ return MHD_YES;
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "select failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ return MHD_NO;
+ }
+ return MHD_run_from_select (daemon, &rs, &ws, &es);
+}
+
+
+#ifdef HAVE_POLL
+/**
+ * Process all of our connections and possibly the server
+ * socket using poll().
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_NO on serious errors, #MHD_YES on success
+ */
+static int
+MHD_poll_all (struct MHD_Daemon *daemon,
+ int may_block)
+{
+ unsigned int num_connections;
+ struct MHD_Connection *pos;
+ struct MHD_Connection *next;
+
+ if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) &&
+ (MHD_YES == resume_suspended_connections (daemon)) )
+ may_block = MHD_NO;
+
+ /* count number of connections and thus determine poll set size */
+ num_connections = 0;
+ for (pos = daemon->connections_head; NULL != pos; pos = pos->next)
+ num_connections++;
+ {
+ MHD_UNSIGNED_LONG_LONG ltimeout;
+ unsigned int i;
+ int timeout;
+ unsigned int poll_server;
+ int poll_listen;
+ int poll_pipe;
+ char tmp;
+ struct pollfd *p;
+
+ p = malloc(sizeof (struct pollfd) * (2 + num_connections));
+ if (NULL == p)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(daemon,
+ "Error allocating memory: %s\n",
+ MHD_strerror_(errno));
+#endif
+ return MHD_NO;
+ }
+ memset (p, 0, sizeof (struct pollfd) * (2 + num_connections));
+ poll_server = 0;
+ poll_listen = -1;
+ if ( (MHD_INVALID_SOCKET != daemon->socket_fd) &&
+ (daemon->connections < daemon->connection_limit) )
+ {
+ /* only listen if we are not at the connection limit */
+ p[poll_server].fd = daemon->socket_fd;
+ p[poll_server].events = POLLIN;
+ p[poll_server].revents = 0;
+ poll_listen = (int) poll_server;
+ poll_server++;
+ }
+ poll_pipe = -1;
+ if (MHD_INVALID_PIPE_ != daemon->wpipe[0])
+ {
+ p[poll_server].fd = daemon->wpipe[0];
+ p[poll_server].events = POLLIN;
+ p[poll_server].revents = 0;
+ poll_pipe = (int) poll_server;
+ poll_server++;
+ }
+ if (may_block == MHD_NO)
+ timeout = 0;
+ else if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) ||
+ (MHD_YES != MHD_get_timeout (daemon, <imeout)) )
+ timeout = -1;
+ else
+ timeout = (ltimeout > INT_MAX) ? INT_MAX : (int) ltimeout;
+
+ i = 0;
+ for (pos = daemon->connections_head; NULL != pos; pos = pos->next)
+ {
+ p[poll_server+i].fd = pos->socket_fd;
+ switch (pos->event_loop_info)
+ {
+ case MHD_EVENT_LOOP_INFO_READ:
+ p[poll_server+i].events |= POLLIN;
+ break;
+ case MHD_EVENT_LOOP_INFO_WRITE:
+ p[poll_server+i].events |= POLLOUT;
+ if (pos->read_buffer_size > pos->read_buffer_offset)
+ p[poll_server+i].events |= POLLIN;
+ break;
+ case MHD_EVENT_LOOP_INFO_BLOCK:
+ if (pos->read_buffer_size > pos->read_buffer_offset)
+ p[poll_server+i].events |= POLLIN;
+ break;
+ case MHD_EVENT_LOOP_INFO_CLEANUP:
+ timeout = 0; /* clean up "pos" immediately */
+ break;
+ }
+ i++;
+ }
+ if (0 == poll_server + num_connections)
+ {
+ free(p);
+ return MHD_YES;
+ }
+ if (MHD_sys_poll_(p, poll_server + num_connections, timeout) < 0)
+ {
+ if (EINTR == MHD_socket_errno_)
+ {
+ free(p);
+ return MHD_YES;
+ }
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "poll failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ free(p);
+ return MHD_NO;
+ }
+ /* handle shutdown */
+ if (MHD_YES == daemon->shutdown)
+ {
+ free(p);
+ return MHD_NO;
+ }
+ i = 0;
+ next = daemon->connections_head;
+ while (NULL != (pos = next))
+ {
+ next = pos->next;
+ switch (pos->event_loop_info)
+ {
+ case MHD_EVENT_LOOP_INFO_READ:
+ /* first, sanity checks */
+ if (i >= num_connections)
+ break; /* connection list changed somehow, retry later ... */
+ if (p[poll_server+i].fd != pos->socket_fd)
+ break; /* fd mismatch, something else happened, retry later ... */
+ /* normal handling */
+ if (0 != (p[poll_server+i].revents & POLLIN))
+ pos->read_handler (pos);
+ pos->idle_handler (pos);
+ i++;
+ break;
+ case MHD_EVENT_LOOP_INFO_WRITE:
+ /* first, sanity checks */
+ if (i >= num_connections)
+ break; /* connection list changed somehow, retry later ... */
+ if (p[poll_server+i].fd != pos->socket_fd)
+ break; /* fd mismatch, something else happened, retry later ... */
+ /* normal handling */
+ if (0 != (p[poll_server+i].revents & POLLIN))
+ pos->read_handler (pos);
+ if (0 != (p[poll_server+i].revents & POLLOUT))
+ pos->write_handler (pos);
+ pos->idle_handler (pos);
+ i++;
+ break;
+ case MHD_EVENT_LOOP_INFO_BLOCK:
+ if (0 != (p[poll_server+i].revents & POLLIN))
+ pos->read_handler (pos);
+ pos->idle_handler (pos);
+ break;
+ case MHD_EVENT_LOOP_INFO_CLEANUP:
+ pos->idle_handler (pos);
+ break;
+ }
+ }
+ /* handle 'listen' FD */
+ if ( (-1 != poll_listen) &&
+ (0 != (p[poll_listen].revents & POLLIN)) )
+ (void) MHD_accept_connection (daemon);
+
+ /* handle pipe FD */
+ if ( (-1 != poll_pipe) &&
+ (0 != (p[poll_pipe].revents & POLLIN)) )
+ (void) MHD_pipe_read_ (daemon->wpipe[0], &tmp, sizeof (tmp));
+
+ free(p);
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Process only the listen socket using poll().
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_NO on serious errors, #MHD_YES on success
+ */
+static int
+MHD_poll_listen_socket (struct MHD_Daemon *daemon,
+ int may_block)
+{
+ struct pollfd p[2];
+ int timeout;
+ unsigned int poll_count;
+ int poll_listen;
+
+ memset (&p, 0, sizeof (p));
+ poll_count = 0;
+ poll_listen = -1;
+ if (MHD_INVALID_SOCKET != daemon->socket_fd)
+ {
+ p[poll_count].fd = daemon->socket_fd;
+ p[poll_count].events = POLLIN;
+ p[poll_count].revents = 0;
+ poll_listen = poll_count;
+ poll_count++;
+ }
+ if (MHD_INVALID_PIPE_ != daemon->wpipe[0])
+ {
+ p[poll_count].fd = daemon->wpipe[0];
+ p[poll_count].events = POLLIN;
+ p[poll_count].revents = 0;
+ poll_count++;
+ }
+ if (MHD_NO == may_block)
+ timeout = 0;
+ else
+ timeout = -1;
+ if (0 == poll_count)
+ return MHD_YES;
+ if (MHD_sys_poll_(p, poll_count, timeout) < 0)
+ {
+ if (EINTR == MHD_socket_errno_)
+ return MHD_YES;
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "poll failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ return MHD_NO;
+ }
+ /* handle shutdown */
+ if (MHD_YES == daemon->shutdown)
+ return MHD_NO;
+ if ( (-1 != poll_listen) &&
+ (0 != (p[poll_listen].revents & POLLIN)) )
+ (void) MHD_accept_connection (daemon);
+ return MHD_YES;
+}
+#endif
+
+
+/**
+ * Do poll()-based processing.
+ *
+ * @param daemon daemon to run poll()-loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_NO on serious errors, #MHD_YES on success
+ */
+static int
+MHD_poll (struct MHD_Daemon *daemon,
+ int may_block)
+{
+#ifdef HAVE_POLL
+ if (MHD_YES == daemon->shutdown)
+ return MHD_NO;
+ if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
+ return MHD_poll_all (daemon, may_block);
+ else
+ return MHD_poll_listen_socket (daemon, may_block);
+#else
+ return MHD_NO;
+#endif
+}
+
+
+#if EPOLL_SUPPORT
+
+/**
+ * How many events to we process at most per epoll() call? Trade-off
+ * between required stack-size and number of system calls we have to
+ * make; 128 should be way enough to avoid more than one system call
+ * for most scenarios, and still be moderate in stack size
+ * consumption. Embedded systems might want to choose a smaller value
+ * --- but why use epoll() on such a system in the first place?
+ */
+#define MAX_EVENTS 128
+
+
+/**
+ * Do epoll()-based processing (this function is allowed to
+ * block if @a may_block is set to #MHD_YES).
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_NO on serious errors, #MHD_YES on success
+ */
+static int
+MHD_epoll (struct MHD_Daemon *daemon,
+ int may_block)
+{
+ struct MHD_Connection *pos;
+ struct MHD_Connection *next;
+ struct epoll_event events[MAX_EVENTS];
+ struct epoll_event event;
+ int timeout_ms;
+ MHD_UNSIGNED_LONG_LONG timeout_ll;
+ int num_events;
+ unsigned int i;
+ unsigned int series_length;
+ char tmp;
+
+ if (-1 == daemon->epoll_fd)
+ return MHD_NO; /* we're down! */
+ if (MHD_YES == daemon->shutdown)
+ return MHD_NO;
+ if ( (MHD_INVALID_SOCKET != daemon->socket_fd) &&
+ (daemon->connections < daemon->connection_limit) &&
+ (MHD_NO == daemon->listen_socket_in_epoll) )
+ {
+ event.events = EPOLLIN;
+ event.data.ptr = daemon;
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_ADD,
+ daemon->socket_fd,
+ &event))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_ctl failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ return MHD_NO;
+ }
+ daemon->listen_socket_in_epoll = MHD_YES;
+ }
+ if ( (MHD_YES == daemon->listen_socket_in_epoll) &&
+ (daemon->connections == daemon->connection_limit) )
+ {
+ /* we're at the connection limit, disable listen socket
+ for event loop for now */
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_DEL,
+ daemon->socket_fd,
+ NULL))
+ MHD_PANIC ("Failed to remove listen FD from epoll set\n");
+ daemon->listen_socket_in_epoll = MHD_NO;
+ }
+ if (MHD_YES == may_block)
+ {
+ if (MHD_YES == MHD_get_timeout (daemon,
+ &timeout_ll))
+ {
+ if (timeout_ll >= (MHD_UNSIGNED_LONG_LONG) INT_MAX)
+ timeout_ms = INT_MAX;
+ else
+ timeout_ms = (int) timeout_ll;
+ }
+ else
+ timeout_ms = -1;
+ }
+ else
+ timeout_ms = 0;
+
+ /* drain 'epoll' event queue; need to iterate as we get at most
+ MAX_EVENTS in one system call here; in practice this should
+ pretty much mean only one round, but better an extra loop here
+ than unfair behavior... */
+ num_events = MAX_EVENTS;
+ while (MAX_EVENTS == num_events)
+ {
+ /* update event masks */
+ num_events = epoll_wait (daemon->epoll_fd,
+ events, MAX_EVENTS, timeout_ms);
+ if (-1 == num_events)
+ {
+ if (EINTR == MHD_socket_errno_)
+ return MHD_YES;
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_wait failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ return MHD_NO;
+ }
+ for (i=0;i<(unsigned int) num_events;i++)
+ {
+ if (NULL == events[i].data.ptr)
+ continue; /* shutdown signal! */
+ if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) &&
+ (daemon->wpipe[0] == events[i].data.fd) )
+ {
+ (void) MHD_pipe_read_ (daemon->wpipe[0], &tmp, sizeof (tmp));
+ continue;
+ }
+ if (daemon != events[i].data.ptr)
+ {
+ /* this is an event relating to a 'normal' connection,
+ remember the event and if appropriate mark the
+ connection as 'eready'. */
+ pos = events[i].data.ptr;
+ if (0 != (events[i].events & EPOLLIN))
+ {
+ pos->epoll_state |= MHD_EPOLL_STATE_READ_READY;
+ if ( ( (MHD_EVENT_LOOP_INFO_READ == pos->event_loop_info) ||
+ (pos->read_buffer_size > pos->read_buffer_offset) ) &&
+ (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) )
+ {
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ pos);
+ pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ }
+ if (0 != (events[i].events & EPOLLOUT))
+ {
+ pos->epoll_state |= MHD_EPOLL_STATE_WRITE_READY;
+ if ( (MHD_EVENT_LOOP_INFO_WRITE == pos->event_loop_info) &&
+ (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) )
+ {
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ pos);
+ pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ }
+ }
+ else /* must be listen socket */
+ {
+ /* run 'accept' until it fails or we are not allowed to take
+ on more connections */
+ series_length = 0;
+ while ( (MHD_YES == MHD_accept_connection (daemon)) &&
+ (daemon->connections < daemon->connection_limit) &&
+ (series_length < 128) )
+ series_length++;
+ }
+ }
+ }
+
+ /* we handle resumes here because we may have ready connections
+ that will not be placed into the epoll list immediately. */
+ if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) &&
+ (MHD_YES == resume_suspended_connections (daemon)) )
+ may_block = MHD_NO;
+
+ /* process events for connections */
+ while (NULL != (pos = daemon->eready_tail))
+ {
+ EDLL_remove (daemon->eready_head,
+ daemon->eready_tail,
+ pos);
+ pos->epoll_state &= ~MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ if (MHD_EVENT_LOOP_INFO_READ == pos->event_loop_info)
+ pos->read_handler (pos);
+ if (MHD_EVENT_LOOP_INFO_WRITE == pos->event_loop_info)
+ pos->write_handler (pos);
+ pos->idle_handler (pos);
+ }
+
+ /* Finally, handle timed-out connections; we need to do this here
+ as the epoll mechanism won't call the 'idle_handler' on everything,
+ as the other event loops do. As timeouts do not get an explicit
+ event, we need to find those connections that might have timed out
+ here.
+
+ Connections with custom timeouts must all be looked at, as we
+ do not bother to sort that (presumably very short) list. */
+ next = daemon->manual_timeout_head;
+ while (NULL != (pos = next))
+ {
+ next = pos->nextX;
+ pos->idle_handler (pos);
+ }
+ /* Connections with the default timeout are sorted by prepending
+ them to the head of the list whenever we touch the connection;
+ thus it sufficies to iterate from the tail until the first
+ connection is NOT timed out */
+ next = daemon->normal_timeout_tail;
+ while (NULL != (pos = next))
+ {
+ next = pos->prevX;
+ pos->idle_handler (pos);
+ if (MHD_CONNECTION_CLOSED != pos->state)
+ break; /* sorted by timeout, no need to visit the rest! */
+ }
+ return MHD_YES;
+}
+#endif
+
+
+/**
+ * Run webserver operations (without blocking unless in client
+ * callbacks). This method should be called by clients in combination
+ * with #MHD_get_fdset if the client-controlled select method is used.
+ *
+ * This function is a convenience method, which is useful if the
+ * fd_sets from #MHD_get_fdset were not directly passed to `select()`;
+ * with this function, MHD will internally do the appropriate `select()`
+ * call itself again. While it is always safe to call #MHD_run (in
+ * external select mode), you should call #MHD_run_from_select if
+ * performance is important (as it saves an expensive call to
+ * `select()`).
+ *
+ * @param daemon daemon to run
+ * @return #MHD_YES on success, #MHD_NO if this
+ * daemon was not started with the right
+ * options for this call.
+ * @ingroup event
+ */
+int
+MHD_run (struct MHD_Daemon *daemon)
+{
+ if ( (MHD_YES == daemon->shutdown) ||
+ (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) ||
+ (0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) )
+ return MHD_NO;
+ if (0 != (daemon->options & MHD_USE_POLL))
+ {
+ MHD_poll (daemon, MHD_NO);
+ MHD_cleanup_connections (daemon);
+ }
+#if EPOLL_SUPPORT
+ else if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY))
+ {
+ MHD_epoll (daemon, MHD_NO);
+ MHD_cleanup_connections (daemon);
+ }
+#endif
+ else
+ {
+ MHD_select (daemon, MHD_NO);
+ /* MHD_select does MHD_cleanup_connections already */
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Thread that runs the select loop until the daemon
+ * is explicitly shut down.
+ *
+ * @param cls 'struct MHD_Deamon' to run select loop in a thread for
+ * @return always 0 (on shutdown)
+ */
+static MHD_THRD_RTRN_TYPE_ MHD_THRD_CALL_SPEC_
+MHD_select_thread (void *cls)
+{
+ struct MHD_Daemon *daemon = cls;
+
+ while (MHD_YES != daemon->shutdown)
+ {
+ if (0 != (daemon->options & MHD_USE_POLL))
+ MHD_poll (daemon, MHD_YES);
+#if EPOLL_SUPPORT
+ else if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY))
+ MHD_epoll (daemon, MHD_YES);
+#endif
+ else
+ MHD_select (daemon, MHD_YES);
+ MHD_cleanup_connections (daemon);
+ }
+ return (MHD_THRD_RTRN_TYPE_)0;
+}
+
+
+/**
+ * Process escape sequences ('%HH') Updates val in place; the
+ * result should be UTF-8 encoded and cannot be larger than the input.
+ * The result must also still be 0-terminated.
+ *
+ * @param cls closure (use NULL)
+ * @param connection handle to connection, not used
+ * @param val value to unescape (modified in the process)
+ * @return length of the resulting val (strlen(val) maybe
+ * shorter afterwards due to elimination of escape sequences)
+ */
+static size_t
+unescape_wrapper (void *cls,
+ struct MHD_Connection *connection,
+ char *val)
+{
+ return MHD_http_unescape (val);
+}
+
+
+/**
+ * Start a webserver on the given port. Variadic version of
+ * #MHD_start_daemon_va.
+ *
+ * @param flags combination of `enum MHD_FLAG` values
+ * @param port port to bind to
+ * @param apc callback to call to check which clients
+ * will be allowed to connect; you can pass NULL
+ * in which case connections from any IP will be
+ * accepted
+ * @param apc_cls extra argument to @a apc
+ * @param dh handler called for all requests (repeatedly)
+ * @param dh_cls extra argument to @a dh
+ * @return NULL on error, handle to daemon on success
+ * @ingroup event
+ */
+struct MHD_Daemon *
+MHD_start_daemon (unsigned int flags,
+ uint16_t port,
+ MHD_AcceptPolicyCallback apc,
+ void *apc_cls,
+ MHD_AccessHandlerCallback dh, void *dh_cls, ...)
+{
+ struct MHD_Daemon *daemon;
+ va_list ap;
+
+ va_start (ap, dh_cls);
+ daemon = MHD_start_daemon_va (flags, port, apc, apc_cls, dh, dh_cls, ap);
+ va_end (ap);
+ return daemon;
+}
+
+
+/**
+ * Stop accepting connections from the listening socket. Allows
+ * clients to continue processing, but stops accepting new
+ * connections. Note that the caller is responsible for closing the
+ * returned socket; however, if MHD is run using threads (anything but
+ * external select mode), it must not be closed until AFTER
+ * #MHD_stop_daemon has been called (as it is theoretically possible
+ * that an existing thread is still using it).
+ *
+ * Note that some thread modes require the caller to have passed
+ * #MHD_USE_PIPE_FOR_SHUTDOWN when using this API. If this daemon is
+ * in one of those modes and this option was not given to
+ * #MHD_start_daemon, this function will return #MHD_INVALID_SOCKET.
+ *
+ * @param daemon daemon to stop accepting new connections for
+ * @return old listen socket on success, #MHD_INVALID_SOCKET if
+ * the daemon was already not listening anymore
+ * @ingroup specialized
+ */
+MHD_socket
+MHD_quiesce_daemon (struct MHD_Daemon *daemon)
+{
+ unsigned int i;
+ MHD_socket ret;
+
+ ret = daemon->socket_fd;
+ if (MHD_INVALID_SOCKET == ret)
+ return MHD_INVALID_SOCKET;
+ if ( (MHD_INVALID_PIPE_ == daemon->wpipe[1]) &&
+ (0 != (daemon->options & (MHD_USE_SELECT_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION))) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Using MHD_quiesce_daemon in this mode requires MHD_USE_PIPE_FOR_SHUTDOWN\n");
+#endif
+ return MHD_INVALID_SOCKET;
+ }
+
+ if (NULL != daemon->worker_pool)
+ for (i = 0; i < daemon->worker_pool_size; i++)
+ {
+ daemon->worker_pool[i].socket_fd = MHD_INVALID_SOCKET;
+#if EPOLL_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (-1 != daemon->worker_pool[i].epoll_fd) &&
+ (MHD_YES == daemon->worker_pool[i].listen_socket_in_epoll) )
+ {
+ if (0 != epoll_ctl (daemon->worker_pool[i].epoll_fd,
+ EPOLL_CTL_DEL,
+ ret,
+ NULL))
+ MHD_PANIC ("Failed to remove listen FD from epoll set\n");
+ daemon->worker_pool[i].listen_socket_in_epoll = MHD_NO;
+ }
+#endif
+ }
+ daemon->socket_fd = MHD_INVALID_SOCKET;
+#if EPOLL_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (-1 != daemon->epoll_fd) &&
+ (MHD_YES == daemon->listen_socket_in_epoll) )
+ {
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_DEL,
+ ret,
+ NULL))
+ MHD_PANIC ("Failed to remove listen FD from epoll set\n");
+ daemon->listen_socket_in_epoll = MHD_NO;
+ }
+#endif
+ return ret;
+}
+
+
+/**
+ * Signature of the MHD custom logger function.
+ *
+ * @param cls closure
+ * @param format format string
+ * @param va arguments to the format string (fprintf-style)
+ */
+typedef void (*VfprintfFunctionPointerType)(void *cls,
+ const char *format,
+ va_list va);
+
+
+/**
+ * Parse a list of options given as varargs.
+ *
+ * @param daemon the daemon to initialize
+ * @param servaddr where to store the server's listen address
+ * @param ap the options
+ * @return #MHD_YES on success, #MHD_NO on error
+ */
+static int
+parse_options_va (struct MHD_Daemon *daemon,
+ const struct sockaddr **servaddr,
+ va_list ap);
+
+
+/**
+ * Parse a list of options given as varargs.
+ *
+ * @param daemon the daemon to initialize
+ * @param servaddr where to store the server's listen address
+ * @param ... the options
+ * @return #MHD_YES on success, #MHD_NO on error
+ */
+static int
+parse_options (struct MHD_Daemon *daemon,
+ const struct sockaddr **servaddr,
+ ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start (ap, servaddr);
+ ret = parse_options_va (daemon, servaddr, ap);
+ va_end (ap);
+ return ret;
+}
+
+
+/**
+ * Parse a list of options given as varargs.
+ *
+ * @param daemon the daemon to initialize
+ * @param servaddr where to store the server's listen address
+ * @param ap the options
+ * @return #MHD_YES on success, #MHD_NO on error
+ */
+static int
+parse_options_va (struct MHD_Daemon *daemon,
+ const struct sockaddr **servaddr,
+ va_list ap)
+{
+ enum MHD_OPTION opt;
+ struct MHD_OptionItem *oa;
+ unsigned int i;
+#if HTTPS_SUPPORT
+ int ret;
+ const char *pstr;
+#endif
+
+ while (MHD_OPTION_END != (opt = (enum MHD_OPTION) va_arg (ap, int)))
+ {
+ switch (opt)
+ {
+ case MHD_OPTION_CONNECTION_MEMORY_LIMIT:
+ daemon->pool_size = va_arg (ap, size_t);
+ break;
+ case MHD_OPTION_CONNECTION_MEMORY_INCREMENT:
+ daemon->pool_increment= va_arg (ap, size_t);
+ break;
+ case MHD_OPTION_CONNECTION_LIMIT:
+ daemon->connection_limit = va_arg (ap, unsigned int);
+ break;
+ case MHD_OPTION_CONNECTION_TIMEOUT:
+ daemon->connection_timeout = va_arg (ap, unsigned int);
+ break;
+ case MHD_OPTION_NOTIFY_COMPLETED:
+ daemon->notify_completed =
+ va_arg (ap, MHD_RequestCompletedCallback);
+ daemon->notify_completed_cls = va_arg (ap, void *);
+ break;
+ case MHD_OPTION_NOTIFY_CONNECTION:
+ daemon->notify_connection =
+ va_arg (ap, MHD_NotifyConnectionCallback);
+ daemon->notify_connection_cls = va_arg (ap, void *);
+ break;
+ case MHD_OPTION_PER_IP_CONNECTION_LIMIT:
+ daemon->per_ip_connection_limit = va_arg (ap, unsigned int);
+ break;
+ case MHD_OPTION_SOCK_ADDR:
+ *servaddr = va_arg (ap, const struct sockaddr *);
+ break;
+ case MHD_OPTION_URI_LOG_CALLBACK:
+ daemon->uri_log_callback =
+ va_arg (ap, LogCallback);
+ daemon->uri_log_callback_cls = va_arg (ap, void *);
+ break;
+ case MHD_OPTION_THREAD_POOL_SIZE:
+ daemon->worker_pool_size = va_arg (ap, unsigned int);
+ if (daemon->worker_pool_size >= (SIZE_MAX / sizeof (struct MHD_Daemon)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Specified thread pool size (%u) too big\n",
+ daemon->worker_pool_size);
+#endif
+ return MHD_NO;
+ }
+ break;
+#if HTTPS_SUPPORT
+ case MHD_OPTION_HTTPS_MEM_KEY:
+ if (0 != (daemon->options & MHD_USE_SSL))
+ daemon->https_mem_key = va_arg (ap, const char *);
+#if HAVE_MESSAGES
+ else
+ MHD_DLOG (daemon,
+ "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n",
+ opt);
+#endif
+ break;
+ case MHD_OPTION_HTTPS_KEY_PASSWORD:
+ if (0 != (daemon->options & MHD_USE_SSL))
+ daemon->https_key_password = va_arg (ap, const char *);
+#if HAVE_MESSAGES
+ else
+ MHD_DLOG (daemon,
+ "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n",
+ opt);
+#endif
+ break;
+ case MHD_OPTION_HTTPS_MEM_CERT:
+ if (0 != (daemon->options & MHD_USE_SSL))
+ daemon->https_mem_cert = va_arg (ap, const char *);
+#if HAVE_MESSAGES
+ else
+ MHD_DLOG (daemon,
+ "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n",
+ opt);
+#endif
+ break;
+ case MHD_OPTION_HTTPS_MEM_TRUST:
+ if (0 != (daemon->options & MHD_USE_SSL))
+ daemon->https_mem_trust = va_arg (ap, const char *);
+#if HAVE_MESSAGES
+ else
+ MHD_DLOG (daemon,
+ "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n",
+ opt);
+#endif
+ break;
+ case MHD_OPTION_HTTPS_CRED_TYPE:
+ daemon->cred_type = (gnutls_credentials_type_t) va_arg (ap, int);
+ break;
+ case MHD_OPTION_HTTPS_MEM_DHPARAMS:
+ if (0 != (daemon->options & MHD_USE_SSL))
+ {
+ const char *arg = va_arg (ap, const char *);
+ gnutls_datum_t dhpar;
+
+ if (gnutls_dh_params_init (&daemon->https_mem_dhparams) < 0)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(daemon,
+ "Error initializing DH parameters\n");
+#endif
+ return MHD_NO;
+ }
+ dhpar.data = (unsigned char *) arg;
+ dhpar.size = strlen (arg);
+ if (gnutls_dh_params_import_pkcs3 (daemon->https_mem_dhparams, &dhpar,
+ GNUTLS_X509_FMT_PEM) < 0)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(daemon,
+ "Bad Diffie-Hellman parameters format\n");
+#endif
+ gnutls_dh_params_deinit (daemon->https_mem_dhparams);
+ return MHD_NO;
+ }
+ daemon->have_dhparams = MHD_YES;
+ }
+ else
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n",
+ opt);
+#endif
+ return MHD_NO;
+ }
+ break;
+ case MHD_OPTION_HTTPS_PRIORITIES:
+ if (0 != (daemon->options & MHD_USE_SSL))
+ {
+ gnutls_priority_deinit (daemon->priority_cache);
+ ret = gnutls_priority_init (&daemon->priority_cache,
+ pstr = va_arg (ap, const char*),
+ NULL);
+ if (GNUTLS_E_SUCCESS != ret)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Setting priorities to `%s' failed: %s\n",
+ pstr,
+ gnutls_strerror (ret));
+#endif
+ daemon->priority_cache = NULL;
+ return MHD_NO;
+ }
+ }
+ break;
+ case MHD_OPTION_HTTPS_CERT_CALLBACK:
+#if GNUTLS_VERSION_MAJOR < 3
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "MHD_OPTION_HTTPS_CERT_CALLBACK requires building MHD with GnuTLS >= 3.0\n");
+#endif
+ return MHD_NO;
+#else
+ if (0 != (daemon->options & MHD_USE_SSL))
+ daemon->cert_callback = va_arg (ap, gnutls_certificate_retrieve_function2 *);
+ break;
+#endif
+#endif
+#ifdef DAUTH_SUPPORT
+ case MHD_OPTION_DIGEST_AUTH_RANDOM:
+ daemon->digest_auth_rand_size = va_arg (ap, size_t);
+ daemon->digest_auth_random = va_arg (ap, const char *);
+ break;
+ case MHD_OPTION_NONCE_NC_SIZE:
+ daemon->nonce_nc_size = va_arg (ap, unsigned int);
+ break;
+#endif
+ case MHD_OPTION_LISTEN_SOCKET:
+ daemon->socket_fd = va_arg (ap, MHD_socket);
+ break;
+ case MHD_OPTION_EXTERNAL_LOGGER:
+#if HAVE_MESSAGES
+ daemon->custom_error_log =
+ va_arg (ap, VfprintfFunctionPointerType);
+ daemon->custom_error_log_cls = va_arg (ap, void *);
+#else
+ va_arg (ap, VfprintfFunctionPointerType);
+ va_arg (ap, void *);
+#endif
+ break;
+ case MHD_OPTION_THREAD_STACK_SIZE:
+ daemon->thread_stack_size = va_arg (ap, size_t);
+ break;
+#ifdef TCP_FASTOPEN
+ case MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE:
+ daemon->fastopen_queue_size = va_arg (ap, unsigned int);
+ break;
+#endif
+ case MHD_OPTION_LISTENING_ADDRESS_REUSE:
+ daemon->listening_address_reuse = va_arg (ap, unsigned int) ? 1 : -1;
+ break;
+ case MHD_OPTION_ARRAY:
+ oa = va_arg (ap, struct MHD_OptionItem*);
+ i = 0;
+ while (MHD_OPTION_END != (opt = oa[i].option))
+ {
+ switch (opt)
+ {
+ /* all options taking 'size_t' */
+ case MHD_OPTION_CONNECTION_MEMORY_LIMIT:
+ case MHD_OPTION_CONNECTION_MEMORY_INCREMENT:
+ case MHD_OPTION_THREAD_STACK_SIZE:
+ if (MHD_YES != parse_options (daemon,
+ servaddr,
+ opt,
+ (size_t) oa[i].value,
+ MHD_OPTION_END))
+ return MHD_NO;
+ break;
+ /* all options taking 'unsigned int' */
+ case MHD_OPTION_NONCE_NC_SIZE:
+ case MHD_OPTION_CONNECTION_LIMIT:
+ case MHD_OPTION_CONNECTION_TIMEOUT:
+ case MHD_OPTION_PER_IP_CONNECTION_LIMIT:
+ case MHD_OPTION_THREAD_POOL_SIZE:
+ case MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE:
+ case MHD_OPTION_LISTENING_ADDRESS_REUSE:
+ if (MHD_YES != parse_options (daemon,
+ servaddr,
+ opt,
+ (unsigned int) oa[i].value,
+ MHD_OPTION_END))
+ return MHD_NO;
+ break;
+ /* all options taking 'enum' */
+ case MHD_OPTION_HTTPS_CRED_TYPE:
+ if (MHD_YES != parse_options (daemon,
+ servaddr,
+ opt,
+ (int) oa[i].value,
+ MHD_OPTION_END))
+ return MHD_NO;
+ break;
+ /* all options taking 'MHD_socket' */
+ case MHD_OPTION_LISTEN_SOCKET:
+ if (MHD_YES != parse_options (daemon,
+ servaddr,
+ opt,
+ (MHD_socket) oa[i].value,
+ MHD_OPTION_END))
+ return MHD_NO;
+ break;
+ /* all options taking one pointer */
+ case MHD_OPTION_SOCK_ADDR:
+ case MHD_OPTION_HTTPS_MEM_KEY:
+ case MHD_OPTION_HTTPS_KEY_PASSWORD:
+ case MHD_OPTION_HTTPS_MEM_CERT:
+ case MHD_OPTION_HTTPS_MEM_TRUST:
+ case MHD_OPTION_HTTPS_MEM_DHPARAMS:
+ case MHD_OPTION_HTTPS_PRIORITIES:
+ case MHD_OPTION_ARRAY:
+ case MHD_OPTION_HTTPS_CERT_CALLBACK:
+ if (MHD_YES != parse_options (daemon,
+ servaddr,
+ opt,
+ oa[i].ptr_value,
+ MHD_OPTION_END))
+ return MHD_NO;
+ break;
+ /* all options taking two pointers */
+ case MHD_OPTION_NOTIFY_COMPLETED:
+ case MHD_OPTION_NOTIFY_CONNECTION:
+ case MHD_OPTION_URI_LOG_CALLBACK:
+ case MHD_OPTION_EXTERNAL_LOGGER:
+ case MHD_OPTION_UNESCAPE_CALLBACK:
+ if (MHD_YES != parse_options (daemon,
+ servaddr,
+ opt,
+ (void *) oa[i].value,
+ oa[i].ptr_value,
+ MHD_OPTION_END))
+ return MHD_NO;
+ break;
+ /* options taking size_t-number followed by pointer */
+ case MHD_OPTION_DIGEST_AUTH_RANDOM:
+ if (MHD_YES != parse_options (daemon,
+ servaddr,
+ opt,
+ (size_t) oa[i].value,
+ oa[i].ptr_value,
+ MHD_OPTION_END))
+ return MHD_NO;
+ break;
+ default:
+ return MHD_NO;
+ }
+ i++;
+ }
+ break;
+ case MHD_OPTION_UNESCAPE_CALLBACK:
+ daemon->unescape_callback =
+ va_arg (ap, UnescapeCallback);
+ daemon->unescape_callback_cls = va_arg (ap, void *);
+ break;
+ default:
+#if HAVE_MESSAGES
+ if (((opt >= MHD_OPTION_HTTPS_MEM_KEY) &&
+ (opt <= MHD_OPTION_HTTPS_PRIORITIES)) || (opt == MHD_OPTION_HTTPS_MEM_TRUST))
+ {
+ MHD_DLOG (daemon,
+ "MHD HTTPS option %d passed to MHD compiled without HTTPS support\n",
+ opt);
+ }
+ else
+ {
+ MHD_DLOG (daemon,
+ "Invalid option %d! (Did you terminate the list with MHD_OPTION_END?)\n",
+ opt);
+ }
+#endif
+ return MHD_NO;
+ }
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Create a listen socket, if possible with SOCK_CLOEXEC flag set.
+ *
+ * @param daemon daemon for which we create the socket
+ * @param domain socket domain (i.e. PF_INET)
+ * @param type socket type (usually SOCK_STREAM)
+ * @param protocol desired protocol, 0 for default
+ */
+static MHD_socket
+create_socket (struct MHD_Daemon *daemon,
+ int domain, int type, int protocol)
+{
+ int ctype = type | SOCK_CLOEXEC;
+ MHD_socket fd;
+
+ /* use SOCK_STREAM rather than ai_socktype: some getaddrinfo
+ * implementations do not set ai_socktype, e.g. RHL6.2. */
+ fd = socket (domain, ctype, protocol);
+ if ( (MHD_INVALID_SOCKET == fd) && (EINVAL == MHD_socket_errno_) && (0 != SOCK_CLOEXEC) )
+ {
+ ctype = type;
+ fd = socket(domain, type, protocol);
+ }
+ if (MHD_INVALID_SOCKET == fd)
+ return MHD_INVALID_SOCKET;
+ if (type == ctype)
+ make_nonblocking_noninheritable (daemon, fd);
+ return fd;
+}
+
+
+#if EPOLL_SUPPORT
+/**
+ * Setup epoll() FD for the daemon and initialize it to listen
+ * on the listen FD.
+ *
+ * @param daemon daemon to initialize for epoll()
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+static int
+setup_epoll_to_listen (struct MHD_Daemon *daemon)
+{
+ struct epoll_event event;
+
+#ifdef HAVE_EPOLL_CREATE1
+ daemon->epoll_fd = epoll_create1 (EPOLL_CLOEXEC);
+#else /* !HAVE_EPOLL_CREATE1 */
+ daemon->epoll_fd = epoll_create (MAX_EVENTS);
+#endif /* !HAVE_EPOLL_CREATE1 */
+ if (-1 == daemon->epoll_fd)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_create1 failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ return MHD_NO;
+ }
+#ifndef HAVE_EPOLL_CREATE1
+ else
+ {
+ int fdflags = fcntl (daemon->epoll_fd, F_GETFD);
+ if (0 > fdflags || 0 > fcntl (daemon->epoll_fd, F_SETFD, fdflags | FD_CLOEXEC))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to change flags on epoll fd: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif /* HAVE_MESSAGES */
+ }
+ }
+#endif /* !HAVE_EPOLL_CREATE1 */
+ if (0 == EPOLL_CLOEXEC)
+ make_nonblocking_noninheritable (daemon,
+ daemon->epoll_fd);
+ if (MHD_INVALID_SOCKET == daemon->socket_fd)
+ return MHD_YES; /* non-listening daemon */
+ event.events = EPOLLIN;
+ event.data.ptr = daemon;
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_ADD,
+ daemon->socket_fd,
+ &event))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_ctl failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ return MHD_NO;
+ }
+ if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) &&
+ (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) )
+ {
+ event.events = EPOLLIN | EPOLLET;
+ event.data.ptr = NULL;
+ event.data.fd = daemon->wpipe[0];
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_ADD,
+ daemon->wpipe[0],
+ &event))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_ctl failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ return MHD_NO;
+ }
+ }
+ daemon->listen_socket_in_epoll = MHD_YES;
+ return MHD_YES;
+}
+#endif
+
+
+/**
+ * Start a webserver on the given port.
+ *
+ * @param flags combination of `enum MHD_FLAG` values
+ * @param port port to bind to (in host byte order)
+ * @param apc callback to call to check which clients
+ * will be allowed to connect; you can pass NULL
+ * in which case connections from any IP will be
+ * accepted
+ * @param apc_cls extra argument to @a apc
+ * @param dh handler called for all requests (repeatedly)
+ * @param dh_cls extra argument to @a dh
+ * @param ap list of options (type-value pairs,
+ * terminated with #MHD_OPTION_END).
+ * @return NULL on error, handle to daemon on success
+ * @ingroup event
+ */
+struct MHD_Daemon *
+MHD_start_daemon_va (unsigned int flags,
+ uint16_t port,
+ MHD_AcceptPolicyCallback apc,
+ void *apc_cls,
+ MHD_AccessHandlerCallback dh, void *dh_cls,
+ va_list ap)
+{
+ const int on = 1;
+ struct MHD_Daemon *daemon;
+ MHD_socket socket_fd;
+ struct sockaddr_in servaddr4;
+#if HAVE_INET6
+ struct sockaddr_in6 servaddr6;
+#endif
+ const struct sockaddr *servaddr = NULL;
+ socklen_t addrlen;
+ unsigned int i;
+ int res_thread_create;
+ int use_pipe;
+
+#ifndef HAVE_INET6
+ if (0 != (flags & MHD_USE_IPv6))
+ return NULL;
+#endif
+#ifndef HAVE_POLL
+ if (0 != (flags & MHD_USE_POLL))
+ return NULL;
+#endif
+#if ! HTTPS_SUPPORT
+ if (0 != (flags & MHD_USE_SSL))
+ return NULL;
+#endif
+#ifndef TCP_FASTOPEN
+ if (0 != (flags & MHD_USE_TCP_FASTOPEN))
+ return NULL;
+#endif
+ if (NULL == dh)
+ return NULL;
+ if (NULL == (daemon = malloc (sizeof (struct MHD_Daemon))))
+ return NULL;
+ memset (daemon, 0, sizeof (struct MHD_Daemon));
+#if EPOLL_SUPPORT
+ daemon->epoll_fd = -1;
+#endif
+ /* try to open listen socket */
+#if HTTPS_SUPPORT
+ if (0 != (flags & MHD_USE_SSL))
+ {
+ gnutls_priority_init (&daemon->priority_cache,
+ "NORMAL",
+ NULL);
+ }
+#endif
+ daemon->socket_fd = MHD_INVALID_SOCKET;
+ daemon->listening_address_reuse = 0;
+ daemon->options = flags;
+#if WINDOWS
+ /* Winsock is broken with respect to 'shutdown';
+ this disables us calling 'shutdown' on W32. */
+ daemon->options |= MHD_USE_EPOLL_TURBO;
+#endif
+ daemon->port = port;
+ daemon->apc = apc;
+ daemon->apc_cls = apc_cls;
+ daemon->default_handler = dh;
+ daemon->default_handler_cls = dh_cls;
+ daemon->connections = 0;
+ daemon->connection_limit = MHD_MAX_CONNECTIONS_DEFAULT;
+ daemon->pool_size = MHD_POOL_SIZE_DEFAULT;
+ daemon->pool_increment = MHD_BUF_INC_SIZE;
+ daemon->unescape_callback = &unescape_wrapper;
+ daemon->connection_timeout = 0; /* no timeout */
+ daemon->wpipe[0] = MHD_INVALID_PIPE_;
+ daemon->wpipe[1] = MHD_INVALID_PIPE_;
+#if HAVE_MESSAGES
+ daemon->custom_error_log = (MHD_LogCallback) &vfprintf;
+ daemon->custom_error_log_cls = stderr;
+#endif
+#ifdef HAVE_LISTEN_SHUTDOWN
+ use_pipe = (0 != (daemon->options & (MHD_USE_NO_LISTEN_SOCKET | MHD_USE_PIPE_FOR_SHUTDOWN)));
+#else
+ use_pipe = 1; /* yes, must use pipe to signal shutdown */
+#endif
+ if (0 == (flags & (MHD_USE_SELECT_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION)))
+ use_pipe = 0; /* useless if we are using 'external' select */
+ if ( (use_pipe) && (0 != MHD_pipe_ (daemon->wpipe)) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to create control pipe: %s\n",
+ MHD_strerror_ (errno));
+#endif
+ free (daemon);
+ return NULL;
+ }
+#ifndef WINDOWS
+ if ( (0 == (flags & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY))) &&
+ (1 == use_pipe) &&
+ (daemon->wpipe[0] >= FD_SETSIZE) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "file descriptor for control pipe exceeds maximum value\n");
+#endif
+ if (0 != MHD_pipe_close_ (daemon->wpipe[0]))
+ MHD_PANIC ("close failed\n");
+ if (0 != MHD_pipe_close_ (daemon->wpipe[1]))
+ MHD_PANIC ("close failed\n");
+ free (daemon);
+ return NULL;
+ }
+#endif
+#ifdef DAUTH_SUPPORT
+ daemon->digest_auth_rand_size = 0;
+ daemon->digest_auth_random = NULL;
+ daemon->nonce_nc_size = 4; /* tiny */
+#endif
+#if HTTPS_SUPPORT
+ if (0 != (flags & MHD_USE_SSL))
+ {
+ daemon->cred_type = GNUTLS_CRD_CERTIFICATE;
+ }
+#endif
+
+
+ if (MHD_YES != parse_options_va (daemon, &servaddr, ap))
+ {
+#if HTTPS_SUPPORT
+ if ( (0 != (flags & MHD_USE_SSL)) &&
+ (NULL != daemon->priority_cache) )
+ gnutls_priority_deinit (daemon->priority_cache);
+#endif
+ free (daemon);
+ return NULL;
+ }
+#ifdef DAUTH_SUPPORT
+ if (daemon->nonce_nc_size > 0)
+ {
+ if ( ( (size_t) (daemon->nonce_nc_size * sizeof (struct MHD_NonceNc))) /
+ sizeof(struct MHD_NonceNc) != daemon->nonce_nc_size)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Specified value for NC_SIZE too large\n");
+#endif
+#if HTTPS_SUPPORT
+ if (0 != (flags & MHD_USE_SSL))
+ gnutls_priority_deinit (daemon->priority_cache);
+#endif
+ free (daemon);
+ return NULL;
+ }
+ daemon->nnc = malloc (daemon->nonce_nc_size * sizeof (struct MHD_NonceNc));
+ if (NULL == daemon->nnc)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to allocate memory for nonce-nc map: %s\n",
+ MHD_strerror_ (errno));
+#endif
+#if HTTPS_SUPPORT
+ if (0 != (flags & MHD_USE_SSL))
+ gnutls_priority_deinit (daemon->priority_cache);
+#endif
+ free (daemon);
+ return NULL;
+ }
+ }
+
+ if (MHD_YES != MHD_mutex_create_ (&daemon->nnc_lock))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "MHD failed to initialize nonce-nc mutex\n");
+#endif
+#if HTTPS_SUPPORT
+ if (0 != (flags & MHD_USE_SSL))
+ gnutls_priority_deinit (daemon->priority_cache);
+#endif
+ free (daemon->nnc);
+ free (daemon);
+ return NULL;
+ }
+#endif
+
+ /* Thread pooling currently works only with internal select thread model */
+ if ( (0 == (flags & MHD_USE_SELECT_INTERNALLY)) &&
+ (daemon->worker_pool_size > 0) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "MHD thread pooling only works with MHD_USE_SELECT_INTERNALLY\n");
+#endif
+ goto free_and_fail;
+ }
+
+ if ( (MHD_USE_SUSPEND_RESUME == (flags & MHD_USE_SUSPEND_RESUME)) &&
+ (0 != (flags & MHD_USE_THREAD_PER_CONNECTION)) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Combining MHD_USE_THREAD_PER_CONNECTION and MHD_USE_SUSPEND_RESUME is not supported.\n");
+#endif
+ goto free_and_fail;
+ }
+
+#ifdef __SYMBIAN32__
+ if (0 != (flags & (MHD_USE_SELECT_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Threaded operations are not supported on Symbian.\n");
+#endif
+ goto free_and_fail;
+ }
+#endif
+ if ( (MHD_INVALID_SOCKET == daemon->socket_fd) &&
+ (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) )
+ {
+ /* try to open listen socket */
+ if (0 != (flags & MHD_USE_IPv6))
+ socket_fd = create_socket (daemon,
+ PF_INET6, SOCK_STREAM, 0);
+ else
+ socket_fd = create_socket (daemon,
+ PF_INET, SOCK_STREAM, 0);
+ if (MHD_INVALID_SOCKET == socket_fd)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to socket failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ goto free_and_fail;
+ }
+
+ /* Apply the socket options according to listening_address_reuse. */
+ if (0 == daemon->listening_address_reuse)
+ {
+ /* No user requirement, use "traditional" default SO_REUSEADDR,
+ and do not fail if it doesn't work */
+ if (0 > setsockopt (socket_fd,
+ SOL_SOCKET,
+ SO_REUSEADDR,
+ (void*)&on, sizeof (on)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "setsockopt failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ }
+ }
+ else if (daemon->listening_address_reuse > 0)
+ {
+ /* User requested to allow reusing listening address:port.
+ * Use SO_REUSEADDR on Windows and SO_REUSEPORT on most platforms.
+ * Fail if SO_REUSEPORT does not exist or setsockopt fails.
+ */
+#ifdef _WIN32
+ /* SO_REUSEADDR on W32 has the same semantics
+ as SO_REUSEPORT on BSD/Linux */
+ if (0 > setsockopt (socket_fd,
+ SOL_SOCKET,
+ SO_REUSEADDR,
+ (void*)&on, sizeof (on)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "setsockopt failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ goto free_and_fail;
+ }
+#else
+#ifndef SO_REUSEPORT
+#ifdef LINUX
+/* Supported since Linux 3.9, but often not present (or commented out)
+ in the headers at this time; but 15 is reserved for this and
+ thus should be safe to use. */
+#define SO_REUSEPORT 15
+#endif
+#endif
+#ifdef SO_REUSEPORT
+ if (0 > setsockopt (socket_fd,
+ SOL_SOCKET,
+ SO_REUSEPORT,
+ (void*)&on, sizeof (on)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "setsockopt failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ goto free_and_fail;
+ }
+#else
+ /* we're supposed to allow address:port re-use, but
+ on this platform we cannot; fail hard */
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Cannot allow listening address reuse: SO_REUSEPORT not defined\n");
+#endif
+ goto free_and_fail;
+#endif
+#endif
+ }
+ else /* if (daemon->listening_address_reuse < 0) */
+ {
+ /* User requested to disallow reusing listening address:port.
+ * Do nothing except for Windows where SO_EXCLUSIVEADDRUSE
+ * is used. Fail if it does not exist or setsockopt fails.
+ */
+#ifdef _WIN32
+#ifdef SO_EXCLUSIVEADDRUSE
+ if (0 > setsockopt (socket_fd,
+ SOL_SOCKET,
+ SO_EXCLUSIVEADDRUSE,
+ (void*)&on, sizeof (on)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "setsockopt failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ goto free_and_fail;
+ }
+#else /* SO_EXCLUSIVEADDRUSE not defined on W32? */
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Cannot disallow listening address reuse: SO_EXCLUSIVEADDRUSE not defined\n");
+#endif
+ goto free_and_fail;
+#endif
+#endif /* _WIN32 */
+ }
+
+ /* check for user supplied sockaddr */
+#if HAVE_INET6
+ if (0 != (flags & MHD_USE_IPv6))
+ addrlen = sizeof (struct sockaddr_in6);
+ else
+#endif
+ addrlen = sizeof (struct sockaddr_in);
+ if (NULL == servaddr)
+ {
+#if HAVE_INET6
+ if (0 != (flags & MHD_USE_IPv6))
+ {
+ memset (&servaddr6, 0, sizeof (struct sockaddr_in6));
+ servaddr6.sin6_family = AF_INET6;
+ servaddr6.sin6_port = htons (port);
+#if HAVE_SOCKADDR_IN_SIN_LEN
+ servaddr6.sin6_len = sizeof (struct sockaddr_in6);
+#endif
+ servaddr = (struct sockaddr *) &servaddr6;
+ }
+ else
+#endif
+ {
+ memset (&servaddr4, 0, sizeof (struct sockaddr_in));
+ servaddr4.sin_family = AF_INET;
+ servaddr4.sin_port = htons (port);
+#if HAVE_SOCKADDR_IN_SIN_LEN
+ servaddr4.sin_len = sizeof (struct sockaddr_in);
+#endif
+ servaddr = (struct sockaddr *) &servaddr4;
+ }
+ }
+ daemon->socket_fd = socket_fd;
+
+ if (0 != (flags & MHD_USE_IPv6))
+ {
+#ifdef IPPROTO_IPV6
+#ifdef IPV6_V6ONLY
+ /* Note: "IPV6_V6ONLY" is declared by Windows Vista ff., see "IPPROTO_IPV6 Socket Options"
+ (http://msdn.microsoft.com/en-us/library/ms738574%28v=VS.85%29.aspx);
+ and may also be missing on older POSIX systems; good luck if you have any of those,
+ your IPv6 socket may then also bind against IPv4 anyway... */
+#ifndef WINDOWS
+ const int
+#else
+ const char
+#endif
+ on = (MHD_USE_DUAL_STACK != (flags & MHD_USE_DUAL_STACK));
+ if (0 > setsockopt (socket_fd,
+ IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof (on)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "setsockopt failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ }
+#endif
+#endif
+ }
+ if (-1 == bind (socket_fd, servaddr, addrlen))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to bind to port %u: %s\n",
+ (unsigned int) port,
+ MHD_socket_last_strerr_ ());
+#endif
+ if (0 != MHD_socket_close_ (socket_fd))
+ MHD_PANIC ("close failed\n");
+ goto free_and_fail;
+ }
+#ifdef TCP_FASTOPEN
+ if (0 != (flags & MHD_USE_TCP_FASTOPEN))
+ {
+ if (0 == daemon->fastopen_queue_size)
+ daemon->fastopen_queue_size = MHD_TCP_FASTOPEN_QUEUE_SIZE_DEFAULT;
+ if (0 != setsockopt (socket_fd,
+ IPPROTO_TCP, TCP_FASTOPEN,
+ &daemon->fastopen_queue_size,
+ sizeof (daemon->fastopen_queue_size)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "setsockopt failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ }
+ }
+#endif
+#if EPOLL_SUPPORT
+ if (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY))
+ {
+ int sk_flags = fcntl (socket_fd, F_GETFL);
+ if (0 != fcntl (socket_fd, F_SETFL, sk_flags | O_NONBLOCK))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to make listen socket non-blocking: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ if (0 != MHD_socket_close_ (socket_fd))
+ MHD_PANIC ("close failed\n");
+ goto free_and_fail;
+ }
+ }
+#endif
+ if (listen (socket_fd, 32) < 0)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to listen for connections: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ if (0 != MHD_socket_close_ (socket_fd))
+ MHD_PANIC ("close failed\n");
+ goto free_and_fail;
+ }
+ }
+ else
+ {
+ socket_fd = daemon->socket_fd;
+ }
+#ifndef WINDOWS
+ if ( (socket_fd >= FD_SETSIZE) &&
+ (0 == (flags & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY)) ) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Socket descriptor larger than FD_SETSIZE: %d > %d\n",
+ socket_fd,
+ FD_SETSIZE);
+#endif
+ if (0 != MHD_socket_close_ (socket_fd))
+ MHD_PANIC ("close failed\n");
+ goto free_and_fail;
+ }
+#endif
+
+#if EPOLL_SUPPORT
+ if ( (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (0 == daemon->worker_pool_size) &&
+ (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) )
+ {
+ if (0 != (flags & MHD_USE_THREAD_PER_CONNECTION))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Combining MHD_USE_THREAD_PER_CONNECTION and MHD_USE_EPOLL_LINUX_ONLY is not supported.\n");
+#endif
+ goto free_and_fail;
+ }
+ if (MHD_YES != setup_epoll_to_listen (daemon))
+ goto free_and_fail;
+ }
+#else
+ if (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "epoll is not supported on this platform by this build.\n");
+#endif
+ goto free_and_fail;
+ }
+#endif
+
+ if (MHD_YES != MHD_mutex_create_ (&daemon->per_ip_connection_mutex))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "MHD failed to initialize IP connection limit mutex\n");
+#endif
+ if ( (MHD_INVALID_SOCKET != socket_fd) &&
+ (0 != MHD_socket_close_ (socket_fd)) )
+ MHD_PANIC ("close failed\n");
+ goto free_and_fail;
+ }
+ if (MHD_YES != MHD_mutex_create_ (&daemon->cleanup_connection_mutex))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "MHD failed to initialize IP connection limit mutex\n");
+#endif
+ (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex);
+ if ( (MHD_INVALID_SOCKET != socket_fd) &&
+ (0 != MHD_socket_close_ (socket_fd)) )
+ MHD_PANIC ("close failed\n");
+ goto free_and_fail;
+ }
+
+#if HTTPS_SUPPORT
+ /* initialize HTTPS daemon certificate aspects & send / recv functions */
+ if ((0 != (flags & MHD_USE_SSL)) && (0 != MHD_TLS_init (daemon)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to initialize TLS support\n");
+#endif
+ if ( (MHD_INVALID_SOCKET != socket_fd) &&
+ (0 != MHD_socket_close_ (socket_fd)) )
+ MHD_PANIC ("close failed\n");
+ (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex);
+ (void) MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex);
+ goto free_and_fail;
+ }
+#endif
+ if ( ( (0 != (flags & MHD_USE_THREAD_PER_CONNECTION)) ||
+ ( (0 != (flags & MHD_USE_SELECT_INTERNALLY)) &&
+ (0 == daemon->worker_pool_size)) ) &&
+ (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) &&
+ (0 != (res_thread_create =
+ create_thread (&daemon->pid, daemon, &MHD_select_thread, daemon))))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to create listen thread: %s\n",
+ MHD_strerror_ (res_thread_create));
+#endif
+ (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex);
+ (void) MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex);
+ if ( (MHD_INVALID_SOCKET != socket_fd) &&
+ (0 != MHD_socket_close_ (socket_fd)) )
+ MHD_PANIC ("close failed\n");
+ goto free_and_fail;
+ }
+ if ( (daemon->worker_pool_size > 0) &&
+ (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) )
+ {
+#if !defined(WINDOWS) || defined(CYGWIN)
+ int sk_flags;
+#else
+ unsigned long sk_flags;
+#endif
+
+ /* Coarse-grained count of connections per thread (note error
+ * due to integer division). Also keep track of how many
+ * connections are leftover after an equal split. */
+ unsigned int conns_per_thread = daemon->connection_limit
+ / daemon->worker_pool_size;
+ unsigned int leftover_conns = daemon->connection_limit
+ % daemon->worker_pool_size;
+
+ i = 0; /* we need this in case fcntl or malloc fails */
+
+ /* Accept must be non-blocking. Multiple children may wake up
+ * to handle a new connection, but only one will win the race.
+ * The others must immediately return. */
+#if !defined(WINDOWS) || defined(CYGWIN)
+ sk_flags = fcntl (socket_fd, F_GETFL);
+ if (sk_flags < 0)
+ goto thread_failed;
+ if (0 != fcntl (socket_fd, F_SETFL, sk_flags | O_NONBLOCK))
+ goto thread_failed;
+#else
+ sk_flags = 1;
+ if (SOCKET_ERROR == ioctlsocket (socket_fd, FIONBIO, &sk_flags))
+ goto thread_failed;
+#endif /* WINDOWS && !CYGWIN */
+
+ /* Allocate memory for pooled objects */
+ daemon->worker_pool = malloc (sizeof (struct MHD_Daemon)
+ * daemon->worker_pool_size);
+ if (NULL == daemon->worker_pool)
+ goto thread_failed;
+
+ /* Start the workers in the pool */
+ for (i = 0; i < daemon->worker_pool_size; ++i)
+ {
+ /* Create copy of the Daemon object for each worker */
+ struct MHD_Daemon *d = &daemon->worker_pool[i];
+
+ memcpy (d, daemon, sizeof (struct MHD_Daemon));
+ /* Adjust pooling params for worker daemons; note that memcpy()
+ has already copied MHD_USE_SELECT_INTERNALLY thread model into
+ the worker threads. */
+ d->master = daemon;
+ d->worker_pool_size = 0;
+ d->worker_pool = NULL;
+
+ if ( (MHD_USE_SUSPEND_RESUME == (flags & MHD_USE_SUSPEND_RESUME)) &&
+ (0 != MHD_pipe_ (d->wpipe)) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to create worker control pipe: %s\n",
+ MHD_pipe_last_strerror_() );
+#endif
+ goto thread_failed;
+ }
+#ifndef WINDOWS
+ if ( (0 == (flags & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY))) &&
+ (MHD_USE_SUSPEND_RESUME == (flags & MHD_USE_SUSPEND_RESUME)) &&
+ (d->wpipe[0] >= FD_SETSIZE) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "file descriptor for worker control pipe exceeds maximum value\n");
+#endif
+ if (0 != MHD_pipe_close_ (d->wpipe[0]))
+ MHD_PANIC ("close failed\n");
+ if (0 != MHD_pipe_close_ (d->wpipe[1]))
+ MHD_PANIC ("close failed\n");
+ goto thread_failed;
+ }
+#endif
+
+ /* Divide available connections evenly amongst the threads.
+ * Thread indexes in [0, leftover_conns) each get one of the
+ * leftover connections. */
+ d->connection_limit = conns_per_thread;
+ if (i < leftover_conns)
+ ++d->connection_limit;
+#if EPOLL_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (MHD_YES != setup_epoll_to_listen (d)) )
+ goto thread_failed;
+#endif
+ /* Must init cleanup connection mutex for each worker */
+ if (MHD_YES != MHD_mutex_create_ (&d->cleanup_connection_mutex))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "MHD failed to initialize cleanup connection mutex for thread worker %d\n", i);
+#endif
+ goto thread_failed;
+ }
+
+ /* Spawn the worker thread */
+ if (0 != (res_thread_create =
+ create_thread (&d->pid, daemon, &MHD_select_thread, d)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to create pool thread: %s\n",
+ MHD_strerror_ (res_thread_create));
+#endif
+ /* Free memory for this worker; cleanup below handles
+ * all previously-created workers. */
+ (void) MHD_mutex_destroy_ (&d->cleanup_connection_mutex);
+ goto thread_failed;
+ }
+ }
+ }
+#if HTTPS_SUPPORT
+ /* API promises to never use the password after initialization,
+ so we additionally NULL it here to not deref a dangling pointer. */
+ daemon->https_key_password = NULL;
+#endif /* HTTPS_SUPPORT */
+
+ return daemon;
+
+thread_failed:
+ /* If no worker threads created, then shut down normally. Calling
+ MHD_stop_daemon (as we do below) doesn't work here since it
+ assumes a 0-sized thread pool means we had been in the default
+ MHD_USE_SELECT_INTERNALLY mode. */
+ if (0 == i)
+ {
+ if ( (MHD_INVALID_SOCKET != socket_fd) &&
+ (0 != MHD_socket_close_ (socket_fd)) )
+ MHD_PANIC ("close failed\n");
+ (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex);
+ (void) MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex);
+ if (NULL != daemon->worker_pool)
+ free (daemon->worker_pool);
+ goto free_and_fail;
+ }
+
+ /* Shutdown worker threads we've already created. Pretend
+ as though we had fully initialized our daemon, but
+ with a smaller number of threads than had been
+ requested. */
+ daemon->worker_pool_size = i;
+ MHD_stop_daemon (daemon);
+ return NULL;
+
+ free_and_fail:
+ /* clean up basic memory state in 'daemon' and return NULL to
+ indicate failure */
+#if EPOLL_SUPPORT
+ if (-1 != daemon->epoll_fd)
+ close (daemon->epoll_fd);
+#endif
+#ifdef DAUTH_SUPPORT
+ free (daemon->nnc);
+ (void) MHD_mutex_destroy_ (&daemon->nnc_lock);
+#endif
+#if HTTPS_SUPPORT
+ if (0 != (flags & MHD_USE_SSL))
+ gnutls_priority_deinit (daemon->priority_cache);
+#endif
+ free (daemon);
+ return NULL;
+}
+
+
+/**
+ * Close the given connection, remove it from all of its
+ * DLLs and move it into the cleanup queue.
+ *
+ * @param pos connection to move to cleanup
+ */
+static void
+close_connection (struct MHD_Connection *pos)
+{
+ struct MHD_Daemon *daemon = pos->daemon;
+
+ MHD_connection_close (pos,
+ MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN);
+ if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
+ return; /* must let thread to the rest */
+ if (pos->connection_timeout == pos->daemon->connection_timeout)
+ XDLL_remove (daemon->normal_timeout_head,
+ daemon->normal_timeout_tail,
+ pos);
+ else
+ XDLL_remove (daemon->manual_timeout_head,
+ daemon->manual_timeout_tail,
+ pos);
+ DLL_remove (daemon->connections_head,
+ daemon->connections_tail,
+ pos);
+ pos->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP;
+ DLL_insert (daemon->cleanup_head,
+ daemon->cleanup_tail,
+ pos);
+}
+
+
+/**
+ * Close all connections for the daemon; must only be called after
+ * all of the threads have been joined and there is no more concurrent
+ * activity on the connection lists.
+ *
+ * @param daemon daemon to close down
+ */
+static void
+close_all_connections (struct MHD_Daemon *daemon)
+{
+ struct MHD_Connection *pos;
+
+ /* first, make sure all threads are aware of shutdown; need to
+ traverse DLLs in peace... */
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to acquire cleanup mutex\n");
+ for (pos = daemon->connections_head; NULL != pos; pos = pos->next)
+ {
+ shutdown (pos->socket_fd,
+ (pos->read_closed == MHD_YES) ? SHUT_WR : SHUT_RDWR);
+#if WINDOWS
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_INVALID_PIPE_ != daemon->wpipe[1]) &&
+ (1 != MHD_pipe_write_ (daemon->wpipe[1], "e", 1)) )
+ MHD_PANIC ("failed to signal shutdown via pipe");
+#endif
+ }
+ if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
+ (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) )
+ MHD_PANIC ("Failed to release cleanup mutex\n");
+
+ /* now, collect threads from thread pool */
+ if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
+ {
+ for (pos = daemon->connections_head; NULL != pos; pos = pos->next)
+ {
+ if (0 != MHD_join_thread_ (pos->pid))
+ MHD_PANIC ("Failed to join a thread\n");
+ pos->thread_joined = MHD_YES;
+ }
+ }
+
+ /* now that we're alone, move everyone to cleanup */
+ while (NULL != (pos = daemon->connections_head))
+ close_connection (pos);
+ MHD_cleanup_connections (daemon);
+}
+
+
+#if EPOLL_SUPPORT
+/**
+ * Shutdown epoll()-event loop by adding 'wpipe' to its event set.
+ *
+ * @param daemon daemon of which the epoll() instance must be signalled
+ */
+static void
+epoll_shutdown (struct MHD_Daemon *daemon)
+{
+ struct epoll_event event;
+
+ if (MHD_INVALID_PIPE_ == daemon->wpipe[1])
+ {
+ /* wpipe was required in this mode, how could this happen? */
+ MHD_PANIC ("Internal error\n");
+ }
+ event.events = EPOLLOUT;
+ event.data.ptr = NULL;
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_ADD,
+ daemon->wpipe[1],
+ &event))
+ MHD_PANIC ("Failed to add wpipe to epoll set to signal termination\n");
+}
+#endif
+
+
+/**
+ * Shutdown an HTTP daemon.
+ *
+ * @param daemon daemon to stop
+ * @ingroup event
+ */
+void
+MHD_stop_daemon (struct MHD_Daemon *daemon)
+{
+ MHD_socket fd;
+ unsigned int i;
+
+ if (NULL == daemon)
+ return;
+ daemon->shutdown = MHD_YES;
+ fd = daemon->socket_fd;
+ daemon->socket_fd = MHD_INVALID_SOCKET;
+ /* Prepare workers for shutdown */
+ if (NULL != daemon->worker_pool)
+ {
+ /* MHD_USE_NO_LISTEN_SOCKET disables thread pools, hence we need to check */
+ for (i = 0; i < daemon->worker_pool_size; ++i)
+ {
+ daemon->worker_pool[i].shutdown = MHD_YES;
+ daemon->worker_pool[i].socket_fd = MHD_INVALID_SOCKET;
+#if EPOLL_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (-1 != daemon->worker_pool[i].epoll_fd) &&
+ (MHD_INVALID_SOCKET == fd) )
+ epoll_shutdown (&daemon->worker_pool[i]);
+#endif
+ }
+ }
+ if (MHD_INVALID_PIPE_ != daemon->wpipe[1])
+ {
+ if (1 != MHD_pipe_write_ (daemon->wpipe[1], "e", 1))
+ MHD_PANIC ("failed to signal shutdown via pipe");
+ }
+#ifdef HAVE_LISTEN_SHUTDOWN
+ else
+ {
+ /* fd might be MHD_INVALID_SOCKET here due to 'MHD_quiesce_daemon' */
+ if (MHD_INVALID_SOCKET != fd)
+ (void) shutdown (fd, SHUT_RDWR);
+ }
+#endif
+#if EPOLL_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (-1 != daemon->epoll_fd) &&
+ (MHD_INVALID_SOCKET == fd) )
+ epoll_shutdown (daemon);
+#endif
+
+#if DEBUG_CLOSE
+#if HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "MHD listen socket shutdown\n");
+#endif
+#endif
+
+
+ /* Signal workers to stop and clean them up */
+ if (NULL != daemon->worker_pool)
+ {
+ /* MHD_USE_NO_LISTEN_SOCKET disables thread pools, hence we need to check */
+ for (i = 0; i < daemon->worker_pool_size; ++i)
+ {
+ if (MHD_INVALID_PIPE_ != daemon->worker_pool[i].wpipe[1])
+ {
+ if (1 != MHD_pipe_write_ (daemon->worker_pool[i].wpipe[1], "e", 1))
+ MHD_PANIC ("failed to signal shutdown via pipe");
+ }
+ if (0 != MHD_join_thread_ (daemon->worker_pool[i].pid))
+ MHD_PANIC ("Failed to join a thread\n");
+ close_all_connections (&daemon->worker_pool[i]);
+ (void) MHD_mutex_destroy_ (&daemon->worker_pool[i].cleanup_connection_mutex);
+#if EPOLL_SUPPORT
+ if ( (-1 != daemon->worker_pool[i].epoll_fd) &&
+ (0 != MHD_socket_close_ (daemon->worker_pool[i].epoll_fd)) )
+ MHD_PANIC ("close failed\n");
+#endif
+ if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) )
+ {
+ if (MHD_INVALID_PIPE_ != daemon->worker_pool[i].wpipe[1])
+ {
+ if (0 != MHD_pipe_close_ (daemon->worker_pool[i].wpipe[0]))
+ MHD_PANIC ("close failed\n");
+ if (0 != MHD_pipe_close_ (daemon->worker_pool[i].wpipe[1]))
+ MHD_PANIC ("close failed\n");
+ }
+ }
+ }
+ free (daemon->worker_pool);
+ }
+ else
+ {
+ /* clean up master threads */
+ if ((0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) ||
+ ((0 != (daemon->options & MHD_USE_SELECT_INTERNALLY))
+ && (0 == daemon->worker_pool_size)))
+ {
+ if (0 != MHD_join_thread_ (daemon->pid))
+ {
+ MHD_PANIC ("Failed to join a thread\n");
+ }
+ }
+ }
+ close_all_connections (daemon);
+ if ( (MHD_INVALID_SOCKET != fd) &&
+ (0 != MHD_socket_close_ (fd)) )
+ MHD_PANIC ("close failed\n");
+
+ /* TLS clean up */
+#if HTTPS_SUPPORT
+ if (MHD_YES == daemon->have_dhparams)
+ {
+ gnutls_dh_params_deinit (daemon->https_mem_dhparams);
+ daemon->have_dhparams = MHD_NO;
+ }
+ if (0 != (daemon->options & MHD_USE_SSL))
+ {
+ gnutls_priority_deinit (daemon->priority_cache);
+ if (daemon->x509_cred)
+ gnutls_certificate_free_credentials (daemon->x509_cred);
+ }
+#endif
+#if EPOLL_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) &&
+ (-1 != daemon->epoll_fd) &&
+ (0 != MHD_socket_close_ (daemon->epoll_fd)) )
+ MHD_PANIC ("close failed\n");
+#endif
+
+#ifdef DAUTH_SUPPORT
+ free (daemon->nnc);
+ (void) MHD_mutex_destroy_ (&daemon->nnc_lock);
+#endif
+ (void) MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex);
+ (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex);
+
+ if (MHD_INVALID_PIPE_ != daemon->wpipe[1])
+ {
+ if (0 != MHD_pipe_close_ (daemon->wpipe[0]))
+ MHD_PANIC ("close failed\n");
+ if (0 != MHD_pipe_close_ (daemon->wpipe[1]))
+ MHD_PANIC ("close failed\n");
+ }
+ free (daemon);
+}
+
+
+/**
+ * Obtain information about the given daemon
+ * (not fully implemented!).
+ *
+ * @param daemon what daemon to get information about
+ * @param info_type what information is desired?
+ * @param ... depends on @a info_type
+ * @return NULL if this information is not available
+ * (or if the @a info_type is unknown)
+ * @ingroup specialized
+ */
+const union MHD_DaemonInfo *
+MHD_get_daemon_info (struct MHD_Daemon *daemon,
+ enum MHD_DaemonInfoType info_type,
+ ...)
+{
+ switch (info_type)
+ {
+ case MHD_DAEMON_INFO_KEY_SIZE:
+ return NULL; /* no longer supported */
+ case MHD_DAEMON_INFO_MAC_KEY_SIZE:
+ return NULL; /* no longer supported */
+ case MHD_DAEMON_INFO_LISTEN_FD:
+ return (const union MHD_DaemonInfo *) &daemon->socket_fd;
+#if EPOLL_SUPPORT
+ case MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY:
+ return (const union MHD_DaemonInfo *) &daemon->epoll_fd;
+#endif
+ case MHD_DAEMON_INFO_CURRENT_CONNECTIONS:
+ MHD_cleanup_connections (daemon);
+ if (daemon->worker_pool)
+ {
+ /* Collect the connection information stored in the workers. */
+ unsigned int i;
+
+ daemon->connections = 0;
+ for (i=0;i<daemon->worker_pool_size;i++)
+ {
+ MHD_cleanup_connections (&daemon->worker_pool[i]);
+ daemon->connections += daemon->worker_pool[i].connections;
+ }
+ }
+ return (const union MHD_DaemonInfo *) &daemon->connections;
+ default:
+ return NULL;
+ };
+}
+
+
+/**
+ * Sets the global error handler to a different implementation. @a cb
+ * will only be called in the case of typically fatal, serious
+ * internal consistency issues. These issues should only arise in the
+ * case of serious memory corruption or similar problems with the
+ * architecture. While @a cb is allowed to return and MHD will then
+ * try to continue, this is never safe.
+ *
+ * The default implementation that is used if no panic function is set
+ * simply prints an error message and calls `abort()`. Alternative
+ * implementations might call `exit()` or other similar functions.
+ *
+ * @param cb new error handler
+ * @param cls passed to @a cb
+ * @ingroup logging
+ */
+void
+MHD_set_panic_func (MHD_PanicCallback cb, void *cls)
+{
+ mhd_panic = cb;
+ mhd_panic_cls = cls;
+}
+
+
+/**
+ * Obtain the version of this library
+ *
+ * @return static version string, e.g. "0.9.9"
+ * @ingroup specialized
+ */
+const char *
+MHD_get_version (void)
+{
+#ifdef PACKAGE_VERSION
+ return PACKAGE_VERSION;
+#else /* !PACKAGE_VERSION */
+ static char ver[12] = "\0\0\0\0\0\0\0\0\0\0\0";
+ if (0 == ver[0])
+ {
+ int res = MHD_snprintf_(ver, sizeof(ver), "%x.%x.%x",
+ (((int)MHD_VERSION >> 24) & 0xFF),
+ (((int)MHD_VERSION >> 16) & 0xFF),
+ (((int)MHD_VERSION >> 8) & 0xFF));
+ if (0 >= res || sizeof(ver) <= res)
+ return "0.0.0"; /* Can't return real version*/
+ }
+ return ver;
+#endif /* !PACKAGE_VERSION */
+}
+
+
+/**
+ * Get information about supported MHD features.
+ * Indicate that MHD was compiled with or without support for
+ * particular feature. Some features require additional support
+ * by kernel. Kernel support is not checked by this function.
+ *
+ * @param feature type of requested information
+ * @return #MHD_YES if feature is supported by MHD, #MHD_NO if
+ * feature is not supported or feature is unknown.
+ * @ingroup specialized
+ */
+_MHD_EXTERN int
+MHD_is_feature_supported(enum MHD_FEATURE feature)
+{
+ switch(feature)
+ {
+ case MHD_FEATURE_MESSGES:
+#if HAVE_MESSAGES
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_SSL:
+#if HTTPS_SUPPORT
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_HTTPS_CERT_CALLBACK:
+#if HTTPS_SUPPORT && GNUTLS_VERSION_MAJOR >= 3
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_IPv6:
+#ifdef HAVE_INET6
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_IPv6_ONLY:
+#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_POLL:
+#ifdef HAVE_POLL
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_EPOLL:
+#if EPOLL_SUPPORT
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_SHUTDOWN_LISTEN_SOCKET:
+#ifdef HAVE_LISTEN_SHUTDOWN
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_SOCKETPAIR:
+#ifdef MHD_DONT_USE_PIPES
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_TCP_FASTOPEN:
+#ifdef TCP_FASTOPEN
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_BASIC_AUTH:
+#if BAUTH_SUPPORT
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_DIGEST_AUTH:
+#if DAUTH_SUPPORT
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_POSTPROCESSOR:
+#if HAVE_POSTPROCESSOR
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ case MHD_FEATURE_HTTPS_KEY_PASSWORD:
+#if HTTPS_SUPPORT && GNUTLS_VERSION_NUMBER >= 0x030111
+ return MHD_YES;
+#else
+ return MHD_NO;
+#endif
+ }
+ return MHD_NO;
+}
+
+
+#if HTTPS_SUPPORT && GCRYPT_VERSION_NUMBER < 0x010600
+#if defined(MHD_USE_POSIX_THREADS)
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#elif defined(MHD_W32_MUTEX_)
+static int gcry_w32_mutex_init (void **ppmtx)
+{
+ *ppmtx = malloc (sizeof (MHD_mutex_));
+
+ if (NULL == *ppmtx)
+ return ENOMEM;
+
+ if (MHD_YES != MHD_mutex_create_ ((MHD_mutex_*)*ppmtx))
+ {
+ free (*ppmtx);
+ *ppmtx = NULL;
+ return EPERM;
+ }
+
+ return 0;
+}
+static int gcry_w32_mutex_destroy (void **ppmtx)
+ { int res = (MHD_YES == MHD_mutex_destroy_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1;
+ free (*ppmtx); return res; }
+static int gcry_w32_mutex_lock (void **ppmtx)
+ { return (MHD_YES == MHD_mutex_lock_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; }
+static int gcry_w32_mutex_unlock (void **ppmtx)
+ { return (MHD_YES == MHD_mutex_unlock_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; }
+
+static struct gcry_thread_cbs gcry_threads_w32 = {
+ (GCRY_THREAD_OPTION_USER | (GCRY_THREAD_OPTION_VERSION << 8)),
+ NULL, gcry_w32_mutex_init, gcry_w32_mutex_destroy,
+ gcry_w32_mutex_lock, gcry_w32_mutex_unlock,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+
+#endif // defined(MHD_W32_MUTEX_)
+#endif // HTTPS_SUPPORT && GCRYPT_VERSION_NUMBER < 0x010600
+
+
+/**
+ * Initialize do setup work.
+ */
+void MHD_init(void)
+{
+ mhd_panic = &mhd_panic_std;
+ mhd_panic_cls = NULL;
+
+#ifdef _WIN32
+ WSADATA wsd;
+ if (0 != WSAStartup(MAKEWORD(2, 2), &wsd))
+ MHD_PANIC ("Failed to initialize winsock\n");
+ mhd_winsock_inited_ = 1;
+ if (2 != LOBYTE(wsd.wVersion) && 2 != HIBYTE(wsd.wVersion))
+ MHD_PANIC ("Winsock version 2.2 is not available\n");
+#endif
+#if HTTPS_SUPPORT
+#if GCRYPT_VERSION_NUMBER < 0x010600
+#if defined(MHD_USE_POSIX_THREADS)
+ if (0 != gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread))
+ MHD_PANIC ("Failed to initialise multithreading in libgcrypt\n");
+#elif defined(MHD_W32_MUTEX_)
+ if (0 != gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_w32))
+ MHD_PANIC ("Failed to initialise multithreading in libgcrypt\n");
+#endif // defined(MHD_W32_MUTEX_)
+ gcry_check_version (NULL);
+#else
+ if (NULL == gcry_check_version ("1.6.0"))
+ MHD_PANIC ("libgcrypt is too old. MHD was compiled for libgcrypt 1.6.0 or newer\n");
+#endif
+ gnutls_global_init ();
+#endif
+}
+
+
+void MHD_fini(void)
+{
+#if HTTPS_SUPPORT
+ gnutls_global_deinit ();
+#endif
+#ifdef _WIN32
+ if (mhd_winsock_inited_)
+ WSACleanup();
+#endif
+}
+
+_SET_INIT_AND_DEINIT_FUNCS(MHD_init, MHD_fini);
+
+/* end of daemon.c */
diff --git a/src/microhttpd/digestauth.c b/src/microhttpd/digestauth.c
new file mode 100644
index 0000000..9c3fe8c
--- /dev/null
+++ b/src/microhttpd/digestauth.c
@@ -0,0 +1,871 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2010, 2011, 2012 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+/**
+ * @file digestauth.c
+ * @brief Implements HTTP digest authentication
+ * @author Amr Ali
+ * @author Matthieu Speder
+ */
+#include "platform.h"
+#include <limits.h>
+#include "internal.h"
+#include "md5.h"
+
+#if defined(_WIN32) && defined(MHD_W32_MUTEX_)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif /* _WIN32 && MHD_W32_MUTEX_ */
+
+#define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE)
+
+/**
+ * Beginning string for any valid Digest authentication header.
+ */
+#define _BASE "Digest "
+
+/**
+ * Maximum length of a username for digest authentication.
+ */
+#define MAX_USERNAME_LENGTH 128
+
+/**
+ * Maximum length of a realm for digest authentication.
+ */
+#define MAX_REALM_LENGTH 256
+
+/**
+ * Maximum length of the response in digest authentication.
+ */
+#define MAX_AUTH_RESPONSE_LENGTH 128
+
+
+/**
+ * convert bin to hex
+ *
+ * @param bin binary data
+ * @param len number of bytes in bin
+ * @param hex pointer to len*2+1 bytes
+ */
+static void
+cvthex (const unsigned char *bin,
+ size_t len,
+ char *hex)
+{
+ size_t i;
+ unsigned int j;
+
+ for (i = 0; i < len; ++i)
+ {
+ j = (bin[i] >> 4) & 0x0f;
+ hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10);
+ j = bin[i] & 0x0f;
+ hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10);
+ }
+ hex[len * 2] = '\0';
+}
+
+
+/**
+ * calculate H(A1) as per RFC2617 spec and store the
+ * result in 'sessionkey'.
+ *
+ * @param alg The hash algorithm used, can be "md5" or "md5-sess"
+ * @param username A `char *' pointer to the username value
+ * @param realm A `char *' pointer to the realm value
+ * @param password A `char *' pointer to the password value
+ * @param nonce A `char *' pointer to the nonce value
+ * @param cnonce A `char *' pointer to the cnonce value
+ * @param sessionkey pointer to buffer of HASH_MD5_HEX_LEN+1 bytes
+ */
+static void
+digest_calc_ha1 (const char *alg,
+ const char *username,
+ const char *realm,
+ const char *password,
+ const char *nonce,
+ const char *cnonce,
+ char *sessionkey)
+{
+ struct MD5Context md5;
+ unsigned char ha1[MD5_DIGEST_SIZE];
+
+ MD5Init (&md5);
+ MD5Update (&md5, username, strlen (username));
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, realm, strlen (realm));
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, password, strlen (password));
+ MD5Final (ha1, &md5);
+ if (MHD_str_equal_caseless_(alg, "md5-sess"))
+ {
+ MD5Init (&md5);
+ MD5Update (&md5, ha1, sizeof (ha1));
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, nonce, strlen (nonce));
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, cnonce, strlen (cnonce));
+ MD5Final (ha1, &md5);
+ }
+ cvthex (ha1, sizeof (ha1), sessionkey);
+}
+
+
+/**
+ * Calculate request-digest/response-digest as per RFC2617 spec
+ *
+ * @param ha1 H(A1)
+ * @param nonce nonce from server
+ * @param noncecount 8 hex digits
+ * @param cnonce client nonce
+ * @param qop qop-value: "", "auth" or "auth-int"
+ * @param method method from request
+ * @param uri requested URL
+ * @param hentity H(entity body) if qop="auth-int"
+ * @param response request-digest or response-digest
+ */
+static void
+digest_calc_response (const char *ha1,
+ const char *nonce,
+ const char *noncecount,
+ const char *cnonce,
+ const char *qop,
+ const char *method,
+ const char *uri,
+ const char *hentity,
+ char *response)
+{
+ struct MD5Context md5;
+ unsigned char ha2[MD5_DIGEST_SIZE];
+ unsigned char resphash[MD5_DIGEST_SIZE];
+ char ha2hex[HASH_MD5_HEX_LEN + 1];
+
+ MD5Init (&md5);
+ MD5Update (&md5, method, strlen(method));
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, uri, strlen(uri));
+#if 0
+ if (0 == strcasecmp(qop, "auth-int"))
+ {
+ /* This is dead code since the rest of this module does
+ not support auth-int. */
+ MD5Update (&md5, ":", 1);
+ if (NULL != hentity)
+ MD5Update (&md5, hentity, strlen(hentity));
+ }
+#endif
+ MD5Final (ha2, &md5);
+ cvthex (ha2, MD5_DIGEST_SIZE, ha2hex);
+ MD5Init (&md5);
+ /* calculate response */
+ MD5Update (&md5, ha1, HASH_MD5_HEX_LEN);
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, nonce, strlen(nonce));
+ MD5Update (&md5, ":", 1);
+ if ('\0' != *qop)
+ {
+ MD5Update (&md5, noncecount, strlen(noncecount));
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, cnonce, strlen(cnonce));
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, qop, strlen(qop));
+ MD5Update (&md5, ":", 1);
+ }
+ MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN);
+ MD5Final (resphash, &md5);
+ cvthex (resphash, sizeof (resphash), response);
+}
+
+
+/**
+ * Lookup subvalue off of the HTTP Authorization header.
+ *
+ * A description of the input format for 'data' is at
+ * http://en.wikipedia.org/wiki/Digest_access_authentication
+ *
+ *
+ * @param dest where to store the result (possibly truncated if
+ * the buffer is not big enough).
+ * @param size size of dest
+ * @param data pointer to the Authorization header
+ * @param key key to look up in data
+ * @return size of the located value, 0 if otherwise
+ */
+static size_t
+lookup_sub_value (char *dest,
+ size_t size,
+ const char *data,
+ const char *key)
+{
+ size_t keylen;
+ size_t len;
+ const char *ptr;
+ const char *eq;
+ const char *q1;
+ const char *q2;
+ const char *qn;
+
+ if (0 == size)
+ return 0;
+ keylen = strlen (key);
+ ptr = data;
+ while ('\0' != *ptr)
+ {
+ if (NULL == (eq = strchr (ptr, '=')))
+ return 0;
+ q1 = eq + 1;
+ while (' ' == *q1)
+ q1++;
+ if ('\"' != *q1)
+ {
+ q2 = strchr (q1, ',');
+ qn = q2;
+ }
+ else
+ {
+ q1++;
+ q2 = strchr (q1, '\"');
+ if (NULL == q2)
+ return 0; /* end quote not found */
+ qn = q2 + 1;
+ }
+ if ((MHD_str_equal_caseless_n_(ptr,
+ key,
+ keylen)) &&
+ (eq == &ptr[keylen]) )
+ {
+ if (NULL == q2)
+ {
+ len = strlen (q1) + 1;
+ if (size > len)
+ size = len;
+ size--;
+ strncpy (dest,
+ q1,
+ size);
+ dest[size] = '\0';
+ return size;
+ }
+ else
+ {
+ if (size > (size_t) ((q2 - q1) + 1))
+ size = (q2 - q1) + 1;
+ size--;
+ memcpy (dest,
+ q1,
+ size);
+ dest[size] = '\0';
+ return size;
+ }
+ }
+ if (NULL == qn)
+ return 0;
+ ptr = strchr (qn, ',');
+ if (NULL == ptr)
+ return 0;
+ ptr++;
+ while (' ' == *ptr)
+ ptr++;
+ }
+ return 0;
+}
+
+
+/**
+ * Check nonce-nc map array with either new nonce counter
+ * or a whole new nonce.
+ *
+ * @param connection The MHD connection structure
+ * @param nonce A pointer that referenced a zero-terminated array of nonce
+ * @param nc The nonce counter, zero to add the nonce to the array
+ * @return MHD_YES if successful, MHD_NO if invalid (or we have no NC array)
+ */
+static int
+check_nonce_nc (struct MHD_Connection *connection,
+ const char *nonce,
+ unsigned long int nc)
+{
+ uint32_t off;
+ uint32_t mod;
+ const char *np;
+
+ mod = connection->daemon->nonce_nc_size;
+ if (0 == mod)
+ return MHD_NO; /* no array! */
+ /* super-fast xor-based "hash" function for HT lookup in nonce array */
+ off = 0;
+ np = nonce;
+ while ('\0' != *np)
+ {
+ off = (off << 8) | (*np ^ (off >> 24));
+ np++;
+ }
+ off = off % mod;
+ /*
+ * Look for the nonce, if it does exist and its corresponding
+ * nonce counter is less than the current nonce counter by 1,
+ * then only increase the nonce counter by one.
+ */
+
+ (void) MHD_mutex_lock_ (&connection->daemon->nnc_lock);
+ if (0 == nc)
+ {
+ strcpy(connection->daemon->nnc[off].nonce,
+ nonce);
+ connection->daemon->nnc[off].nc = 0;
+ (void) MHD_mutex_unlock_ (&connection->daemon->nnc_lock);
+ return MHD_YES;
+ }
+ if ( (nc <= connection->daemon->nnc[off].nc) ||
+ (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) )
+ {
+ (void) MHD_mutex_unlock_ (&connection->daemon->nnc_lock);
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Stale nonce received. If this happens a lot, you should probably increase the size of the nonce array.\n");
+#endif
+ return MHD_NO;
+ }
+ connection->daemon->nnc[off].nc = nc;
+ (void) MHD_mutex_unlock_ (&connection->daemon->nnc_lock);
+ return MHD_YES;
+}
+
+
+/**
+ * Get the username from the authorization header sent by the client
+ *
+ * @param connection The MHD connection structure
+ * @return NULL if no username could be found, a pointer
+ * to the username if found
+ * @ingroup authentication
+ */
+char *
+MHD_digest_auth_get_username(struct MHD_Connection *connection)
+{
+ size_t len;
+ char user[MAX_USERNAME_LENGTH];
+ const char *header;
+
+ if (NULL == (header = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION)))
+ return NULL;
+ if (0 != strncmp (header, _BASE, strlen (_BASE)))
+ return NULL;
+ header += strlen (_BASE);
+ if (0 == (len = lookup_sub_value (user,
+ sizeof (user),
+ header,
+ "username")))
+ return NULL;
+ return strdup (user);
+}
+
+
+/**
+ * Calculate the server nonce so that it mitigates replay attacks
+ * The current format of the nonce is ...
+ * H(timestamp ":" method ":" random ":" uri ":" realm) + Hex(timestamp)
+ *
+ * @param nonce_time The amount of time in seconds for a nonce to be invalid
+ * @param method HTTP method
+ * @param rnd A pointer to a character array for the random seed
+ * @param rnd_size The size of the random seed array @a rnd
+ * @param uri HTTP URI (in MHD, without the arguments ("?k=v")
+ * @param realm A string of characters that describes the realm of auth.
+ * @param nonce A pointer to a character array for the nonce to put in
+ */
+static void
+calculate_nonce (uint32_t nonce_time,
+ const char *method,
+ const char *rnd,
+ size_t rnd_size,
+ const char *uri,
+ const char *realm,
+ char *nonce)
+{
+ struct MD5Context md5;
+ unsigned char timestamp[4];
+ unsigned char tmpnonce[MD5_DIGEST_SIZE];
+ char timestamphex[sizeof(timestamp) * 2 + 1];
+
+ MD5Init (&md5);
+ timestamp[0] = (nonce_time & 0xff000000) >> 0x18;
+ timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10;
+ timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08;
+ timestamp[3] = (nonce_time & 0x000000ff);
+ MD5Update (&md5, timestamp, 4);
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, method, strlen (method));
+ MD5Update (&md5, ":", 1);
+ if (rnd_size > 0)
+ MD5Update (&md5, rnd, rnd_size);
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, uri, strlen (uri));
+ MD5Update (&md5, ":", 1);
+ MD5Update (&md5, realm, strlen (realm));
+ MD5Final (tmpnonce, &md5);
+ cvthex (tmpnonce, sizeof (tmpnonce), nonce);
+ cvthex (timestamp, 4, timestamphex);
+ strncat (nonce, timestamphex, 8);
+}
+
+
+/**
+ * Test if the given key-value pair is in the headers for the
+ * given connection.
+ *
+ * @param connection the connection
+ * @param key the key
+ * @param value the value, can be NULL
+ * @return #MHD_YES if the key-value pair is in the headers,
+ * #MHD_NO if not
+ */
+static int
+test_header (struct MHD_Connection *connection,
+ const char *key,
+ const char *value)
+{
+ struct MHD_HTTP_Header *pos;
+
+ for (pos = connection->headers_received; NULL != pos; pos = pos->next)
+ {
+ if (MHD_GET_ARGUMENT_KIND != pos->kind)
+ continue;
+ if (0 != strcmp (key, pos->header))
+ continue;
+ if ( (NULL == value) &&
+ (NULL == pos->value) )
+ return MHD_YES;
+ if ( (NULL == value) ||
+ (NULL == pos->value) ||
+ (0 != strcmp (value, pos->value)) )
+ continue;
+ return MHD_YES;
+ }
+ return MHD_NO;
+}
+
+
+/**
+ * Check that the arguments given by the client as part
+ * of the authentication header match the arguments we
+ * got as part of the HTTP request URI.
+ *
+ * @param connection connections with headers to compare against
+ * @param args argument URI string (after "?" in URI)
+ * @return MHD_YES if the arguments match,
+ * MHD_NO if not
+ */
+static int
+check_argument_match (struct MHD_Connection *connection,
+ const char *args)
+{
+ struct MHD_HTTP_Header *pos;
+ char *argb;
+ char *argp;
+ char *equals;
+ char *amper;
+ unsigned int num_headers;
+
+ argb = strdup(args);
+ if (NULL == argb)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(connection->daemon,
+ "Failed to allocate memory for copy of URI arguments\n");
+#endif /* HAVE_MESSAGES */
+ return MHD_NO;
+ }
+ num_headers = 0;
+ argp = argb;
+ while ( (NULL != argp) &&
+ ('\0' != argp[0]) )
+ {
+ equals = strchr (argp, '=');
+ if (NULL == equals)
+ {
+ /* add with 'value' NULL */
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ argp);
+ if (MHD_YES != test_header (connection, argp, NULL))
+ return MHD_NO;
+ num_headers++;
+ break;
+ }
+ equals[0] = '\0';
+ equals++;
+ amper = strchr (equals, '&');
+ if (NULL != amper)
+ {
+ amper[0] = '\0';
+ amper++;
+ }
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ argp);
+ connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
+ connection,
+ equals);
+ if (! test_header (connection, argp, equals))
+ return MHD_NO;
+ num_headers++;
+ argp = amper;
+ }
+
+ /* also check that the number of headers matches */
+ for (pos = connection->headers_received; NULL != pos; pos = pos->next)
+ {
+ if (MHD_GET_ARGUMENT_KIND != pos->kind)
+ continue;
+ num_headers--;
+ }
+ if (0 != num_headers)
+ return MHD_NO;
+ return MHD_YES;
+}
+
+
+/**
+ * Authenticates the authorization header sent by the client
+ *
+ * @param connection The MHD connection structure
+ * @param realm The realm presented to the client
+ * @param username The username needs to be authenticated
+ * @param password The password used in the authentication
+ * @param nonce_timeout The amount of time for a nonce to be
+ * invalid in seconds
+ * @return #MHD_YES if authenticated, #MHD_NO if not,
+ * #MHD_INVALID_NONCE if nonce is invalid
+ * @ingroup authentication
+ */
+int
+MHD_digest_auth_check (struct MHD_Connection *connection,
+ const char *realm,
+ const char *username,
+ const char *password,
+ unsigned int nonce_timeout)
+{
+ size_t len;
+ const char *header;
+ char *end;
+ char nonce[MAX_NONCE_LENGTH];
+ char cnonce[MAX_NONCE_LENGTH];
+ char qop[15]; /* auth,auth-int */
+ char nc[20];
+ char response[MAX_AUTH_RESPONSE_LENGTH];
+ const char *hentity = NULL; /* "auth-int" is not supported */
+ char ha1[HASH_MD5_HEX_LEN + 1];
+ char respexp[HASH_MD5_HEX_LEN + 1];
+ char noncehashexp[HASH_MD5_HEX_LEN + 9];
+ uint32_t nonce_time;
+ uint32_t t;
+ size_t left; /* number of characters left in 'header' for 'uri' */
+ unsigned long int nci;
+
+ header = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ if (NULL == header)
+ return MHD_NO;
+ if (0 != strncmp(header, _BASE, strlen(_BASE)))
+ return MHD_NO;
+ header += strlen (_BASE);
+ left = strlen (header);
+
+ {
+ char un[MAX_USERNAME_LENGTH];
+
+ len = lookup_sub_value (un,
+ sizeof (un),
+ header, "username");
+ if ( (0 == len) ||
+ (0 != strcmp(username, un)) )
+ return MHD_NO;
+ left -= strlen ("username") + len;
+ }
+
+ {
+ char r[MAX_REALM_LENGTH];
+
+ len = lookup_sub_value(r,
+ sizeof (r),
+ header, "realm");
+ if ( (0 == len) ||
+ (0 != strcmp(realm, r)) )
+ return MHD_NO;
+ left -= strlen ("realm") + len;
+ }
+
+ if (0 == (len = lookup_sub_value (nonce,
+ sizeof (nonce),
+ header, "nonce")))
+ return MHD_NO;
+ left -= strlen ("nonce") + len;
+ if (left > 32 * 1024)
+ {
+ /* we do not permit URIs longer than 32k, as we want to
+ make sure to not blow our stack (or per-connection
+ heap memory limit). Besides, 32k is already insanely
+ large, but of course in theory the
+ #MHD_OPTION_CONNECTION_MEMORY_LIMIT might be very large
+ and would thus permit sending a >32k authorization
+ header value. */
+ return MHD_NO;
+ }
+ {
+ char *uri;
+
+ uri = malloc(left + 1);
+ if (NULL == uri)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(connection->daemon,
+ "Failed to allocate memory for auth header processing\n");
+#endif /* HAVE_MESSAGES */
+ return MHD_NO;
+ }
+ if (0 == lookup_sub_value (uri,
+ left + 1,
+ header, "uri"))
+ {
+ free(uri);
+ return MHD_NO;
+ }
+
+ /* 8 = 4 hexadecimal numbers for the timestamp */
+ nonce_time = strtoul (nonce + len - 8, (char **)NULL, 16);
+ t = (uint32_t) MHD_monotonic_time();
+ /*
+ * First level vetting for the nonce validity: if the timestamp
+ * attached to the nonce exceeds `nonce_timeout', then the nonce is
+ * invalid.
+ */
+ if ( (t > nonce_time + nonce_timeout) ||
+ (nonce_time + nonce_timeout < nonce_time) )
+ {
+ free(uri);
+ return MHD_INVALID_NONCE;
+ }
+ if (0 != strncmp (uri,
+ connection->url,
+ strlen (connection->url)))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Authentication failed, URI does not match.\n");
+#endif
+ free(uri);
+ return MHD_NO;
+ }
+ {
+ const char *args = strchr (uri, '?');
+
+ if (NULL == args)
+ args = "";
+ else
+ args++;
+ if (MHD_YES !=
+ check_argument_match (connection,
+ args) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Authentication failed, arguments do not match.\n");
+#endif
+ free(uri);
+ return MHD_NO;
+ }
+ }
+ calculate_nonce (nonce_time,
+ connection->method,
+ connection->daemon->digest_auth_random,
+ connection->daemon->digest_auth_rand_size,
+ connection->url,
+ realm,
+ noncehashexp);
+ /*
+ * Second level vetting for the nonce validity
+ * if the timestamp attached to the nonce is valid
+ * and possibly fabricated (in case of an attack)
+ * the attacker must also know the random seed to be
+ * able to generate a "sane" nonce, which if he does
+ * not, the nonce fabrication process going to be
+ * very hard to achieve.
+ */
+
+ if (0 != strcmp (nonce, noncehashexp))
+ {
+ free(uri);
+ return MHD_INVALID_NONCE;
+ }
+ if ( (0 == lookup_sub_value (cnonce,
+ sizeof (cnonce),
+ header, "cnonce")) ||
+ (0 == lookup_sub_value (qop, sizeof (qop), header, "qop")) ||
+ ( (0 != strcmp (qop, "auth")) &&
+ (0 != strcmp (qop, "")) ) ||
+ (0 == lookup_sub_value (nc, sizeof (nc), header, "nc")) ||
+ (0 == lookup_sub_value (response, sizeof (response), header, "response")) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Authentication failed, invalid format.\n");
+#endif
+ free(uri);
+ return MHD_NO;
+ }
+ nci = strtoul (nc, &end, 16);
+ if ( ('\0' != *end) ||
+ ( (LONG_MAX == nci) &&
+ (ERANGE == errno) ) )
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Authentication failed, invalid format.\n");
+#endif
+ free(uri);
+ return MHD_NO; /* invalid nonce format */
+ }
+ /*
+ * Checking if that combination of nonce and nc is sound
+ * and not a replay attack attempt. Also adds the nonce
+ * to the nonce-nc map if it does not exist there.
+ */
+
+ if (MHD_YES != check_nonce_nc (connection, nonce, nci))
+ {
+ free(uri);
+ return MHD_NO;
+ }
+
+ digest_calc_ha1("md5",
+ username,
+ realm,
+ password,
+ nonce,
+ cnonce,
+ ha1);
+ digest_calc_response (ha1,
+ nonce,
+ nc,
+ cnonce,
+ qop,
+ connection->method,
+ uri,
+ hentity,
+ respexp);
+ free(uri);
+ return (0 == strcmp(response, respexp))
+ ? MHD_YES
+ : MHD_NO;
+ }
+}
+
+
+/**
+ * Queues a response to request authentication from the client
+ *
+ * @param connection The MHD connection structure
+ * @param realm the realm presented to the client
+ * @param opaque string to user for opaque value
+ * @param response reply to send; should contain the "access denied"
+ * body; note that this function will set the "WWW Authenticate"
+ * header and that the caller should not do this
+ * @param signal_stale #MHD_YES if the nonce is invalid to add
+ * 'stale=true' to the authentication header
+ * @return #MHD_YES on success, #MHD_NO otherwise
+ * @ingroup authentication
+ */
+int
+MHD_queue_auth_fail_response (struct MHD_Connection *connection,
+ const char *realm,
+ const char *opaque,
+ struct MHD_Response *response,
+ int signal_stale)
+{
+ int ret;
+ size_t hlen;
+ char nonce[HASH_MD5_HEX_LEN + 9];
+
+ /* Generating the server nonce */
+ calculate_nonce ((uint32_t) MHD_monotonic_time(),
+ connection->method,
+ connection->daemon->digest_auth_random,
+ connection->daemon->digest_auth_rand_size,
+ connection->url,
+ realm,
+ nonce);
+ if (MHD_YES != check_nonce_nc (connection, nonce, 0))
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ "Could not register nonce (is the nonce array size zero?).\n");
+#endif
+ return MHD_NO;
+ }
+ /* Building the authentication header */
+ hlen = MHD_snprintf_(NULL,
+ 0,
+ "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
+ realm,
+ nonce,
+ opaque,
+ signal_stale
+ ? ",stale=\"true\""
+ : "");
+ {
+ char *header;
+
+ header = malloc(hlen + 1);
+ if (NULL == header)
+ {
+#if HAVE_MESSAGES
+ MHD_DLOG(connection->daemon,
+ "Failed to allocate memory for auth response header\n");
+#endif /* HAVE_MESSAGES */
+ return MHD_NO;
+ }
+
+ MHD_snprintf_(header,
+ hlen + 1,
+ "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
+ realm,
+ nonce,
+ opaque,
+ signal_stale
+ ? ",stale=\"true\""
+ : "");
+ ret = MHD_add_response_header(response,
+ MHD_HTTP_HEADER_WWW_AUTHENTICATE,
+ header);
+ free(header);
+ }
+ if (MHD_YES == ret)
+ ret = MHD_queue_response(connection,
+ MHD_HTTP_UNAUTHORIZED,
+ response);
+ return ret;
+}
+
+
+/* end of digestauth.c */
diff --git a/src/microhttpd/internal.c b/src/microhttpd/internal.c
new file mode 100644
index 0000000..6a9188f
--- /dev/null
+++ b/src/microhttpd/internal.c
@@ -0,0 +1,195 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file microhttpd/internal.c
+ * @brief internal shared structures
+ * @author Daniel Pittman
+ * @author Christian Grothoff
+ */
+
+#include "internal.h"
+
+#if HAVE_MESSAGES
+#if DEBUG_STATES
+/**
+ * State to string dictionary.
+ */
+const char *
+MHD_state_to_string (enum MHD_CONNECTION_STATE state)
+{
+ switch (state)
+ {
+ case MHD_CONNECTION_INIT:
+ return "connection init";
+ case MHD_CONNECTION_URL_RECEIVED:
+ return "connection url received";
+ case MHD_CONNECTION_HEADER_PART_RECEIVED:
+ return "header partially received";
+ case MHD_CONNECTION_HEADERS_RECEIVED:
+ return "headers received";
+ case MHD_CONNECTION_HEADERS_PROCESSED:
+ return "headers processed";
+ case MHD_CONNECTION_CONTINUE_SENDING:
+ return "continue sending";
+ case MHD_CONNECTION_CONTINUE_SENT:
+ return "continue sent";
+ case MHD_CONNECTION_BODY_RECEIVED:
+ return "body received";
+ case MHD_CONNECTION_FOOTER_PART_RECEIVED:
+ return "footer partially received";
+ case MHD_CONNECTION_FOOTERS_RECEIVED:
+ return "footers received";
+ case MHD_CONNECTION_HEADERS_SENDING:
+ return "headers sending";
+ case MHD_CONNECTION_HEADERS_SENT:
+ return "headers sent";
+ case MHD_CONNECTION_NORMAL_BODY_READY:
+ return "normal body ready";
+ case MHD_CONNECTION_NORMAL_BODY_UNREADY:
+ return "normal body unready";
+ case MHD_CONNECTION_CHUNKED_BODY_READY:
+ return "chunked body ready";
+ case MHD_CONNECTION_CHUNKED_BODY_UNREADY:
+ return "chunked body unready";
+ case MHD_CONNECTION_BODY_SENT:
+ return "body sent";
+ case MHD_CONNECTION_FOOTERS_SENDING:
+ return "footers sending";
+ case MHD_CONNECTION_FOOTERS_SENT:
+ return "footers sent";
+ case MHD_CONNECTION_CLOSED:
+ return "closed";
+ case MHD_TLS_CONNECTION_INIT:
+ return "secure connection init";
+ default:
+ return "unrecognized connection state";
+ }
+}
+#endif
+#endif
+
+#if HAVE_MESSAGES
+/**
+ * fprintf-like helper function for logging debug
+ * messages.
+ */
+void
+MHD_DLOG (const struct MHD_Daemon *daemon, const char *format, ...)
+{
+ va_list va;
+
+ if (0 == (daemon->options & MHD_USE_DEBUG))
+ return;
+ va_start (va, format);
+ daemon->custom_error_log (daemon->custom_error_log_cls, format, va);
+ va_end (va);
+}
+#endif
+
+
+/**
+ * Convert all occurences of '+' to ' '.
+ *
+ * @param arg string that is modified (in place), must be 0-terminated
+ */
+void
+MHD_unescape_plus (char *arg)
+{
+ char *p;
+
+ for (p=strchr (arg, '+'); NULL != p; p = strchr (p + 1, '+'))
+ *p = ' ';
+}
+
+
+/**
+ * Process escape sequences ('%HH') Updates val in place; the
+ * result should be UTF-8 encoded and cannot be larger than the input.
+ * The result must also still be 0-terminated.
+ *
+ * @param val value to unescape (modified in the process)
+ * @return length of the resulting val (strlen(val) maybe
+ * shorter afterwards due to elimination of escape sequences)
+ */
+size_t
+MHD_http_unescape (char *val)
+{
+ char *rpos = val;
+ char *wpos = val;
+ char *end;
+ unsigned int num;
+ char buf3[3];
+
+ while ('\0' != *rpos)
+ {
+ switch (*rpos)
+ {
+ case '%':
+ if ( ('\0' == rpos[1]) ||
+ ('\0' == rpos[2]) )
+ {
+ *wpos = '\0';
+ return wpos - val;
+ }
+ buf3[0] = rpos[1];
+ buf3[1] = rpos[2];
+ buf3[2] = '\0';
+ num = strtoul (buf3, &end, 16);
+ if ('\0' == *end)
+ {
+ *wpos = (char)((unsigned char) num);
+ wpos++;
+ rpos += 3;
+ break;
+ }
+ /* intentional fall through! */
+ default:
+ *wpos = *rpos;
+ wpos++;
+ rpos++;
+ }
+ }
+ *wpos = '\0'; /* add 0-terminator */
+ return wpos - val; /* = strlen(val) */
+}
+
+
+/**
+ * Equivalent to time(NULL) but tries to use some sort of monotonic
+ * clock that isn't affected by someone setting the system real time
+ * clock.
+ *
+ * @return 'current' time
+ */
+time_t
+MHD_monotonic_time (void)
+{
+#ifdef HAVE_CLOCK_GETTIME
+#ifdef CLOCK_MONOTONIC
+ struct timespec ts;
+
+ if (0 == clock_gettime (CLOCK_MONOTONIC, &ts))
+ return ts.tv_sec;
+#endif
+#endif
+ return time (NULL);
+}
+
+/* end of internal.c */
diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h
new file mode 100644
index 0000000..286aee6
--- /dev/null
+++ b/src/microhttpd/internal.h
@@ -0,0 +1,1459 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007-2015 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file microhttpd/internal.h
+ * @brief internal shared structures
+ * @author Daniel Pittman
+ * @author Christian Grothoff
+ */
+
+#ifndef INTERNAL_H
+#define INTERNAL_H
+
+#include "platform.h"
+#include "microhttpd.h"
+#include "platform_interface.h"
+#if HTTPS_SUPPORT
+#include <gnutls/gnutls.h>
+#if GNUTLS_VERSION_MAJOR >= 3
+#include <gnutls/abstract.h>
+#endif
+#endif
+#if EPOLL_SUPPORT
+#include <sys/epoll.h>
+#endif
+#if HAVE_NETINET_TCP_H
+/* for TCP_FASTOPEN */
+#include <netinet/tcp.h>
+#endif
+
+
+/**
+ * Should we perform additional sanity checks at runtime (on our internal
+ * invariants)? This may lead to aborts, but can be useful for debugging.
+ */
+#define EXTRA_CHECKS MHD_NO
+
+#define MHD_MAX(a,b) ((a)<(b)) ? (b) : (a)
+#define MHD_MIN(a,b) ((a)<(b)) ? (a) : (b)
+
+
+/**
+ * Minimum size by which MHD tries to increment read/write buffers.
+ * We usually begin with half the available pool space for the
+ * IO-buffer, but if absolutely needed we additively grow by the
+ * number of bytes given here (up to -- theoretically -- the full pool
+ * space).
+ */
+#define MHD_BUF_INC_SIZE 1024
+
+
+/**
+ * Handler for fatal errors.
+ */
+extern MHD_PanicCallback mhd_panic;
+
+/**
+ * Closure argument for "mhd_panic".
+ */
+extern void *mhd_panic_cls;
+
+/* If we have Clang or gcc >= 4.5, use __buildin_unreachable() */
+#if defined(__clang__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+#define BUILTIN_NOT_REACHED __builtin_unreachable()
+#else
+#define BUILTIN_NOT_REACHED
+#endif
+
+
+#if HAVE_MESSAGES
+/**
+ * Trigger 'panic' action based on fatal errors.
+ *
+ * @param msg error message (const char *)
+ */
+#define MHD_PANIC(msg) do { mhd_panic (mhd_panic_cls, __FILE__, __LINE__, msg); BUILTIN_NOT_REACHED; } while (0)
+#else
+/**
+ * Trigger 'panic' action based on fatal errors.
+ *
+ * @param msg error message (const char *)
+ */
+#define MHD_PANIC(msg) do { mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL); BUILTIN_NOT_REACHED; } while (0)
+#endif
+
+
+/**
+ * State of the socket with respect to epoll (bitmask).
+ */
+enum MHD_EpollState
+ {
+
+ /**
+ * The socket is not involved with a defined state in epoll right
+ * now.
+ */
+ MHD_EPOLL_STATE_UNREADY = 0,
+
+ /**
+ * epoll told us that data was ready for reading, and we did
+ * not consume all of it yet.
+ */
+ MHD_EPOLL_STATE_READ_READY = 1,
+
+ /**
+ * epoll told us that space was available for writing, and we did
+ * not consume all of it yet.
+ */
+ MHD_EPOLL_STATE_WRITE_READY = 2,
+
+ /**
+ * Is this connection currently in the 'eready' EDLL?
+ */
+ MHD_EPOLL_STATE_IN_EREADY_EDLL = 4,
+
+ /**
+ * Is this connection currently in the 'epoll' set?
+ */
+ MHD_EPOLL_STATE_IN_EPOLL_SET = 8,
+
+ /**
+ * Is this connection currently suspended?
+ */
+ MHD_EPOLL_STATE_SUSPENDED = 16
+ };
+
+
+/**
+ * What is this connection waiting for?
+ */
+enum MHD_ConnectionEventLoopInfo
+ {
+ /**
+ * We are waiting to be able to read.
+ */
+ MHD_EVENT_LOOP_INFO_READ = 0,
+
+ /**
+ * We are waiting to be able to write.
+ */
+ MHD_EVENT_LOOP_INFO_WRITE = 1,
+
+ /**
+ * We are waiting for the application to provide data.
+ */
+ MHD_EVENT_LOOP_INFO_BLOCK = 2,
+
+ /**
+ * We are finished and are awaiting cleanup.
+ */
+ MHD_EVENT_LOOP_INFO_CLEANUP = 3
+ };
+
+
+/**
+ * Maximum length of a nonce in digest authentication. 32(MD5 Hex) +
+ * 8(Timestamp Hex) + 1(NULL); hence 41 should suffice, but Opera
+ * (already) takes more (see Mantis #1633), so we've increased the
+ * value to support something longer...
+ */
+#define MAX_NONCE_LENGTH 129
+
+
+/**
+ * A structure representing the internal holder of the
+ * nonce-nc map.
+ */
+struct MHD_NonceNc
+{
+
+ /**
+ * Nonce counter, a value that increases for each subsequent
+ * request for the same nonce.
+ */
+ unsigned long int nc;
+
+ /**
+ * Nonce value:
+ */
+ char nonce[MAX_NONCE_LENGTH];
+
+};
+
+#if HAVE_MESSAGES
+/**
+ * fprintf-like helper function for logging debug
+ * messages.
+ */
+void
+MHD_DLOG (const struct MHD_Daemon *daemon,
+ const char *format, ...);
+#endif
+
+
+/**
+ * Header or cookie in HTTP request or response.
+ */
+struct MHD_HTTP_Header
+{
+ /**
+ * Headers are kept in a linked list.
+ */
+ struct MHD_HTTP_Header *next;
+
+ /**
+ * The name of the header (key), without
+ * the colon.
+ */
+ char *header;
+
+ /**
+ * The value of the header.
+ */
+ char *value;
+
+ /**
+ * Type of the header (where in the HTTP
+ * protocol is this header from).
+ */
+ enum MHD_ValueKind kind;
+
+};
+
+
+/**
+ * Representation of a response.
+ */
+struct MHD_Response
+{
+
+ /**
+ * Headers to send for the response. Initially
+ * the linked list is created in inverse order;
+ * the order should be inverted before sending!
+ */
+ struct MHD_HTTP_Header *first_header;
+
+ /**
+ * Buffer pointing to data that we are supposed
+ * to send as a response.
+ */
+ char *data;
+
+ /**
+ * Closure to give to the content reader @e crc
+ * and content reader free callback @e crfc.
+ */
+ void *crc_cls;
+
+ /**
+ * How do we get more data? NULL if we are
+ * given all of the data up front.
+ */
+ MHD_ContentReaderCallback crc;
+
+ /**
+ * NULL if data must not be freed, otherwise
+ * either user-specified callback or "&free".
+ */
+ MHD_ContentReaderFreeCallback crfc;
+
+ /**
+ * Mutex to synchronize access to @e data, @e size and
+ * @e reference_count.
+ */
+ MHD_mutex_ mutex;
+
+ /**
+ * Set to #MHD_SIZE_UNKNOWN if size is not known.
+ */
+ uint64_t total_size;
+
+ /**
+ * At what offset in the stream is the
+ * beginning of @e data located?
+ */
+ uint64_t data_start;
+
+ /**
+ * Offset to start reading from when using @e fd.
+ */
+ off_t fd_off;
+
+ /**
+ * Number of bytes ready in @e data (buffer may be larger
+ * than what is filled with payload).
+ */
+ size_t data_size;
+
+ /**
+ * Size of the data buffer @e data.
+ */
+ size_t data_buffer_size;
+
+ /**
+ * Reference count for this response. Free
+ * once the counter hits zero.
+ */
+ unsigned int reference_count;
+
+ /**
+ * File-descriptor if this response is FD-backed.
+ */
+ int fd;
+
+ /**
+ * Flags set for the MHD response.
+ */
+ enum MHD_ResponseFlags flags;
+
+};
+
+
+/**
+ * States in a state machine for a connection.
+ *
+ * Transitions are any-state to CLOSED, any state to state+1,
+ * FOOTERS_SENT to INIT. CLOSED is the terminal state and
+ * INIT the initial state.
+ *
+ * Note that transitions for *reading* happen only after
+ * the input has been processed; transitions for
+ * *writing* happen after the respective data has been
+ * put into the write buffer (the write does not have
+ * to be completed yet). A transition to CLOSED or INIT
+ * requires the write to be complete.
+ */
+enum MHD_CONNECTION_STATE
+{
+ /**
+ * Connection just started (no headers received).
+ * Waiting for the line with the request type, URL and version.
+ */
+ MHD_CONNECTION_INIT = 0,
+
+ /**
+ * 1: We got the URL (and request type and version). Wait for a header line.
+ */
+ MHD_CONNECTION_URL_RECEIVED = MHD_CONNECTION_INIT + 1,
+
+ /**
+ * 2: We got part of a multi-line request header. Wait for the rest.
+ */
+ MHD_CONNECTION_HEADER_PART_RECEIVED = MHD_CONNECTION_URL_RECEIVED + 1,
+
+ /**
+ * 3: We got the request headers. Process them.
+ */
+ MHD_CONNECTION_HEADERS_RECEIVED = MHD_CONNECTION_HEADER_PART_RECEIVED + 1,
+
+ /**
+ * 4: We have processed the request headers. Send 100 continue.
+ */
+ MHD_CONNECTION_HEADERS_PROCESSED = MHD_CONNECTION_HEADERS_RECEIVED + 1,
+
+ /**
+ * 5: We have processed the headers and need to send 100 CONTINUE.
+ */
+ MHD_CONNECTION_CONTINUE_SENDING = MHD_CONNECTION_HEADERS_PROCESSED + 1,
+
+ /**
+ * 6: We have sent 100 CONTINUE (or do not need to). Read the message body.
+ */
+ MHD_CONNECTION_CONTINUE_SENT = MHD_CONNECTION_CONTINUE_SENDING + 1,
+
+ /**
+ * 7: We got the request body. Wait for a line of the footer.
+ */
+ MHD_CONNECTION_BODY_RECEIVED = MHD_CONNECTION_CONTINUE_SENT + 1,
+
+ /**
+ * 8: We got part of a line of the footer. Wait for the
+ * rest.
+ */
+ MHD_CONNECTION_FOOTER_PART_RECEIVED = MHD_CONNECTION_BODY_RECEIVED + 1,
+
+ /**
+ * 9: We received the entire footer. Wait for a response to be queued
+ * and prepare the response headers.
+ */
+ MHD_CONNECTION_FOOTERS_RECEIVED = MHD_CONNECTION_FOOTER_PART_RECEIVED + 1,
+
+ /**
+ * 10: We have prepared the response headers in the writ buffer.
+ * Send the response headers.
+ */
+ MHD_CONNECTION_HEADERS_SENDING = MHD_CONNECTION_FOOTERS_RECEIVED + 1,
+
+ /**
+ * 11: We have sent the response headers. Get ready to send the body.
+ */
+ MHD_CONNECTION_HEADERS_SENT = MHD_CONNECTION_HEADERS_SENDING + 1,
+
+ /**
+ * 12: We are ready to send a part of a non-chunked body. Send it.
+ */
+ MHD_CONNECTION_NORMAL_BODY_READY = MHD_CONNECTION_HEADERS_SENT + 1,
+
+ /**
+ * 13: We are waiting for the client to provide more
+ * data of a non-chunked body.
+ */
+ MHD_CONNECTION_NORMAL_BODY_UNREADY = MHD_CONNECTION_NORMAL_BODY_READY + 1,
+
+ /**
+ * 14: We are ready to send a chunk.
+ */
+ MHD_CONNECTION_CHUNKED_BODY_READY = MHD_CONNECTION_NORMAL_BODY_UNREADY + 1,
+
+ /**
+ * 15: We are waiting for the client to provide a chunk of the body.
+ */
+ MHD_CONNECTION_CHUNKED_BODY_UNREADY = MHD_CONNECTION_CHUNKED_BODY_READY + 1,
+
+ /**
+ * 16: We have sent the response body. Prepare the footers.
+ */
+ MHD_CONNECTION_BODY_SENT = MHD_CONNECTION_CHUNKED_BODY_UNREADY + 1,
+
+ /**
+ * 17: We have prepared the response footer. Send it.
+ */
+ MHD_CONNECTION_FOOTERS_SENDING = MHD_CONNECTION_BODY_SENT + 1,
+
+ /**
+ * 18: We have sent the response footer. Shutdown or restart.
+ */
+ MHD_CONNECTION_FOOTERS_SENT = MHD_CONNECTION_FOOTERS_SENDING + 1,
+
+ /**
+ * 19: This connection is to be closed.
+ */
+ MHD_CONNECTION_CLOSED = MHD_CONNECTION_FOOTERS_SENT + 1,
+
+ /**
+ * 20: This connection is finished (only to be freed)
+ */
+ MHD_CONNECTION_IN_CLEANUP = MHD_CONNECTION_CLOSED + 1,
+
+ /*
+ * SSL/TLS connection states
+ */
+
+ /**
+ * The initial connection state for all secure connectoins
+ * Handshake messages will be processed in this state & while
+ * in the 'MHD_TLS_HELLO_REQUEST' state
+ */
+ MHD_TLS_CONNECTION_INIT = MHD_CONNECTION_IN_CLEANUP + 1
+
+};
+
+/**
+ * Should all state transitions be printed to stderr?
+ */
+#define DEBUG_STATES MHD_NO
+
+
+#if HAVE_MESSAGES
+#if DEBUG_STATES
+const char *
+MHD_state_to_string (enum MHD_CONNECTION_STATE state);
+#endif
+#endif
+
+/**
+ * Function to receive plaintext data.
+ *
+ * @param conn the connection struct
+ * @param write_to where to write received data
+ * @param max_bytes maximum number of bytes to receive
+ * @return number of bytes written to write_to
+ */
+typedef ssize_t
+(*ReceiveCallback) (struct MHD_Connection *conn,
+ void *write_to,
+ size_t max_bytes);
+
+
+/**
+ * Function to transmit plaintext data.
+ *
+ * @param conn the connection struct
+ * @param read_from where to read data to transmit
+ * @param max_bytes maximum number of bytes to transmit
+ * @return number of bytes transmitted
+ */
+typedef ssize_t
+(*TransmitCallback) (struct MHD_Connection *conn,
+ const void *write_to,
+ size_t max_bytes);
+
+
+/**
+ * State kept for each HTTP request.
+ */
+struct MHD_Connection
+{
+
+#if EPOLL_SUPPORT
+ /**
+ * Next pointer for the EDLL listing connections that are epoll-ready.
+ */
+ struct MHD_Connection *nextE;
+
+ /**
+ * Previous pointer for the EDLL listing connections that are epoll-ready.
+ */
+ struct MHD_Connection *prevE;
+#endif
+
+ /**
+ * Next pointer for the DLL describing our IO state.
+ */
+ struct MHD_Connection *next;
+
+ /**
+ * Previous pointer for the DLL describing our IO state.
+ */
+ struct MHD_Connection *prev;
+
+ /**
+ * Next pointer for the XDLL organizing connections by timeout.
+ * This DLL can be either the
+ * 'manual_timeout_head/manual_timeout_tail' or the
+ * 'normal_timeout_head/normal_timeout_tail', depending on whether a
+ * custom timeout is set for the connection.
+ */
+ struct MHD_Connection *nextX;
+
+ /**
+ * Previous pointer for the XDLL organizing connections by timeout.
+ */
+ struct MHD_Connection *prevX;
+
+ /**
+ * Reference to the MHD_Daemon struct.
+ */
+ struct MHD_Daemon *daemon;
+
+ /**
+ * Linked list of parsed headers.
+ */
+ struct MHD_HTTP_Header *headers_received;
+
+ /**
+ * Tail of linked list of parsed headers.
+ */
+ struct MHD_HTTP_Header *headers_received_tail;
+
+ /**
+ * Response to transmit (initially NULL).
+ */
+ struct MHD_Response *response;
+
+ /**
+ * The memory pool is created whenever we first read
+ * from the TCP stream and destroyed at the end of
+ * each request (and re-created for the next request).
+ * In the meantime, this pointer is NULL. The
+ * pool is used for all connection-related data
+ * except for the response (which maybe shared between
+ * connections) and the IP address (which persists
+ * across individual requests).
+ */
+ struct MemoryPool *pool;
+
+ /**
+ * We allow the main application to associate some pointer with the
+ * HTTP request, which is passed to each #MHD_AccessHandlerCallback
+ * and some other API calls. Here is where we store it. (MHD does
+ * not know or care what it is).
+ */
+ void *client_context;
+
+ /**
+ * We allow the main application to associate some pointer with the
+ * TCP connection (which may span multiple HTTP requests). Here is
+ * where we store it. (MHD does not know or care what it is).
+ * The location is given to the #MHD_NotifyConnectionCallback and
+ * also accessible via #MHD_CONNECTION_INFO_SOCKET_CONTEXT.
+ */
+ void *socket_context;
+
+ /**
+ * Request method. Should be GET/POST/etc. Allocated
+ * in pool.
+ */
+ char *method;
+
+ /**
+ * Requested URL (everything after "GET" only). Allocated
+ * in pool.
+ */
+ char *url;
+
+ /**
+ * HTTP version string (i.e. http/1.1). Allocated
+ * in pool.
+ */
+ char *version;
+
+ /**
+ * Buffer for reading requests. Allocated
+ * in pool. Actually one byte larger than
+ * @e read_buffer_size (if non-NULL) to allow for
+ * 0-termination.
+ */
+ char *read_buffer;
+
+ /**
+ * Buffer for writing response (headers only). Allocated
+ * in pool.
+ */
+ char *write_buffer;
+
+ /**
+ * Last incomplete header line during parsing of headers.
+ * Allocated in pool. Only valid if state is
+ * either #MHD_CONNECTION_HEADER_PART_RECEIVED or
+ * #MHD_CONNECTION_FOOTER_PART_RECEIVED.
+ */
+ char *last;
+
+ /**
+ * Position after the colon on the last incomplete header
+ * line during parsing of headers.
+ * Allocated in pool. Only valid if state is
+ * either #MHD_CONNECTION_HEADER_PART_RECEIVED or
+ * #MHD_CONNECTION_FOOTER_PART_RECEIVED.
+ */
+ char *colon;
+
+ /**
+ * Foreign address (of length @e addr_len). MALLOCED (not
+ * in pool!).
+ */
+ struct sockaddr *addr;
+
+ /**
+ * Thread handle for this connection (if we are using
+ * one thread per connection).
+ */
+ MHD_thread_handle_ pid;
+
+ /**
+ * Size of read_buffer (in bytes). This value indicates
+ * how many bytes we're willing to read into the buffer;
+ * the real buffer is one byte longer to allow for
+ * adding zero-termination (when needed).
+ */
+ size_t read_buffer_size;
+
+ /**
+ * Position where we currently append data in
+ * read_buffer (last valid position).
+ */
+ size_t read_buffer_offset;
+
+ /**
+ * Size of write_buffer (in bytes).
+ */
+ size_t write_buffer_size;
+
+ /**
+ * Offset where we are with sending from write_buffer.
+ */
+ size_t write_buffer_send_offset;
+
+ /**
+ * Last valid location in write_buffer (where do we
+ * append and up to where is it safe to send?)
+ */
+ size_t write_buffer_append_offset;
+
+ /**
+ * How many more bytes of the body do we expect
+ * to read? #MHD_SIZE_UNKNOWN for unknown.
+ */
+ uint64_t remaining_upload_size;
+
+ /**
+ * Current write position in the actual response
+ * (excluding headers, content only; should be 0
+ * while sending headers).
+ */
+ uint64_t response_write_position;
+
+ /**
+ * Position in the 100 CONTINUE message that
+ * we need to send when receiving http 1.1 requests.
+ */
+ size_t continue_message_write_offset;
+
+ /**
+ * Length of the foreign address.
+ */
+ socklen_t addr_len;
+
+ /**
+ * Last time this connection had any activity
+ * (reading or writing).
+ */
+ time_t last_activity;
+
+ /**
+ * After how many seconds of inactivity should
+ * this connection time out? Zero for no timeout.
+ */
+ unsigned int connection_timeout;
+
+ /**
+ * Did we ever call the "default_handler" on this connection?
+ * (this flag will determine if we call the 'notify_completed'
+ * handler when the connection closes down).
+ */
+ int client_aware;
+
+ /**
+ * Socket for this connection. Set to #MHD_INVALID_SOCKET if
+ * this connection has died (daemon should clean
+ * up in that case).
+ */
+ MHD_socket socket_fd;
+
+ /**
+ * Has this socket been closed for reading (i.e. other side closed
+ * the connection)? If so, we must completely close the connection
+ * once we are done sending our response (and stop trying to read
+ * from this socket).
+ */
+ int read_closed;
+
+ /**
+ * Set to #MHD_YES if the thread has been joined.
+ */
+ int thread_joined;
+
+ /**
+ * Are we currently inside the "idle" handler (to avoid recursively invoking it).
+ */
+ int in_idle;
+
+#if EPOLL_SUPPORT
+ /**
+ * What is the state of this socket in relation to epoll?
+ */
+ enum MHD_EpollState epoll_state;
+#endif
+
+ /**
+ * State in the FSM for this connection.
+ */
+ enum MHD_CONNECTION_STATE state;
+
+ /**
+ * What is this connection waiting for?
+ */
+ enum MHD_ConnectionEventLoopInfo event_loop_info;
+
+ /**
+ * HTTP response code. Only valid if response object
+ * is already set.
+ */
+ unsigned int responseCode;
+
+ /**
+ * Set to MHD_YES if the response's content reader
+ * callback failed to provide data the last time
+ * we tried to read from it. In that case, the
+ * write socket should be marked as unready until
+ * the CRC call succeeds.
+ */
+ int response_unready;
+
+ /**
+ * Are we receiving with chunked encoding? This will be set to
+ * MHD_YES after we parse the headers and are processing the body
+ * with chunks. After we are done with the body and we are
+ * processing the footers; once the footers are also done, this will
+ * be set to MHD_NO again (before the final call to the handler).
+ */
+ int have_chunked_upload;
+
+ /**
+ * If we are receiving with chunked encoding, where are we right
+ * now? Set to 0 if we are waiting to receive the chunk size;
+ * otherwise, this is the size of the current chunk. A value of
+ * zero is also used when we're at the end of the chunks.
+ */
+ size_t current_chunk_size;
+
+ /**
+ * If we are receiving with chunked encoding, where are we currently
+ * with respect to the current chunk (at what offset / position)?
+ */
+ size_t current_chunk_offset;
+
+ /**
+ * Handler used for processing read connection operations
+ */
+ int (*read_handler) (struct MHD_Connection *connection);
+
+ /**
+ * Handler used for processing write connection operations
+ */
+ int (*write_handler) (struct MHD_Connection *connection);
+
+ /**
+ * Handler used for processing idle connection operations
+ */
+ int (*idle_handler) (struct MHD_Connection *connection);
+
+ /**
+ * Function used for reading HTTP request stream.
+ */
+ ReceiveCallback recv_cls;
+
+ /**
+ * Function used for writing HTTP response stream.
+ */
+ TransmitCallback send_cls;
+
+#if HTTPS_SUPPORT
+ /**
+ * State required for HTTPS/SSL/TLS support.
+ */
+ gnutls_session_t tls_session;
+
+ /**
+ * Memory location to return for protocol session info.
+ */
+ int protocol;
+
+ /**
+ * Memory location to return for protocol session info.
+ */
+ int cipher;
+
+ /**
+ * Could it be that we are ready to read due to TLS buffers
+ * even though the socket is not?
+ */
+ int tls_read_ready;
+#endif
+
+ /**
+ * Is the connection suspended?
+ */
+ int suspended;
+
+ /**
+ * Is the connection wanting to resume?
+ */
+ int resuming;
+};
+
+/**
+ * Signature of function called to log URI accesses.
+ *
+ * @param cls closure
+ * @param uri uri being accessed
+ * @param con connection handle
+ * @return new closure
+ */
+typedef void *
+(*LogCallback)(void * cls,
+ const char * uri,
+ struct MHD_Connection *con);
+
+/**
+ * Signature of function called to unescape URIs. See also
+ * #MHD_http_unescape().
+ *
+ * @param cls closure
+ * @param conn connection handle
+ * @param uri 0-terminated string to unescape (should be updated)
+ * @return length of the resulting string
+ */
+typedef size_t
+(*UnescapeCallback)(void *cls,
+ struct MHD_Connection *conn,
+ char *uri);
+
+
+/**
+ * State kept for each MHD daemon. All connections are kept in two
+ * doubly-linked lists. The first one reflects the state of the
+ * connection in terms of what operations we are waiting for (read,
+ * write, locally blocked, cleanup) whereas the second is about its
+ * timeout state (default or custom).
+ */
+struct MHD_Daemon
+{
+
+ /**
+ * Callback function for all requests.
+ */
+ MHD_AccessHandlerCallback default_handler;
+
+ /**
+ * Closure argument to default_handler.
+ */
+ void *default_handler_cls;
+
+ /**
+ * Head of doubly-linked list of our current, active connections.
+ */
+ struct MHD_Connection *connections_head;
+
+ /**
+ * Tail of doubly-linked list of our current, active connections.
+ */
+ struct MHD_Connection *connections_tail;
+
+ /**
+ * Head of doubly-linked list of our current but suspended connections.
+ */
+ struct MHD_Connection *suspended_connections_head;
+
+ /**
+ * Tail of doubly-linked list of our current but suspended connections.
+ */
+ struct MHD_Connection *suspended_connections_tail;
+
+ /**
+ * Head of doubly-linked list of connections to clean up.
+ */
+ struct MHD_Connection *cleanup_head;
+
+ /**
+ * Tail of doubly-linked list of connections to clean up.
+ */
+ struct MHD_Connection *cleanup_tail;
+
+#if EPOLL_SUPPORT
+ /**
+ * Head of EDLL of connections ready for processing (in epoll mode).
+ */
+ struct MHD_Connection *eready_head;
+
+ /**
+ * Tail of EDLL of connections ready for processing (in epoll mode)
+ */
+ struct MHD_Connection *eready_tail;
+#endif
+
+ /**
+ * Head of the XDLL of ALL connections with a default ('normal')
+ * timeout, sorted by timeout (earliest at the tail, most recently
+ * used connection at the head). MHD can just look at the tail of
+ * this list to determine the timeout for all of its elements;
+ * whenever there is an event of a connection, the connection is
+ * moved back to the tail of the list.
+ *
+ * All connections by default start in this list; if a custom
+ * timeout that does not match 'connection_timeout' is set, they
+ * are moved to the 'manual_timeout_head'-XDLL.
+ */
+ struct MHD_Connection *normal_timeout_head;
+
+ /**
+ * Tail of the XDLL of ALL connections with a default timeout,
+ * sorted by timeout (earliest timeout at the tail).
+ */
+ struct MHD_Connection *normal_timeout_tail;
+
+ /**
+ * Head of the XDLL of ALL connections with a non-default/custom
+ * timeout, unsorted. MHD will do a O(n) scan over this list to
+ * determine the current timeout.
+ */
+ struct MHD_Connection *manual_timeout_head;
+
+ /**
+ * Tail of the XDLL of ALL connections with a non-default/custom
+ * timeout, unsorted.
+ */
+ struct MHD_Connection *manual_timeout_tail;
+
+ /**
+ * Function to call to check if we should accept or reject an
+ * incoming request. May be NULL.
+ */
+ MHD_AcceptPolicyCallback apc;
+
+ /**
+ * Closure argument to apc.
+ */
+ void *apc_cls;
+
+ /**
+ * Function to call when we are done processing
+ * a particular request. May be NULL.
+ */
+ MHD_RequestCompletedCallback notify_completed;
+
+ /**
+ * Closure argument to notify_completed.
+ */
+ void *notify_completed_cls;
+
+ /**
+ * Function to call when we are starting/stopping
+ * a connection. May be NULL.
+ */
+ MHD_NotifyConnectionCallback notify_connection;
+
+ /**
+ * Closure argument to notify_connection.
+ */
+ void *notify_connection_cls;
+
+ /**
+ * Function to call with the full URI at the
+ * beginning of request processing. May be NULL.
+ * <p>
+ * Returns the initial pointer to internal state
+ * kept by the client for the request.
+ */
+ LogCallback uri_log_callback;
+
+ /**
+ * Closure argument to @e uri_log_callback.
+ */
+ void *uri_log_callback_cls;
+
+ /**
+ * Function to call when we unescape escape sequences.
+ */
+ UnescapeCallback unescape_callback;
+
+ /**
+ * Closure for @e unescape_callback.
+ */
+ void *unescape_callback_cls;
+
+#if HAVE_MESSAGES
+ /**
+ * Function for logging error messages (if we
+ * support error reporting).
+ */
+ void (*custom_error_log) (void *cls, const char *fmt, va_list va);
+
+ /**
+ * Closure argument to custom_error_log.
+ */
+ void *custom_error_log_cls;
+#endif
+
+ /**
+ * Pointer to master daemon (NULL if this is the master)
+ */
+ struct MHD_Daemon *master;
+
+ /**
+ * Worker daemons (one per thread)
+ */
+ struct MHD_Daemon *worker_pool;
+
+ /**
+ * Table storing number of connections per IP
+ */
+ void *per_ip_connection_count;
+
+ /**
+ * Size of the per-connection memory pools.
+ */
+ size_t pool_size;
+
+ /**
+ * Increment for growth of the per-connection memory pools.
+ */
+ size_t pool_increment;
+
+ /**
+ * Size of threads created by MHD.
+ */
+ size_t thread_stack_size;
+
+ /**
+ * Number of worker daemons
+ */
+ unsigned int worker_pool_size;
+
+ /**
+ * The select thread handle (if we have internal select)
+ */
+ MHD_thread_handle_ pid;
+
+ /**
+ * Mutex for per-IP connection counts.
+ */
+ MHD_mutex_ per_ip_connection_mutex;
+
+ /**
+ * Mutex for (modifying) access to the "cleanup" connection DLL.
+ */
+ MHD_mutex_ cleanup_connection_mutex;
+
+ /**
+ * Listen socket.
+ */
+ MHD_socket socket_fd;
+
+ /**
+ * Whether to allow/disallow/ignore reuse of listening address.
+ * The semantics is the following:
+ * 0: ignore (user did not ask for neither allow/disallow, use SO_REUSEADDR)
+ * >0: allow (use SO_REUSEPORT on most platforms, SO_REUSEADDR on Windows)
+ * <0: disallow (mostly no action, SO_EXCLUSIVEADDRUSE on Windows)
+ */
+ int listening_address_reuse;
+
+#if EPOLL_SUPPORT
+ /**
+ * File descriptor associated with our epoll loop.
+ */
+ int epoll_fd;
+
+ /**
+ * MHD_YES if the listen socket is in the 'epoll' set,
+ * MHD_NO if not.
+ */
+ int listen_socket_in_epoll;
+#endif
+
+ /**
+ * Pipe we use to signal shutdown, unless
+ * 'HAVE_LISTEN_SHUTDOWN' is defined AND we have a listen
+ * socket (which we can then 'shutdown' to stop listening).
+ * MHD can be build with usage of socketpair instead of
+ * pipe (forced on W32).
+ */
+ MHD_pipe wpipe[2];
+
+ /**
+ * Are we shutting down?
+ */
+ int shutdown;
+
+ /*
+ * Do we need to process resuming connections?
+ */
+ int resuming;
+
+ /**
+ * Number of active parallel connections.
+ */
+ unsigned int connections;
+
+ /**
+ * Limit on the number of parallel connections.
+ */
+ unsigned int connection_limit;
+
+ /**
+ * After how many seconds of inactivity should
+ * connections time out? Zero for no timeout.
+ */
+ unsigned int connection_timeout;
+
+ /**
+ * Maximum number of connections per IP, or 0 for
+ * unlimited.
+ */
+ unsigned int per_ip_connection_limit;
+
+ /**
+ * Daemon's flags (bitfield).
+ */
+ enum MHD_FLAG options;
+
+ /**
+ * Listen port.
+ */
+ uint16_t port;
+
+#if HTTPS_SUPPORT
+ /**
+ * Desired cipher algorithms.
+ */
+ gnutls_priority_t priority_cache;
+
+ /**
+ * What kind of credentials are we offering
+ * for SSL/TLS?
+ */
+ gnutls_credentials_type_t cred_type;
+
+ /**
+ * Server x509 credentials
+ */
+ gnutls_certificate_credentials_t x509_cred;
+
+ /**
+ * Diffie-Hellman parameters
+ */
+ gnutls_dh_params_t dh_params;
+
+#if GNUTLS_VERSION_MAJOR >= 3
+ /**
+ * Function that can be used to obtain the certificate. Needed
+ * for SNI support. See #MHD_OPTION_HTTPS_CERT_CALLBACK.
+ */
+ gnutls_certificate_retrieve_function2 *cert_callback;
+#endif
+
+ /**
+ * Pointer to our SSL/TLS key (in ASCII) in memory.
+ */
+ const char *https_mem_key;
+
+ /**
+ * Pointer to our SSL/TLS certificate (in ASCII) in memory.
+ */
+ const char *https_mem_cert;
+
+ /**
+ * Pointer to 0-terminated HTTPS passphrase in memory.
+ */
+ const char *https_key_password;
+
+ /**
+ * Pointer to our SSL/TLS certificate authority (in ASCII) in memory.
+ */
+ const char *https_mem_trust;
+
+ /**
+ * Our Diffie-Hellman parameters in memory.
+ */
+ gnutls_dh_params_t https_mem_dhparams;
+
+ /**
+ * #MHD_YES if we have initialized @e https_mem_dhparams.
+ */
+ int have_dhparams;
+
+ /**
+ * For how many connections do we have 'tls_read_ready' set to MHD_YES?
+ * Used to avoid O(n) traversal over all connections when determining
+ * event-loop timeout (as it needs to be zero if there is any connection
+ * which might have ready data within TLS).
+ */
+ unsigned int num_tls_read_ready;
+
+#endif
+
+#ifdef DAUTH_SUPPORT
+
+ /**
+ * Character array of random values.
+ */
+ const char *digest_auth_random;
+
+ /**
+ * An array that contains the map nonce-nc.
+ */
+ struct MHD_NonceNc *nnc;
+
+ /**
+ * A rw-lock for synchronizing access to `nnc'.
+ */
+ MHD_mutex_ nnc_lock;
+
+ /**
+ * Size of `digest_auth_random.
+ */
+ size_t digest_auth_rand_size;
+
+ /**
+ * Size of the nonce-nc array.
+ */
+ unsigned int nonce_nc_size;
+
+#endif
+
+#ifdef TCP_FASTOPEN
+ /**
+ * The queue size for incoming SYN + DATA packets.
+ */
+ unsigned int fastopen_queue_size;
+#endif
+};
+
+
+#if EXTRA_CHECKS
+#define EXTRA_CHECK(a) do { if (!(a)) abort(); } while (0)
+#else
+#define EXTRA_CHECK(a)
+#endif
+
+
+/**
+ * Insert an element at the head of a DLL. Assumes that head, tail and
+ * element are structs with prev and next fields.
+ *
+ * @param head pointer to the head of the DLL
+ * @param tail pointer to the tail of the DLL
+ * @param element element to insert
+ */
+#define DLL_insert(head,tail,element) do { \
+ EXTRA_CHECK (NULL == (element)->next); \
+ EXTRA_CHECK (NULL == (element)->prev); \
+ (element)->next = (head); \
+ (element)->prev = NULL; \
+ if ((tail) == NULL) \
+ (tail) = element; \
+ else \
+ (head)->prev = element; \
+ (head) = (element); } while (0)
+
+
+/**
+ * Remove an element from a DLL. Assumes
+ * that head, tail and element are structs
+ * with prev and next fields.
+ *
+ * @param head pointer to the head of the DLL
+ * @param tail pointer to the tail of the DLL
+ * @param element element to remove
+ */
+#define DLL_remove(head,tail,element) do { \
+ EXTRA_CHECK ( (NULL != (element)->next) || ((element) == (tail))); \
+ EXTRA_CHECK ( (NULL != (element)->prev) || ((element) == (head))); \
+ if ((element)->prev == NULL) \
+ (head) = (element)->next; \
+ else \
+ (element)->prev->next = (element)->next; \
+ if ((element)->next == NULL) \
+ (tail) = (element)->prev; \
+ else \
+ (element)->next->prev = (element)->prev; \
+ (element)->next = NULL; \
+ (element)->prev = NULL; } while (0)
+
+
+
+/**
+ * Insert an element at the head of a XDLL. Assumes that head, tail and
+ * element are structs with prevX and nextX fields.
+ *
+ * @param head pointer to the head of the XDLL
+ * @param tail pointer to the tail of the XDLL
+ * @param element element to insert
+ */
+#define XDLL_insert(head,tail,element) do { \
+ EXTRA_CHECK (NULL == (element)->nextX); \
+ EXTRA_CHECK (NULL == (element)->prevX); \
+ (element)->nextX = (head); \
+ (element)->prevX = NULL; \
+ if (NULL == (tail)) \
+ (tail) = element; \
+ else \
+ (head)->prevX = element; \
+ (head) = (element); } while (0)
+
+
+/**
+ * Remove an element from a XDLL. Assumes
+ * that head, tail and element are structs
+ * with prevX and nextX fields.
+ *
+ * @param head pointer to the head of the XDLL
+ * @param tail pointer to the tail of the XDLL
+ * @param element element to remove
+ */
+#define XDLL_remove(head,tail,element) do { \
+ EXTRA_CHECK ( (NULL != (element)->nextX) || ((element) == (tail))); \
+ EXTRA_CHECK ( (NULL != (element)->prevX) || ((element) == (head))); \
+ if (NULL == (element)->prevX) \
+ (head) = (element)->nextX; \
+ else \
+ (element)->prevX->nextX = (element)->nextX; \
+ if (NULL == (element)->nextX) \
+ (tail) = (element)->prevX; \
+ else \
+ (element)->nextX->prevX = (element)->prevX; \
+ (element)->nextX = NULL; \
+ (element)->prevX = NULL; } while (0)
+
+
+/**
+ * Insert an element at the head of a EDLL. Assumes that head, tail and
+ * element are structs with prevE and nextE fields.
+ *
+ * @param head pointer to the head of the EDLL
+ * @param tail pointer to the tail of the EDLL
+ * @param element element to insert
+ */
+#define EDLL_insert(head,tail,element) do { \
+ (element)->nextE = (head); \
+ (element)->prevE = NULL; \
+ if ((tail) == NULL) \
+ (tail) = element; \
+ else \
+ (head)->prevE = element; \
+ (head) = (element); } while (0)
+
+
+/**
+ * Remove an element from a EDLL. Assumes
+ * that head, tail and element are structs
+ * with prevE and nextE fields.
+ *
+ * @param head pointer to the head of the EDLL
+ * @param tail pointer to the tail of the EDLL
+ * @param element element to remove
+ */
+#define EDLL_remove(head,tail,element) do { \
+ if ((element)->prevE == NULL) \
+ (head) = (element)->nextE; \
+ else \
+ (element)->prevE->nextE = (element)->nextE; \
+ if ((element)->nextE == NULL) \
+ (tail) = (element)->prevE; \
+ else \
+ (element)->nextE->prevE = (element)->prevE; \
+ (element)->nextE = NULL; \
+ (element)->prevE = NULL; } while (0)
+
+
+/**
+ * Equivalent to `time(NULL)` but tries to use some sort of monotonic
+ * clock that isn't affected by someone setting the system real time
+ * clock.
+ *
+ * @return 'current' time
+ */
+time_t
+MHD_monotonic_time(void);
+
+
+/**
+ * Convert all occurences of '+' to ' '.
+ *
+ * @param arg string that is modified (in place), must be 0-terminated
+ */
+void
+MHD_unescape_plus (char *arg);
+
+
+#endif
diff --git a/src/microhttpd/md5.c b/src/microhttpd/md5.c
new file mode 100644
index 0000000..e35d5f2
--- /dev/null
+++ b/src/microhttpd/md5.c
@@ -0,0 +1,267 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+/* Brutally hacked by John Walker back from ANSI C to K&R (no
+ prototypes) to maintain the tradition that Netfone will compile
+ with Sun's original "cc". */
+
+#include "md5.h"
+
+
+#ifndef HIGHFIRST
+#define byteReverse(buf, len) /* Nothing */
+#else
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void
+byteReverse(unsigned char *buf,
+ unsigned longs)
+{
+ uint32_t t;
+ do {
+ t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(uint32_t *) buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif
+
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void
+MD5Transform(uint32_t buf[4],
+ uint32_t in[16])
+{
+ uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(struct MD5Context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(struct MD5Context *ctx,
+ const void *data,
+ unsigned len)
+{
+ const unsigned char *buf = data;
+ uint32_t t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final (unsigned char digest[16],
+ struct MD5Context *ctx)
+{
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8)
+ {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ }
+ else
+ {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ((uint32_t *) ctx->in)[14] = ctx->bits[0];
+ ((uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset(ctx, 0, sizeof(struct MD5Context)); /* In case it's sensitive */
+}
+
+/* end of md5.c */
diff --git a/src/microhttpd/md5.h b/src/microhttpd/md5.h
new file mode 100644
index 0000000..5e77774
--- /dev/null
+++ b/src/microhttpd/md5.h
@@ -0,0 +1,52 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+/* Brutally hacked by John Walker back from ANSI C to K&R (no
+ prototypes) to maintain the tradition that Netfone will compile
+ with Sun's original "cc". */
+
+#ifndef MD5_H
+#define MD5_H
+
+#include "platform.h"
+#ifdef WORDS_BIGENDIAN
+#define HIGHFIRST
+#endif
+
+#define MD5_DIGEST_SIZE 16
+
+struct MD5Context
+{
+ uint32_t buf[4];
+ uint32_t bits[2];
+ unsigned char in[64];
+};
+
+
+void
+MD5Init(struct MD5Context *ctx);
+
+void
+MD5Update(struct MD5Context *ctx,
+ const void *buf,
+ unsigned len);
+
+void
+MD5Final(unsigned char digest[MD5_DIGEST_SIZE],
+ struct MD5Context *ctx);
+
+#endif /* !MD5_H */
diff --git a/src/microhttpd/memorypool.c b/src/microhttpd/memorypool.c
new file mode 100644
index 0000000..2f39931
--- /dev/null
+++ b/src/microhttpd/memorypool.c
@@ -0,0 +1,284 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009, 2010 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file memorypool.c
+ * @brief memory pool
+ * @author Christian Grothoff
+ */
+#include "memorypool.h"
+
+/* define MAP_ANONYMOUS for Mac OS X */
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+#ifndef MAP_FAILED
+#define MAP_FAILED ((void*)-1)
+#endif
+
+/**
+ * Align to 2x word size (as GNU libc does).
+ */
+#define ALIGN_SIZE (2 * sizeof(void*))
+
+/**
+ * Round up 'n' to a multiple of ALIGN_SIZE.
+ */
+#define ROUND_TO_ALIGN(n) ((n+(ALIGN_SIZE-1)) & (~(ALIGN_SIZE-1)))
+
+
+/**
+ * Handle for a memory pool. Pools are not reentrant and must not be
+ * used by multiple threads.
+ */
+struct MemoryPool
+{
+
+ /**
+ * Pointer to the pool's memory
+ */
+ char *memory;
+
+ /**
+ * Size of the pool.
+ */
+ size_t size;
+
+ /**
+ * Offset of the first unallocated byte.
+ */
+ size_t pos;
+
+ /**
+ * Offset of the last unallocated byte.
+ */
+ size_t end;
+
+ /**
+ * #MHD_NO if pool was malloc'ed, #MHD_YES if mmapped (VirtualAlloc'ed for W32).
+ */
+ int is_mmap;
+};
+
+
+/**
+ * Create a memory pool.
+ *
+ * @param max maximum size of the pool
+ * @return NULL on error
+ */
+struct MemoryPool *
+MHD_pool_create (size_t max)
+{
+ struct MemoryPool *pool;
+
+ pool = malloc (sizeof (struct MemoryPool));
+ if (NULL == pool)
+ return NULL;
+#if defined(MAP_ANONYMOUS) || defined(_WIN32)
+ if (max <= 32 * 1024)
+ pool->memory = MAP_FAILED;
+ else
+#if defined(MAP_ANONYMOUS) && !defined(_WIN32)
+ pool->memory = mmap (NULL, max, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+#elif defined(_WIN32)
+ pool->memory = VirtualAlloc(NULL, max, MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE);
+#endif
+#else
+ pool->memory = MAP_FAILED;
+#endif
+ if ((pool->memory == MAP_FAILED) || (pool->memory == NULL))
+ {
+ pool->memory = malloc (max);
+ if (pool->memory == NULL)
+ {
+ free (pool);
+ return NULL;
+ }
+ pool->is_mmap = MHD_NO;
+ }
+ else
+ {
+ pool->is_mmap = MHD_YES;
+ }
+ pool->pos = 0;
+ pool->end = max;
+ pool->size = max;
+ return pool;
+}
+
+
+/**
+ * Destroy a memory pool.
+ *
+ * @param pool memory pool to destroy
+ */
+void
+MHD_pool_destroy (struct MemoryPool *pool)
+{
+ if (pool == NULL)
+ return;
+ if (pool->is_mmap == MHD_NO)
+ free (pool->memory);
+ else
+#if defined(MAP_ANONYMOUS) && !defined(_WIN32)
+ munmap (pool->memory, pool->size);
+#elif defined(_WIN32)
+ VirtualFree(pool->memory, 0, MEM_RELEASE);
+#else
+ abort();
+#endif
+ free (pool);
+}
+
+
+/**
+ * Allocate size bytes from the pool.
+ *
+ * @param pool memory pool to use for the operation
+ * @param size number of bytes to allocate
+ * @param from_end allocate from end of pool (set to #MHD_YES);
+ * use this for small, persistent allocations that
+ * will never be reallocated
+ * @return NULL if the pool cannot support size more
+ * bytes
+ */
+void *
+MHD_pool_allocate (struct MemoryPool *pool,
+ size_t size, int from_end)
+{
+ void *ret;
+ size_t asize;
+
+ asize = ROUND_TO_ALIGN (size);
+ if ( (0 == asize) && (0 != size) )
+ return NULL; /* size too close to SIZE_MAX */
+ if ((pool->pos + asize > pool->end) || (pool->pos + asize < pool->pos))
+ return NULL;
+ if (from_end == MHD_YES)
+ {
+ ret = &pool->memory[pool->end - asize];
+ pool->end -= asize;
+ }
+ else
+ {
+ ret = &pool->memory[pool->pos];
+ pool->pos += asize;
+ }
+ return ret;
+}
+
+
+/**
+ * Reallocate a block of memory obtained from the pool.
+ * This is particularly efficient when growing or
+ * shrinking the block that was last (re)allocated.
+ * If the given block is not the most recenlty
+ * (re)allocated block, the memory of the previous
+ * allocation may be leaked until the pool is
+ * destroyed (and copying the data maybe required).
+ *
+ * @param pool memory pool to use for the operation
+ * @param old the existing block
+ * @param old_size the size of the existing block
+ * @param new_size the new size of the block
+ * @return new address of the block, or
+ * NULL if the pool cannot support @a new_size
+ * bytes (old continues to be valid for @a old_size)
+ */
+void *
+MHD_pool_reallocate (struct MemoryPool *pool,
+ void *old,
+ size_t old_size,
+ size_t new_size)
+{
+ void *ret;
+ size_t asize;
+
+ asize = ROUND_TO_ALIGN (new_size);
+ if ( (0 == asize) && (0 != new_size) )
+ return NULL; /* new_size too close to SIZE_MAX */
+ if ((pool->end < old_size) || (pool->end < asize))
+ return NULL; /* unsatisfiable or bogus request */
+
+ if ((pool->pos >= old_size) && (&pool->memory[pool->pos - old_size] == old))
+ {
+ /* was the previous allocation - optimize! */
+ if (pool->pos + asize - old_size <= pool->end)
+ {
+ /* fits */
+ pool->pos += asize - old_size;
+ if (asize < old_size) /* shrinking - zero again! */
+ memset (&pool->memory[pool->pos], 0, old_size - asize);
+ return old;
+ }
+ /* does not fit */
+ return NULL;
+ }
+ if (asize <= old_size)
+ return old; /* cannot shrink, no need to move */
+ if ((pool->pos + asize >= pool->pos) &&
+ (pool->pos + asize <= pool->end))
+ {
+ /* fits */
+ ret = &pool->memory[pool->pos];
+ memcpy (ret, old, old_size);
+ pool->pos += asize;
+ return ret;
+ }
+ /* does not fit */
+ return NULL;
+}
+
+
+/**
+ * Clear all entries from the memory pool except
+ * for @a keep of the given @a size.
+ *
+ * @param pool memory pool to use for the operation
+ * @param keep pointer to the entry to keep (maybe NULL)
+ * @param size how many bytes need to be kept at this address
+ * @return addr new address of @a keep (if it had to change)
+ */
+void *
+MHD_pool_reset (struct MemoryPool *pool,
+ void *keep,
+ size_t size)
+{
+ if (NULL != keep)
+ {
+ if (keep != pool->memory)
+ {
+ memmove (pool->memory, keep, size);
+ keep = pool->memory;
+ }
+ }
+ pool->end = pool->size;
+ memset (&pool->memory[size],
+ 0,
+ pool->size - size);
+ if (NULL != keep)
+ pool->pos = ROUND_TO_ALIGN(size);
+ return keep;
+}
+
+
+/* end of memorypool.c */
diff --git a/src/microhttpd/memorypool.h b/src/microhttpd/memorypool.h
new file mode 100644
index 0000000..4db952a
--- /dev/null
+++ b/src/microhttpd/memorypool.h
@@ -0,0 +1,114 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file memorypool.h
+ * @brief memory pool; mostly used for efficient (de)allocation
+ * for each connection and bounding memory use for each
+ * request
+ * @author Christian Grothoff
+ */
+
+#ifndef MEMORYPOOL_H
+#define MEMORYPOOL_H
+
+#include "internal.h"
+
+/**
+ * Opaque handle for a memory pool.
+ * Pools are not reentrant and must not be used
+ * by multiple threads.
+ */
+struct MemoryPool;
+
+
+/**
+ * Create a memory pool.
+ *
+ * @param max maximum size of the pool
+ * @return NULL on error
+ */
+struct MemoryPool *
+MHD_pool_create (size_t max);
+
+
+/**
+ * Destroy a memory pool.
+ *
+ * @param pool memory pool to destroy
+ */
+void
+MHD_pool_destroy (struct MemoryPool *pool);
+
+
+/**
+ * Allocate size bytes from the pool.
+ *
+ * @param pool memory pool to use for the operation
+ * @param size number of bytes to allocate
+ * @param from_end allocate from end of pool (set to MHD_YES);
+ * use this for small, persistent allocations that
+ * will never be reallocated
+ * @return NULL if the pool cannot support size more
+ * bytes
+ */
+void *
+MHD_pool_allocate (struct MemoryPool *pool,
+ size_t size, int from_end);
+
+
+/**
+ * Reallocate a block of memory obtained from the pool.
+ * This is particularly efficient when growing or
+ * shrinking the block that was last (re)allocated.
+ * If the given block is not the most recenlty
+ * (re)allocated block, the memory of the previous
+ * allocation may be leaked until the pool is
+ * destroyed (and copying the data maybe required).
+ *
+ * @param pool memory pool to use for the operation
+ * @param old the existing block
+ * @param old_size the size of the existing block
+ * @param new_size the new size of the block
+ * @return new address of the block, or
+ * NULL if the pool cannot support new_size
+ * bytes (old continues to be valid for old_size)
+ */
+void *
+MHD_pool_reallocate (struct MemoryPool *pool,
+ void *old,
+ size_t old_size,
+ size_t new_size);
+
+
+/**
+ * Clear all entries from the memory pool except
+ * for "keep" of the given "size".
+ *
+ * @param pool memory pool to use for the operation
+ * @param keep pointer to the entry to keep (maybe NULL)
+ * @param size how many bytes need to be kept at this address
+ * @return addr new address of "keep" (if it had to change)
+ */
+void *
+MHD_pool_reset (struct MemoryPool *pool,
+ void *keep,
+ size_t size);
+
+#endif
diff --git a/src/microhttpd/microhttpd_dll_res.rc.in b/src/microhttpd/microhttpd_dll_res.rc.in
new file mode 100644
index 0000000..923f3a9
--- /dev/null
+++ b/src/microhttpd/microhttpd_dll_res.rc.in
@@ -0,0 +1,35 @@
+/* W32 resources for .dll */
+
+#include <winresrc.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION @PACKAGE_VERSION_MAJOR@,@PACKAGE_VERSION_MINOR@,@PACKAGE_VERSION_SUBMINOR@,0
+ PRODUCTVERSION @PACKAGE_VERSION_MAJOR@,@PACKAGE_VERSION_MINOR@,@PACKAGE_VERSION_SUBMINOR@,0
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS 0
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "04090000" /* Lang = US English, Charset = ASCII */
+ BEGIN
+ VALUE "ProductName", "GNU libmicrohttpd\0"
+ VALUE "ProductVersion", "@PACKAGE_VERSION@\0"
+ VALUE "FileVersion", "@PACKAGE_VERSION@\0"
+ VALUE "FileDescription", "GNU libmicrohttpd dll for Windows (GCC build)\0"
+ VALUE "InternalName", "libmicrohttpd\0"
+ VALUE "OriginalFilename", "libmicrohttpd.dll\0"
+ VALUE "CompanyName", "Free Software Foundation\0"
+ VALUE "LegalCopyright", "Copyright (C) 2007-2015 Christian Grothoff and project contributors\0"
+ VALUE "Comments", "http://www.gnu.org/software/libmicrohttpd/\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 0 /* US English, ASCII */
+ END
+END
+
diff --git a/src/microhttpd/postprocessor.c b/src/microhttpd/postprocessor.c
new file mode 100644
index 0000000..d371f3d
--- /dev/null
+++ b/src/microhttpd/postprocessor.c
@@ -0,0 +1,1191 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007-2013 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file postprocessor.c
+ * @brief Methods for parsing POST data
+ * @author Christian Grothoff
+ */
+
+#include "internal.h"
+
+/**
+ * Size of on-stack buffer that we use for un-escaping of the value.
+ * We use a pretty small value to be nice to the stack on embedded
+ * systems.
+ */
+#define XBUF_SIZE 512
+
+/**
+ * States in the PP parser's state machine.
+ */
+enum PP_State
+{
+ /* general states */
+ PP_Error,
+ PP_Done,
+ PP_Init,
+ PP_NextBoundary,
+
+ /* url encoding-states */
+ PP_ProcessValue,
+ PP_ExpectNewLine,
+
+ /* post encoding-states */
+ PP_ProcessEntryHeaders,
+ PP_PerformCheckMultipart,
+ PP_ProcessValueToBoundary,
+ PP_PerformCleanup,
+
+ /* nested post-encoding states */
+ PP_Nested_Init,
+ PP_Nested_PerformMarking,
+ PP_Nested_ProcessEntryHeaders,
+ PP_Nested_ProcessValueToBoundary,
+ PP_Nested_PerformCleanup
+
+};
+
+
+enum RN_State
+{
+ /**
+ * No RN-preprocessing in this state.
+ */
+ RN_Inactive = 0,
+
+ /**
+ * If the next character is CR, skip it. Otherwise,
+ * just go inactive.
+ */
+ RN_OptN = 1,
+
+ /**
+ * Expect LFCR (and only LFCR). As always, we also
+ * expect only LF or only CR.
+ */
+ RN_Full = 2,
+
+ /**
+ * Expect either LFCR or '--'LFCR. If '--'LFCR, transition into dash-state
+ * for the main state machine
+ */
+ RN_Dash = 3,
+
+ /**
+ * Got a single dash, expect second dash.
+ */
+ RN_Dash2 = 4
+};
+
+
+/**
+ * Bits for the globally known fields that
+ * should not be deleted when we exit the
+ * nested state.
+ */
+enum NE_State
+{
+ NE_none = 0,
+ NE_content_name = 1,
+ NE_content_type = 2,
+ NE_content_filename = 4,
+ NE_content_transfer_encoding = 8
+};
+
+
+/**
+ * Internal state of the post-processor. Note that the fields
+ * are sorted by type to enable optimal packing by the compiler.
+ */
+struct MHD_PostProcessor
+{
+
+ /**
+ * The connection for which we are doing
+ * POST processing.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Function to call with POST data.
+ */
+ MHD_PostDataIterator ikvi;
+
+ /**
+ * Extra argument to ikvi.
+ */
+ void *cls;
+
+ /**
+ * Encoding as given by the headers of the
+ * connection.
+ */
+ const char *encoding;
+
+ /**
+ * Primary boundary (points into encoding string)
+ */
+ const char *boundary;
+
+ /**
+ * Nested boundary (if we have multipart/mixed encoding).
+ */
+ char *nested_boundary;
+
+ /**
+ * Pointer to the name given in disposition.
+ */
+ char *content_name;
+
+ /**
+ * Pointer to the (current) content type.
+ */
+ char *content_type;
+
+ /**
+ * Pointer to the (current) filename.
+ */
+ char *content_filename;
+
+ /**
+ * Pointer to the (current) encoding.
+ */
+ char *content_transfer_encoding;
+
+ /**
+ * Unprocessed value bytes due to escape
+ * sequences (URL-encoding only).
+ */
+ char xbuf[8];
+
+ /**
+ * Size of our buffer for the key.
+ */
+ size_t buffer_size;
+
+ /**
+ * Current position in the key buffer.
+ */
+ size_t buffer_pos;
+
+ /**
+ * Current position in xbuf.
+ */
+ size_t xbuf_pos;
+
+ /**
+ * Current offset in the value being processed.
+ */
+ uint64_t value_offset;
+
+ /**
+ * strlen(boundary) -- if boundary != NULL.
+ */
+ size_t blen;
+
+ /**
+ * strlen(nested_boundary) -- if nested_boundary != NULL.
+ */
+ size_t nlen;
+
+ /**
+ * Do we have to call the 'ikvi' callback when processing the
+ * multipart post body even if the size of the payload is zero?
+ * Set to #MHD_YES whenever we parse a new multiparty entry header,
+ * and to #MHD_NO the first time we call the 'ikvi' callback.
+ * Used to ensure that we do always call 'ikvi' even if the
+ * payload is empty (but not more than once).
+ */
+ int must_ikvi;
+
+ /**
+ * State of the parser.
+ */
+ enum PP_State state;
+
+ /**
+ * Side-state-machine: skip LRCR (or just LF).
+ * Set to 0 if we are not in skip mode. Set to 2
+ * if a LFCR is expected, set to 1 if a CR should
+ * be skipped if it is the next character.
+ */
+ enum RN_State skip_rn;
+
+ /**
+ * If we are in skip_rn with "dash" mode and
+ * do find 2 dashes, what state do we go into?
+ */
+ enum PP_State dash_state;
+
+ /**
+ * Which headers are global? (used to tell which
+ * headers were only valid for the nested multipart).
+ */
+ enum NE_State have;
+
+};
+
+
+/**
+ * Create a `struct MHD_PostProcessor`.
+ *
+ * A `struct MHD_PostProcessor` can be used to (incrementally) parse
+ * the data portion of a POST request. Note that some buggy browsers
+ * fail to set the encoding type. If you want to support those, you
+ * may have to call #MHD_set_connection_value with the proper encoding
+ * type before creating a post processor (if no supported encoding
+ * type is set, this function will fail).
+ *
+ * @param connection the connection on which the POST is
+ * happening (used to determine the POST format)
+ * @param buffer_size maximum number of bytes to use for
+ * internal buffering (used only for the parsing,
+ * specifically the parsing of the keys). A
+ * tiny value (256-1024) should be sufficient.
+ * Do NOT use a value smaller than 256. For good
+ * performance, use 32 or 64k (i.e. 65536).
+ * @param iter iterator to be called with the parsed data,
+ * Must NOT be NULL.
+ * @param iter_cls first argument to @a iter
+ * @return NULL on error (out of memory, unsupported encoding),
+ * otherwise a PP handle
+ * @ingroup request
+ */
+struct MHD_PostProcessor *
+MHD_create_post_processor (struct MHD_Connection *connection,
+ size_t buffer_size,
+ MHD_PostDataIterator iter, void *iter_cls)
+{
+ struct MHD_PostProcessor *ret;
+ const char *encoding;
+ const char *boundary;
+ size_t blen;
+
+ if ((buffer_size < 256) || (connection == NULL) || (iter == NULL))
+ mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL);
+ encoding = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONTENT_TYPE);
+ if (encoding == NULL)
+ return NULL;
+ boundary = NULL;
+ if (!MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, encoding,
+ strlen (MHD_HTTP_POST_ENCODING_FORM_URLENCODED)))
+ {
+ if (!MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, encoding,
+ strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)))
+ return NULL;
+ boundary =
+ &encoding[strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)];
+ /* Q: should this be "strcasestr"? */
+ boundary = strstr (boundary, "boundary=");
+ if (NULL == boundary)
+ return NULL; /* failed to determine boundary */
+ boundary += strlen ("boundary=");
+ blen = strlen (boundary);
+ if ((blen == 0) || (blen * 2 + 2 > buffer_size))
+ return NULL; /* (will be) out of memory or invalid boundary */
+ if ( (boundary[0] == '"') && (boundary[blen - 1] == '"') )
+ {
+ /* remove enclosing quotes */
+ ++boundary;
+ blen -= 2;
+ }
+ }
+ else
+ blen = 0;
+ buffer_size += 4; /* round up to get nice block sizes despite boundary search */
+
+ /* add +1 to ensure we ALWAYS have a zero-termination at the end */
+ if (NULL == (ret = malloc (sizeof (struct MHD_PostProcessor) + buffer_size + 1)))
+ return NULL;
+ memset (ret, 0, sizeof (struct MHD_PostProcessor) + buffer_size + 1);
+ ret->connection = connection;
+ ret->ikvi = iter;
+ ret->cls = iter_cls;
+ ret->encoding = encoding;
+ ret->buffer_size = buffer_size;
+ ret->state = PP_Init;
+ ret->blen = blen;
+ ret->boundary = boundary;
+ ret->skip_rn = RN_Inactive;
+ return ret;
+}
+
+
+/**
+ * Process url-encoded POST data.
+ *
+ * @param pp post processor context
+ * @param post_data upload data
+ * @param post_data_len number of bytes in @a post_data
+ * @return #MHD_YES on success, #MHD_NO if there was an error processing the data
+ */
+static int
+post_process_urlencoded (struct MHD_PostProcessor *pp,
+ const char *post_data,
+ size_t post_data_len)
+{
+ size_t equals;
+ size_t amper;
+ size_t poff;
+ size_t xoff;
+ size_t delta;
+ int end_of_value_found;
+ char *buf;
+ char xbuf[XBUF_SIZE + 1];
+
+ buf = (char *) &pp[1];
+ poff = 0;
+ while (poff < post_data_len)
+ {
+ switch (pp->state)
+ {
+ case PP_Error:
+ return MHD_NO;
+ case PP_Done:
+ /* did not expect to receive more data */
+ pp->state = PP_Error;
+ return MHD_NO;
+ case PP_Init:
+ equals = 0;
+ while ((equals + poff < post_data_len) &&
+ (post_data[equals + poff] != '='))
+ equals++;
+ if (equals + pp->buffer_pos > pp->buffer_size)
+ {
+ pp->state = PP_Error; /* out of memory */
+ return MHD_NO;
+ }
+ memcpy (&buf[pp->buffer_pos], &post_data[poff], equals);
+ pp->buffer_pos += equals;
+ if (equals + poff == post_data_len)
+ return MHD_YES; /* no '=' yet */
+ buf[pp->buffer_pos] = '\0'; /* 0-terminate key */
+ pp->buffer_pos = 0; /* reset for next key */
+ MHD_unescape_plus (buf);
+ MHD_http_unescape (buf);
+ poff += equals + 1;
+ pp->state = PP_ProcessValue;
+ pp->value_offset = 0;
+ break;
+ case PP_ProcessValue:
+ /* obtain rest of value from previous iteration */
+ memcpy (xbuf, pp->xbuf, pp->xbuf_pos);
+ xoff = pp->xbuf_pos;
+ pp->xbuf_pos = 0;
+
+ /* find last position in input buffer that is part of the value */
+ amper = 0;
+ while ((amper + poff < post_data_len) &&
+ (amper < XBUF_SIZE) &&
+ (post_data[amper + poff] != '&') &&
+ (post_data[amper + poff] != '\n') &&
+ (post_data[amper + poff] != '\r'))
+ amper++;
+ end_of_value_found = ((amper + poff < post_data_len) &&
+ ((post_data[amper + poff] == '&') ||
+ (post_data[amper + poff] == '\n') ||
+ (post_data[amper + poff] == '\r')));
+ /* compute delta, the maximum number of bytes that we will be able to
+ process right now (either amper-limited of xbuf-size limited) */
+ delta = amper;
+ if (delta > XBUF_SIZE - xoff)
+ delta = XBUF_SIZE - xoff;
+
+ /* move input into processing buffer */
+ memcpy (&xbuf[xoff], &post_data[poff], delta);
+ xoff += delta;
+ poff += delta;
+
+ /* find if escape sequence is at the end of the processing buffer;
+ if so, exclude those from processing (reduce delta to point at
+ end of processed region) */
+ delta = xoff;
+ if ((delta > 0) && (xbuf[delta - 1] == '%'))
+ delta--;
+ else if ((delta > 1) && (xbuf[delta - 2] == '%'))
+ delta -= 2;
+
+ /* if we have an incomplete escape sequence, save it to
+ pp->xbuf for later */
+ if (delta < xoff)
+ {
+ memcpy (pp->xbuf, &xbuf[delta], xoff - delta);
+ pp->xbuf_pos = xoff - delta;
+ xoff = delta;
+ }
+
+ /* If we have nothing to do (delta == 0) and
+ not just because the value is empty (are
+ waiting for more data), go for next iteration */
+ if ((xoff == 0) && (poff == post_data_len))
+ continue;
+
+ /* unescape */
+ xbuf[xoff] = '\0'; /* 0-terminate in preparation */
+ MHD_unescape_plus (xbuf);
+ xoff = MHD_http_unescape (xbuf);
+ /* finally: call application! */
+ pp->must_ikvi = MHD_NO;
+ if (MHD_NO == pp->ikvi (pp->cls, MHD_POSTDATA_KIND, (const char *) &pp[1], /* key */
+ NULL, NULL, NULL, xbuf, pp->value_offset,
+ xoff))
+ {
+ pp->state = PP_Error;
+ return MHD_NO;
+ }
+ pp->value_offset += xoff;
+
+ /* are we done with the value? */
+ if (end_of_value_found)
+ {
+ /* we found the end of the value! */
+ if ((post_data[poff] == '\n') || (post_data[poff] == '\r'))
+ {
+ pp->state = PP_ExpectNewLine;
+ }
+ else if (post_data[poff] == '&')
+ {
+ poff++; /* skip '&' */
+ pp->state = PP_Init;
+ }
+ }
+ break;
+ case PP_ExpectNewLine:
+ if ((post_data[poff] == '\n') || (post_data[poff] == '\r'))
+ {
+ poff++;
+ /* we are done, report error if we receive any more... */
+ pp->state = PP_Done;
+ return MHD_YES;
+ }
+ return MHD_NO;
+ default:
+ mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL); /* should never happen! */
+ }
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * If the given line matches the prefix, strdup the
+ * rest of the line into the suffix ptr.
+ *
+ * @param prefix prefix to match
+ * @param line line to match prefix in
+ * @param suffix set to a copy of the rest of the line, starting at the end of the match
+ * @return #MHD_YES if there was a match, #MHD_NO if not
+ */
+static int
+try_match_header (const char *prefix, char *line, char **suffix)
+{
+ if (NULL != *suffix)
+ return MHD_NO;
+ while (*line != 0)
+ {
+ if (MHD_str_equal_caseless_n_ (prefix, line, strlen (prefix)))
+ {
+ *suffix = strdup (&line[strlen (prefix)]);
+ return MHD_YES;
+ }
+ ++line;
+ }
+ return MHD_NO;
+}
+
+
+/**
+ *
+ * @param pp post processor context
+ * @param boundary boundary to look for
+ * @param blen number of bytes in boundary
+ * @param ioffptr set to the end of the boundary if found,
+ * otherwise incremented by one (FIXME: quirky API!)
+ * @param next_state state to which we should advance the post processor
+ * if the boundary is found
+ * @param next_dash_state dash_state to which we should advance the
+ * post processor if the boundary is found
+ * @return #MHD_NO if the boundary is not found, #MHD_YES if we did find it
+ */
+static int
+find_boundary (struct MHD_PostProcessor *pp,
+ const char *boundary,
+ size_t blen,
+ size_t *ioffptr,
+ enum PP_State next_state, enum PP_State next_dash_state)
+{
+ char *buf = (char *) &pp[1];
+ const char *dash;
+
+ if (pp->buffer_pos < 2 + blen)
+ {
+ if (pp->buffer_pos == pp->buffer_size)
+ pp->state = PP_Error; /* out of memory */
+ // ++(*ioffptr);
+ return MHD_NO; /* not enough data */
+ }
+ if ((0 != memcmp ("--", buf, 2)) || (0 != memcmp (&buf[2], boundary, blen)))
+ {
+ if (pp->state != PP_Init)
+ {
+ /* garbage not allowed */
+ pp->state = PP_Error;
+ }
+ else
+ {
+ /* skip over garbage (RFC 2046, 5.1.1) */
+ dash = memchr (buf, '-', pp->buffer_pos);
+ if (NULL == dash)
+ (*ioffptr) += pp->buffer_pos; /* skip entire buffer */
+ else
+ if (dash == buf)
+ (*ioffptr)++; /* at least skip one byte */
+ else
+ (*ioffptr) += dash - buf; /* skip to first possible boundary */
+ }
+ return MHD_NO; /* expected boundary */
+ }
+ /* remove boundary from buffer */
+ (*ioffptr) += 2 + blen;
+ /* next: start with headers */
+ pp->skip_rn = RN_Dash;
+ pp->state = next_state;
+ pp->dash_state = next_dash_state;
+ return MHD_YES;
+}
+
+
+/**
+ * In buf, there maybe an expression '$key="$value"'. If that is the
+ * case, copy a copy of $value to destination.
+ *
+ * If destination is already non-NULL, do nothing.
+ */
+static void
+try_get_value (const char *buf,
+ const char *key,
+ char **destination)
+{
+ const char *spos;
+ const char *bpos;
+ const char *endv;
+ size_t klen;
+ size_t vlen;
+
+ if (NULL != *destination)
+ return;
+ bpos = buf;
+ klen = strlen (key);
+ while (NULL != (spos = strstr (bpos, key)))
+ {
+ if ((spos[klen] != '=') || ((spos != buf) && (spos[-1] != ' ')))
+ {
+ /* no match */
+ bpos = spos + 1;
+ continue;
+ }
+ if (spos[klen + 1] != '"')
+ return; /* not quoted */
+ if (NULL == (endv = strchr (&spos[klen + 2], '\"')))
+ return; /* no end-quote */
+ vlen = endv - spos - klen - 1;
+ *destination = malloc (vlen);
+ if (NULL == *destination)
+ return; /* out of memory */
+ (*destination)[vlen - 1] = '\0';
+ memcpy (*destination, &spos[klen + 2], vlen - 1);
+ return; /* success */
+ }
+}
+
+
+/**
+ * Go over the headers of the part and update
+ * the fields in "pp" according to what we find.
+ * If we are at the end of the headers (as indicated
+ * by an empty line), transition into next_state.
+ *
+ * @param pp post processor context
+ * @param ioffptr set to how many bytes have been
+ * processed
+ * @param next_state state to which the post processor should
+ * be advanced if we find the end of the headers
+ * @return #MHD_YES if we can continue processing,
+ * #MHD_NO on error or if we do not have
+ * enough data yet
+ */
+static int
+process_multipart_headers (struct MHD_PostProcessor *pp,
+ size_t *ioffptr, enum PP_State next_state)
+{
+ char *buf = (char *) &pp[1];
+ size_t newline;
+
+ newline = 0;
+ while ((newline < pp->buffer_pos) &&
+ (buf[newline] != '\r') && (buf[newline] != '\n'))
+ newline++;
+ if (newline == pp->buffer_size)
+ {
+ pp->state = PP_Error;
+ return MHD_NO; /* out of memory */
+ }
+ if (newline == pp->buffer_pos)
+ return MHD_NO; /* will need more data */
+ if (0 == newline)
+ {
+ /* empty line - end of headers */
+ pp->skip_rn = RN_Full;
+ pp->state = next_state;
+ return MHD_YES;
+ }
+ /* got an actual header */
+ if (buf[newline] == '\r')
+ pp->skip_rn = RN_OptN;
+ buf[newline] = '\0';
+ if (MHD_str_equal_caseless_n_ ("Content-disposition: ",
+ buf, strlen ("Content-disposition: ")))
+ {
+ try_get_value (&buf[strlen ("Content-disposition: ")],
+ "name", &pp->content_name);
+ try_get_value (&buf[strlen ("Content-disposition: ")],
+ "filename", &pp->content_filename);
+ }
+ else
+ {
+ try_match_header ("Content-type: ", buf, &pp->content_type);
+ try_match_header ("Content-Transfer-Encoding: ",
+ buf, &pp->content_transfer_encoding);
+ }
+ (*ioffptr) += newline + 1;
+ return MHD_YES;
+}
+
+
+/**
+ * We have the value until we hit the given boundary;
+ * process accordingly.
+ *
+ * @param pp post processor context
+ * @param ioffptr incremented based on the number of bytes processed
+ * @param boundary the boundary to look for
+ * @param blen strlen(boundary)
+ * @param next_state what state to go into after the
+ * boundary was found
+ * @param next_dash_state state to go into if the next
+ * boundary ends with "--"
+ * @return #MHD_YES if we can continue processing,
+ * #MHD_NO on error or if we do not have
+ * enough data yet
+ */
+static int
+process_value_to_boundary (struct MHD_PostProcessor *pp,
+ size_t *ioffptr,
+ const char *boundary,
+ size_t blen,
+ enum PP_State next_state,
+ enum PP_State next_dash_state)
+{
+ char *buf = (char *) &pp[1];
+ size_t newline;
+ const char *r;
+
+ /* all data in buf until the boundary
+ (\r\n--+boundary) is part of the value */
+ newline = 0;
+ while (1)
+ {
+ while (newline + 4 < pp->buffer_pos)
+ {
+ r = memchr (&buf[newline], '\r', pp->buffer_pos - newline - 4);
+ if (NULL == r)
+ {
+ newline = pp->buffer_pos - 4;
+ break;
+ }
+ newline = r - buf;
+ if (0 == memcmp ("\r\n--", &buf[newline], 4))
+ break;
+ newline++;
+ }
+ if (newline + pp->blen + 4 <= pp->buffer_pos)
+ {
+ /* can check boundary */
+ if (0 != memcmp (&buf[newline + 4], boundary, pp->blen))
+ {
+ /* no boundary, "\r\n--" is part of content, skip */
+ newline += 4;
+ continue;
+ }
+ else
+ {
+ /* boundary found, process until newline then
+ skip boundary and go back to init */
+ pp->skip_rn = RN_Dash;
+ pp->state = next_state;
+ pp->dash_state = next_dash_state;
+ (*ioffptr) += pp->blen + 4; /* skip boundary as well */
+ buf[newline] = '\0';
+ break;
+ }
+ }
+ else
+ {
+ /* cannot check for boundary, process content that
+ we have and check again later; except, if we have
+ no content, abort (out of memory) */
+ if ((0 == newline) && (pp->buffer_pos == pp->buffer_size))
+ {
+ pp->state = PP_Error;
+ return MHD_NO;
+ }
+ break;
+ }
+ }
+ /* newline is either at beginning of boundary or
+ at least at the last character that we are sure
+ is not part of the boundary */
+ if ( ( (MHD_YES == pp->must_ikvi) ||
+ (0 != newline) ) &&
+ (MHD_NO == pp->ikvi (pp->cls,
+ MHD_POSTDATA_KIND,
+ pp->content_name,
+ pp->content_filename,
+ pp->content_type,
+ pp->content_transfer_encoding,
+ buf, pp->value_offset, newline)) )
+ {
+ pp->state = PP_Error;
+ return MHD_NO;
+ }
+ pp->must_ikvi = MHD_NO;
+ pp->value_offset += newline;
+ (*ioffptr) += newline;
+ return MHD_YES;
+}
+
+
+/**
+ *
+ * @param pp post processor context
+ */
+static void
+free_unmarked (struct MHD_PostProcessor *pp)
+{
+ if ((NULL != pp->content_name) && (0 == (pp->have & NE_content_name)))
+ {
+ free (pp->content_name);
+ pp->content_name = NULL;
+ }
+ if ((NULL != pp->content_type) && (0 == (pp->have & NE_content_type)))
+ {
+ free (pp->content_type);
+ pp->content_type = NULL;
+ }
+ if ((NULL != pp->content_filename) &&
+ (0 == (pp->have & NE_content_filename)))
+ {
+ free (pp->content_filename);
+ pp->content_filename = NULL;
+ }
+ if ((NULL != pp->content_transfer_encoding) &&
+ (0 == (pp->have & NE_content_transfer_encoding)))
+ {
+ free (pp->content_transfer_encoding);
+ pp->content_transfer_encoding = NULL;
+ }
+}
+
+
+/**
+ * Decode multipart POST data.
+ *
+ * @param pp post processor context
+ * @param post_data data to decode
+ * @param post_data_len number of bytes in @a post_data
+ * @return #MHD_NO on error,
+ */
+static int
+post_process_multipart (struct MHD_PostProcessor *pp,
+ const char *post_data,
+ size_t post_data_len)
+{
+ char *buf;
+ size_t max;
+ size_t ioff;
+ size_t poff;
+ int state_changed;
+
+ buf = (char *) &pp[1];
+ ioff = 0;
+ poff = 0;
+ state_changed = 1;
+ while ((poff < post_data_len) ||
+ ((pp->buffer_pos > 0) && (state_changed != 0)))
+ {
+ /* first, move as much input data
+ as possible to our internal buffer */
+ max = pp->buffer_size - pp->buffer_pos;
+ if (max > post_data_len - poff)
+ max = post_data_len - poff;
+ memcpy (&buf[pp->buffer_pos], &post_data[poff], max);
+ poff += max;
+ pp->buffer_pos += max;
+ if ((max == 0) && (state_changed == 0) && (poff < post_data_len))
+ {
+ pp->state = PP_Error;
+ return MHD_NO; /* out of memory */
+ }
+ state_changed = 0;
+
+ /* first state machine for '\r'-'\n' and '--' handling */
+ switch (pp->skip_rn)
+ {
+ case RN_Inactive:
+ break;
+ case RN_OptN:
+ if (buf[0] == '\n')
+ {
+ ioff++;
+ pp->skip_rn = RN_Inactive;
+ goto AGAIN;
+ }
+ /* fall-through! */
+ case RN_Dash:
+ if (buf[0] == '-')
+ {
+ ioff++;
+ pp->skip_rn = RN_Dash2;
+ goto AGAIN;
+ }
+ pp->skip_rn = RN_Full;
+ /* fall-through! */
+ case RN_Full:
+ if (buf[0] == '\r')
+ {
+ if ((pp->buffer_pos > 1) && (buf[1] == '\n'))
+ {
+ pp->skip_rn = RN_Inactive;
+ ioff += 2;
+ }
+ else
+ {
+ pp->skip_rn = RN_OptN;
+ ioff++;
+ }
+ goto AGAIN;
+ }
+ if (buf[0] == '\n')
+ {
+ ioff++;
+ pp->skip_rn = RN_Inactive;
+ goto AGAIN;
+ }
+ pp->skip_rn = RN_Inactive;
+ pp->state = PP_Error;
+ return MHD_NO; /* no '\r\n' */
+ case RN_Dash2:
+ if (buf[0] == '-')
+ {
+ ioff++;
+ pp->skip_rn = RN_Full;
+ pp->state = pp->dash_state;
+ goto AGAIN;
+ }
+ pp->state = PP_Error;
+ break;
+ }
+
+ /* main state engine */
+ switch (pp->state)
+ {
+ case PP_Error:
+ return MHD_NO;
+ case PP_Done:
+ /* did not expect to receive more data */
+ pp->state = PP_Error;
+ return MHD_NO;
+ case PP_Init:
+ /**
+ * Per RFC2046 5.1.1 NOTE TO IMPLEMENTORS, consume anything
+ * prior to the first multipart boundary:
+ *
+ * > There appears to be room for additional information prior
+ * > to the first boundary delimiter line and following the
+ * > final boundary delimiter line. These areas should
+ * > generally be left blank, and implementations must ignore
+ * > anything that appears before the first boundary delimiter
+ * > line or after the last one.
+ */
+ (void) find_boundary (pp,
+ pp->boundary,
+ pp->blen,
+ &ioff,
+ PP_ProcessEntryHeaders, PP_Done);
+ break;
+ case PP_NextBoundary:
+ if (MHD_NO == find_boundary (pp,
+ pp->boundary,
+ pp->blen,
+ &ioff,
+ PP_ProcessEntryHeaders, PP_Done))
+ {
+ if (pp->state == PP_Error)
+ return MHD_NO;
+ goto END;
+ }
+ break;
+ case PP_ProcessEntryHeaders:
+ pp->must_ikvi = MHD_YES;
+ if (MHD_NO ==
+ process_multipart_headers (pp, &ioff, PP_PerformCheckMultipart))
+ {
+ if (pp->state == PP_Error)
+ return MHD_NO;
+ else
+ goto END;
+ }
+ state_changed = 1;
+ break;
+ case PP_PerformCheckMultipart:
+ if ((pp->content_type != NULL) &&
+ (MHD_str_equal_caseless_n_ (pp->content_type,
+ "multipart/mixed",
+ strlen ("multipart/mixed"))))
+ {
+ pp->nested_boundary = strstr (pp->content_type, "boundary=");
+ if (pp->nested_boundary == NULL)
+ {
+ pp->state = PP_Error;
+ return MHD_NO;
+ }
+ pp->nested_boundary =
+ strdup (&pp->nested_boundary[strlen ("boundary=")]);
+ if (pp->nested_boundary == NULL)
+ {
+ /* out of memory */
+ pp->state = PP_Error;
+ return MHD_NO;
+ }
+ /* free old content type, we will need that field
+ for the content type of the nested elements */
+ free (pp->content_type);
+ pp->content_type = NULL;
+ pp->nlen = strlen (pp->nested_boundary);
+ pp->state = PP_Nested_Init;
+ state_changed = 1;
+ break;
+ }
+ pp->state = PP_ProcessValueToBoundary;
+ pp->value_offset = 0;
+ state_changed = 1;
+ break;
+ case PP_ProcessValueToBoundary:
+ if (MHD_NO == process_value_to_boundary (pp,
+ &ioff,
+ pp->boundary,
+ pp->blen,
+ PP_PerformCleanup,
+ PP_Done))
+ {
+ if (pp->state == PP_Error)
+ return MHD_NO;
+ break;
+ }
+ break;
+ case PP_PerformCleanup:
+ /* clean up state of one multipart form-data element! */
+ pp->have = NE_none;
+ free_unmarked (pp);
+ if (pp->nested_boundary != NULL)
+ {
+ free (pp->nested_boundary);
+ pp->nested_boundary = NULL;
+ }
+ pp->state = PP_ProcessEntryHeaders;
+ state_changed = 1;
+ break;
+ case PP_Nested_Init:
+ if (pp->nested_boundary == NULL)
+ {
+ pp->state = PP_Error;
+ return MHD_NO;
+ }
+ if (MHD_NO == find_boundary (pp,
+ pp->nested_boundary,
+ pp->nlen,
+ &ioff,
+ PP_Nested_PerformMarking,
+ PP_NextBoundary /* or PP_Error? */ ))
+ {
+ if (pp->state == PP_Error)
+ return MHD_NO;
+ goto END;
+ }
+ break;
+ case PP_Nested_PerformMarking:
+ /* remember what headers were given
+ globally */
+ pp->have = NE_none;
+ if (pp->content_name != NULL)
+ pp->have |= NE_content_name;
+ if (pp->content_type != NULL)
+ pp->have |= NE_content_type;
+ if (pp->content_filename != NULL)
+ pp->have |= NE_content_filename;
+ if (pp->content_transfer_encoding != NULL)
+ pp->have |= NE_content_transfer_encoding;
+ pp->state = PP_Nested_ProcessEntryHeaders;
+ state_changed = 1;
+ break;
+ case PP_Nested_ProcessEntryHeaders:
+ pp->value_offset = 0;
+ if (MHD_NO ==
+ process_multipart_headers (pp, &ioff,
+ PP_Nested_ProcessValueToBoundary))
+ {
+ if (pp->state == PP_Error)
+ return MHD_NO;
+ else
+ goto END;
+ }
+ state_changed = 1;
+ break;
+ case PP_Nested_ProcessValueToBoundary:
+ if (MHD_NO == process_value_to_boundary (pp,
+ &ioff,
+ pp->nested_boundary,
+ pp->nlen,
+ PP_Nested_PerformCleanup,
+ PP_NextBoundary))
+ {
+ if (pp->state == PP_Error)
+ return MHD_NO;
+ break;
+ }
+ break;
+ case PP_Nested_PerformCleanup:
+ free_unmarked (pp);
+ pp->state = PP_Nested_ProcessEntryHeaders;
+ state_changed = 1;
+ break;
+ default:
+ mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL); /* should never happen! */
+ }
+ AGAIN:
+ if (ioff > 0)
+ {
+ memmove (buf, &buf[ioff], pp->buffer_pos - ioff);
+ pp->buffer_pos -= ioff;
+ ioff = 0;
+ state_changed = 1;
+ }
+ }
+END:
+ if (ioff != 0)
+ {
+ memmove (buf, &buf[ioff], pp->buffer_pos - ioff);
+ pp->buffer_pos -= ioff;
+ }
+ if (poff < post_data_len)
+ {
+ pp->state = PP_Error;
+ return MHD_NO; /* serious error */
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Parse and process POST data. Call this function when POST data is
+ * available (usually during an #MHD_AccessHandlerCallback) with the
+ * "upload_data" and "upload_data_size". Whenever possible, this will
+ * then cause calls to the #MHD_PostDataIterator.
+ *
+ * @param pp the post processor
+ * @param post_data @a post_data_len bytes of POST data
+ * @param post_data_len length of @a post_data
+ * @return #MHD_YES on success, #MHD_NO on error
+ * (out-of-memory, iterator aborted, parse error)
+ * @ingroup request
+ */
+int
+MHD_post_process (struct MHD_PostProcessor *pp,
+ const char *post_data, size_t post_data_len)
+{
+ if (0 == post_data_len)
+ return MHD_YES;
+ if (NULL == pp)
+ return MHD_NO;
+ if (MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, pp->encoding,
+ strlen(MHD_HTTP_POST_ENCODING_FORM_URLENCODED)))
+ return post_process_urlencoded (pp, post_data, post_data_len);
+ if (MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, pp->encoding,
+ strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)))
+ return post_process_multipart (pp, post_data, post_data_len);
+ /* this should never be reached */
+ return MHD_NO;
+}
+
+
+/**
+ * Release PostProcessor resources.
+ *
+ * @param pp post processor context to destroy
+ * @return #MHD_YES if processing completed nicely,
+ * #MHD_NO if there were spurious characters / formatting
+ * problems; it is common to ignore the return
+ * value of this function
+ * @ingroup request
+ */
+int
+MHD_destroy_post_processor (struct MHD_PostProcessor *pp)
+{
+ int ret;
+
+ if (NULL == pp)
+ return MHD_YES;
+ if (PP_ProcessValue == pp->state)
+ {
+ /* key without terminated value left at the end of the
+ buffer; fake receiving a termination character to
+ ensure it is also processed */
+ post_process_urlencoded (pp, "\n", 1);
+ }
+ /* These internal strings need cleaning up since
+ the post-processing may have been interrupted
+ at any stage */
+ if ((pp->xbuf_pos > 0) ||
+ ( (pp->state != PP_Done) &&
+ (pp->state != PP_ExpectNewLine)))
+ ret = MHD_NO;
+ else
+ ret = MHD_YES;
+ pp->have = NE_none;
+ free_unmarked (pp);
+ if (pp->nested_boundary != NULL)
+ free (pp->nested_boundary);
+ free (pp);
+ return ret;
+}
+
+/* end of postprocessor.c */
diff --git a/src/microhttpd/reason_phrase.c b/src/microhttpd/reason_phrase.c
new file mode 100644
index 0000000..3afe8af
--- /dev/null
+++ b/src/microhttpd/reason_phrase.c
@@ -0,0 +1,160 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2011 Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+/**
+ * @file reason_phrase.c
+ * @brief Tables of the string response phrases
+ * @author Elliot Glaysher
+ * @author Christian Grothoff (minor code clean up)
+ */
+#include "platform.h"
+#include "reason_phrase.h"
+
+#ifndef NULL
+#define NULL (void*)0
+#endif
+
+static const char *invalid_hundred[] = { NULL };
+
+static const char *const one_hundred[] = {
+ "Continue",
+ "Switching Protocols",
+ "Processing"
+};
+
+static const char *const two_hundred[] = {
+ "OK",
+ "Created",
+ "Accepted",
+ "Non-Authoritative Information",
+ "No Content",
+ "Reset Content",
+ "Partial Content",
+ "Multi Status"
+};
+
+static const char *const three_hundred[] = {
+ "Multiple Choices",
+ "Moved Permanently",
+ "Moved Temporarily",
+ "See Other",
+ "Not Modified",
+ "Use Proxy",
+ "Switch Proxy",
+ "Temporary Redirect"
+};
+
+static const char *const four_hundred[] = {
+ "Bad Request",
+ "Unauthorized",
+ "Payment Required",
+ "Forbidden",
+ "Not Found",
+ "Method Not Allowed",
+ "Not Acceptable",
+ "Proxy Authentication Required",
+ "Request Time-out",
+ "Conflict",
+ "Gone",
+ "Length Required",
+ "Precondition Failed",
+ "Request Entity Too Large",
+ "Request-URI Too Large",
+ "Unsupported Media Type",
+ "Requested Range Not Satisfiable",
+ "Expectation Failed",
+ "Unknown",
+ "Unknown",
+ "Unknown", /* 420 */
+ "Unknown",
+ "Unprocessable Entity",
+ "Locked",
+ "Failed Dependency",
+ "Unordered Collection",
+ "Upgrade Required",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unknown", /* 430 */
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unknown", /* 435 */
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unknown", /* 440 */
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "No Response",
+ "Unknown", /* 445 */
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Retry With",
+ "Blocked by Windows Parental Controls", /* 450 */
+ "Unavailable For Legal Reasons"
+};
+
+static const char *const five_hundred[] = {
+ "Internal Server Error",
+ "Not Implemented",
+ "Bad Gateway",
+ "Service Unavailable",
+ "Gateway Time-out",
+ "HTTP Version not supported",
+ "Variant Also Negotiates",
+ "Insufficient Storage",
+ "Unknown",
+ "Bandwidth Limit Exceeded",
+ "Not Extended"
+};
+
+
+struct MHD_Reason_Block
+{
+ unsigned int max;
+ const char *const*data;
+};
+
+#define BLOCK(m) { (sizeof(m) / sizeof(char*)), m }
+
+static const struct MHD_Reason_Block reasons[] = {
+ BLOCK (invalid_hundred),
+ BLOCK (one_hundred),
+ BLOCK (two_hundred),
+ BLOCK (three_hundred),
+ BLOCK (four_hundred),
+ BLOCK (five_hundred),
+};
+
+
+const char *
+MHD_get_reason_phrase_for (unsigned int code)
+{
+ if ( (code >= 100) &&
+ (code < 600) &&
+ (reasons[code / 100].max > (code % 100)) )
+ return reasons[code / 100].data[code % 100];
+ return "Unknown";
+}
diff --git a/src/microhttpd/reason_phrase.h b/src/microhttpd/reason_phrase.h
new file mode 100644
index 0000000..e442232
--- /dev/null
+++ b/src/microhttpd/reason_phrase.h
@@ -0,0 +1,37 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Lymba
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file reason_phrase.c
+ * @brief Tables of the string response phrases
+ * @author Elliot Glaysher
+ */
+
+#ifndef REASON_PHRASE_H
+#define REASON_PHRASE_H
+
+/**
+ * Returns the string reason phrase for a response code.
+ *
+ * If we don't have a string for a status code, we give the first
+ * message in that status code class.
+ */
+const char *MHD_get_reason_phrase_for (unsigned int code);
+
+#endif
diff --git a/src/microhttpd/response.c b/src/microhttpd/response.c
new file mode 100644
index 0000000..47a439e
--- /dev/null
+++ b/src/microhttpd/response.c
@@ -0,0 +1,525 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009, 2010 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file response.c
+ * @brief Methods for managing response objects
+ * @author Daniel Pittman
+ * @author Christian Grothoff
+ */
+
+#include "internal.h"
+#include "response.h"
+
+#if defined(_WIN32) && defined(MHD_W32_MUTEX_)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif /* _WIN32 && MHD_W32_MUTEX_ */
+#if defined(_WIN32)
+#include <io.h> /* for lseek(), read() */
+#endif /* _WIN32 */
+
+
+/**
+ * Add a header or footer line to the response.
+ *
+ * @param response response to add a header to
+ * @param kind header or footer
+ * @param header the header to add
+ * @param content value to add
+ * @return #MHD_NO on error (i.e. invalid header or content format).
+ */
+static int
+add_response_entry (struct MHD_Response *response,
+ enum MHD_ValueKind kind,
+ const char *header,
+ const char *content)
+{
+ struct MHD_HTTP_Header *hdr;
+
+ if ( (NULL == response) ||
+ (NULL == header) ||
+ (NULL == content) ||
+ (0 == strlen (header)) ||
+ (0 == strlen (content)) ||
+ (NULL != strchr (header, '\t')) ||
+ (NULL != strchr (header, '\r')) ||
+ (NULL != strchr (header, '\n')) ||
+ (NULL != strchr (content, '\t')) ||
+ (NULL != strchr (content, '\r')) ||
+ (NULL != strchr (content, '\n')) )
+ return MHD_NO;
+ if (NULL == (hdr = malloc (sizeof (struct MHD_HTTP_Header))))
+ return MHD_NO;
+ if (NULL == (hdr->header = strdup (header)))
+ {
+ free (hdr);
+ return MHD_NO;
+ }
+ if (NULL == (hdr->value = strdup (content)))
+ {
+ free (hdr->header);
+ free (hdr);
+ return MHD_NO;
+ }
+ hdr->kind = kind;
+ hdr->next = response->first_header;
+ response->first_header = hdr;
+ return MHD_YES;
+}
+
+
+/**
+ * Add a header line to the response.
+ *
+ * @param response response to add a header to
+ * @param header the header to add
+ * @param content value to add
+ * @return #MHD_NO on error (i.e. invalid header or content format).
+ * @ingroup response
+ */
+int
+MHD_add_response_header (struct MHD_Response *response,
+ const char *header, const char *content)
+{
+ return add_response_entry (response,
+ MHD_HEADER_KIND,
+ header,
+ content);
+}
+
+
+/**
+ * Add a footer line to the response.
+ *
+ * @param response response to remove a header from
+ * @param footer the footer to delete
+ * @param content value to delete
+ * @return #MHD_NO on error (i.e. invalid footer or content format).
+ * @ingroup response
+ */
+int
+MHD_add_response_footer (struct MHD_Response *response,
+ const char *footer, const char *content)
+{
+ return add_response_entry (response,
+ MHD_FOOTER_KIND,
+ footer,
+ content);
+}
+
+
+/**
+ * Delete a header (or footer) line from the response.
+ *
+ * @param response response to remove a header from
+ * @param header the header to delete
+ * @param content value to delete
+ * @return #MHD_NO on error (no such header known)
+ * @ingroup response
+ */
+int
+MHD_del_response_header (struct MHD_Response *response,
+ const char *header,
+ const char *content)
+{
+ struct MHD_HTTP_Header *pos;
+ struct MHD_HTTP_Header *prev;
+
+ if ( (NULL == header) || (NULL == content) )
+ return MHD_NO;
+ prev = NULL;
+ pos = response->first_header;
+ while (pos != NULL)
+ {
+ if ((0 == strcmp (header, pos->header)) &&
+ (0 == strcmp (content, pos->value)))
+ {
+ free (pos->header);
+ free (pos->value);
+ if (NULL == prev)
+ response->first_header = pos->next;
+ else
+ prev->next = pos->next;
+ free (pos);
+ return MHD_YES;
+ }
+ prev = pos;
+ pos = pos->next;
+ }
+ return MHD_NO;
+}
+
+
+/**
+ * Get all of the headers (and footers) added to a response.
+ *
+ * @param response response to query
+ * @param iterator callback to call on each header;
+ * maybe NULL (then just count headers)
+ * @param iterator_cls extra argument to @a iterator
+ * @return number of entries iterated over
+ * @ingroup response
+ */
+int
+MHD_get_response_headers (struct MHD_Response *response,
+ MHD_KeyValueIterator iterator, void *iterator_cls)
+{
+ struct MHD_HTTP_Header *pos;
+ int numHeaders = 0;
+
+ for (pos = response->first_header; NULL != pos; pos = pos->next)
+ {
+ numHeaders++;
+ if ((NULL != iterator) &&
+ (MHD_YES != iterator (iterator_cls,
+ pos->kind, pos->header, pos->value)))
+ break;
+ }
+ return numHeaders;
+}
+
+
+/**
+ * Get a particular header (or footer) from the response.
+ *
+ * @param response response to query
+ * @param key which header to get
+ * @return NULL if header does not exist
+ * @ingroup response
+ */
+const char *
+MHD_get_response_header (struct MHD_Response *response,
+ const char *key)
+{
+ struct MHD_HTTP_Header *pos;
+
+ if (NULL == key)
+ return NULL;
+ for (pos = response->first_header; NULL != pos; pos = pos->next)
+ if (0 == strcmp (key, pos->header))
+ return pos->value;
+ return NULL;
+}
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the data portion of the response, #MHD_SIZE_UNKNOWN for unknown
+ * @param block_size preferred block size for querying crc (advisory only,
+ * MHD may still call @a crc using smaller chunks); this
+ * is essentially the buffer size used for IO, clients
+ * should pick a value that is appropriate for IO and
+ * memory performance requirements
+ * @param crc callback to use to obtain response data
+ * @param crc_cls extra argument to @a crc
+ * @param crfc callback to call to free @a crc_cls resources
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @ingroup response
+ */
+struct MHD_Response *
+MHD_create_response_from_callback (uint64_t size,
+ size_t block_size,
+ MHD_ContentReaderCallback crc,
+ void *crc_cls,
+ MHD_ContentReaderFreeCallback crfc)
+{
+ struct MHD_Response *response;
+
+ if ((NULL == crc) || (0 == block_size))
+ return NULL;
+ if (NULL == (response = malloc (sizeof (struct MHD_Response) + block_size)))
+ return NULL;
+ memset (response, 0, sizeof (struct MHD_Response));
+ response->fd = -1;
+ response->data = (void *) &response[1];
+ response->data_buffer_size = block_size;
+ if (MHD_YES != MHD_mutex_create_ (&response->mutex))
+ {
+ free (response);
+ return NULL;
+ }
+ response->crc = crc;
+ response->crfc = crfc;
+ response->crc_cls = crc_cls;
+ response->reference_count = 1;
+ response->total_size = size;
+ return response;
+}
+
+
+/**
+ * Set special flags and options for a response.
+ *
+ * @param response the response to modify
+ * @param flags to set for the response
+ * @param ... #MHD_RO_END terminated list of options
+ * @return #MHD_YES on success, #MHD_NO on error
+ */
+int
+MHD_set_response_options (struct MHD_Response *response,
+ enum MHD_ResponseFlags flags,
+ ...)
+{
+ va_list ap;
+ int ret;
+ enum MHD_ResponseOptions ro;
+
+ ret = MHD_YES;
+ response->flags = flags;
+ va_start (ap, flags);
+ while (MHD_RO_END != (ro = va_arg (ap, enum MHD_ResponseOptions)))
+ {
+ switch (ro)
+ {
+ default:
+ ret = MHD_NO;
+ break;
+ }
+ }
+ va_end (ap);
+ return ret;
+}
+
+
+/**
+ * Given a file descriptor, read data from the file
+ * to generate the response.
+ *
+ * @param cls pointer to the response
+ * @param pos offset in the file to access
+ * @param buf where to write the data
+ * @param max number of bytes to write at most
+ * @return number of bytes written
+ */
+static ssize_t
+file_reader (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ struct MHD_Response *response = cls;
+ ssize_t n;
+
+ (void) lseek (response->fd, pos + response->fd_off, SEEK_SET);
+ n = read (response->fd, buf, max);
+ if (0 == n)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ if (n < 0)
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ return n;
+}
+
+
+/**
+ * Destroy file reader context. Closes the file
+ * descriptor.
+ *
+ * @param cls pointer to file descriptor
+ */
+static void
+free_callback (void *cls)
+{
+ struct MHD_Response *response = cls;
+
+ (void) close (response->fd);
+ response->fd = -1;
+}
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the data portion of the response
+ * @param fd file descriptor referring to a file on disk with the
+ * data; will be closed when response is destroyed;
+ * fd should be in 'blocking' mode
+ * @param offset offset to start reading from in the file;
+ * Be careful! `off_t` may have been compiled to be a
+ * 64-bit variable for MHD, in which case your application
+ * also has to be compiled using the same options! Read
+ * the MHD manual for more details.
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @ingroup response
+ */
+struct MHD_Response *
+MHD_create_response_from_fd_at_offset (size_t size,
+ int fd,
+ off_t offset)
+{
+ struct MHD_Response *response;
+
+ response = MHD_create_response_from_callback (size,
+ 4 * 1024,
+ &file_reader,
+ NULL,
+ &free_callback);
+ if (NULL == response)
+ return NULL;
+ response->fd = fd;
+ response->fd_off = offset;
+ response->crc_cls = response;
+ return response;
+}
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the data portion of the response
+ * @param fd file descriptor referring to a file on disk with the data
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @ingroup response
+ */
+struct MHD_Response *
+MHD_create_response_from_fd (size_t size,
+ int fd)
+{
+ return MHD_create_response_from_fd_at_offset (size, fd, 0);
+}
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the @a data portion of the response
+ * @param data the data itself
+ * @param must_free libmicrohttpd should free data when done
+ * @param must_copy libmicrohttpd must make a copy of @a data
+ * right away, the data maybe released anytime after
+ * this call returns
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @deprecated use #MHD_create_response_from_buffer instead
+ * @ingroup response
+ */
+struct MHD_Response *
+MHD_create_response_from_data (size_t size,
+ void *data, int must_free, int must_copy)
+{
+ struct MHD_Response *response;
+ void *tmp;
+
+ if ((NULL == data) && (size > 0))
+ return NULL;
+ if (NULL == (response = malloc (sizeof (struct MHD_Response))))
+ return NULL;
+ memset (response, 0, sizeof (struct MHD_Response));
+ response->fd = -1;
+ if (MHD_YES != MHD_mutex_create_ (&response->mutex))
+ {
+ free (response);
+ return NULL;
+ }
+ if ((must_copy) && (size > 0))
+ {
+ if (NULL == (tmp = malloc (size)))
+ {
+ (void) MHD_mutex_destroy_ (&response->mutex);
+ free (response);
+ return NULL;
+ }
+ memcpy (tmp, data, size);
+ must_free = MHD_YES;
+ data = tmp;
+ }
+ response->crc = NULL;
+ response->crfc = must_free ? &free : NULL;
+ response->crc_cls = must_free ? data : NULL;
+ response->reference_count = 1;
+ response->total_size = size;
+ response->data = data;
+ response->data_size = size;
+ return response;
+}
+
+
+/**
+ * Create a response object. The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param size size of the data portion of the response
+ * @param buffer size bytes containing the response's data portion
+ * @param mode flags for buffer management
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ * @ingroup response
+ */
+struct MHD_Response *
+MHD_create_response_from_buffer (size_t size,
+ void *buffer,
+ enum MHD_ResponseMemoryMode mode)
+{
+ return MHD_create_response_from_data (size,
+ buffer,
+ mode == MHD_RESPMEM_MUST_FREE,
+ mode == MHD_RESPMEM_MUST_COPY);
+}
+
+
+/**
+ * Destroy a response object and associated resources. Note that
+ * libmicrohttpd may keep some of the resources around if the response
+ * is still in the queue for some clients, so the memory may not
+ * necessarily be freed immediatley.
+ *
+ * @param response response to destroy
+ * @ingroup response
+ */
+void
+MHD_destroy_response (struct MHD_Response *response)
+{
+ struct MHD_HTTP_Header *pos;
+
+ if (NULL == response)
+ return;
+ (void) MHD_mutex_lock_ (&response->mutex);
+ if (0 != --(response->reference_count))
+ {
+ (void) MHD_mutex_unlock_ (&response->mutex);
+ return;
+ }
+ (void) MHD_mutex_unlock_ (&response->mutex);
+ (void) MHD_mutex_destroy_ (&response->mutex);
+ if (response->crfc != NULL)
+ response->crfc (response->crc_cls);
+ while (NULL != response->first_header)
+ {
+ pos = response->first_header;
+ response->first_header = pos->next;
+ free (pos->header);
+ free (pos->value);
+ free (pos);
+ }
+ free (response);
+}
+
+
+void
+MHD_increment_response_rc (struct MHD_Response *response)
+{
+ (void) MHD_mutex_lock_ (&response->mutex);
+ (response->reference_count)++;
+ (void) MHD_mutex_unlock_ (&response->mutex);
+}
+
+
+/* end of response.c */
diff --git a/src/microhttpd/response.h b/src/microhttpd/response.h
new file mode 100644
index 0000000..5785ec9
--- /dev/null
+++ b/src/microhttpd/response.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Daniel Pittman and Christian Grothoff
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+/**
+ * @file response.h
+ * @brief Methods for managing response objects
+ * @author Daniel Pittman
+ * @author Christian Grothoff
+ */
+
+#ifndef RESPONSE_H
+#define RESPONSE_H
+
+/**
+ * Increment response RC. Should this be part of the
+ * public API?
+ */
+void
+MHD_increment_response_rc (struct MHD_Response *response);
+
+
+#endif
diff --git a/src/microhttpd/test_daemon.c b/src/microhttpd/test_daemon.c
new file mode 100644
index 0000000..13450ba
--- /dev/null
+++ b/src/microhttpd/test_daemon.c
@@ -0,0 +1,167 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_daemon.c
+ * @brief Testcase for libmicrohttpd starts and stops
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+
+static int
+testStartError ()
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_DEBUG, 0, NULL, NULL, NULL, NULL);
+ if (d != NULL)
+ return 1;
+ return 0;
+}
+
+static int
+apc_nothing (void *cls, const struct sockaddr *addr, socklen_t addrlen)
+{
+ return MHD_NO;
+}
+
+static int
+apc_all (void *cls, const struct sockaddr *addr, socklen_t addrlen)
+{
+ return MHD_YES;
+}
+
+static int
+ahc_nothing (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ return MHD_NO;
+}
+
+static int
+testStartStop ()
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080,
+ &apc_nothing,
+ NULL, &ahc_nothing, NULL, MHD_OPTION_END);
+ if (d == NULL)
+ return 2;
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testExternalRun ()
+{
+ struct MHD_Daemon *d;
+ fd_set rs;
+ MHD_socket maxfd;
+ int i;
+
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1081,
+ &apc_all, NULL, &ahc_nothing, NULL, MHD_OPTION_END);
+
+ if (d == NULL)
+ return 4;
+ i = 0;
+ while (i < 15)
+ {
+ maxfd = 0;
+ FD_ZERO (&rs);
+ if (MHD_YES != MHD_get_fdset (d, &rs, &rs, &rs, &maxfd))
+ {
+ MHD_stop_daemon (d);
+ return 256;
+ }
+ if (MHD_run (d) == MHD_NO)
+ {
+ MHD_stop_daemon (d);
+ return 8;
+ }
+ i++;
+ }
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testThread ()
+{
+ struct MHD_Daemon *d;
+ d = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_SELECT_INTERNALLY,
+ 1082,
+ &apc_all, NULL, &ahc_nothing, NULL, MHD_OPTION_END);
+
+ if (d == NULL)
+ return 16;
+ if (MHD_run (d) != MHD_NO)
+ return 32;
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithread ()
+{
+ struct MHD_Daemon *d;
+ d = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_THREAD_PER_CONNECTION,
+ 1083,
+ &apc_all, NULL, &ahc_nothing, NULL, MHD_OPTION_END);
+
+ if (d == NULL)
+ return 64;
+ if (MHD_run (d) != MHD_NO)
+ return 128;
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ int errorCount = 0;
+ errorCount += testStartError ();
+ errorCount += testStartStop ();
+ errorCount += testExternalRun ();
+ errorCount += testThread ();
+ errorCount += testMultithread ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/microhttpd/test_postprocessor.c b/src/microhttpd/test_postprocessor.c
new file mode 100644
index 0000000..66bab81
--- /dev/null
+++ b/src/microhttpd/test_postprocessor.c
@@ -0,0 +1,349 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007,2013 Christian Grothoff
+
+ libmicrohttpd 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 3, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_postprocessor.c
+ * @brief Testcase for postprocessor
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include "internal.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+/**
+ * Array of values that the value checker "wants".
+ * Each series of checks should be terminated by
+ * five NULL-entries.
+ */
+const char *want[] = {
+#define URL_DATA "abc=def&x=5"
+#define URL_START 0
+ "abc", NULL, NULL, NULL, "def",
+ "x", NULL, NULL, NULL, "5",
+#define URL_END (URL_START + 10)
+ NULL, NULL, NULL, NULL, NULL,
+#define FORM_DATA "--AaB03x\r\ncontent-disposition: form-data; name=\"field1\"\r\n\r\nJoe Blow\r\n--AaB03x\r\ncontent-disposition: form-data; name=\"pics\"; filename=\"file1.txt\"\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: binary\r\n\r\nfiledata\r\n--AaB03x--\r\n"
+#define FORM_START (URL_END + 5)
+ "field1", NULL, NULL, NULL, "Joe Blow",
+ "pics", "file1.txt", "text/plain", "binary", "filedata",
+#define FORM_END (FORM_START + 10)
+ NULL, NULL, NULL, NULL, NULL,
+#define FORM_NESTED_DATA "--AaB03x\r\ncontent-disposition: form-data; name=\"field1\"\r\n\r\nJane Blow\r\n--AaB03x\r\ncontent-disposition: form-data; name=\"pics\"\r\nContent-type: multipart/mixed, boundary=BbC04y\r\n\r\n--BbC04y\r\nContent-disposition: attachment; filename=\"file1.txt\"\r\nContent-Type: text/plain\r\n\r\nfiledata1\r\n--BbC04y\r\nContent-disposition: attachment; filename=\"file2.gif\"\r\nContent-type: image/gif\r\nContent-Transfer-Encoding: binary\r\n\r\nfiledata2\r\n--BbC04y--\r\n--AaB03x--"
+#define FORM_NESTED_START (FORM_END + 5)
+ "field1", NULL, NULL, NULL, "Jane Blow",
+ "pics", "file1.txt", "text/plain", NULL, "filedata1",
+ "pics", "file2.gif", "image/gif", "binary", "filedata2",
+#define FORM_NESTED_END (FORM_NESTED_START + 15)
+ NULL, NULL, NULL, NULL, NULL,
+#define URL_EMPTY_VALUE_DATA "key1=value1&key2=&key3="
+#define URL_EMPTY_VALUE_START (FORM_NESTED_END + 5)
+ "key1", NULL, NULL, NULL, "value1",
+ "key2", NULL, NULL, NULL, "",
+ "key3", NULL, NULL, NULL, "",
+#define URL_EMPTY_VALUE_END (URL_EMPTY_VALUE_START + 15)
+ NULL, NULL, NULL, NULL, NULL
+};
+
+static int
+mismatch (const char *a, const char *b)
+{
+ if (a == b)
+ return 0;
+ if ((a == NULL) || (b == NULL))
+ return 1;
+ return 0 != strcmp (a, b);
+}
+
+static int
+value_checker (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *data, uint64_t off, size_t size)
+{
+ int *want_off = cls;
+ int idx = *want_off;
+
+#if 0
+ fprintf (stderr,
+ "VC: `%s' `%s' `%s' `%s' `%.*s'\n",
+ key, filename, content_type, transfer_encoding,
+ (int) size,
+ data);
+#endif
+ if ( (0 != off) && (0 == size) )
+ return MHD_YES;
+ if ((idx < 0) ||
+ (want[idx] == NULL) ||
+ (0 != strcmp (key, want[idx])) ||
+ (mismatch (filename, want[idx + 1])) ||
+ (mismatch (content_type, want[idx + 2])) ||
+ (mismatch (transfer_encoding, want[idx + 3])) ||
+ (0 != memcmp (data, &want[idx + 4][off], size)))
+ {
+ *want_off = -1;
+ return MHD_NO;
+ }
+ if (off + size == strlen (want[idx + 4]))
+ *want_off = idx + 5;
+ return MHD_YES;
+
+}
+
+
+static int
+test_urlencoding ()
+{
+ struct MHD_Connection connection;
+ struct MHD_HTTP_Header header;
+ struct MHD_PostProcessor *pp;
+ unsigned int want_off = URL_START;
+ int i;
+ int delta;
+ size_t size;
+
+ memset (&connection, 0, sizeof (struct MHD_Connection));
+ memset (&header, 0, sizeof (struct MHD_HTTP_Header));
+ connection.headers_received = &header;
+ header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
+ header.value = MHD_HTTP_POST_ENCODING_FORM_URLENCODED;
+ header.kind = MHD_HEADER_KIND;
+ pp = MHD_create_post_processor (&connection,
+ 1024, &value_checker, &want_off);
+ i = 0;
+ size = strlen (URL_DATA);
+ while (i < size)
+ {
+ delta = 1 + MHD_random_ () % (size - i);
+ MHD_post_process (pp, &URL_DATA[i], delta);
+ i += delta;
+ }
+ MHD_destroy_post_processor (pp);
+ if (want_off != URL_END)
+ return 1;
+ return 0;
+}
+
+
+static int
+test_multipart_garbage ()
+{
+ struct MHD_Connection connection;
+ struct MHD_HTTP_Header header;
+ struct MHD_PostProcessor *pp;
+ unsigned int want_off;
+ size_t size = strlen (FORM_DATA);
+ size_t splitpoint;
+ char xdata[size + 3];
+
+ /* fill in evil garbage at the beginning */
+ xdata[0] = '-';
+ xdata[1] = 'x';
+ xdata[2] = '\r';
+ memcpy (&xdata[3], FORM_DATA, size);
+ size += 3;
+
+ size = strlen (FORM_DATA);
+ for (splitpoint = 1; splitpoint < size; splitpoint++)
+ {
+ want_off = FORM_START;
+ memset (&connection, 0, sizeof (struct MHD_Connection));
+ memset (&header, 0, sizeof (struct MHD_HTTP_Header));
+ connection.headers_received = &header;
+ header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
+ header.value =
+ MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x";
+ header.kind = MHD_HEADER_KIND;
+ pp = MHD_create_post_processor (&connection,
+ 1024, &value_checker, &want_off);
+ MHD_post_process (pp, xdata, splitpoint);
+ MHD_post_process (pp, &xdata[splitpoint], size - splitpoint);
+ MHD_destroy_post_processor (pp);
+ if (want_off != FORM_END)
+ return (int) splitpoint;
+ }
+ return 0;
+}
+
+
+static int
+test_multipart_splits ()
+{
+ struct MHD_Connection connection;
+ struct MHD_HTTP_Header header;
+ struct MHD_PostProcessor *pp;
+ unsigned int want_off;
+ size_t size;
+ size_t splitpoint;
+
+ size = strlen (FORM_DATA);
+ for (splitpoint = 1; splitpoint < size; splitpoint++)
+ {
+ want_off = FORM_START;
+ memset (&connection, 0, sizeof (struct MHD_Connection));
+ memset (&header, 0, sizeof (struct MHD_HTTP_Header));
+ connection.headers_received = &header;
+ header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
+ header.value =
+ MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x";
+ header.kind = MHD_HEADER_KIND;
+ pp = MHD_create_post_processor (&connection,
+ 1024, &value_checker, &want_off);
+ MHD_post_process (pp, FORM_DATA, splitpoint);
+ MHD_post_process (pp, &FORM_DATA[splitpoint], size - splitpoint);
+ MHD_destroy_post_processor (pp);
+ if (want_off != FORM_END)
+ return (int) splitpoint;
+ }
+ return 0;
+}
+
+
+static int
+test_multipart ()
+{
+ struct MHD_Connection connection;
+ struct MHD_HTTP_Header header;
+ struct MHD_PostProcessor *pp;
+ unsigned int want_off = FORM_START;
+ int i;
+ int delta;
+ size_t size;
+
+ memset (&connection, 0, sizeof (struct MHD_Connection));
+ memset (&header, 0, sizeof (struct MHD_HTTP_Header));
+ connection.headers_received = &header;
+ header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
+ header.value =
+ MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x";
+ header.kind = MHD_HEADER_KIND;
+ pp = MHD_create_post_processor (&connection,
+ 1024, &value_checker, &want_off);
+ i = 0;
+ size = strlen (FORM_DATA);
+ while (i < size)
+ {
+ delta = 1 + MHD_random_ () % (size - i);
+ MHD_post_process (pp, &FORM_DATA[i], delta);
+ i += delta;
+ }
+ MHD_destroy_post_processor (pp);
+ if (want_off != FORM_END)
+ return 2;
+ return 0;
+}
+
+
+static int
+test_nested_multipart ()
+{
+ struct MHD_Connection connection;
+ struct MHD_HTTP_Header header;
+ struct MHD_PostProcessor *pp;
+ unsigned int want_off = FORM_NESTED_START;
+ int i;
+ int delta;
+ size_t size;
+
+ memset (&connection, 0, sizeof (struct MHD_Connection));
+ memset (&header, 0, sizeof (struct MHD_HTTP_Header));
+ connection.headers_received = &header;
+ header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
+ header.value =
+ MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x";
+ header.kind = MHD_HEADER_KIND;
+ pp = MHD_create_post_processor (&connection,
+ 1024, &value_checker, &want_off);
+ i = 0;
+ size = strlen (FORM_NESTED_DATA);
+ while (i < size)
+ {
+ delta = 1 + MHD_random_ () % (size - i);
+ MHD_post_process (pp, &FORM_NESTED_DATA[i], delta);
+ i += delta;
+ }
+ MHD_destroy_post_processor (pp);
+ if (want_off != FORM_NESTED_END)
+ return 4;
+ return 0;
+}
+
+
+static int
+test_empty_value ()
+{
+ struct MHD_Connection connection;
+ struct MHD_HTTP_Header header;
+ struct MHD_PostProcessor *pp;
+ unsigned int want_off = URL_EMPTY_VALUE_START;
+ int i;
+ int delta;
+ size_t size;
+
+ memset (&connection, 0, sizeof (struct MHD_Connection));
+ memset (&header, 0, sizeof (struct MHD_HTTP_Header));
+ connection.headers_received = &header;
+ header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
+ header.value = MHD_HTTP_POST_ENCODING_FORM_URLENCODED;
+ header.kind = MHD_HEADER_KIND;
+ pp = MHD_create_post_processor (&connection,
+ 1024, &value_checker, &want_off);
+ i = 0;
+ size = strlen (URL_EMPTY_VALUE_DATA);
+ while (i < size)
+ {
+ delta = 1 + MHD_random_ () % (size - i);
+ MHD_post_process (pp, &URL_EMPTY_VALUE_DATA[i], delta);
+ i += delta;
+ }
+ MHD_destroy_post_processor (pp);
+ if (want_off != URL_EMPTY_VALUE_END)
+ return 8;
+ return 0;
+}
+
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ errorCount += test_multipart_splits ();
+ errorCount += test_multipart_garbage ();
+ errorCount += test_urlencoding ();
+ errorCount += test_multipart ();
+ errorCount += test_nested_multipart ();
+ errorCount += test_empty_value ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/microhttpd/test_postprocessor_amp.c b/src/microhttpd/test_postprocessor_amp.c
new file mode 100644
index 0000000..73f72f9
--- /dev/null
+++ b/src/microhttpd/test_postprocessor_amp.c
@@ -0,0 +1,47 @@
+#include "platform.h"
+#include "microhttpd.h"
+#include "internal.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+
+int check_post(void *cls, enum MHD_ValueKind kind, const char* key,
+ const char* filename, const char* content_type,
+ const char* content_encoding, const char* data,
+ uint64_t off, size_t size)
+{
+ if ((0 != strcmp(key, "a")) && (0 != strcmp(key, "b")))
+ {
+ printf("ERROR: got unexpected '%s'\n", key);
+ }
+
+ return MHD_YES;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ struct MHD_Connection connection;
+ struct MHD_HTTP_Header header;
+ struct MHD_PostProcessor *pp;
+
+ memset (&connection, 0, sizeof (struct MHD_Connection));
+ memset (&header, 0, sizeof (struct MHD_HTTP_Header));
+ connection.headers_received = &header;
+ header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
+ header.value = MHD_HTTP_POST_ENCODING_FORM_URLENCODED;
+ header.kind = MHD_HEADER_KIND;
+
+ pp = MHD_create_post_processor (&connection,
+ 4096, &check_post, NULL);
+
+ const char* post = "a=xx+xx+xxx+xxxxx+xxxx+xxxxxxxx+xxx+xxxxxx+xxx+xxx+xxxxxxx+xxxxx%0A+++++++xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%0A+++++++--%3E%0A++++++++++++++%3Cxxxxx+xxxxx%3D%22xxx%25%22%3E%0A+++++++++++%3Cxx%3E%0A+++++++++++++++%3Cxx+xxxxxxx%3D%22x%22+xxxxx%3D%22xxxxx%22%3E%0A+++++++++++++++++++%3Cxxxxx+xxxxx%3D%22xxx%25%22%3E%0A+++++++++++++++++++++++%3Cxx%3E%0A+++++++++++++++++++++++++++%3Cxx+xxxxx%3D%22xxxx%22%3E%0A+++++++++++++++++++++++++++++++%3Cx+xxxxx%3D%22xxxx-xxxxx%3Axxxxx%22%3Exxxxx%3A%3C%2Fx%3E%0A%0A+++++++++++++++++++++++++++++++%3Cx+xxxxx%3D%22xxxx-xxxxx%3Axxxxx%22%3Exxx%3A%3C%2Fx%3E%0A%0A+++++++++++++++++++++++++++++++%3Cx+xxxxx%3D%22xxxx-xxxxx%3Axxxxx%3B+xxxx-xxxxxx%3A+xxxx%3B%22%3Exxxxx+xxxxx%3A%3C%2Fx%3E%0A+++++++++++++++++++++++++++%3C%2Fxx%3E%0A+++++++++++++++++++++++%3C%2Fxx%3E%0A+++++++++++++++++++%3C%2Fxxxxx%3E%0A+++++++++++++++%3C%2Fxx%3E%0A+++++++++++++++%3Cxx+xxxxx%3D%22xxxx-xxxxx%3A+xxxxx%3B+xxxxx%3A+xxxx%22%3E%26xxxxx%3B+%3Cxxxx%0A+++++++++++++++++++++++xxxxx%3D%22xxxxxxxxxxxxxxx%22%3Exxxx.xx%3C%2Fxxxx%3E%0A+++++++++++++++%3C%2Fxx%3E%0A+++++++++++%3C%2Fxx%3E%0A++++++++++++++++++++++++++%3Cxx%3E%0A+++++++++++++++++++%3Cxx+xxxxx%3D%22xxxx-xxxxx%3A+xxxxx%3B+xxxxx%3A+xxxx%22%3E%26xxxxx%3B+%3Cxxxx%0A+++++++++++++++++++++++++++xxxxx%3D%22xxxxxxxxxxxxxxx%22%3Exxx.xx%3C%2Fxxxx%3E%0A+++++++++++++++++++%3C%2Fxx%3E%0A+++++++++++++++%3C%2Fxx%3E%0A++++++++++++++++++++++%3Cxx%3E%0A+++++++++++++++%3Cxx+xxxxx%3D%22xxxx-xxxxx%3A+xxxxx%3Bxxxx-xxxxxx%3A+xxxx%3B+xxxxx%3A+xxxx%22%3E%26xxxxx%3B+%3Cxxxx%0A+++++++++++++++++++++++xxxxx%3D%22xxxxxxxxxxxxxxx%22%3Exxxx.xx%3C%2Fxxxx%3E%3C%2Fxx%3E%0A+++++++++++%3C%2Fxx%3E%0A+++++++%3C%2Fxxxxx%3E%0A+++++++%3C%21--%0A+++++++xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%0A+++++++xxx+xx+xxxxx+xxxxxxx+xxxxxxx%0A+++++++xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%0A+++++++--%3E%0A+++%3C%2Fxxx%3E%0A%0A%0A%0A+++%3Cxxx+xxxxx%3D%22xxxxxxxxx%22+xx%3D%22xxxxxxxxx%22%3E%3C%2Fxxx%3E%0A%0A+++%3Cxxx+xx%3D%22xxxx%22+xxxxx%3D%22xxxx%22%3E%0A+++++++%3Cxxxxx+xxxxx%3D%22xxxxxxxxx%22%3E%0A+++++++++++%3Cxx%3E%0A+++++++++++++++%3Cxx+xxxxxxx%3D%22x%22+xx%3D%22xxxxxxxxxxxxx%22+xxxxx%3D%22xxxxxxxxxxxxx%22%3E%0A+++++++++++++++++++%3Cxxx+xx%3D%22xxxxxx%22%3E%3C%2Fxxx%3E%0A+++++++++++++++%3C%2Fxx%3E%0A+++++++++++%3C%2Fxx%3E%0A+++++++++++%3Cxx%3E%0A+++++++++++++++%3Cxx+xx%3D%22xxxxxxxxxxxxxxxxx%22+xxxxx%3D%22xxxxxxxxxxxxxxxxx%22%3E%3C%2Fxx%3E%0A+++++++++++++++%3Cxx+xx%3D%22xxxxxxxxxxxxxx%22+xxxxx%3D%22xxxxxxxxxxxxxx%22%3E%0A+++++++++++++++++++%3Cxxx+xx%3D%22xxxxxxx%22%3E%3C%2Fxxx%3E%0A+++++++++++++++%3C%2Fxx%3E%0A+++++++++++%3C%2Fxx%3E%0A+++++++++++%3Cxx%3E%0A+++++++++++++++%3Cxx+xxxxxxx%3D%22x%22+xx%3D%22xxxxxxxxxxxxx%22+xxxxx%3D%22xxxxxxxxxxxxx%22%3E%0A+++++++++++++++++++%3Cxxx+xx%3D%22xxxxxx%22%3E%3C%2Fxxx%3E%0A+++++++++++++++%3C%2Fxx%3E%0A+++++++++++%3C%2Fxx%3E%0A+++++++%3C%2Fxxxxx%3E%0A+++%3C%2Fxxx%3E%0A%3C%2Fxxx%3E%0A%0A%3Cxxx+xx%3D%22xxxxxx%22%3E%3C%2Fxxx%3E%0A%0A%3C%2Fxxxx%3E%0A%3C%2Fxxxx%3E+&b=value";
+
+ MHD_post_process (pp, post, strlen(post));
+ MHD_destroy_post_processor (pp);
+
+ return 0;
+}
+
diff --git a/src/microhttpd/test_postprocessor_large.c b/src/microhttpd/test_postprocessor_large.c
new file mode 100644
index 0000000..0af4ab9
--- /dev/null
+++ b/src/microhttpd/test_postprocessor_large.c
@@ -0,0 +1,105 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_postprocessor_large.c
+ * @brief Testcase with very large input for postprocessor
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include "internal.h"
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+static int
+value_checker (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *data, uint64_t off, size_t size)
+{
+ unsigned int *pos = cls;
+#if 0
+ fprintf (stderr,
+ "VC: %llu %u `%s' `%s' `%s' `%s' `%.*s'\n",
+ off, size,
+ key, filename, content_type, transfer_encoding, size, data);
+#endif
+ if (size == 0)
+ return MHD_YES;
+ *pos += size;
+ return MHD_YES;
+
+}
+
+
+static int
+test_simple_large ()
+{
+ struct MHD_Connection connection;
+ struct MHD_HTTP_Header header;
+ struct MHD_PostProcessor *pp;
+ int i;
+ int delta;
+ size_t size;
+ char data[102400];
+ unsigned int pos;
+
+ pos = 0;
+ memset (data, 'A', sizeof (data));
+ memcpy (data, "key=", 4);
+ data[sizeof (data) - 1] = '\0';
+ memset (&connection, 0, sizeof (struct MHD_Connection));
+ memset (&header, 0, sizeof (struct MHD_HTTP_Header));
+ connection.headers_received = &header;
+ header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
+ header.value = MHD_HTTP_POST_ENCODING_FORM_URLENCODED;
+ header.kind = MHD_HEADER_KIND;
+ pp = MHD_create_post_processor (&connection, 1024, &value_checker, &pos);
+ i = 0;
+ size = strlen (data);
+ while (i < size)
+ {
+ delta = 1 + MHD_random_ () % (size - i);
+ MHD_post_process (pp, &data[i], delta);
+ i += delta;
+ }
+ MHD_destroy_post_processor (pp);
+ if (pos != sizeof (data) - 5) /* minus 0-termination and 'key=' */
+ return 1;
+ return 0;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ errorCount += test_simple_large ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/microhttpd/tsearch.c b/src/microhttpd/tsearch.c
new file mode 100644
index 0000000..8889f32
--- /dev/null
+++ b/src/microhttpd/tsearch.c
@@ -0,0 +1,125 @@
+/* $NetBSD: tsearch.c,v 1.3 1999/09/16 11:45:37 lukem Exp $ */
+
+/*
+ * Tree search generalized from Knuth (6.2.2) Algorithm T just like
+ * the AT&T man page says.
+ *
+ * The node_t structure is for internal use only, lint doesn't grok it.
+ *
+ * Written by reading the System V Interface Definition, not the code.
+ *
+ * Totally public domain.
+ */
+
+#ifndef _MSC_FULL_VER
+#include <sys/cdefs.h>
+#endif //! _MSC_FULL_VER
+#define _SEARCH_PRIVATE
+#include "tsearch.h"
+#include <stdlib.h>
+
+/* find or insert datum into search tree */
+void *
+tsearch(vkey, vrootp, compar)
+ const void *vkey; /* key to be located */
+ void **vrootp; /* address of tree root */
+ int (*compar)(const void *, const void *);
+{
+ node_t *q;
+ node_t **rootp = (node_t **)vrootp;
+
+ if (rootp == NULL)
+ return NULL;
+
+ while (*rootp != NULL) { /* Knuth's T1: */
+ int r;
+
+ if ((r = (*compar)(vkey, (*rootp)->key)) == 0) /* T2: */
+ return *rootp; /* we found it! */
+
+ rootp = (r < 0) ?
+ &(*rootp)->llink : /* T3: follow left branch */
+ &(*rootp)->rlink; /* T4: follow right branch */
+ }
+
+ q = malloc(sizeof(node_t)); /* T5: key not found */
+ if (q != 0) { /* make new node */
+ *rootp = q; /* link new node to old */
+ /* LINTED const castaway ok */
+ q->key = (void *)vkey; /* initialize new node */
+ q->llink = q->rlink = NULL;
+ }
+ return q;
+}
+
+/* find a node, or return 0 */
+void *
+tfind(vkey, vrootp, compar)
+ const void *vkey; /* key to be found */
+ void * const *vrootp; /* address of the tree root */
+ int (*compar)(const void *, const void *);
+{
+ node_t **rootp = (node_t **)vrootp;
+
+ if (rootp == NULL)
+ return NULL;
+
+ while (*rootp != NULL) { /* T1: */
+ int r;
+
+ if ((r = (*compar)(vkey, (*rootp)->key)) == 0) /* T2: */
+ return *rootp; /* key found */
+ rootp = (r < 0) ?
+ &(*rootp)->llink : /* T3: follow left branch */
+ &(*rootp)->rlink; /* T4: follow right branch */
+ }
+ return NULL;
+}
+
+/*
+ * delete node with given key
+ *
+ * vkey: key to be deleted
+ * vrootp: address of the root of the tree
+ * compar: function to carry out node comparisons
+ */
+void *
+tdelete(const void * __restrict vkey, void ** __restrict vrootp,
+ int (*compar)(const void *, const void *))
+{
+ node_t **rootp = (node_t **)vrootp;
+ node_t *p, *q, *r;
+ int cmp;
+
+ if (rootp == NULL || (p = *rootp) == NULL)
+ return NULL;
+
+ while ((cmp = (*compar)(vkey, (*rootp)->key)) != 0) {
+ p = *rootp;
+ rootp = (cmp < 0) ?
+ &(*rootp)->llink : /* follow llink branch */
+ &(*rootp)->rlink; /* follow rlink branch */
+ if (*rootp == NULL)
+ return NULL; /* key not found */
+ }
+ r = (*rootp)->rlink; /* D1: */
+ if ((q = (*rootp)->llink) == NULL) /* Left NULL? */
+ q = r;
+ else if (r != NULL) { /* Right link is NULL? */
+ if (r->llink == NULL) { /* D2: Find successor */
+ r->llink = q;
+ q = r;
+ } else { /* D3: Find NULL link */
+ for (q = r->llink; q->llink != NULL; q = r->llink)
+ r = q;
+ r->llink = q->rlink;
+ q->llink = (*rootp)->llink;
+ q->rlink = (*rootp)->rlink;
+ }
+ }
+ free(*rootp); /* D4: Free node */
+ *rootp = q; /* link parent to new node */
+ return p;
+}
+
+/* end of tsearch.c */
diff --git a/src/microhttpd/tsearch.h b/src/microhttpd/tsearch.h
new file mode 100644
index 0000000..ec9c92d
--- /dev/null
+++ b/src/microhttpd/tsearch.h
@@ -0,0 +1,50 @@
+/*-
+ * Written by J.T. Conklin <jtc@netbsd.org>
+ * Public domain.
+ *
+ * $NetBSD: search.h,v 1.12 1999/02/22 10:34:28 christos Exp $
+ * $FreeBSD: release/9.0.0/include/search.h 105250 2002-10-16 14:29:23Z robert $
+ */
+
+#ifndef _SEARCH_H_
+#define _SEARCH_H_
+
+#ifndef _MSC_FULL_VER
+#include <sys/cdefs.h>
+#endif /* _MSC_FULL_VER */
+#if !defined(__BEGIN_DECLS) || !defined(__END_DECLS)
+#if defined(__cplusplus)
+#define __BEGIN_DECLS extern "C" {
+#define __END_DECLS };
+#else /* !__cplusplus */
+#define __BEGIN_DECLS
+#define __END_DECLS
+#endif /* !__cplusplus */
+#endif /* !__BEGIN_DECLS || !__END_DECLS */
+#include <sys/types.h>
+
+typedef enum {
+ preorder,
+ postorder,
+ endorder,
+ leaf
+} VISIT;
+
+#ifdef _SEARCH_PRIVATE
+typedef struct node {
+ char *key;
+ struct node *llink, *rlink;
+} node_t;
+#endif
+
+__BEGIN_DECLS
+void *tdelete(const void * __restrict, void ** __restrict,
+ int (*)(const void *, const void *));
+void *tfind(const void *, void * const *,
+ int (*)(const void *, const void *));
+void *tsearch(const void *, void **, int (*)(const void *, const void *));
+void twalk(const void *, void (*)(const void *, VISIT, int));
+void tdestroy(void *, void (*)(void *));
+__END_DECLS
+
+#endif /* !_SEARCH_H_ */
diff --git a/src/microspdy/Makefile.am b/src/microspdy/Makefile.am
new file mode 100644
index 0000000..7fb2c37
--- /dev/null
+++ b/src/microspdy/Makefile.am
@@ -0,0 +1,40 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/microspdy
+
+AM_CFLAGS = $(HIDDEN_VISIBILITY_CFLAGS)
+
+
+lib_LTLIBRARIES = \
+ libmicrospdy.la
+
+libmicrospdy_la_SOURCES = \
+ io.h io.c \
+ io_openssl.h io_openssl.c \
+ io_raw.h io_raw.c \
+ structures.h structures.c \
+ internal.h internal.c \
+ daemon.h daemon.c \
+ stream.h stream.c \
+ compression.h compression.c \
+ session.h session.c \
+ applicationlayer.c applicationlayer.h \
+ alstructures.c alstructures.h
+libmicrospdy_la_LIBADD = \
+ $(SPDY_LIBDEPS)
+
+libmicrospdy_la_LDFLAGS = \
+ $(SPDY_LIB_LDFLAGS)
+
+libmicrospdy_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(SPDY_LIB_CPPFLAGS) \
+ -DBUILDING_MHD_LIB=1
+
+libmicrospdy_la_CFLAGS = -Wextra \
+ $(AM_CFLAGS) $(SPDY_LIB_CFLAGS)
+
+
+if USE_COVERAGE
+ AM_CFLAGS += --coverage
+endif
diff --git a/src/microspdy/Makefile.in b/src/microspdy/Makefile.in
new file mode 100644
index 0000000..d1a4588
--- /dev/null
+++ b/src/microspdy/Makefile.in
@@ -0,0 +1,820 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@USE_COVERAGE_TRUE@am__append_1 = --coverage
+subdir = src/microspdy
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libmicrospdy_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_libmicrospdy_la_OBJECTS = libmicrospdy_la-io.lo \
+ libmicrospdy_la-io_openssl.lo libmicrospdy_la-io_raw.lo \
+ libmicrospdy_la-structures.lo libmicrospdy_la-internal.lo \
+ libmicrospdy_la-daemon.lo libmicrospdy_la-stream.lo \
+ libmicrospdy_la-compression.lo libmicrospdy_la-session.lo \
+ libmicrospdy_la-applicationlayer.lo \
+ libmicrospdy_la-alstructures.lo
+libmicrospdy_la_OBJECTS = $(am_libmicrospdy_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libmicrospdy_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(libmicrospdy_la_CFLAGS) $(CFLAGS) $(libmicrospdy_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmicrospdy_la_SOURCES)
+DIST_SOURCES = $(libmicrospdy_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/microspdy
+
+AM_CFLAGS = $(HIDDEN_VISIBILITY_CFLAGS) $(am__append_1)
+lib_LTLIBRARIES = \
+ libmicrospdy.la
+
+libmicrospdy_la_SOURCES = \
+ io.h io.c \
+ io_openssl.h io_openssl.c \
+ io_raw.h io_raw.c \
+ structures.h structures.c \
+ internal.h internal.c \
+ daemon.h daemon.c \
+ stream.h stream.c \
+ compression.h compression.c \
+ session.h session.c \
+ applicationlayer.c applicationlayer.h \
+ alstructures.c alstructures.h
+
+libmicrospdy_la_LIBADD = \
+ $(SPDY_LIBDEPS)
+
+libmicrospdy_la_LDFLAGS = \
+ $(SPDY_LIB_LDFLAGS)
+
+libmicrospdy_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(SPDY_LIB_CPPFLAGS) \
+ -DBUILDING_MHD_LIB=1
+
+libmicrospdy_la_CFLAGS = -Wextra \
+ $(AM_CFLAGS) $(SPDY_LIB_CFLAGS)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/microspdy/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/microspdy/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmicrospdy.la: $(libmicrospdy_la_OBJECTS) $(libmicrospdy_la_DEPENDENCIES) $(EXTRA_libmicrospdy_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libmicrospdy_la_LINK) -rpath $(libdir) $(libmicrospdy_la_OBJECTS) $(libmicrospdy_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-alstructures.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-applicationlayer.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-compression.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-daemon.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-internal.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-io.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-io_openssl.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-io_raw.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-session.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-stream.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmicrospdy_la-structures.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+libmicrospdy_la-io.lo: io.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-io.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-io.Tpo -c -o libmicrospdy_la-io.lo `test -f 'io.c' || echo '$(srcdir)/'`io.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-io.Tpo $(DEPDIR)/libmicrospdy_la-io.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='io.c' object='libmicrospdy_la-io.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-io.lo `test -f 'io.c' || echo '$(srcdir)/'`io.c
+
+libmicrospdy_la-io_openssl.lo: io_openssl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-io_openssl.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-io_openssl.Tpo -c -o libmicrospdy_la-io_openssl.lo `test -f 'io_openssl.c' || echo '$(srcdir)/'`io_openssl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-io_openssl.Tpo $(DEPDIR)/libmicrospdy_la-io_openssl.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='io_openssl.c' object='libmicrospdy_la-io_openssl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-io_openssl.lo `test -f 'io_openssl.c' || echo '$(srcdir)/'`io_openssl.c
+
+libmicrospdy_la-io_raw.lo: io_raw.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-io_raw.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-io_raw.Tpo -c -o libmicrospdy_la-io_raw.lo `test -f 'io_raw.c' || echo '$(srcdir)/'`io_raw.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-io_raw.Tpo $(DEPDIR)/libmicrospdy_la-io_raw.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='io_raw.c' object='libmicrospdy_la-io_raw.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-io_raw.lo `test -f 'io_raw.c' || echo '$(srcdir)/'`io_raw.c
+
+libmicrospdy_la-structures.lo: structures.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-structures.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-structures.Tpo -c -o libmicrospdy_la-structures.lo `test -f 'structures.c' || echo '$(srcdir)/'`structures.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-structures.Tpo $(DEPDIR)/libmicrospdy_la-structures.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='structures.c' object='libmicrospdy_la-structures.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-structures.lo `test -f 'structures.c' || echo '$(srcdir)/'`structures.c
+
+libmicrospdy_la-internal.lo: internal.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-internal.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-internal.Tpo -c -o libmicrospdy_la-internal.lo `test -f 'internal.c' || echo '$(srcdir)/'`internal.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-internal.Tpo $(DEPDIR)/libmicrospdy_la-internal.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='internal.c' object='libmicrospdy_la-internal.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-internal.lo `test -f 'internal.c' || echo '$(srcdir)/'`internal.c
+
+libmicrospdy_la-daemon.lo: daemon.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-daemon.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-daemon.Tpo -c -o libmicrospdy_la-daemon.lo `test -f 'daemon.c' || echo '$(srcdir)/'`daemon.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-daemon.Tpo $(DEPDIR)/libmicrospdy_la-daemon.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='daemon.c' object='libmicrospdy_la-daemon.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-daemon.lo `test -f 'daemon.c' || echo '$(srcdir)/'`daemon.c
+
+libmicrospdy_la-stream.lo: stream.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-stream.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-stream.Tpo -c -o libmicrospdy_la-stream.lo `test -f 'stream.c' || echo '$(srcdir)/'`stream.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-stream.Tpo $(DEPDIR)/libmicrospdy_la-stream.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stream.c' object='libmicrospdy_la-stream.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-stream.lo `test -f 'stream.c' || echo '$(srcdir)/'`stream.c
+
+libmicrospdy_la-compression.lo: compression.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-compression.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-compression.Tpo -c -o libmicrospdy_la-compression.lo `test -f 'compression.c' || echo '$(srcdir)/'`compression.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-compression.Tpo $(DEPDIR)/libmicrospdy_la-compression.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='compression.c' object='libmicrospdy_la-compression.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-compression.lo `test -f 'compression.c' || echo '$(srcdir)/'`compression.c
+
+libmicrospdy_la-session.lo: session.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-session.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-session.Tpo -c -o libmicrospdy_la-session.lo `test -f 'session.c' || echo '$(srcdir)/'`session.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-session.Tpo $(DEPDIR)/libmicrospdy_la-session.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='session.c' object='libmicrospdy_la-session.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-session.lo `test -f 'session.c' || echo '$(srcdir)/'`session.c
+
+libmicrospdy_la-applicationlayer.lo: applicationlayer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-applicationlayer.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-applicationlayer.Tpo -c -o libmicrospdy_la-applicationlayer.lo `test -f 'applicationlayer.c' || echo '$(srcdir)/'`applicationlayer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-applicationlayer.Tpo $(DEPDIR)/libmicrospdy_la-applicationlayer.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='applicationlayer.c' object='libmicrospdy_la-applicationlayer.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-applicationlayer.lo `test -f 'applicationlayer.c' || echo '$(srcdir)/'`applicationlayer.c
+
+libmicrospdy_la-alstructures.lo: alstructures.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -MT libmicrospdy_la-alstructures.lo -MD -MP -MF $(DEPDIR)/libmicrospdy_la-alstructures.Tpo -c -o libmicrospdy_la-alstructures.lo `test -f 'alstructures.c' || echo '$(srcdir)/'`alstructures.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmicrospdy_la-alstructures.Tpo $(DEPDIR)/libmicrospdy_la-alstructures.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='alstructures.c' object='libmicrospdy_la-alstructures.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrospdy_la_CPPFLAGS) $(CPPFLAGS) $(libmicrospdy_la_CFLAGS) $(CFLAGS) -c -o libmicrospdy_la-alstructures.lo `test -f 'alstructures.c' || echo '$(srcdir)/'`alstructures.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/microspdy/alstructures.c b/src/microspdy/alstructures.c
new file mode 100644
index 0000000..b588a1c
--- /dev/null
+++ b/src/microspdy/alstructures.c
@@ -0,0 +1,41 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file alstructures.c
+ * @brief structures only for the application layer
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "alstructures.h"
+#include "internal.h"
+
+void
+SPDY_destroy_request (struct SPDY_Request *request)
+{
+ if(NULL == request)
+ {
+ SPDYF_DEBUG("request is NULL");
+ return;
+ }
+ //strings into request struct are just references to strings in
+ //headers, so no need to free them twice
+ SPDY_name_value_destroy(request->headers);
+ free(request);
+}
diff --git a/src/microspdy/alstructures.h b/src/microspdy/alstructures.h
new file mode 100644
index 0000000..2eb36e8
--- /dev/null
+++ b/src/microspdy/alstructures.h
@@ -0,0 +1,79 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file alstructures.h
+ * @brief structures only for the application layer
+ * @author Andrey Uzunov
+ */
+
+#ifndef ALSTRUCTURES_H
+#define ALSTRUCTURES_H
+
+#include "platform.h"
+
+
+/**
+ * Represents a SPDY request.
+ */
+struct SPDY_Request
+{
+ /**
+ * SPDY stream in whose context the request was received
+ */
+ struct SPDYF_Stream *stream;
+
+ /**
+ * Other HTTP headers from the request
+ */
+ struct SPDY_NameValue *headers;
+
+ /**
+ * HTTP method
+ */
+ char *method;
+
+ /**
+ * HTTP path
+ */
+ char *path;
+
+ /**
+ * HTTP version just like in HTTP request/response:
+ * "HTTP/1.0" or "HTTP/1.1" currently
+ */
+ char *version;
+
+ /**
+ * called host as in HTTP
+ */
+ char *host;
+
+ /**
+ * The scheme used ("http" or "https")
+ */
+ char *scheme;
+
+ /**
+ * Extra field to be used by the user with set/get func for whatever
+ * purpose he wants.
+ */
+ void *user_cls;
+};
+
+#endif
diff --git a/src/microspdy/applicationlayer.c b/src/microspdy/applicationlayer.c
new file mode 100644
index 0000000..bf16b78
--- /dev/null
+++ b/src/microspdy/applicationlayer.c
@@ -0,0 +1,748 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file applicationlayer.c
+ * @brief SPDY application or HTTP layer
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "applicationlayer.h"
+#include "alstructures.h"
+#include "structures.h"
+#include "internal.h"
+#include "daemon.h"
+#include "session.h"
+
+
+void
+spdy_callback_response_done(void *cls,
+ struct SPDY_Response *response,
+ struct SPDY_Request *request,
+ enum SPDY_RESPONSE_RESULT status,
+ bool streamopened)
+{
+ (void)cls;
+ (void)status;
+ (void)streamopened;
+
+ SPDY_destroy_request(request);
+ SPDY_destroy_response(response);
+}
+
+
+/**
+ * Callback called when new stream is created. It extracts the info from
+ * the stream to create (HTTP) request object and pass it to the client.
+ *
+ * @param cls
+ * @param stream the new SPDY stream
+ * @return SPDY_YES on success, SPDY_NO on memomry error
+ */
+static int
+spdy_handler_new_stream (void *cls,
+ struct SPDYF_Stream * stream)
+{
+ (void)cls;
+ unsigned int i;
+ char *method = NULL;
+ char *path = NULL;
+ char *version = NULL;
+ char *host = NULL;
+ char *scheme = NULL;
+ struct SPDY_Request * request = NULL;
+ struct SPDY_NameValue * headers = NULL;
+ struct SPDY_NameValue * iterator = stream->headers;
+ struct SPDY_Daemon *daemon;
+
+ daemon = stream->session->daemon;
+
+ //if the user doesn't care, ignore it
+ if(NULL == daemon->new_request_cb)
+ return SPDY_YES;
+
+ if(NULL == (headers=SPDY_name_value_create()))
+ goto free_and_fail;
+
+ if(NULL==(request = malloc(sizeof(struct SPDY_Request))))
+ goto free_and_fail;
+
+ memset(request, 0, sizeof(struct SPDY_Request));
+ request->stream = stream;
+
+ /* extract the mandatory fields from stream->headers' structure
+ * to pass them to the client */
+ while(iterator != NULL)
+ {
+ if(strcmp(":method",iterator->name) == 0)
+ {
+ if(1 != iterator->num_values)
+ break;
+ method = iterator->value[0];
+ }
+ else if(strcmp(":path",iterator->name) == 0)
+ {
+ if(1 != iterator->num_values)
+ break;
+ path = iterator->value[0];
+ }
+ else if(strcmp(":version",iterator->name) == 0)
+ {
+ if(1 != iterator->num_values)
+ break;
+ version = iterator->value[0];
+ }
+ else if(strcmp(":host",iterator->name) == 0)
+ {
+ //TODO can it have more values?
+ if(1 != iterator->num_values)
+ break;
+ host = iterator->value[0];
+ }
+ else if(strcmp(":scheme",iterator->name) == 0)
+ {
+ if(1 != iterator->num_values)
+ break;
+ scheme = iterator->value[0];
+ }
+ else
+ for(i=0; i<iterator->num_values; ++i)
+ if (SPDY_YES != SPDY_name_value_add(headers,iterator->name,iterator->value[i]))
+ {
+ SPDY_destroy_request(request);
+ goto free_and_fail;
+ }
+
+ iterator = iterator->next;
+ }
+
+ request->method=method;
+ request->path=path;
+ request->version=version;
+ request->host=host;
+ request->scheme=scheme;
+ request->headers=headers;
+
+ //check request validity, all these fields are mandatory for a request
+ if(NULL == method || strlen(method) == 0
+ || NULL == path || strlen(path) == 0
+ || NULL == version || strlen(version) == 0
+ || NULL == host || strlen(host) == 0
+ || NULL == scheme || strlen(scheme) == 0
+ )
+ {
+ //TODO HTTP 400 Bad Request must be answered
+
+ SPDYF_DEBUG("Bad request");
+
+ SPDY_destroy_request(request);
+
+ return SPDY_YES;
+ }
+
+ //call client's callback function to notify
+ daemon->new_request_cb(daemon->cls,
+ request,
+ stream->priority,
+ method,
+ path,
+ version,
+ host,
+ scheme,
+ headers,
+ !stream->is_in_closed);
+
+ stream->cls = request;
+
+ return SPDY_YES;
+
+ //for GOTO
+ free_and_fail:
+
+ SPDY_name_value_destroy(headers);
+ return SPDY_NO;
+}
+
+
+/**
+ * TODO
+ */
+static int
+spdy_handler_new_data (void * cls,
+ struct SPDYF_Stream *stream,
+ const void * buf,
+ size_t size,
+ bool more)
+{
+ return stream->session->daemon->received_data_cb(cls, stream->cls, buf, size, more);
+}
+
+
+
+/**
+ * Callback to be called when the response queue object was handled and
+ * the data was already sent or discarded.
+ *
+ * @param cls
+ * @param response_queue the object which is being handled
+ * @param status shows if actually the response was sent or it was
+ * discarded by the lib for any reason (e.g., closing session,
+ * closing stream, stopping daemon, etc.). It is possible that
+ * status indicates an error but parts of the response headers
+ * and/or body (in one
+ * or several frames) were already sent to the client.
+ */
+static void
+spdy_handler_response_queue_result(void * cls,
+ struct SPDYF_Response_Queue *response_queue,
+ enum SPDY_RESPONSE_RESULT status)
+{
+ int streamopened;
+ struct SPDY_Request *request = (struct SPDY_Request *)cls;
+
+ SPDYF_ASSERT( ( (NULL == response_queue->data_frame) &&
+ (NULL != response_queue->control_frame) ) ||
+ ( (NULL != response_queue->data_frame) &&
+ (NULL == response_queue->control_frame) ),
+ "response queue must have either control frame or data frame");
+
+ streamopened = !response_queue->stream->is_out_closed;
+
+ response_queue->rrcb(response_queue->rrcb_cls, response_queue->response, request, status, streamopened);
+}
+
+
+int
+(SPDY_init) (enum SPDY_IO_SUBSYSTEM io_subsystem, ...)
+{
+ SPDYF_ASSERT(SPDYF_BUFFER_SIZE >= SPDY_MAX_SUPPORTED_FRAME_SIZE,
+ "Buffer size is less than max supported frame size!");
+ SPDYF_ASSERT(SPDY_MAX_SUPPORTED_FRAME_SIZE >= 32,
+ "Max supported frame size must be bigger than the minimal value!");
+ SPDYF_ASSERT(SPDY_IO_SUBSYSTEM_NONE == spdyf_io_initialized,
+ "SPDY_init must be called only once per program or after SPDY_deinit");
+
+ if(SPDY_IO_SUBSYSTEM_OPENSSL & io_subsystem)
+ {
+ SPDYF_openssl_global_init();
+ spdyf_io_initialized |= SPDY_IO_SUBSYSTEM_OPENSSL;
+ }
+ else if(SPDY_IO_SUBSYSTEM_RAW & io_subsystem)
+ {
+ SPDYF_raw_global_init();
+ spdyf_io_initialized |= SPDY_IO_SUBSYSTEM_RAW;
+ }
+
+ SPDYF_ASSERT(SPDY_IO_SUBSYSTEM_NONE != spdyf_io_initialized,
+ "SPDY_init could not find even one IO subsystem");
+
+ return SPDY_YES;
+}
+
+
+void
+SPDY_deinit ()
+{
+ SPDYF_ASSERT(SPDY_IO_SUBSYSTEM_NONE != spdyf_io_initialized,
+ "SPDY_init has not been called!");
+
+ if(SPDY_IO_SUBSYSTEM_OPENSSL & spdyf_io_initialized)
+ SPDYF_openssl_global_deinit();
+ else if(SPDY_IO_SUBSYSTEM_RAW & spdyf_io_initialized)
+ SPDYF_raw_global_deinit();
+
+ spdyf_io_initialized = SPDY_IO_SUBSYSTEM_NONE;
+}
+
+
+void
+SPDY_run (struct SPDY_Daemon *daemon)
+{
+ if(NULL == daemon)
+ {
+ SPDYF_DEBUG("daemon is NULL");
+ return;
+ }
+
+ SPDYF_run(daemon);
+}
+
+
+int
+SPDY_get_timeout (struct SPDY_Daemon *daemon,
+ unsigned long long *timeout)
+{
+ if(NULL == daemon)
+ {
+ SPDYF_DEBUG("daemon is NULL");
+ return SPDY_INPUT_ERROR;
+ }
+
+ return SPDYF_get_timeout(daemon,timeout);
+}
+
+
+int
+SPDY_get_fdset (struct SPDY_Daemon *daemon,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set)
+{
+ if(NULL == daemon
+ || NULL == read_fd_set
+ || NULL == write_fd_set
+ || NULL == except_fd_set)
+ {
+ SPDYF_DEBUG("a parameter is NULL");
+ return SPDY_INPUT_ERROR;
+ }
+
+ return SPDYF_get_fdset(daemon,
+ read_fd_set,
+ write_fd_set,
+ except_fd_set,
+ false);
+}
+
+
+struct SPDY_Daemon *
+SPDY_start_daemon (uint16_t port,
+ const char *certfile,
+ const char *keyfile,
+ SPDY_NewSessionCallback nscb,
+ SPDY_SessionClosedCallback sccb,
+ SPDY_NewRequestCallback nrcb,
+ SPDY_NewDataCallback npdcb,
+ void * cls,
+ ...)
+{
+ struct SPDY_Daemon *daemon;
+ va_list valist;
+
+ if(SPDY_IO_SUBSYSTEM_NONE == spdyf_io_initialized)
+ {
+ SPDYF_DEBUG("library not initialized");
+ return NULL;
+ }
+ /*
+ * for now make this checks in framing layer
+ if(NULL == certfile)
+ {
+ SPDYF_DEBUG("certfile is NULL");
+ return NULL;
+ }
+ if(NULL == keyfile)
+ {
+ SPDYF_DEBUG("keyfile is NULL");
+ return NULL;
+ }
+ */
+
+ va_start(valist, cls);
+ daemon = SPDYF_start_daemon_va ( port,
+ certfile,
+ keyfile,
+ nscb,
+ sccb,
+ nrcb,
+ npdcb,
+ &spdy_handler_new_stream,
+ &spdy_handler_new_data,
+ cls,
+ NULL,
+ valist
+ );
+ va_end(valist);
+
+ return daemon;
+}
+
+
+void
+SPDY_stop_daemon (struct SPDY_Daemon *daemon)
+{
+ if(NULL == daemon)
+ {
+ SPDYF_DEBUG("daemon is NULL");
+ return;
+ }
+
+ SPDYF_stop_daemon(daemon);
+}
+
+
+struct SPDY_Response *
+SPDY_build_response(int status,
+ const char * statustext,
+ const char * version,
+ struct SPDY_NameValue * headers,
+ const void * data,
+ size_t size)
+{
+ struct SPDY_Response *response = NULL;
+ struct SPDY_NameValue ** all_headers = NULL; //TODO maybe array in stack is enough
+ char *fullstatus = NULL;
+ int ret;
+ int num_hdr_containers = 1;
+
+ if(NULL == version)
+ {
+ SPDYF_DEBUG("version is NULL");
+ return NULL;
+ }
+
+ if(NULL == (response = malloc(sizeof(struct SPDY_Response))))
+ goto free_and_fail;
+ memset(response, 0, sizeof(struct SPDY_Response));
+
+ if(NULL != headers && !SPDYF_name_value_is_empty(headers))
+ num_hdr_containers = 2;
+
+ if(NULL == (all_headers = malloc(num_hdr_containers * sizeof(struct SPDY_NameValue *))))
+ goto free_and_fail;
+ memset(all_headers, 0, num_hdr_containers * sizeof(struct SPDY_NameValue *));
+
+ if(2 == num_hdr_containers)
+ all_headers[1] = headers;
+
+ if(NULL == (all_headers[0] = SPDY_name_value_create()))
+ goto free_and_fail;
+
+ if(NULL == statustext)
+ ret = asprintf(&fullstatus, "%i", status);
+ else
+ ret = asprintf(&fullstatus, "%i %s", status, statustext);
+ if(-1 == ret)
+ goto free_and_fail;
+
+ if(SPDY_YES != SPDY_name_value_add(all_headers[0], ":status", fullstatus))
+ goto free_and_fail;
+
+ free(fullstatus);
+ fullstatus = NULL;
+
+ if(SPDY_YES != SPDY_name_value_add(all_headers[0], ":version", version))
+ goto free_and_fail;
+
+ if(0 >= (response->headers_size = SPDYF_name_value_to_stream(all_headers,
+ num_hdr_containers,
+ &(response->headers))))
+ goto free_and_fail;
+
+ SPDY_name_value_destroy(all_headers[0]);
+ free(all_headers);
+ all_headers = NULL;
+
+ if(size > 0)
+ {
+ //copy the data to the response object
+ if(NULL == (response->data = malloc(size)))
+ {
+ free(response->headers);
+ goto free_and_fail;
+ }
+ memcpy(response->data, data, size);
+ response->data_size = size;
+ }
+
+ return response;
+
+ //for GOTO
+ free_and_fail:
+
+ free(fullstatus);
+ if(NULL != all_headers)
+ SPDY_name_value_destroy(all_headers[0]);
+ free(all_headers);
+ free(response);
+
+ return NULL;
+}
+
+
+struct SPDY_Response *
+SPDY_build_response_with_callback(int status,
+ const char * statustext,
+ const char * version,
+ struct SPDY_NameValue * headers,
+ SPDY_ResponseCallback rcb,
+ void *rcb_cls,
+ uint32_t block_size)
+{
+ struct SPDY_Response *response;
+
+ if(NULL == rcb)
+ {
+ SPDYF_DEBUG("rcb is NULL");
+ return NULL;
+ }
+ if(block_size > SPDY_MAX_SUPPORTED_FRAME_SIZE)
+ {
+ SPDYF_DEBUG("block_size is wrong");
+ return NULL;
+ }
+
+ if(0 == block_size)
+ block_size = SPDY_MAX_SUPPORTED_FRAME_SIZE;
+
+ response = SPDY_build_response(status,
+ statustext,
+ version,
+ headers,
+ NULL,
+ 0);
+
+ if(NULL == response)
+ {
+ return NULL;
+ }
+
+ response->rcb = rcb;
+ response->rcb_cls = rcb_cls;
+ response->rcb_block_size = block_size;
+
+ return response;
+}
+
+
+int
+SPDY_queue_response (struct SPDY_Request * request,
+ struct SPDY_Response *response,
+ bool closestream,
+ bool consider_priority,
+ SPDY_ResponseResultCallback rrcb,
+ void * rrcb_cls)
+{
+ struct SPDYF_Response_Queue *headers_to_queue;
+ struct SPDYF_Response_Queue *body_to_queue;
+ SPDYF_ResponseQueueResultCallback frqcb = NULL;
+ void *frqcb_cls = NULL;
+ int int_consider_priority = consider_priority ? SPDY_YES : SPDY_NO;
+
+ if(NULL == request)
+ {
+ SPDYF_DEBUG("request is NULL");
+ return SPDY_INPUT_ERROR;
+ }
+ if(NULL == response)
+ {
+ SPDYF_DEBUG("request is NULL");
+ return SPDY_INPUT_ERROR;
+ }
+
+ if(request->stream->is_out_closed
+ || SPDY_SESSION_STATUS_CLOSING == request->stream->session->status)
+ return SPDY_NO;
+
+ if(NULL != rrcb)
+ {
+ frqcb_cls = request;
+ frqcb = &spdy_handler_response_queue_result;
+ }
+
+ if(response->data_size > 0)
+ {
+ //SYN_REPLY and DATA will be queued
+
+ if(NULL == (headers_to_queue = SPDYF_response_queue_create(false,
+ response->headers,
+ response->headers_size,
+ response,
+ request->stream,
+ false,
+ NULL,
+ NULL,
+ NULL,
+ NULL)))
+ {
+ return SPDY_NO;
+ }
+
+ if(NULL == (body_to_queue = SPDYF_response_queue_create(true,
+ response->data,
+ response->data_size,
+ response,
+ request->stream,
+ closestream,
+ frqcb,
+ frqcb_cls,
+ rrcb,
+ rrcb_cls)))
+ {
+ SPDYF_response_queue_destroy(headers_to_queue);
+ return SPDY_NO;
+ }
+
+ SPDYF_queue_response (headers_to_queue,
+ request->stream->session,
+ int_consider_priority);
+
+ SPDYF_queue_response (body_to_queue,
+ request->stream->session,
+ int_consider_priority);
+ }
+ else if(NULL == response->rcb)
+ {
+ //no "body" will be queued, e.g. HTTP 404 without body
+
+ if(NULL == (headers_to_queue = SPDYF_response_queue_create(false,
+ response->headers,
+ response->headers_size,
+ response,
+ request->stream,
+ closestream,
+ frqcb,
+ frqcb_cls,
+ rrcb,
+ rrcb_cls)))
+ {
+ return SPDY_NO;
+ }
+
+ SPDYF_queue_response (headers_to_queue,
+ request->stream->session,
+ int_consider_priority);
+ }
+ else
+ {
+ //response with callbacks
+
+ if(NULL == (headers_to_queue = SPDYF_response_queue_create(false,
+ response->headers,
+ response->headers_size,
+ response,
+ request->stream,
+ false,
+ NULL,
+ NULL,
+ NULL,
+ NULL)))
+ {
+ return SPDY_NO;
+ }
+
+ if(NULL == (body_to_queue = SPDYF_response_queue_create(true,
+ response->data,
+ response->data_size,
+ response,
+ request->stream,
+ closestream,
+ frqcb,
+ frqcb_cls,
+ rrcb,
+ rrcb_cls)))
+ {
+ SPDYF_response_queue_destroy(headers_to_queue);
+ return SPDY_NO;
+ }
+
+ SPDYF_queue_response (headers_to_queue,
+ request->stream->session,
+ int_consider_priority);
+
+ SPDYF_queue_response (body_to_queue,
+ request->stream->session,
+ int_consider_priority);
+ }
+
+ return SPDY_YES;
+}
+
+
+socklen_t
+SPDY_get_remote_addr(struct SPDY_Session * session,
+ struct sockaddr ** addr)
+{
+ if(NULL == session)
+ {
+ SPDYF_DEBUG("session is NULL");
+ return 0;
+ }
+
+ *addr = session->addr;
+
+ return session->addr_len;
+}
+
+
+struct SPDY_Session *
+SPDY_get_session_for_request(const struct SPDY_Request * request)
+{
+ if(NULL == request)
+ {
+ SPDYF_DEBUG("request is NULL");
+ return NULL;
+ }
+
+ return request->stream->session;
+}
+
+
+void *
+SPDY_get_cls_from_session(struct SPDY_Session * session)
+{
+ if(NULL == session)
+ {
+ SPDYF_DEBUG("session is NULL");
+ return NULL;
+ }
+
+ return session->user_cls;
+}
+
+
+void
+SPDY_set_cls_to_session(struct SPDY_Session * session,
+ void * cls)
+{
+ if(NULL == session)
+ {
+ SPDYF_DEBUG("session is NULL");
+ return;
+ }
+
+ session->user_cls = cls;
+}
+
+
+void *
+SPDY_get_cls_from_request(struct SPDY_Request * request)
+{
+ if(NULL == request)
+ {
+ SPDYF_DEBUG("request is NULL");
+ return NULL;
+ }
+
+ return request->user_cls;
+}
+
+
+void
+SPDY_set_cls_to_request(struct SPDY_Request * request,
+ void * cls)
+{
+ if(NULL == request)
+ {
+ SPDYF_DEBUG("request is NULL");
+ return;
+ }
+
+ request->user_cls = cls;
+}
diff --git a/src/microspdy/applicationlayer.h b/src/microspdy/applicationlayer.h
new file mode 100644
index 0000000..a36760f
--- /dev/null
+++ b/src/microspdy/applicationlayer.h
@@ -0,0 +1,31 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file applicationlayer.h
+ * @brief SPDY application or HTTP layer
+ * @author Andrey Uzunov
+ */
+
+#ifndef APPLICATIONLAYER_H
+#define APPLICATIONLAYER_H
+
+#include "platform.h"
+
+
+#endif
diff --git a/src/microspdy/compression.c b/src/microspdy/compression.c
new file mode 100644
index 0000000..532ab64
--- /dev/null
+++ b/src/microspdy/compression.c
@@ -0,0 +1,441 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file compression.c
+ * @brief zlib handling functions
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "structures.h"
+#include "internal.h"
+#include "compression.h"
+
+/* spdy ver 3 specific dictionary used by zlib */
+static const unsigned char
+spdyf_zlib_dictionary[] = {
+ 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i
+ 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h
+ 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p
+ 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e
+ 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - -
+ 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - -
+ 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t -
+ 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
+ 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e
+ 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c
+ 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - -
+ 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l
+ 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e -
+ 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
+ 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s
+ 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e -
+ 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w
+ 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h
+ 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o
+ 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c
+ 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r
+ 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o
+ 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n
+ 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
+ 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e
+ 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
+ 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - -
+ 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t -
+ 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e
+ 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
+ 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g
+ 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o
+ 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - -
+ 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
+ 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - -
+ 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t
+ 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - -
+ 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
+ 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - -
+ 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - -
+ 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - -
+ 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t
+ 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i
+ 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f
+ 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i
+ 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h -
+ 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o
+ 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s
+ 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - -
+ 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e -
+ 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - -
+ 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g
+ 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f -
+ 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i
+ 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e
+ 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t
+ 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e
+ 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - -
+ 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r
+ 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - -
+ 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a -
+ 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y
+ 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - -
+ 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a
+ 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - -
+ 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - -
+ 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r
+ 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r
+ 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r -
+ 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e
+ 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e -
+ 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l
+ 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r
+ 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e
+ 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g -
+ 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a
+ 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s
+ 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t
+ 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y
+ 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a -
+ 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i
+ 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w
+ 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n
+ 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - -
+ 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d
+ 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t -
+ 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u
+ 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0
+ 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v
+ 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - -
+ 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1
+ 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r
+ 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b
+ 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s
+ 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i
+ 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e
+ 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e -
+ 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i
+ 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2
+ 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5
+ 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0
+ 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3
+ 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7
+ 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0
+ 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4
+ 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1
+ 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1
+ 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4
+ 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4
+ 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N
+ 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o
+ 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e
+ 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a
+ 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 -
+ 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e
+ 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o
+ 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m
+ 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4
+ 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0
+ 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h
+ 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0
+ 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d
+ 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N
+ 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d
+ 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e
+ 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r
+ 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o
+ 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t
+ 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e
+ 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 -
+ 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e -
+ 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a
+ 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F
+ 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A
+ 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J
+ 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A
+ 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t -
+ 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v -
+ 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0
+ 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n
+ 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W
+ 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u -
+ 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a
+ 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - -
+ 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k
+ 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t -
+ 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a
+ 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i
+ 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g
+ 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g
+ 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
+ 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
+ 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l
+ 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l
+ 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t
+ 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r
+ 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l
+ 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t
+ 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e
+ 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e
+ 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d
+ 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e
+ 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c
+ 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i
+ 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 -
+ 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - -
+ 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 -
+};
+
+
+int
+SPDYF_zlib_deflate_init(z_stream *strm)
+{
+ int ret;
+
+ strm->zalloc = Z_NULL;
+ strm->zfree = Z_NULL;
+ strm->opaque = Z_NULL;
+ //the second argument is "level of compression"
+ //use 0 for no compression; 9 for best compression
+ ret = deflateInit(strm, Z_DEFAULT_COMPRESSION);
+ if(ret != Z_OK)
+ {
+ SPDYF_DEBUG("deflate init");
+ return SPDY_NO;
+ }
+ ret = deflateSetDictionary(strm,
+ spdyf_zlib_dictionary,
+ sizeof(spdyf_zlib_dictionary));
+ if(ret != Z_OK)
+ {
+ SPDYF_DEBUG("deflate set dict");
+ deflateEnd(strm);
+ return SPDY_NO;
+ }
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_zlib_deflate_end(z_stream *strm)
+{
+ deflateEnd(strm);
+}
+
+int
+SPDYF_zlib_deflate(z_stream *strm,
+ const void *src,
+ size_t src_size,
+ size_t *data_used,
+ void **dest,
+ size_t *dest_size)
+{
+ int ret;
+ int flush;
+ unsigned int have;
+ Bytef out[SPDYF_ZLIB_CHUNK];
+
+ *dest = NULL;
+ *dest_size = 0;
+
+ do
+ {
+ /* check for big data bigger than the buffer used */
+ if(src_size > SPDYF_ZLIB_CHUNK)
+ {
+ strm->avail_in = SPDYF_ZLIB_CHUNK;
+ src_size -= SPDYF_ZLIB_CHUNK;
+ /* flush is used for the loop to detect if we still
+ * need to supply additional
+ * data to the stream via avail_in and next_in. */
+ flush = Z_NO_FLUSH;
+ }
+ else
+ {
+ strm->avail_in = src_size;
+ flush = Z_SYNC_FLUSH;
+ }
+ *data_used += strm->avail_in;
+
+ strm->next_in = (Bytef *)src;
+
+ /* Loop while output data is available */
+ do
+ {
+ strm->avail_out = SPDYF_ZLIB_CHUNK;
+ strm->next_out = out;
+
+ /* No need to check return value of deflate.
+ * (See zlib documentation at http://www.zlib.net/zlib_how.html */
+ ret = deflate(strm, flush);
+ have = SPDYF_ZLIB_CHUNK - strm->avail_out;
+
+ /* (Re)allocate memory for dest and keep track of it's size. */
+ *dest_size += have;
+ *dest = realloc(*dest, *dest_size);
+ if(!*dest)
+ {
+ SPDYF_DEBUG("realloc data for result");
+ deflateEnd(strm);
+ return SPDY_NO;
+ }
+ memcpy((*dest) + ((*dest_size) - have), out, have);
+ }
+ while(strm->avail_out == 0);
+ /* At this point, all of the input data should already
+ * have been used. */
+ SPDYF_ASSERT(strm->avail_in == 0,"compressing bug");
+ }
+ while(flush != Z_SYNC_FLUSH);
+
+ return Z_OK == ret ? SPDY_YES : SPDY_NO;
+}
+
+
+int
+SPDYF_zlib_inflate_init(z_stream *strm)
+{
+ int ret;
+
+ strm->zalloc = Z_NULL;
+ strm->zfree = Z_NULL;
+ strm->opaque = Z_NULL;
+ strm->avail_in = 0;
+ strm->next_in = Z_NULL;
+ //change 15 to lower value for performance and benchmark
+ //"The windowBits parameter is the base two logarithm of the
+ // maximum window size (the size of the history buffer)."
+ ret = inflateInit2(strm, 15);
+ if(ret != Z_OK)
+ {
+ SPDYF_DEBUG("Cannot inflateInit2 the stream");
+ return SPDY_NO;
+ }
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_zlib_inflate_end(z_stream *strm)
+{
+ inflateEnd(strm);
+}
+
+
+int
+SPDYF_zlib_inflate(z_stream *strm,
+ const void *src,
+ size_t src_size,
+ void **dest,
+ size_t *dest_size)
+{
+ int ret = Z_OK;
+ uint32_t have;
+ Bytef out[SPDYF_ZLIB_CHUNK];
+
+ *dest = NULL;
+ *dest_size = 0;
+
+ /* decompress until deflate stream ends or end of file */
+ do
+ {
+ if(src_size > SPDYF_ZLIB_CHUNK)
+ {
+ strm->avail_in = SPDYF_ZLIB_CHUNK;
+ src_size -= SPDYF_ZLIB_CHUNK;
+ }
+ else
+ {
+ strm->avail_in = src_size;
+ src_size = 0;
+ }
+
+ if(strm->avail_in == 0){
+ //the loop breaks always here as the stream never ends
+ break;
+ }
+
+ strm->next_in = (Bytef *) src;
+ /* run inflate() on input until output buffer not full */
+ do {
+ strm->avail_out = SPDYF_ZLIB_CHUNK;
+ strm->next_out = out;
+ ret = inflate(strm, Z_SYNC_FLUSH);
+
+ switch (ret)
+ {
+ case Z_STREAM_ERROR:
+ SPDYF_DEBUG("Error on inflate");
+ //no inflateEnd here, same in zlib example
+ return SPDY_NO;
+
+ case Z_NEED_DICT:
+ ret = inflateSetDictionary(strm,
+ spdyf_zlib_dictionary,
+ sizeof(spdyf_zlib_dictionary));
+ if(ret != Z_OK)
+ {
+ SPDYF_DEBUG("Error on inflateSetDictionary");
+ inflateEnd(strm);
+ return SPDY_NO;
+ }
+ ret = inflate(strm, Z_SYNC_FLUSH);
+ if(Z_STREAM_ERROR == ret)
+ {
+ SPDYF_DEBUG("Error on inflate");
+ return SPDY_NO;
+ }
+ break;
+
+ case Z_DATA_ERROR:
+ SPDYF_DEBUG("Z_DATA_ERROR");
+ inflateEnd(strm);
+ return SPDY_NO;
+
+ case Z_MEM_ERROR:
+ SPDYF_DEBUG("Z_MEM_ERROR");
+ inflateEnd(strm);
+ return SPDY_NO;
+ }
+ have = SPDYF_ZLIB_CHUNK - strm->avail_out;
+ *dest_size += have;
+ /* (re)alloc memory for the output buffer */
+ *dest = realloc(*dest, *dest_size);
+ if(!*dest)
+ {
+ SPDYF_DEBUG("Cannot realloc memory");
+ inflateEnd(strm);
+ return SPDY_NO;
+ }
+ memcpy((*dest) + ((*dest_size) - have), out, have);
+ }
+ while (0 == strm->avail_out);
+ }
+ while (Z_STREAM_END != ret);
+
+ return Z_OK == ret || Z_STREAM_END == ret ? SPDY_YES : SPDY_NO;
+}
diff --git a/src/microspdy/compression.h b/src/microspdy/compression.h
new file mode 100644
index 0000000..40746e7
--- /dev/null
+++ b/src/microspdy/compression.h
@@ -0,0 +1,117 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file compression.h
+ * @brief zlib handling functions
+ * @author Andrey Uzunov
+ */
+
+#ifndef COMPRESSION_H
+#define COMPRESSION_H
+
+#include "platform.h"
+
+/* size of buffers used by zlib on (de)compressing */
+#define SPDYF_ZLIB_CHUNK 16384
+
+
+/**
+ * Initializes the zlib stream for compression. Must be called once
+ * for a session on initialization.
+ *
+ * @param strm Zlib stream on which we work
+ * @return SPDY_NO if zlib failed. SPDY_YES otherwise
+ */
+int
+SPDYF_zlib_deflate_init(z_stream *strm);
+
+
+/**
+ * Deinitializes the zlib stream for compression. Should be called once
+ * for a session on cleaning up.
+ *
+ * @param strm Zlib stream on which we work
+ */
+void
+SPDYF_zlib_deflate_end(z_stream *strm);
+
+
+/**
+ * Compressing stream with zlib.
+ *
+ * @param strm Zlib stream on which we work
+ * @param src stream of the data to be compressed
+ * @param src_size size of the data
+ * @param data_used the number of bytes from src_stream that were used
+ * TODO do we need
+ * @param dest the resulting compressed stream. Should be NULL. Must be
+ * freed later manually.
+ * @param dest_size size of the data after compression
+ * @return SPDY_NO if malloc or zlib failed. SPDY_YES otherwise
+ */
+int
+SPDYF_zlib_deflate(z_stream *strm,
+ const void *src,
+ size_t src_size,
+ size_t *data_used,
+ void **dest,
+ size_t *dest_size);
+
+
+/**
+ * Initializes the zlib stream for decompression. Must be called once
+ * for a session.
+ *
+ * @param strm Zlib stream on which we work
+ * @return SPDY_NO if zlib failed. SPDY_YES otherwise
+ */
+int
+SPDYF_zlib_inflate_init(z_stream *strm);
+
+
+/**
+ * Deinitializes the zlib stream for decompression. Should be called once
+ * for a session on cleaning up.
+ *
+ * @param strm Zlib stream on which we work
+ */
+void
+SPDYF_zlib_inflate_end(z_stream *strm);
+
+
+/**
+ * Decompressing stream with zlib.
+ *
+ * @param strm Zlib stream on which we work
+ * @param src stream of the data to be decompressed
+ * @param src_size size of the data
+ * @param dest the resulting decompressed stream. Should be NULL. Must
+ * be freed manually.
+ * @param dest_size size of the data after decompression
+ * @return SPDY_NO if malloc or zlib failed. SPDY_YES otherwise. If the
+ * function fails, the SPDY session must be closed
+ */
+int
+SPDYF_zlib_inflate(z_stream *strm,
+ const void *src,
+ size_t src_size,
+ void **dest,
+ size_t *dest_size);
+
+#endif
diff --git a/src/microspdy/daemon.c b/src/microspdy/daemon.c
new file mode 100644
index 0000000..32285ae
--- /dev/null
+++ b/src/microspdy/daemon.c
@@ -0,0 +1,544 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file microspdy/daemon.c
+ * @brief daemon functionality
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "structures.h"
+#include "internal.h"
+#include "session.h"
+#include "io.h"
+
+
+/**
+ * Default implementation of the panic function,
+ * prints an error message and aborts.
+ *
+ * @param cls unused
+ * @param file name of the file with the problem
+ * @param line line number with the problem
+ * @param reason error message with details
+ */
+static void
+spdyf_panic_std (void *cls,
+ const char *file,
+ unsigned int line,
+ const char *reason)
+{
+ (void)cls;
+ fprintf (stdout, "Fatal error in libmicrospdy %s:%u: %s\n",
+ file, line, reason);
+ //raise(SIGINT); //used for gdb
+ abort ();
+}
+
+
+/**
+ * Global handler for fatal errors.
+ */
+SPDY_PanicCallback spdyf_panic = &spdyf_panic_std;
+
+
+/**
+ * Global closure argument for "spdyf_panic".
+ */
+void *spdyf_panic_cls;
+
+
+/**
+ * Free resources associated with all closed connections.
+ * (destroy responses, free buffers, etc.).
+ *
+ * @param daemon daemon to clean up
+ */
+static void
+spdyf_cleanup_sessions (struct SPDY_Daemon *daemon)
+{
+ struct SPDY_Session *session;
+
+ while (NULL != (session = daemon->cleanup_head))
+ {
+ DLL_remove (daemon->cleanup_head,
+ daemon->cleanup_tail,
+ session);
+
+ SPDYF_session_destroy(session);
+ }
+}
+
+
+/**
+ * Closing of all connections handled by the daemon.
+ *
+ * @param daemon SPDY daemon
+ */
+static void
+spdyf_close_all_sessions (struct SPDY_Daemon *daemon)
+{
+ struct SPDY_Session *session;
+
+ while (NULL != (session = daemon->sessions_head))
+ {
+ //prepare GOAWAY frame
+ SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_OK, true);
+ //try to send the frame (it is best effort, so it will maybe sent)
+ SPDYF_session_write(session,true);
+ SPDYF_session_close(session);
+ }
+
+ spdyf_cleanup_sessions(daemon);
+}
+
+
+/**
+ * Parse a list of options given as varargs.
+ *
+ * @param daemon the daemon to initialize
+ * @param valist the options
+ * @return SPDY_YES on success, SPDY_NO on error
+ */
+static int
+spdyf_parse_options_va (struct SPDY_Daemon *daemon,
+ va_list valist)
+{
+ enum SPDY_DAEMON_OPTION opt;
+
+ while (SPDY_DAEMON_OPTION_END != (opt = (enum SPDY_DAEMON_OPTION) va_arg (valist, int)))
+ {
+ if(opt & daemon->options)
+ {
+ SPDYF_DEBUG("Daemon option %i used twice",opt);
+ return SPDY_NO;
+ }
+ daemon->options |= opt;
+
+ switch (opt)
+ {
+ case SPDY_DAEMON_OPTION_SESSION_TIMEOUT:
+ daemon->session_timeout = va_arg (valist, unsigned int) * 1000;
+ break;
+ case SPDY_DAEMON_OPTION_SOCK_ADDR:
+ daemon->address = va_arg (valist, struct sockaddr *);
+ break;
+ case SPDY_DAEMON_OPTION_FLAGS:
+ daemon->flags = va_arg (valist, enum SPDY_DAEMON_FLAG);
+ break;
+ case SPDY_DAEMON_OPTION_IO_SUBSYSTEM:
+ daemon->io_subsystem = va_arg (valist, enum SPDY_IO_SUBSYSTEM);
+ break;
+ case SPDY_DAEMON_OPTION_MAX_NUM_FRAMES:
+ daemon->max_num_frames = va_arg (valist, uint32_t);
+ break;
+ default:
+ SPDYF_DEBUG("Wrong option for the daemon %i",opt);
+ return SPDY_NO;
+ }
+ }
+ return SPDY_YES;
+}
+
+
+void
+SPDY_set_panic_func (SPDY_PanicCallback cb,
+ void *cls)
+{
+ spdyf_panic = cb;
+ spdyf_panic_cls = cls;
+}
+
+
+struct SPDY_Daemon *
+SPDYF_start_daemon_va (uint16_t port,
+ const char *certfile,
+ const char *keyfile,
+ SPDY_NewSessionCallback nscb,
+ SPDY_SessionClosedCallback sccb,
+ SPDY_NewRequestCallback nrcb,
+ SPDY_NewDataCallback npdcb,
+ SPDYF_NewStreamCallback fnscb,
+ SPDYF_NewDataCallback fndcb,
+ void * cls,
+ void * fcls,
+ va_list valist)
+{
+ struct SPDY_Daemon *daemon = NULL;
+ int afamily;
+ int option_on = 1;
+ int ret;
+ struct sockaddr_in* servaddr4 = NULL;
+#if HAVE_INET6
+ struct sockaddr_in6* servaddr6 = NULL;
+#endif
+ socklen_t addrlen;
+
+ if (NULL == (daemon = malloc (sizeof (struct SPDY_Daemon))))
+ {
+ SPDYF_DEBUG("malloc");
+ return NULL;
+ }
+ memset (daemon, 0, sizeof (struct SPDY_Daemon));
+ daemon->socket_fd = -1;
+ daemon->port = port;
+
+ if(SPDY_YES != spdyf_parse_options_va (daemon, valist))
+ {
+ SPDYF_DEBUG("parse");
+ goto free_and_fail;
+ }
+
+ if(0 == daemon->max_num_frames)
+ daemon->max_num_frames = SPDYF_NUM_SENT_FRAMES_AT_ONCE;
+
+ if(!port && NULL == daemon->address)
+ {
+ SPDYF_DEBUG("Port is 0");
+ goto free_and_fail;
+ }
+ if(0 == daemon->io_subsystem)
+ daemon->io_subsystem = SPDY_IO_SUBSYSTEM_OPENSSL;
+
+ if(SPDY_YES != SPDYF_io_set_daemon(daemon, daemon->io_subsystem))
+ goto free_and_fail;
+
+ if(SPDY_IO_SUBSYSTEM_RAW != daemon->io_subsystem)
+ {
+ if (NULL == certfile
+ || NULL == (daemon->certfile = strdup (certfile)))
+ {
+ SPDYF_DEBUG("strdup (certfile)");
+ goto free_and_fail;
+ }
+ if (NULL == keyfile
+ || NULL == (daemon->keyfile = strdup (keyfile)))
+ {
+ SPDYF_DEBUG("strdup (keyfile)");
+ goto free_and_fail;
+ }
+ }
+
+ daemon->new_session_cb = nscb;
+ daemon->session_closed_cb = sccb;
+ daemon->new_request_cb = nrcb;
+ daemon->received_data_cb = npdcb;
+ daemon->cls = cls;
+ daemon->fcls = fcls;
+ daemon->fnew_stream_cb = fnscb;
+ daemon->freceived_data_cb = fndcb;
+
+#if HAVE_INET6
+ //handling IPv6
+ if((daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6)
+ && NULL != daemon->address && AF_INET6 != daemon->address->sa_family)
+ {
+ SPDYF_DEBUG("SPDY_DAEMON_FLAG_ONLY_IPV6 set but IPv4 address provided");
+ goto free_and_fail;
+ }
+
+ addrlen = sizeof (struct sockaddr_in6);
+
+ if(NULL == daemon->address)
+ {
+ if (NULL == (servaddr6 = malloc (addrlen)))
+ {
+ SPDYF_DEBUG("malloc");
+ goto free_and_fail;
+ }
+ memset (servaddr6, 0, addrlen);
+ servaddr6->sin6_family = AF_INET6;
+ servaddr6->sin6_addr = in6addr_any;
+ servaddr6->sin6_port = htons (port);
+ daemon->address = (struct sockaddr *) servaddr6;
+ }
+
+ if(AF_INET6 == daemon->address->sa_family)
+ {
+ afamily = PF_INET6;
+ }
+ else
+ {
+ afamily = PF_INET;
+ }
+#else
+ //handling IPv4
+ if(daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6)
+ {
+ SPDYF_DEBUG("SPDY_DAEMON_FLAG_ONLY_IPV6 set but no support");
+ goto free_and_fail;
+ }
+
+ addrlen = sizeof (struct sockaddr_in);
+
+ if(NULL == daemon->address)
+ {
+ if (NULL == (servaddr4 = malloc (addrlen)))
+ {
+ SPDYF_DEBUG("malloc");
+ goto free_and_fail;
+ }
+ memset (servaddr4, 0, addrlen);
+ servaddr4->sin_family = AF_INET;
+ servaddr4->sin_addr = INADDR_ANY;
+ servaddr4->sin_port = htons (port);
+ daemon->address = (struct sockaddr *) servaddr4;
+ }
+
+ afamily = PF_INET;
+#endif
+
+ daemon->socket_fd = socket (afamily, SOCK_STREAM, 0);
+ if (-1 == daemon->socket_fd)
+ {
+ SPDYF_DEBUG("sock");
+ goto free_and_fail;
+ }
+
+ //setting option for the socket to reuse address
+ ret = setsockopt(daemon->socket_fd, SOL_SOCKET, SO_REUSEADDR, &option_on, sizeof(option_on));
+ if(ret)
+ {
+ SPDYF_DEBUG("WARNING: SO_REUSEADDR was not set for the server");
+ }
+
+#if HAVE_INET6
+ if(daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6)
+ {
+ ret = setsockopt(daemon->socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &option_on, sizeof(option_on));
+ if(ret)
+ {
+ SPDYF_DEBUG("setsockopt with IPPROTO_IPV6 failed");
+ goto free_and_fail;
+ }
+ }
+#endif
+
+ if (-1 == bind (daemon->socket_fd, daemon->address, addrlen))
+ {
+ SPDYF_DEBUG("bind %i",errno);
+ goto free_and_fail;
+ }
+
+ if (listen (daemon->socket_fd, 20) < 0)
+ {
+ SPDYF_DEBUG("listen %i",errno);
+ goto free_and_fail;
+ }
+
+ if(SPDY_YES != daemon->fio_init(daemon))
+ {
+ SPDYF_DEBUG("tls");
+ goto free_and_fail;
+ }
+
+ return daemon;
+
+ //for GOTO
+ free_and_fail:
+ if(daemon->socket_fd > 0)
+ (void)close (daemon->socket_fd);
+
+ free(servaddr4);
+#if HAVE_INET6
+ free(servaddr6);
+#endif
+ if(NULL != daemon->certfile)
+ free(daemon->certfile);
+ if(NULL != daemon->keyfile)
+ free(daemon->keyfile);
+ free (daemon);
+
+ return NULL;
+}
+
+
+void
+SPDYF_stop_daemon (struct SPDY_Daemon *daemon)
+{
+ daemon->fio_deinit(daemon);
+
+ shutdown (daemon->socket_fd, SHUT_RDWR);
+ spdyf_close_all_sessions (daemon);
+ (void)close (daemon->socket_fd);
+
+ if(!(SPDY_DAEMON_OPTION_SOCK_ADDR & daemon->options))
+ free(daemon->address);
+
+ free(daemon->certfile);
+ free(daemon->keyfile);
+
+ free(daemon);
+}
+
+
+int
+SPDYF_get_timeout (struct SPDY_Daemon *daemon,
+ unsigned long long *timeout)
+{
+ unsigned long long earliest_deadline = 0;
+ unsigned long long now;
+ struct SPDY_Session *pos;
+ bool have_timeout;
+
+ if(0 == daemon->session_timeout)
+ return SPDY_NO;
+
+ now = SPDYF_monotonic_time();
+ have_timeout = false;
+ for (pos = daemon->sessions_head; NULL != pos; pos = pos->next)
+ {
+ if ( (! have_timeout) ||
+ (earliest_deadline > pos->last_activity + daemon->session_timeout) )
+ earliest_deadline = pos->last_activity + daemon->session_timeout;
+
+ have_timeout = true;
+
+ if (SPDY_YES == pos->fio_is_pending(pos))
+ {
+ earliest_deadline = 0;
+ break;
+ }
+ }
+
+ if (!have_timeout)
+ return SPDY_NO;
+ if (earliest_deadline <= now)
+ *timeout = 0;
+ else
+ *timeout = earliest_deadline - now;
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_get_fdset (struct SPDY_Daemon *daemon,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ bool all)
+{
+ (void)except_fd_set;
+ struct SPDY_Session *pos;
+ int fd;
+ int max_fd = -1;
+
+ fd = daemon->socket_fd;
+ if (-1 != fd)
+ {
+ FD_SET (fd, read_fd_set);
+ /* update max file descriptor */
+ max_fd = fd;
+ }
+
+ for (pos = daemon->sessions_head; NULL != pos; pos = pos->next)
+ {
+ fd = pos->socket_fd;
+ FD_SET(fd, read_fd_set);
+ if (all
+ || (NULL != pos->response_queue_head) //frames pending
+ || (NULL != pos->write_buffer) //part of last frame pending
+ || (SPDY_SESSION_STATUS_CLOSING == pos->status) //the session is about to be closed
+ || (daemon->session_timeout //timeout passed for the session
+ && (pos->last_activity + daemon->session_timeout < SPDYF_monotonic_time()))
+ || (SPDY_YES == pos->fio_is_pending(pos)) //data in TLS' read buffer pending
+ || ((pos->read_buffer_offset - pos->read_buffer_beginning) > 0) // data in lib's read buffer pending
+ )
+ FD_SET(fd, write_fd_set);
+ if(fd > max_fd)
+ max_fd = fd;
+ }
+
+ return max_fd;
+}
+
+
+void
+SPDYF_run (struct SPDY_Daemon *daemon)
+{
+ struct SPDY_Session *pos;
+ struct SPDY_Session *next;
+ int num_ready;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ struct timeval timeout;
+ int ds;
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ //here we need really all descriptors to see later which are ready
+ max = SPDYF_get_fdset(daemon,&rs,&ws,&es, true);
+
+ num_ready = select (max + 1, &rs, &ws, &es, &timeout);
+
+ if(num_ready < 1)
+ return;
+
+ if ( (-1 != (ds = daemon->socket_fd)) &&
+ (FD_ISSET (ds, &rs)) ){
+ SPDYF_session_accept(daemon);
+ }
+
+ next = daemon->sessions_head;
+ while (NULL != (pos = next))
+ {
+ next = pos->next;
+ ds = pos->socket_fd;
+ if (ds != -1)
+ {
+ //fill the read buffer
+ if (FD_ISSET (ds, &rs) || pos->fio_is_pending(pos)){
+ SPDYF_session_read(pos);
+ }
+
+ //do something with the data in read buffer
+ if(SPDY_NO == SPDYF_session_idle(pos))
+ {
+ //the session was closed, cannot write anymore
+ //continue;
+ }
+
+ //write whatever has been put to the response queue
+ //during read or idle operation, something might be put
+ //on the response queue, thus call write operation
+ if (FD_ISSET (ds, &ws)){
+ if(SPDY_NO == SPDYF_session_write(pos, false))
+ {
+ //SPDYF_session_close(pos);
+ //continue;
+ }
+ }
+
+ /* the response queue has been flushed for half closed
+ * connections, so let close them */
+ /*if(pos->read_closed)
+ {
+ SPDYF_session_close(pos);
+ }*/
+ }
+ }
+
+ spdyf_cleanup_sessions(daemon);
+}
diff --git a/src/microspdy/daemon.h b/src/microspdy/daemon.h
new file mode 100644
index 0000000..cb3ed5f
--- /dev/null
+++ b/src/microspdy/daemon.h
@@ -0,0 +1,130 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file daemon.h
+ * @brief daemon functionality
+ * @author Andrey Uzunov
+ */
+
+#ifndef DAEMON_H
+#define DAEMON_H
+
+#include "platform.h"
+
+
+/**
+ * Global flags containing the initialized IO subsystems.
+ */
+enum SPDY_IO_SUBSYSTEM spdyf_io_initialized;
+
+
+/**
+ * Start a SPDDY webserver on the given port.
+ *
+ * @param port port to bind to
+ * @param certfile path to the certificate that will be used by server
+ * @param keyfile path to the keyfile for the certificate
+ * @param nscb callback called when a new SPDY session is
+ * established by a client
+ * @param sccb callback called when a client closes the session
+ * @param nrcb callback called when a client sends request
+ * @param npdcb callback called when HTTP POST params are received
+ * after request
+ * @param fnscb callback called when new stream is opened by a client
+ * @param fndcb callback called when new data -- within a data frame --
+ * is received by the server
+ * @param cls extra argument to all of the callbacks without those
+ * specific only for the framing layer
+ * @param fcls extra argument to all of the callbacks, specific only for
+ * the framing layer (those vars starting with 'f').
+ * @param valist va_list of options (type-value pairs,
+ * terminated with SPDY_DAEMON_OPTION_END).
+ * @return NULL on error, handle to daemon on success
+ */
+struct SPDY_Daemon *
+SPDYF_start_daemon_va (uint16_t port,
+ const char *certfile,
+ const char *keyfile,
+ SPDY_NewSessionCallback nscb,
+ SPDY_SessionClosedCallback sccb,
+ SPDY_NewRequestCallback nrcb,
+ SPDY_NewDataCallback npdcb,
+ SPDYF_NewStreamCallback fnscb,
+ SPDYF_NewDataCallback fndcb,
+ void * cls,
+ void * fcls,
+ va_list valist);
+
+
+/**
+ * Run webserver operations (without blocking unless
+ * in client callbacks). This method must be called in the client event
+ * loop.
+ *
+ * @param daemon daemon to run
+ */
+void
+SPDYF_run (struct SPDY_Daemon *daemon);
+
+
+/**
+ * Obtain timeout value for select for this daemon. The returned value
+ * is how long select
+ * should at most block, not the timeout value set for connections.
+ *
+ * @param daemon daemon to query for timeout
+ * @param timeout set to the timeout (in milliseconds)
+ * @return SPDY_YES on success, SPDY_NO if no connections exist that
+ * would necessiate the use of a timeout right now
+ */
+int
+SPDYF_get_timeout (struct SPDY_Daemon *daemon,
+ unsigned long long *timeout);
+
+
+/**
+ * Obtain the select sets for this daemon. The idea of SPDYF_get_fdset
+ * is to return such descriptors that the select in the application can
+ * return and SPDY_run can be called only when this is really needed.
+ * That means not all sockets will be added to write_fd_set.
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param all add all session's descriptors to write_fd_set or not
+ * @return largest FD added
+ */
+int
+SPDYF_get_fdset (struct SPDY_Daemon *daemon,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ bool all);
+
+
+/**
+ * Shutdown the daemon.
+ *
+ * @param daemon daemon to stop
+ */
+void
+SPDYF_stop_daemon (struct SPDY_Daemon *daemon);
+
+#endif
diff --git a/src/microspdy/internal.c b/src/microspdy/internal.c
new file mode 100644
index 0000000..f0d2fc1
--- /dev/null
+++ b/src/microspdy/internal.c
@@ -0,0 +1,40 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file microspdy/internal.c
+ * @brief internal functions and macros for the framing layer
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "structures.h"
+
+
+unsigned long long
+SPDYF_monotonic_time (void)
+{
+#ifdef HAVE_CLOCK_GETTIME
+#ifdef CLOCK_MONOTONIC
+ struct timespec ts;
+ if (0 == clock_gettime (CLOCK_MONOTONIC, &ts))
+ return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+#endif
+#endif
+ return time (NULL) * 1000;
+}
diff --git a/src/microspdy/internal.h b/src/microspdy/internal.h
new file mode 100644
index 0000000..e618a5a
--- /dev/null
+++ b/src/microspdy/internal.h
@@ -0,0 +1,198 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file microspdy/internal.h
+ * @brief internal functions and macros for the framing layer
+ * @author Andrey Uzunov
+ */
+
+#ifndef INTERNAL_H_H
+#define INTERNAL_H_H
+
+#include "platform.h"
+#include "microspdy.h"
+
+/**
+ * size of read buffers for each connection
+ * must be at least the size of SPDY_MAX_SUPPORTED_FRAME_SIZE
+ */
+#define SPDYF_BUFFER_SIZE 8192
+
+/**
+ * initial size of window for each stream (this is for the data
+ * within data frames that can be handled)
+ */
+#define SPDYF_INITIAL_WINDOW_SIZE 65536
+
+/**
+ * number of frames written to the socket at once. After X frames
+ * everything should be run again. In this way the application can
+ * response to more important requests while a big file is still
+ * being transmitted to the client
+ */
+#define SPDYF_NUM_SENT_FRAMES_AT_ONCE 10
+
+
+/**
+ * Handler for fatal errors.
+ */
+extern SPDY_PanicCallback spdyf_panic;
+
+
+/**
+ * Closure argument for "mhd_panic".
+ */
+extern void *spdyf_panic_cls;
+
+
+/**
+ * Trigger 'panic' action based on fatal errors.
+ *
+ * @param msg error message (const char *)
+ */
+#define SPDYF_PANIC(msg) \
+ spdyf_panic (spdyf_panic_cls, __FILE__, __LINE__, msg)
+
+
+/**
+ * Asserts the validity of an expression.
+ *
+ * @param expr (bool)
+ * @param msg message to print on error (const char *)
+ */
+#define SPDYF_ASSERT(expr, msg) \
+ if(!(expr)){\
+ SPDYF_PANIC(msg);\
+ abort();\
+ }
+
+
+/**
+ * Convert 24 bit integer from host byte order to network byte order.
+ *
+ * @param n input value (int32_t)
+ * @return converted value (uint32_t)
+ */
+#if HAVE_BIG_ENDIAN
+#define HTON24(n) n
+#else
+#define HTON24(n) (((((uint32_t)(n) & 0xFF)) << 16)\
+ | (((uint32_t)(n) & 0xFF00))\
+ | ((((uint32_t)(n) & 0xFF0000)) >> 16))
+#endif
+
+
+/**
+ * Convert 24 bit integer from network byte order to host byte order.
+ *
+ * @param n input value (int32_t)
+ * @return converted value (uint32_t)
+ */
+#if HAVE_BIG_ENDIAN
+#define NTOH24(n) n
+#else
+#define NTOH24(n) (((((uint32_t)(n) & 0xFF)) << 16)\
+ | (((uint32_t)(n) & 0xFF00))\
+ | ((((uint32_t)(n) & 0xFF0000)) >> 16))
+#endif
+
+
+/**
+ * Convert 31 bit integer from network byte order to host byte order.
+ *
+ * @param n input value (int32_t)
+ * @return converted value (uint32_t)
+ */
+#if HAVE_BIG_ENDIAN
+#define NTOH31(n) n
+#else
+#define NTOH31(n) (((((uint32_t)(n) & 0x7F)) << 24) | \
+ ((((uint32_t)(n) & 0xFF00)) << 8) | \
+ ((((uint32_t)(n) & 0xFF0000)) >> 8) | \
+ ((((uint32_t)(n) & 0xFF000000)) >> 24))
+#endif
+
+
+/**
+ * Convert 31 bit integer from host byte order to network byte order.
+ *
+ * @param n input value (int32_t)
+ * @return converted value (uint32_t)
+ */
+#if HAVE_BIG_ENDIAN
+#define HTON31(n) n
+#else
+#define HTON31(n) (((((uint32_t)(n) & 0xFF)) << 24) | \
+ ((((uint32_t)(n) & 0xFF00)) << 8) | \
+ ((((uint32_t)(n) & 0xFF0000)) >> 8) | \
+ ((((uint32_t)(n) & 0x7F000000)) >> 24))
+#endif
+
+
+/**
+ * Print formatted debug value.
+ *
+ * @param fmt format (const char *)
+ * @param ... args for format
+ */
+#define SPDYF_DEBUG(fmt, ...) do { \
+ fprintf (stdout, "%s\n%u: ",__FILE__, __LINE__);\
+ fprintf(stdout,fmt,##__VA_ARGS__);\
+ fprintf(stdout,"\n");\
+ fflush(stdout); } while (0)
+
+
+/**
+ * Print stream for debuging.
+ *
+ * @param strm (void *)
+ * @param size (int)
+ */
+#define SPDYF_PRINT_STREAM(strm, size) do { \
+ int ___i;\
+ for(___i=0;___i<size;___i++){\
+ fprintf(stdout,"%x ",*((uint8_t *) strm + ___i));\
+ fflush(stdout);\
+ }\
+ fprintf(stdout,"\n");\
+ } while (0)
+
+
+/**
+ * Print message and raise SIGINT for debug purposes.
+ *
+ * @param msg message (const char *)
+ */
+#define SPDYF_SIGINT(msg) do { \
+ fprintf(stdout,"%i : %s\n", __LINE__,__FILE__);\
+ fprintf(stdout,msg);\
+ fprintf(stdout,"\n");\
+ fflush(stdout);\
+ raise(SIGINT); } while (0)
+
+
+/**
+ * Returns monotonic time, to be used for session timeouts.
+ *
+ * @return time in milliseconds
+ */
+unsigned long long
+SPDYF_monotonic_time(void);
+
+#endif
diff --git a/src/microspdy/io.c b/src/microspdy/io.c
new file mode 100644
index 0000000..c333c89
--- /dev/null
+++ b/src/microspdy/io.c
@@ -0,0 +1,90 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file io.c
+ * @brief Generic functions for IO.
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "structures.h"
+#include "internal.h"
+#include "io.h"
+
+
+int
+SPDYF_io_set_daemon(struct SPDY_Daemon *daemon,
+ enum SPDY_IO_SUBSYSTEM io_subsystem)
+{
+ switch(io_subsystem)
+ {
+ case SPDY_IO_SUBSYSTEM_OPENSSL:
+ daemon->fio_init = &SPDYF_openssl_init;
+ daemon->fio_deinit = &SPDYF_openssl_deinit;
+ break;
+
+ case SPDY_IO_SUBSYSTEM_RAW:
+ daemon->fio_init = &SPDYF_raw_init;
+ daemon->fio_deinit = &SPDYF_raw_deinit;
+ break;
+
+ case SPDY_IO_SUBSYSTEM_NONE:
+ default:
+ SPDYF_DEBUG("Unsupported subsystem");
+ return SPDY_NO;
+ }
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_io_set_session(struct SPDY_Session *session,
+ enum SPDY_IO_SUBSYSTEM io_subsystem)
+{
+ switch(io_subsystem)
+ {
+ case SPDY_IO_SUBSYSTEM_OPENSSL:
+ session->fio_new_session = &SPDYF_openssl_new_session;
+ session->fio_close_session = &SPDYF_openssl_close_session;
+ session->fio_is_pending = &SPDYF_openssl_is_pending;
+ session->fio_recv = &SPDYF_openssl_recv;
+ session->fio_send = &SPDYF_openssl_send;
+ session->fio_before_write = &SPDYF_openssl_before_write;
+ session->fio_after_write = &SPDYF_openssl_after_write;
+ break;
+
+ case SPDY_IO_SUBSYSTEM_RAW:
+ session->fio_new_session = &SPDYF_raw_new_session;
+ session->fio_close_session = &SPDYF_raw_close_session;
+ session->fio_is_pending = &SPDYF_raw_is_pending;
+ session->fio_recv = &SPDYF_raw_recv;
+ session->fio_send = &SPDYF_raw_send;
+ session->fio_before_write = &SPDYF_raw_before_write;
+ session->fio_after_write = &SPDYF_raw_after_write;
+ break;
+
+ case SPDY_IO_SUBSYSTEM_NONE:
+ default:
+ SPDYF_DEBUG("Unsupported subsystem");
+ return SPDY_NO;
+ }
+
+ return SPDY_YES;
+}
diff --git a/src/microspdy/io.h b/src/microspdy/io.h
new file mode 100644
index 0000000..c28ba21
--- /dev/null
+++ b/src/microspdy/io.h
@@ -0,0 +1,216 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file io.h
+ * @brief Signatures for IO functions.
+ * @author Andrey Uzunov
+ */
+
+#ifndef IO_H
+#define IO_H
+
+#include "platform.h"
+#include "io_openssl.h"
+#include "io_raw.h"
+
+
+/**
+ * Used for return code when reading and writing to the TLS socket.
+ */
+enum SPDY_IO_ERROR
+{
+ /**
+ * The connection was closed by the other party.
+ */
+ SPDY_IO_ERROR_CLOSED = 0,
+
+ /**
+ * Any kind of error ocurred. The session has to be closed.
+ */
+ SPDY_IO_ERROR_ERROR = -2,
+
+ /**
+ * The function had to return without processing any data. The whole
+ * cycle of events has to be called again (SPDY_run) as something
+ * either has to be written or read or the the syscall was
+ * interrupted by a signal.
+ */
+ SPDY_IO_ERROR_AGAIN = -3,
+};
+
+
+/**
+ * Global initializing. Must be called only once in the program.
+ *
+ */
+typedef void
+(*SPDYF_IOGlobalInit) ();
+
+
+/**
+ * Global deinitializing for the whole program. Should be called
+ * at the end of the program.
+ *
+ */
+typedef void
+(*SPDYF_IOGlobalDeinit) ();
+
+
+/**
+ * Initializing of io context for a specific daemon.
+ * Must be called when the daemon starts.
+ *
+ * @param daemon SPDY_Daemon for which io will be used. Daemon's
+ * certificate and key file are used for tls.
+ * @return SPDY_YES on success or SPDY_NO on error
+ */
+typedef int
+(*SPDYF_IOInit) (struct SPDY_Daemon *daemon);
+
+
+/**
+ * Deinitializing io context for a daemon. Should be called
+ * when the deamon is stopped.
+ *
+ * @param daemon SPDY_Daemon which is being stopped
+ */
+typedef void
+(*SPDYF_IODeinit) (struct SPDY_Daemon *daemon);
+
+
+/**
+ * Initializing io for a specific connection. Must be called
+ * after the connection has been accepted.
+ *
+ * @param session SPDY_Session whose socket will be used
+ * @return SPDY_NO if some funcs inside fail. SPDY_YES otherwise
+ */
+typedef int
+(*SPDYF_IONewSession) (struct SPDY_Session *session);
+
+
+/**
+ * Deinitializing io for a specific connection. Should be called
+ * closing session's socket.
+ *
+ * @param session SPDY_Session whose socket is used
+ */
+typedef void
+(*SPDYF_IOCloseSession) (struct SPDY_Session *session);
+
+
+/**
+ * Reading from session's socket. Reads available data and put it to the
+ * buffer.
+ *
+ * @param session for which data is received
+ * @param buffer where data from the socket will be written to
+ * @param size of the buffer
+ * @return number of bytes (at most size) read from the connection
+ * 0 if the other party has closed the connection
+ * SPDY_IO_ERROR code on error
+ */
+typedef int
+(*SPDYF_IORecv) (struct SPDY_Session *session,
+ void * buffer,
+ size_t size);
+
+
+/**
+ * Writing to session's socket. Writes the data given into the buffer to the
+ * socket.
+ *
+ * @param session whose context is used
+ * @param buffer from where data will be written to the socket
+ * @param size number of bytes to be taken from the buffer
+ * @return number of bytes (at most size) from the buffer that has been
+ * written to the connection
+ * 0 if the other party has closed the connection
+ * SPDY_IO_ERROR code on error
+ */
+typedef int
+(*SPDYF_IOSend) (struct SPDY_Session *session,
+ const void * buffer,
+ size_t size);
+
+
+/**
+ * Checks if there is data staying in the buffers of the underlying
+ * system that waits to be read. In case of TLS, this will call
+ * something like SSL_pending().
+ *
+ * @param session which is checked
+ * @return SPDY_YES if data is pending or SPDY_NO otherwise
+ */
+typedef int
+(*SPDYF_IOIsPending) (struct SPDY_Session *session);
+
+
+/**
+ * Called just before frames are about to be processed and written
+ * to the socket.
+ *
+ * @param session
+ * @return SPDY_NO if writing must not happen in the call;
+ * SPDY_YES otherwise
+ */
+typedef int
+(*SPDYF_IOBeforeWrite) (struct SPDY_Session *session);
+
+
+/**
+ * Called just after frames have been processed and written
+ * to the socket.
+ *
+ * @param session
+ * @param was_written has the same value as the write function for the
+ * session will return
+ * @return returned value will be used by the write function to return
+ */
+typedef int
+(*SPDYF_IOAfterWrite) (struct SPDY_Session *session,
+ int was_written);
+
+
+/**
+ * Sets callbacks for the daemon with regard to the IO subsystem.
+ *
+ * @param daemon
+ * @param io_subsystem the IO subsystem that will
+ * be initialized and used by daemon.
+ * @return SPDY_YES on success or SPDY_NO otherwise
+ */
+int
+SPDYF_io_set_daemon(struct SPDY_Daemon *daemon,
+ enum SPDY_IO_SUBSYSTEM io_subsystem);
+
+
+/**
+ * Sets callbacks for the session with regard to the IO subsystem.
+ *
+ * @param session
+ * @param io_subsystem the IO subsystem that will
+ * be initialized and used by session.
+ * @return SPDY_YES on success or SPDY_NO otherwise
+ */
+int
+SPDYF_io_set_session(struct SPDY_Session *session,
+ enum SPDY_IO_SUBSYSTEM io_subsystem);
+
+#endif
diff --git a/src/microspdy/io_openssl.c b/src/microspdy/io_openssl.c
new file mode 100644
index 0000000..f71a923
--- /dev/null
+++ b/src/microspdy/io_openssl.c
@@ -0,0 +1,280 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file io_openssl.c
+ * @brief TLS handling using libssl. The current code assumes that
+ * blocking I/O is in use.
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "internal.h"
+#include "session.h"
+#include "io_openssl.h"
+
+
+/**
+ * Callback to advertise spdy ver. 3 in Next Protocol Negotiation
+ *
+ * @param ssl openssl context for a connection
+ * @param out must be set to the raw data that is advertised in NPN
+ * @param outlen must be set to size of out
+ * @param arg
+ * @return SSL_TLSEXT_ERR_OK to do advertising
+ */
+static int
+spdyf_next_protos_advertised_cb (SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg)
+{
+ (void)ssl;
+ (void)arg;
+ static unsigned char npn_spdy3[] = {0x06, // length of "spdy/3"
+ 0x73,0x70,0x64,0x79,0x2f,0x33};// spdy/3
+
+ *out = npn_spdy3;
+ *outlen = 7; // total length of npn_spdy3
+ return SSL_TLSEXT_ERR_OK;
+}
+
+
+void
+SPDYF_openssl_global_init()
+{
+ //error strings are now not used by the lib
+ //SSL_load_error_strings();
+ //init libssl
+ SSL_library_init(); //always returns 1
+ //the table for looking up algos is not used now by the lib
+ //OpenSSL_add_all_algorithms();
+}
+
+
+void
+SPDYF_openssl_global_deinit()
+{
+ //if SSL_load_error_strings was called
+ //ERR_free_strings();
+ //if OpenSSL_add_all_algorithms was called
+ //EVP_cleanup();
+}
+
+
+int
+SPDYF_openssl_init(struct SPDY_Daemon *daemon)
+{
+ int options;
+ //create ssl context. TLSv1 used
+ if(NULL == (daemon->io_context = SSL_CTX_new(TLSv1_server_method())))
+ {
+ SPDYF_DEBUG("Couldn't create ssl context");
+ return SPDY_NO;
+ }
+ //set options for tls
+ //TODO DH is not enabled for easier debugging
+ //SSL_CTX_set_options(daemon->io_context, SSL_OP_SINGLE_DH_USE);
+
+ //TODO here session tickets are disabled for easier debuging with
+ //wireshark when using Chrome
+ // SSL_OP_NO_COMPRESSION disables TLS compression to avoid CRIME attack
+ options = SSL_OP_NO_TICKET;
+#ifdef SSL_OP_NO_COMPRESSION
+ options |= SSL_OP_NO_COMPRESSION;
+#elif OPENSSL_VERSION_NUMBER >= 0x00908000L /* workaround for OpenSSL 0.9.8 */
+ sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
+#endif
+
+ SSL_CTX_set_options(daemon->io_context, options);
+ if(1 != SSL_CTX_use_certificate_file(daemon->io_context, daemon->certfile , SSL_FILETYPE_PEM))
+ {
+ SPDYF_DEBUG("Couldn't load the cert file");
+ SSL_CTX_free(daemon->io_context);
+ return SPDY_NO;
+ }
+ if(1 != SSL_CTX_use_PrivateKey_file(daemon->io_context, daemon->keyfile, SSL_FILETYPE_PEM))
+ {
+ SPDYF_DEBUG("Couldn't load the name file");
+ SSL_CTX_free(daemon->io_context);
+ return SPDY_NO;
+ }
+ SSL_CTX_set_next_protos_advertised_cb(daemon->io_context, &spdyf_next_protos_advertised_cb, NULL);
+ if (1 != SSL_CTX_set_cipher_list(daemon->io_context, "HIGH"))
+ {
+ SPDYF_DEBUG("Couldn't set the desired cipher list");
+ SSL_CTX_free(daemon->io_context);
+ return SPDY_NO;
+ }
+
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_openssl_deinit(struct SPDY_Daemon *daemon)
+{
+ SSL_CTX_free(daemon->io_context);
+}
+
+
+int
+SPDYF_openssl_new_session(struct SPDY_Session *session)
+{
+ int ret;
+
+ if(NULL == (session->io_context = SSL_new(session->daemon->io_context)))
+ {
+ SPDYF_DEBUG("Couldn't create ssl structure");
+ return SPDY_NO;
+ }
+ if(1 != (ret = SSL_set_fd(session->io_context, session->socket_fd)))
+ {
+ SPDYF_DEBUG("SSL_set_fd %i",ret);
+ SSL_free(session->io_context);
+ session->io_context = NULL;
+ return SPDY_NO;
+ }
+
+ //for non-blocking I/O SSL_accept may return -1
+ //and this function won't work
+ if(1 != (ret = SSL_accept(session->io_context)))
+ {
+ SPDYF_DEBUG("SSL_accept %i",ret);
+ SSL_free(session->io_context);
+ session->io_context = NULL;
+ return SPDY_NO;
+ }
+ /* alternatively
+ SSL_set_accept_state(session->io_context);
+ * may be called and then the negotiation will be done on reading
+ */
+
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_openssl_close_session(struct SPDY_Session *session)
+{
+ //SSL_shutdown sends TLS "close notify" as in TLS standard.
+ //The function may fail as it waits for the other party to also close
+ //the TLS session. The lib just sends it and will close the socket
+ //after that because the browsers don't seem to care much about
+ //"close notify"
+ SSL_shutdown(session->io_context);
+
+ SSL_free(session->io_context);
+}
+
+
+int
+SPDYF_openssl_recv(struct SPDY_Session *session,
+ void * buffer,
+ size_t size)
+{
+ int ret;
+ int n = SSL_read(session->io_context,
+ buffer,
+ size);
+ //if(n > 0) SPDYF_DEBUG("recvd: %i",n);
+ if (n <= 0)
+ {
+ ret = SSL_get_error(session->io_context, n);
+ switch(ret)
+ {
+ case SSL_ERROR_ZERO_RETURN:
+ return 0;
+
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ return SPDY_IO_ERROR_AGAIN;
+
+ case SSL_ERROR_SYSCALL:
+ if(EINTR == errno)
+ return SPDY_IO_ERROR_AGAIN;
+ return SPDY_IO_ERROR_ERROR;
+ default:
+ return SPDY_IO_ERROR_ERROR;
+ }
+ }
+
+ return n;
+}
+
+
+int
+SPDYF_openssl_send(struct SPDY_Session *session,
+ const void * buffer,
+ size_t size)
+{
+ int ret;
+
+ int n = SSL_write(session->io_context,
+ buffer,
+ size);
+ //if(n > 0) SPDYF_DEBUG("sent: %i",n);
+ if (n <= 0)
+ {
+ ret = SSL_get_error(session->io_context, n);
+ switch(ret)
+ {
+ case SSL_ERROR_ZERO_RETURN:
+ return 0;
+
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ return SPDY_IO_ERROR_AGAIN;
+
+ case SSL_ERROR_SYSCALL:
+ if(EINTR == errno)
+ return SPDY_IO_ERROR_AGAIN;
+ return SPDY_IO_ERROR_ERROR;
+ default:
+ return SPDY_IO_ERROR_ERROR;
+ }
+ }
+
+ return n;
+}
+
+
+int
+SPDYF_openssl_is_pending(struct SPDY_Session *session)
+{
+ /* From openssl docs:
+ * BUGS
+SSL_pending() takes into account only bytes from the TLS/SSL record that is currently being processed (if any). If the SSL object's read_ahead flag is set, additional protocol bytes may have been read containing more TLS/SSL records; these are ignored by SSL_pending().
+ */
+ return SSL_pending(session->io_context) > 0 ? SPDY_YES : SPDY_NO;
+}
+
+
+int
+SPDYF_openssl_before_write(struct SPDY_Session *session)
+{
+ (void)session;
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_openssl_after_write(struct SPDY_Session *session, int was_written)
+{
+ (void)session;
+
+ return was_written;
+}
diff --git a/src/microspdy/io_openssl.h b/src/microspdy/io_openssl.h
new file mode 100644
index 0000000..a4e9429
--- /dev/null
+++ b/src/microspdy/io_openssl.h
@@ -0,0 +1,166 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file io_openssl.h
+ * @brief TLS handling. openssl with NPN is used, but as long as the
+ * functions conform to this interface file, other libraries
+ * can be used.
+ * @author Andrey Uzunov
+ */
+
+#ifndef IO_OPENSSL_H
+#define IO_OPENSSL_H
+
+#include "platform.h"
+#include "io.h"
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/rand.h>
+
+
+/**
+ * Global initializing of openssl. Must be called only once in the program.
+ *
+ */
+void
+SPDYF_openssl_global_init();
+
+
+/**
+ * Global deinitializing of openssl for the whole program. Should be called
+ * at the end of the program.
+ *
+ */
+void
+SPDYF_openssl_global_deinit();
+
+
+/**
+ * Initializing of openssl for a specific daemon.
+ * Must be called when the daemon starts.
+ *
+ * @param daemon SPDY_Daemon for which openssl will be used. Daemon's
+ * certificate and key file are used.
+ * @return SPDY_YES on success or SPDY_NO on error
+ */
+int
+SPDYF_openssl_init(struct SPDY_Daemon *daemon);
+
+
+/**
+ * Deinitializing openssl for a daemon. Should be called
+ * when the deamon is stopped.
+ *
+ * @param daemon SPDY_Daemon which is being stopped
+ */
+void
+SPDYF_openssl_deinit(struct SPDY_Daemon *daemon);
+
+
+/**
+ * Initializing openssl for a specific connection. Must be called
+ * after the connection has been accepted.
+ *
+ * @param session SPDY_Session whose socket will be used by openssl
+ * @return SPDY_NO if some openssl funcs fail. SPDY_YES otherwise
+ */
+int
+SPDYF_openssl_new_session(struct SPDY_Session *session);
+
+
+/**
+ * Deinitializing openssl for a specific connection. Should be called
+ * closing session's socket.
+ *
+ * @param session SPDY_Session whose socket is used by openssl
+ */
+void
+SPDYF_openssl_close_session(struct SPDY_Session *session);
+
+
+/**
+ * Reading from a TLS socket. Reads available data and put it to the
+ * buffer.
+ *
+ * @param session for which data is received
+ * @param buffer where data from the socket will be written to
+ * @param size of the buffer
+ * @return number of bytes (at most size) read from the TLS connection
+ * 0 if the other party has closed the connection
+ * SPDY_IO_ERROR code on error
+ */
+int
+SPDYF_openssl_recv(struct SPDY_Session *session,
+ void * buffer,
+ size_t size);
+
+
+/**
+ * Writing to a TLS socket. Writes the data given into the buffer to the
+ * TLS socket.
+ *
+ * @param session whose context is used
+ * @param buffer from where data will be written to the socket
+ * @param size number of bytes to be taken from the buffer
+ * @return number of bytes (at most size) from the buffer that has been
+ * written to the TLS connection
+ * 0 if the other party has closed the connection
+ * SPDY_IO_ERROR code on error
+ */
+int
+SPDYF_openssl_send(struct SPDY_Session *session,
+ const void * buffer,
+ size_t size);
+
+
+/**
+ * Checks if there is data staying in the buffers of the underlying
+ * system that waits to be read.
+ *
+ * @param session which is checked
+ * @return SPDY_YES if data is pending or SPDY_NO otherwise
+ */
+int
+SPDYF_openssl_is_pending(struct SPDY_Session *session);
+
+
+/**
+ * Nothing.
+ *
+ * @param session
+ * @return SPDY_NO if writing must not happen in the call;
+ * SPDY_YES otherwise
+ */
+int
+SPDYF_openssl_before_write(struct SPDY_Session *session);
+
+
+/**
+ * Nothing.
+ *
+ * @param session
+ * @param was_written has the same value as the write function for the
+ * session will return
+ * @return returned value will be used by the write function to return
+ */
+int
+SPDYF_openssl_after_write(struct SPDY_Session *session, int was_written);
+
+
+#endif
diff --git a/src/microspdy/io_raw.c b/src/microspdy/io_raw.c
new file mode 100644
index 0000000..722f347
--- /dev/null
+++ b/src/microspdy/io_raw.c
@@ -0,0 +1,194 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file io_raw.c
+ * @brief IO for SPDY without TLS.
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "internal.h"
+#include "session.h"
+#include "io_raw.h"
+//TODO put in in the right place
+#include <netinet/tcp.h>
+
+
+void
+SPDYF_raw_global_init()
+{
+}
+
+
+void
+SPDYF_raw_global_deinit()
+{
+}
+
+
+int
+SPDYF_raw_init(struct SPDY_Daemon *daemon)
+{
+ (void)daemon;
+
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_raw_deinit(struct SPDY_Daemon *daemon)
+{
+ (void)daemon;
+}
+
+
+int
+SPDYF_raw_new_session(struct SPDY_Session *session)
+{
+ int fd_flags;
+ int val = 1;
+ int ret;
+
+ //setting the socket to be non-blocking
+ fd_flags = fcntl (session->socket_fd, F_GETFL);
+ if ( -1 == fd_flags
+ || 0 != fcntl (session->socket_fd, F_SETFL, fd_flags | O_NONBLOCK))
+ SPDYF_DEBUG("WARNING: Couldn't set the new connection to be non-blocking");
+
+ if(SPDY_DAEMON_FLAG_NO_DELAY & session->daemon->flags)
+ {
+ ret = setsockopt(session->socket_fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
+ if(-1 == ret)
+ SPDYF_DEBUG("WARNING: Couldn't set the new connection to TCP_NODELAY");
+ }
+
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_raw_close_session(struct SPDY_Session *session)
+{
+ (void)session;
+}
+
+
+int
+SPDYF_raw_recv(struct SPDY_Session *session,
+ void * buffer,
+ size_t size)
+{
+ int n = read(session->socket_fd,
+ buffer,
+ size);
+ //if(n > 0) SPDYF_DEBUG("recvd: %i",n);
+ if (n < 0)
+ {
+ switch(errno)
+ {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ return SPDY_IO_ERROR_AGAIN;
+
+ default:
+ return SPDY_IO_ERROR_ERROR;
+ }
+ }
+
+ return n;
+}
+
+
+int
+SPDYF_raw_send(struct SPDY_Session *session,
+ const void * buffer,
+ size_t size)
+{
+ int n = write(session->socket_fd,
+ buffer,
+ size);
+ //if(n > 0) SPDYF_DEBUG("sent: %i",n);
+ if (n < 0)
+ {
+ switch(errno)
+ {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ return SPDY_IO_ERROR_AGAIN;
+
+ default:
+ return SPDY_IO_ERROR_ERROR;
+ }
+ }
+
+ return n;
+}
+
+
+int
+SPDYF_raw_is_pending(struct SPDY_Session *session)
+{
+ (void)session;
+
+ return SPDY_NO;
+}
+
+
+int
+SPDYF_raw_before_write(struct SPDY_Session *session)
+{
+#if HAVE_DECL_TCP_CORK
+ if(0 == (SPDY_DAEMON_FLAG_NO_DELAY & session->daemon->flags))
+ {
+ int val = 1;
+ int ret;
+
+ ret = setsockopt(session->socket_fd, IPPROTO_TCP, TCP_CORK, &val, (socklen_t)sizeof(val));
+ if(-1 == ret)
+ SPDYF_DEBUG("WARNING: Couldn't set the new connection to TCP_CORK");
+ }
+#endif
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_raw_after_write(struct SPDY_Session *session, int was_written)
+{
+#if HAVE_DECL_TCP_CORK
+ if(SPDY_YES == was_written && 0 == (SPDY_DAEMON_FLAG_NO_DELAY & session->daemon->flags))
+ {
+ int val = 0;
+ int ret;
+
+ ret = setsockopt(session->socket_fd, IPPROTO_TCP, TCP_CORK, &val, (socklen_t)sizeof(val));
+ if(-1 == ret)
+ SPDYF_DEBUG("WARNING: Couldn't unset the new connection to TCP_CORK");
+ }
+
+#endif
+ return was_written;
+}
diff --git a/src/microspdy/io_raw.h b/src/microspdy/io_raw.h
new file mode 100644
index 0000000..8ca830b
--- /dev/null
+++ b/src/microspdy/io_raw.h
@@ -0,0 +1,158 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file io_raw.h
+ * @brief IO for SPDY without TLS.
+ * @author Andrey Uzunov
+ */
+
+#ifndef IO_RAW_H
+#define IO_RAW_H
+
+#include "platform.h"
+
+
+/**
+ * Must be called only once in the program.
+ *
+ */
+void
+SPDYF_raw_global_init();
+
+
+/**
+ * Should be called
+ * at the end of the program.
+ *
+ */
+void
+SPDYF_raw_global_deinit();
+
+
+/**
+ * Must be called when the daemon starts.
+ *
+ * @param daemon SPDY_Daemon
+ * @return SPDY_YES on success or SPDY_NO on error
+ */
+int
+SPDYF_raw_init(struct SPDY_Daemon *daemon);
+
+
+/**
+ * Should be called
+ * when the deamon is stopped.
+ *
+ * @param daemon SPDY_Daemon which is being stopped
+ */
+void
+SPDYF_raw_deinit(struct SPDY_Daemon *daemon);
+
+
+/**
+ * Must be called
+ * after the connection has been accepted.
+ *
+ * @param session SPDY_Session whose socket will be used
+ * @return SPDY_NO if some funcs fail. SPDY_YES otherwise
+ */
+int
+SPDYF_raw_new_session(struct SPDY_Session *session);
+
+
+/**
+ * Should be called
+ * closing session's socket.
+ *
+ * @param session SPDY_Session whose socket is used
+ */
+void
+SPDYF_raw_close_session(struct SPDY_Session *session);
+
+
+/**
+ * Reading from socket. Reads available data and put it to the
+ * buffer.
+ *
+ * @param session for which data is received
+ * @param buffer where data from the socket will be written to
+ * @param size of the buffer
+ * @return number of bytes (at most size) read from the connection
+ * 0 if the other party has closed the connection
+ * SPDY_IO_ERROR code on error
+ */
+int
+SPDYF_raw_recv(struct SPDY_Session *session,
+ void * buffer,
+ size_t size);
+
+
+/**
+ * Writing to socket. Writes the data given into the buffer to the
+ * socket.
+ *
+ * @param session whose context is used
+ * @param buffer from where data will be written to the socket
+ * @param size number of bytes to be taken from the buffer
+ * @return number of bytes (at most size) from the buffer that has been
+ * written to the connection
+ * 0 if the other party has closed the connection
+ * SPDY_IO_ERROR code on error
+ */
+int
+SPDYF_raw_send(struct SPDY_Session *session,
+ const void * buffer,
+ size_t size);
+
+
+/**
+ * Checks if there is data staying in the buffers of the underlying
+ * system that waits to be read. Always returns SPDY_NO, as we do not
+ * use a subsystem here.
+ *
+ * @param session which is checked
+ * @return SPDY_YES if data is pending or SPDY_NO otherwise
+ */
+int
+SPDYF_raw_is_pending(struct SPDY_Session *session);
+
+
+/**
+ * Sets TCP_CORK.
+ *
+ * @param session
+ * @return SPDY_NO if writing must not happen in the call;
+ * SPDY_YES otherwise
+ */
+int
+SPDYF_raw_before_write(struct SPDY_Session *session);
+
+
+/**
+ * Unsets TCP_CORK.
+ *
+ * @param session
+ * @param was_written has the same value as the write function for the
+ * session will return
+ * @return returned value will be used by the write function to return
+ */
+int
+SPDYF_raw_after_write(struct SPDY_Session *session, int was_written);
+
+#endif
diff --git a/src/microspdy/session.c b/src/microspdy/session.c
new file mode 100644
index 0000000..3714c25
--- /dev/null
+++ b/src/microspdy/session.c
@@ -0,0 +1,1769 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file session.c
+ * @brief TCP connection/SPDY session handling. So far most of the
+ * functions for handling SPDY framing layer are here.
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "structures.h"
+#include "internal.h"
+#include "session.h"
+#include "compression.h"
+#include "stream.h"
+#include "io.h"
+
+
+/**
+ * Handler for reading the full SYN_STREAM frame after we know that
+ * the frame is such.
+ * The function waits for the full frame and then changes status
+ * of the session. New stream is created.
+ *
+ * @param session SPDY_Session whose read buffer is used.
+ */
+static void
+spdyf_handler_read_syn_stream (struct SPDY_Session *session)
+{
+ size_t name_value_strm_size = 0;
+ unsigned int compressed_data_size;
+ int ret;
+ void *name_value_strm = NULL;
+ struct SPDYF_Control_Frame *frame;
+ struct SPDY_NameValue *headers;
+
+ SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status
+ || SPDY_SESSION_STATUS_WAIT_FOR_BODY == session->status,
+ "the function is called wrong");
+
+ frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls;
+
+ //handle subheaders
+ if(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status)
+ {
+ if(0 == frame->length)
+ {
+ //protocol error: incomplete frame
+ //we just ignore it since there is no stream id for which to
+ //send RST_STREAM
+ //TODO maybe GOAWAY and closing session is appropriate
+ SPDYF_DEBUG("zero long SYN_STREAM received");
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+ free(frame);
+ return;
+ }
+
+ if(SPDY_YES != SPDYF_stream_new(session))
+ {
+ /* waiting for some more fields to create new stream
+ or something went wrong, SPDYF_stream_new has handled the
+ situation */
+ return;
+ }
+
+ session->current_stream_id = session->streams_head->stream_id;
+ if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE)
+ {
+ //TODO no need to create stream if this happens
+ session->status = SPDY_SESSION_STATUS_IGNORE_BYTES;
+ return;
+ }
+ else
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY;
+ }
+
+ //handle body
+
+ //start reading the compressed name/value pairs (http headers)
+ compressed_data_size = frame->length //everything after length field
+ - 10;//4B stream id, 4B assoc strem id, 2B priority, unused and slot
+
+ if(session->read_buffer_offset - session->read_buffer_beginning < compressed_data_size)
+ {
+ // the full frame is not yet here, try later
+ return;
+ }
+
+ if ( (compressed_data_size > 0) &&
+ (SPDY_YES !=
+ SPDYF_zlib_inflate(&session->zlib_recv_stream,
+ session->read_buffer + session->read_buffer_beginning,
+ compressed_data_size,
+ &name_value_strm,
+ &name_value_strm_size)) )
+ {
+ /* something went wrong on inflating,
+ * the state of the stream for decompression is unknown
+ * and we may not be able to read anything more received on
+ * this session,
+ * so it is better to close the session */
+ free(name_value_strm);
+ free(frame);
+
+ /* mark the session for closing and close it, when
+ * everything on the output queue is already written */
+ session->status = SPDY_SESSION_STATUS_FLUSHING;
+
+ SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_INTERNAL_ERROR, false);
+
+ return;
+ }
+
+ if(0 == name_value_strm_size || 0 == compressed_data_size)
+ {
+ //Protocol error: send RST_STREAM
+ if(SPDY_YES != SPDYF_prepare_rst_stream(session, session->streams_head,
+ SPDY_RST_STREAM_STATUS_PROTOCOL_ERROR))
+ {
+ //no memory, try later to send RST
+ free(name_value_strm);
+ return;
+ }
+ }
+ else
+ {
+ ret = SPDYF_name_value_from_stream(name_value_strm, name_value_strm_size, &headers);
+ if(SPDY_NO == ret)
+ {
+ //memory error, try later
+ free(name_value_strm);
+ return;
+ }
+
+ session->streams_head->headers = headers;
+ //inform the application layer for the new stream received
+ if(SPDY_YES != session->daemon->fnew_stream_cb(session->daemon->fcls, session->streams_head))
+ {
+ //memory error, try later
+ free(name_value_strm);
+ return;
+ }
+
+ session->read_buffer_beginning += compressed_data_size;
+ }
+
+ //SPDYF_DEBUG("syn_stream received: id %i", session->current_stream_id);
+
+ //change state to wait for new frame
+ free(name_value_strm);
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+ free(frame);
+}
+
+
+/**
+ * Handler for reading the GOAWAY frame after we know that
+ * the frame is such.
+ * The function waits for the full frame and then changes status
+ * of the session.
+ *
+ * @param session SPDY_Session whose read buffer is used.
+ */
+static void
+spdyf_handler_read_goaway (struct SPDY_Session *session)
+{
+ struct SPDYF_Control_Frame *frame;
+ uint32_t last_good_stream_id;
+ uint32_t status_int;
+ enum SPDY_GOAWAY_STATUS status;
+
+ SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status,
+ "the function is called wrong");
+
+ frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls;
+
+ if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE)
+ {
+ //this is a protocol error/attack
+ session->status = SPDY_SESSION_STATUS_IGNORE_BYTES;
+ return;
+ }
+
+ if(0 != frame->flags || 8 != frame->length)
+ {
+ //this is a protocol error
+ SPDYF_DEBUG("wrong GOAWAY received");
+ //anyway, it will be handled
+ }
+
+ if((session->read_buffer_offset - session->read_buffer_beginning) < frame->length)
+ {
+ //not all fields are received
+ //try later
+ return;
+ }
+
+ //mark that the session is almost closed
+ session->is_goaway_received = true;
+
+ if(8 == frame->length)
+ {
+ memcpy(&last_good_stream_id, session->read_buffer + session->read_buffer_beginning, 4);
+ last_good_stream_id = NTOH31(last_good_stream_id);
+ session->read_buffer_beginning += 4;
+
+ memcpy(&status_int, session->read_buffer + session->read_buffer_beginning, 4);
+ status = ntohl(status_int);
+ session->read_buffer_beginning += 4;
+
+ //TODO do something with last_good
+
+ //SPDYF_DEBUG("Received GOAWAY; status=%i; lastgood=%i",status,last_good_stream_id);
+
+ //do something according to the status
+ //TODO
+ switch(status)
+ {
+ case SPDY_GOAWAY_STATUS_OK:
+ break;
+ case SPDY_GOAWAY_STATUS_PROTOCOL_ERROR:
+ break;
+ case SPDY_GOAWAY_STATUS_INTERNAL_ERROR:
+ break;
+ }
+
+ //SPDYF_DEBUG("goaway received: status %i", status);
+ }
+
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+ free(frame);
+}
+
+
+/**
+ * Handler for reading RST_STREAM frames. After receiving the frame
+ * the stream moves into closed state and status
+ * of the session is changed. Frames, belonging to this stream, which
+ * are still at the output queue, will be ignored later.
+ *
+ * @param session SPDY_Session whose read buffer is used.
+ */
+static void
+spdyf_handler_read_rst_stream (struct SPDY_Session *session)
+{
+ struct SPDYF_Control_Frame *frame;
+ uint32_t stream_id;
+ int32_t status_int;
+ //enum SPDY_RST_STREAM_STATUS status; //for debug
+ struct SPDYF_Stream *stream;
+
+ SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status,
+ "the function is called wrong");
+
+ frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls;
+
+ if(0 != frame->flags || 8 != frame->length)
+ {
+ //this is a protocol error
+ SPDYF_DEBUG("wrong RST_STREAM received");
+ //ignore as a large frame
+ session->status = SPDY_SESSION_STATUS_IGNORE_BYTES;
+ return;
+ }
+
+ if((session->read_buffer_offset - session->read_buffer_beginning) < frame->length)
+ {
+ //not all fields are received
+ //try later
+ return;
+ }
+
+ memcpy(&stream_id, session->read_buffer + session->read_buffer_beginning, 4);
+ stream_id = NTOH31(stream_id);
+ session->read_buffer_beginning += 4;
+
+ memcpy(&status_int, session->read_buffer + session->read_buffer_beginning, 4);
+ //status = ntohl(status_int); //for debug
+ session->read_buffer_beginning += 4;
+
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+ free(frame);
+
+ //mark the stream as closed
+ stream = session->streams_head;
+ while(NULL != stream)
+ {
+ if(stream_id == stream->stream_id)
+ {
+ stream->is_in_closed = true;
+ stream->is_out_closed = true;
+ break;
+ }
+ stream = stream->next;
+ }
+
+ //SPDYF_DEBUG("Received RST_STREAM; status=%i; id=%i",status,stream_id);
+
+ //do something according to the status
+ //TODO
+ /*switch(status)
+ {
+ case SPDY_RST_STREAM_STATUS_PROTOCOL_ERROR:
+ break;
+ }*/
+}
+
+
+/**
+ * Handler for reading DATA frames. In requests they are used for POST
+ * arguments.
+ *
+ * @param session SPDY_Session whose read buffer is used.
+ */
+static void
+spdyf_handler_read_data (struct SPDY_Session *session)
+{
+ int ret;
+ struct SPDYF_Data_Frame * frame;
+ struct SPDYF_Stream * stream;
+
+ SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status
+ || SPDY_SESSION_STATUS_WAIT_FOR_BODY == session->status,
+ "the function is called wrong");
+
+ //SPDYF_DEBUG("DATA frame received (POST?). Ignoring");
+
+ //SPDYF_SIGINT("");
+
+ frame = (struct SPDYF_Data_Frame *)session->frame_handler_cls;
+
+ //handle subheaders
+ if(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status)
+ {
+ if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE)
+ {
+ session->status = SPDY_SESSION_STATUS_IGNORE_BYTES;
+ return;
+ }
+ else
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY;
+ }
+
+ //handle body
+
+ if(session->read_buffer_offset - session->read_buffer_beginning
+ >= frame->length)
+ {
+ stream = SPDYF_stream_find(frame->stream_id, session);
+
+ if(NULL == stream || stream->is_in_closed || NULL == session->daemon->received_data_cb)
+ {
+ if(NULL == session->daemon->received_data_cb)
+ SPDYF_DEBUG("No callback for DATA frame set; Ignoring DATA frame!");
+
+ //TODO send error?
+
+ //TODO for now ignore frame
+ session->read_buffer_beginning += frame->length;
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+ free(frame);
+ return;
+ }
+
+ ret = session->daemon->freceived_data_cb(session->daemon->cls,
+ stream,
+ session->read_buffer + session->read_buffer_beginning,
+ frame->length,
+ 0 == (SPDY_DATA_FLAG_FIN & frame->flags));
+
+ session->read_buffer_beginning += frame->length;
+
+ stream->window_size -= frame->length;
+
+ //TODO close in and send rst maybe
+ SPDYF_ASSERT(SPDY_YES == ret, "Cancel POST data is not yet implemented");
+
+ if(SPDY_DATA_FLAG_FIN & frame->flags)
+ {
+ stream->is_in_closed = true;
+ }
+ else if(stream->window_size < SPDYF_INITIAL_WINDOW_SIZE / 2)
+ {
+ //very simple implementation of flow control
+ //when the window's size is under the half of the initial value,
+ //increase it again up to the initial value
+
+ //prepare WINDOW_UPDATE
+ if(SPDY_YES == SPDYF_prepare_window_update(session, stream,
+ SPDYF_INITIAL_WINDOW_SIZE - stream->window_size))
+ {
+ stream->window_size = SPDYF_INITIAL_WINDOW_SIZE;
+ }
+ //else: do it later
+ }
+
+ //SPDYF_DEBUG("data received: id %i", frame->stream_id);
+
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+ free(frame);
+ }
+}
+
+
+int
+SPDYF_handler_write_syn_reply (struct SPDY_Session *session)
+{
+ struct SPDYF_Response_Queue *response_queue = session->response_queue_head;
+ struct SPDYF_Stream *stream = response_queue->stream;
+ struct SPDYF_Control_Frame control_frame;
+ void *compressed_headers = NULL;
+ size_t compressed_headers_size=0;
+ size_t used_data=0;
+ size_t total_size;
+ uint32_t stream_id_nbo;
+
+ SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment");
+
+ memcpy(&control_frame, response_queue->control_frame, sizeof(control_frame));
+
+ if(SPDY_YES != SPDYF_zlib_deflate(&session->zlib_send_stream,
+ response_queue->data,
+ response_queue->data_size,
+ &used_data,
+ &compressed_headers,
+ &compressed_headers_size))
+ {
+ /* something went wrong on compressing,
+ * the state of the stream for compression is unknown
+ * and we may not be able to send anything more on
+ * this session,
+ * so it is better to close the session right now */
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+
+ free(compressed_headers);
+
+ return SPDY_NO;
+ }
+
+ //TODO do we need this used_Data
+ SPDYF_ASSERT(used_data == response_queue->data_size, "not everything was used by zlib");
+
+ total_size = sizeof(struct SPDYF_Control_Frame) //SPDY header
+ + 4 // stream id as "subheader"
+ + compressed_headers_size;
+
+ if(NULL == (session->write_buffer = malloc(total_size)))
+ {
+ /* no memory
+ * since we do not save the compressed data anywhere and
+ * the sending zlib stream is already in new state, we must
+ * close the session */
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+
+ free(compressed_headers);
+
+ return SPDY_NO;
+ }
+ session->write_buffer_beginning = 0;
+ session->write_buffer_offset = 0;
+ session->write_buffer_size = total_size;
+
+ control_frame.length = compressed_headers_size + 4; // compressed data + stream_id
+ SPDYF_CONTROL_FRAME_HTON(&control_frame);
+
+ //put frame headers to write buffer
+ memcpy(session->write_buffer + session->write_buffer_offset,&control_frame,sizeof(struct SPDYF_Control_Frame));
+ session->write_buffer_offset += sizeof(struct SPDYF_Control_Frame);
+
+ //put stream id to write buffer
+ stream_id_nbo = HTON31(stream->stream_id);
+ memcpy(session->write_buffer + session->write_buffer_offset, &stream_id_nbo, 4);
+ session->write_buffer_offset += 4;
+
+ //put compressed name/value pairs to write buffer
+ memcpy(session->write_buffer + session->write_buffer_offset, compressed_headers, compressed_headers_size);
+ session->write_buffer_offset += compressed_headers_size;
+
+ SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1");
+ SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2");
+
+ //DEBUG CODE, break compression state to see what happens
+/* SPDYF_zlib_deflate(&session->zlib_send_stream,
+ "1234567890",
+ 10,
+ &used_data,
+ &compressed_headers,
+ &compressed_headers_size);
+*/
+ free(compressed_headers);
+
+ session->last_replied_to_stream_id = stream->stream_id;
+
+ //SPDYF_DEBUG("syn_reply sent: id %i", stream->stream_id);
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_handler_write_goaway (struct SPDY_Session *session)
+{
+ struct SPDYF_Response_Queue *response_queue = session->response_queue_head;
+ struct SPDYF_Control_Frame control_frame;
+ size_t total_size;
+ int last_good_stream_id;
+
+ SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment");
+
+ memcpy(&control_frame, response_queue->control_frame, sizeof(control_frame));
+
+ session->is_goaway_sent = true;
+
+ total_size = sizeof(struct SPDYF_Control_Frame) //SPDY header
+ + 4 // last good stream id as "subheader"
+ + 4; // status code as "subheader"
+
+ if(NULL == (session->write_buffer = malloc(total_size)))
+ {
+ return SPDY_NO;
+ }
+ session->write_buffer_beginning = 0;
+ session->write_buffer_offset = 0;
+ session->write_buffer_size = total_size;
+
+ control_frame.length = 8; // always for GOAWAY
+ SPDYF_CONTROL_FRAME_HTON(&control_frame);
+
+ //put frame headers to write buffer
+ memcpy(session->write_buffer + session->write_buffer_offset,&control_frame,sizeof(struct SPDYF_Control_Frame));
+ session->write_buffer_offset += sizeof(struct SPDYF_Control_Frame);
+
+ //put last good stream id to write buffer
+ last_good_stream_id = HTON31(session->last_replied_to_stream_id);
+ memcpy(session->write_buffer + session->write_buffer_offset, &last_good_stream_id, 4);
+ session->write_buffer_offset += 4;
+
+ //put "data" to write buffer. This is the status
+ memcpy(session->write_buffer + session->write_buffer_offset, response_queue->data, 4);
+ session->write_buffer_offset += 4;
+ //data is not freed by the destroy function so:
+ //free(response_queue->data);
+
+ //SPDYF_DEBUG("goaway sent: status %i", NTOH31(*(uint32_t*)(response_queue->data)));
+
+ SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1");
+ SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2");
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_handler_write_data (struct SPDY_Session *session)
+{
+ struct SPDYF_Response_Queue *response_queue = session->response_queue_head;
+ struct SPDYF_Response_Queue *new_response_queue;
+ size_t total_size;
+ struct SPDYF_Data_Frame data_frame;
+ ssize_t ret;
+ bool more;
+
+ SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment");
+
+ memcpy(&data_frame, response_queue->data_frame, sizeof(data_frame));
+
+ if(NULL == response_queue->response->rcb)
+ {
+ //standard response with data into the struct
+ SPDYF_ASSERT(NULL != response_queue->data, "no data for the response");
+
+ total_size = sizeof(struct SPDYF_Data_Frame) //SPDY header
+ + response_queue->data_size;
+
+ if(NULL == (session->write_buffer = malloc(total_size)))
+ {
+ return SPDY_NO;
+ }
+ session->write_buffer_beginning = 0;
+ session->write_buffer_offset = 0;
+ session->write_buffer_size = total_size;
+
+ data_frame.length = response_queue->data_size;
+ SPDYF_DATA_FRAME_HTON(&data_frame);
+
+ //put SPDY headers to the writing buffer
+ memcpy(session->write_buffer + session->write_buffer_offset,&data_frame,sizeof(struct SPDYF_Data_Frame));
+ session->write_buffer_offset += sizeof(struct SPDYF_Data_Frame);
+
+ //put data to the writing buffer
+ memcpy(session->write_buffer + session->write_buffer_offset, response_queue->data, response_queue->data_size);
+ session->write_buffer_offset += response_queue->data_size;
+ }
+ else
+ {
+ /* response with callbacks. The lib will produce more than 1
+ * data frames
+ */
+
+ total_size = sizeof(struct SPDYF_Data_Frame) //SPDY header
+ + SPDY_MAX_SUPPORTED_FRAME_SIZE; //max possible size
+
+ if(NULL == (session->write_buffer = malloc(total_size)))
+ {
+ return SPDY_NO;
+ }
+ session->write_buffer_beginning = 0;
+ session->write_buffer_offset = 0;
+ session->write_buffer_size = total_size;
+
+ ret = response_queue->response->rcb(response_queue->response->rcb_cls,
+ session->write_buffer + sizeof(struct SPDYF_Data_Frame),
+ response_queue->response->rcb_block_size,
+ &more);
+
+ if(ret < 0 || ret > response_queue->response->rcb_block_size)
+ {
+ free(session->write_buffer);
+ session->write_buffer = NULL;
+
+ //send RST_STREAM
+ if(SPDY_YES == (ret = SPDYF_prepare_rst_stream(session,
+ response_queue->stream,
+ SPDY_RST_STREAM_STATUS_INTERNAL_ERROR)))
+ {
+ return SPDY_NO;
+ }
+
+ //else no memory
+ //for now close session
+ //TODO what?
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+
+ return SPDY_NO;
+ }
+ if(0 == ret && more)
+ {
+ //the app couldn't write anything to buf but later will
+ free(session->write_buffer);
+ session->write_buffer = NULL;
+ session->write_buffer_size = 0;
+
+ if(NULL != response_queue->next)
+ {
+ //put the frame at the end of the queue
+ //otherwise - head of line blocking
+ session->response_queue_head = response_queue->next;
+ session->response_queue_head->prev = NULL;
+ session->response_queue_tail->next = response_queue;
+ response_queue->prev = session->response_queue_tail;
+ response_queue->next = NULL;
+ session->response_queue_tail = response_queue;
+ }
+
+ return SPDY_YES;
+ }
+
+ if(more)
+ {
+ //create another response queue object to call the user cb again
+ if(NULL == (new_response_queue = SPDYF_response_queue_create(true,
+ NULL,
+ 0,
+ response_queue->response,
+ response_queue->stream,
+ false,
+ response_queue->frqcb,
+ response_queue->frqcb_cls,
+ response_queue->rrcb,
+ response_queue->rrcb_cls)))
+ {
+ //TODO send RST_STREAM
+ //for now close session
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+
+ free(session->write_buffer);
+ session->write_buffer = NULL;
+ return SPDY_NO;
+ }
+
+ //put it at second position on the queue
+ new_response_queue->prev = response_queue;
+ new_response_queue->next = response_queue->next;
+ if(NULL == response_queue->next)
+ {
+ session->response_queue_tail = new_response_queue;
+ }
+ else
+ {
+ response_queue->next->prev = new_response_queue;
+ }
+ response_queue->next = new_response_queue;
+
+ response_queue->frqcb = NULL;
+ response_queue->frqcb_cls = NULL;
+ response_queue->rrcb = NULL;
+ response_queue->rrcb_cls = NULL;
+ }
+ else
+ {
+ data_frame.flags |= SPDY_DATA_FLAG_FIN;
+ }
+
+ data_frame.length = ret;
+ SPDYF_DATA_FRAME_HTON(&data_frame);
+
+ //put SPDY headers to the writing buffer
+ memcpy(session->write_buffer + session->write_buffer_offset,
+ &data_frame,
+ sizeof(struct SPDYF_Data_Frame));
+ session->write_buffer_offset += sizeof(struct SPDYF_Data_Frame);
+ session->write_buffer_offset += ret;
+ session->write_buffer_size = session->write_buffer_offset;
+ }
+
+ //SPDYF_DEBUG("data sent: id %i", NTOH31(data_frame.stream_id));
+
+ SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1");
+ SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2");
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_handler_write_rst_stream (struct SPDY_Session *session)
+{
+ struct SPDYF_Response_Queue *response_queue = session->response_queue_head;
+ struct SPDYF_Control_Frame control_frame;
+ size_t total_size;
+
+ SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment");
+
+ memcpy(&control_frame, response_queue->control_frame, sizeof(control_frame));
+
+ total_size = sizeof(struct SPDYF_Control_Frame) //SPDY header
+ + 4 // stream id as "subheader"
+ + 4; // status code as "subheader"
+
+ if(NULL == (session->write_buffer = malloc(total_size)))
+ {
+ return SPDY_NO;
+ }
+ session->write_buffer_beginning = 0;
+ session->write_buffer_offset = 0;
+ session->write_buffer_size = total_size;
+
+ control_frame.length = 8; // always for RST_STREAM
+ SPDYF_CONTROL_FRAME_HTON(&control_frame);
+
+ //put frame headers to write buffer
+ memcpy(session->write_buffer + session->write_buffer_offset,&control_frame,sizeof(struct SPDYF_Control_Frame));
+ session->write_buffer_offset += sizeof(struct SPDYF_Control_Frame);
+
+ //put stream id to write buffer. This is the status
+ memcpy(session->write_buffer + session->write_buffer_offset, response_queue->data, 8);
+ session->write_buffer_offset += 8;
+ //data is not freed by the destroy function so:
+ //free(response_queue->data);
+
+ //SPDYF_DEBUG("rst_stream sent: id %i", NTOH31((((uint64_t)response_queue->data) & 0xFFFF0000) >> 32));
+
+ SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1");
+ SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2");
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_handler_write_window_update (struct SPDY_Session *session)
+{
+ struct SPDYF_Response_Queue *response_queue = session->response_queue_head;
+ struct SPDYF_Control_Frame control_frame;
+ size_t total_size;
+
+ SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment");
+
+ memcpy(&control_frame, response_queue->control_frame, sizeof(control_frame));
+
+ total_size = sizeof(struct SPDYF_Control_Frame) //SPDY header
+ + 4 // stream id as "subheader"
+ + 4; // delta-window-size as "subheader"
+
+ if(NULL == (session->write_buffer = malloc(total_size)))
+ {
+ return SPDY_NO;
+ }
+ session->write_buffer_beginning = 0;
+ session->write_buffer_offset = 0;
+ session->write_buffer_size = total_size;
+
+ control_frame.length = 8; // always for WINDOW_UPDATE
+ SPDYF_CONTROL_FRAME_HTON(&control_frame);
+
+ //put frame headers to write buffer
+ memcpy(session->write_buffer + session->write_buffer_offset,&control_frame,sizeof(struct SPDYF_Control_Frame));
+ session->write_buffer_offset += sizeof(struct SPDYF_Control_Frame);
+
+ //put stream id and delta-window-size to write buffer
+ memcpy(session->write_buffer + session->write_buffer_offset, response_queue->data, 8);
+ session->write_buffer_offset += 8;
+
+ //SPDYF_DEBUG("window_update sent: id %i", NTOH31((((uint64_t)response_queue->data) & 0xFFFF0000) >> 32));
+
+ SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1");
+ SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2");
+
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_handler_ignore_frame (struct SPDY_Session *session)
+{
+ struct SPDYF_Control_Frame *frame;
+
+ SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status
+ || SPDY_SESSION_STATUS_WAIT_FOR_BODY == session->status,
+ "the function is called wrong");
+
+
+ frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls;
+
+ //handle subheaders
+ if(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status)
+ {
+ if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE)
+ {
+ session->status = SPDY_SESSION_STATUS_IGNORE_BYTES;
+ return;
+ }
+ else
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY;
+ }
+
+ //handle body
+
+ if(session->read_buffer_offset - session->read_buffer_beginning
+ >= frame->length)
+ {
+ session->read_buffer_beginning += frame->length;
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+ free(frame);
+ }
+}
+
+
+int
+SPDYF_session_read (struct SPDY_Session *session)
+{
+ int bytes_read;
+ bool reallocate;
+ size_t actual_buf_size;
+
+ if(SPDY_SESSION_STATUS_CLOSING == session->status
+ || SPDY_SESSION_STATUS_FLUSHING == session->status)
+ return SPDY_NO;
+
+ //if the read buffer is full to the end, we need to reallocate space
+ if (session->read_buffer_size == session->read_buffer_offset)
+ {
+ //but only if the state of the session requires it
+ //i.e. no further proceeding is possible without reallocation
+ reallocate = false;
+ actual_buf_size = session->read_buffer_offset
+ - session->read_buffer_beginning;
+ switch(session->status)
+ {
+ case SPDY_SESSION_STATUS_WAIT_FOR_HEADER:
+
+ case SPDY_SESSION_STATUS_IGNORE_BYTES:
+ //we need space for a whole control frame header
+ if(actual_buf_size < sizeof(struct SPDYF_Control_Frame))
+ reallocate = true;
+ break;
+
+ case SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER:
+
+ case SPDY_SESSION_STATUS_WAIT_FOR_BODY:
+ //we need as many bytes as set in length field of the
+ //header
+ SPDYF_ASSERT(NULL != session->frame_handler_cls,
+ "no frame for session");
+ if(session->frame_handler != &spdyf_handler_read_data)
+ {
+ if(actual_buf_size
+ < ((struct SPDYF_Control_Frame *)session->frame_handler_cls)->length)
+ reallocate = true;
+ }
+ else
+ {
+ if(actual_buf_size
+ < ((struct SPDYF_Data_Frame *)session->frame_handler_cls)->length)
+ reallocate = true;
+ }
+ break;
+
+ case SPDY_SESSION_STATUS_CLOSING:
+ case SPDY_SESSION_STATUS_FLUSHING:
+ //nothing needed
+ break;
+ }
+
+ if(reallocate)
+ {
+ //reuse the space in the buffer that was already read by the lib
+ memmove(session->read_buffer,
+ session->read_buffer + session->read_buffer_beginning,
+ session->read_buffer_offset - session->read_buffer_beginning);
+
+ session->read_buffer_offset -= session->read_buffer_beginning;
+ session->read_buffer_beginning = 0;
+ }
+ else
+ {
+ //will read next time
+ //TODO optimize it, memmove more often?
+ return SPDY_NO;
+ }
+ }
+
+ session->last_activity = SPDYF_monotonic_time();
+
+ //actual read from the TLS socket
+ bytes_read = session->fio_recv(session,
+ session->read_buffer + session->read_buffer_offset,
+ session->read_buffer_size - session->read_buffer_offset);
+
+ switch(bytes_read)
+ {
+ case SPDY_IO_ERROR_CLOSED:
+ //The TLS connection was closed by the other party, clean
+ //or not
+ shutdown (session->socket_fd, SHUT_RD);
+ session->read_closed = true;
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+ return SPDY_YES;
+
+ case SPDY_IO_ERROR_ERROR:
+ //any kind of error in the TLS subsystem
+ //try to prepare GOAWAY frame
+ SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_INTERNAL_ERROR, false);
+ //try to flush the queue when write is called
+ session->status = SPDY_SESSION_STATUS_FLUSHING;
+ return SPDY_YES;
+
+ case SPDY_IO_ERROR_AGAIN:
+ //read or write should be called again; leave it for the
+ //next time
+ return SPDY_NO;
+
+ //default:
+ //something was really read from the TLS subsystem
+ //just continue
+ }
+
+ session->read_buffer_offset += bytes_read;
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_session_write (struct SPDY_Session *session,
+ bool only_one_frame)
+{
+ unsigned int i;
+ int bytes_written;
+ struct SPDYF_Response_Queue *queue_head;
+ struct SPDYF_Response_Queue *response_queue;
+
+ if(SPDY_SESSION_STATUS_CLOSING == session->status)
+ return SPDY_NO;
+
+ if(SPDY_NO == session->fio_before_write(session))
+ return SPDY_NO;
+
+ for(i=0;
+ only_one_frame
+ ? i < 1
+ : i < session->max_num_frames;
+ ++i)
+ {
+ //if the buffer is not null, part of the last frame is still
+ //pending to be sent
+ if(NULL == session->write_buffer)
+ {
+ //discard frames on closed streams
+ response_queue = session->response_queue_head;
+
+ while(NULL != response_queue)
+ {
+ //if stream is closed, remove not yet sent frames
+ //associated with it
+ //GOAWAY frames are not associated to streams
+ //and still need to be sent
+ if(NULL == response_queue->stream
+ || !response_queue->stream->is_out_closed)
+ break;
+
+ DLL_remove(session->response_queue_head,session->response_queue_tail,response_queue);
+
+ if(NULL != response_queue->frqcb)
+ {
+ response_queue->frqcb(response_queue->frqcb_cls, response_queue, SPDY_RESPONSE_RESULT_STREAM_CLOSED);
+ }
+
+ SPDYF_response_queue_destroy(response_queue);
+ response_queue = session->response_queue_head;
+ }
+
+ if(NULL == session->response_queue_head)
+ break;//nothing on the queue
+
+ //get next data from queue and put it to the write buffer
+ // to send it
+ if(SPDY_NO == session->response_queue_head->process_response_handler(session))
+ {
+ //error occured and the handler changed or not the
+ //session's status appropriately
+ if(SPDY_SESSION_STATUS_CLOSING == session->status)
+ {
+ //try to send GOAWAY first if the current frame is different
+ if(session->response_queue_head->is_data
+ || SPDY_CONTROL_FRAME_TYPES_GOAWAY
+ != session->response_queue_head->control_frame->type)
+ {
+ session->status = SPDY_SESSION_STATUS_FLUSHING;
+ SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_INTERNAL_ERROR, true);
+ SPDYF_session_write(session,true);
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+ }
+ return SPDY_YES;
+ }
+
+ //just return from the loop to return from this function
+ ++i;
+ break;
+ }
+
+ //check if something was prepared for writing
+ //on respones with callbacks it is possible that their is no
+ //data available
+ if(0 == session->write_buffer_size)//nothing to write
+ {
+ if(response_queue != session->response_queue_head)
+ {
+ //the handler modified the queue
+ continue;
+ }
+ else
+ {
+ //no need to try the same frame again
+ ++i;
+ break;
+ }
+ }
+ }
+
+ session->last_activity = SPDYF_monotonic_time();
+
+ //actual write to the IO
+ bytes_written = session->fio_send(session,
+ session->write_buffer + session->write_buffer_beginning,
+ session->write_buffer_offset - session->write_buffer_beginning);
+
+ switch(bytes_written)
+ {
+ case SPDY_IO_ERROR_CLOSED:
+ //The TLS connection was closed by the other party, clean
+ //or not
+ shutdown (session->socket_fd, SHUT_RD);
+ session->read_closed = true;
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+ return SPDY_YES;
+
+ case SPDY_IO_ERROR_ERROR:
+ //any kind of error in the TLS subsystem
+ //forbid more writing
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+ return SPDY_YES;
+
+ case SPDY_IO_ERROR_AGAIN:
+ //read or write should be called again; leave it for the
+ //next time; return from the function as we do not now
+ //whether reading or writing is needed
+ return i>0 ? SPDY_YES : SPDY_NO;
+
+ //default:
+ //something was really read from the TLS subsystem
+ //just continue
+ }
+
+ session->write_buffer_beginning += bytes_written;
+
+ //check if the full buffer was written
+ if(session->write_buffer_beginning == session->write_buffer_size)
+ {
+ //that response is handled, remove it from queue
+ free(session->write_buffer);
+ session->write_buffer = NULL;
+ session->write_buffer_size = 0;
+ queue_head = session->response_queue_head;
+ if(NULL == queue_head->next)
+ {
+ session->response_queue_head = NULL;
+ session->response_queue_tail = NULL;
+ }
+ else
+ {
+ session->response_queue_head = queue_head->next;
+ session->response_queue_head->prev = NULL;
+ }
+
+ //set stream to closed if the frame's fin flag is set
+ SPDYF_stream_set_flags_on_write(queue_head);
+
+ if(NULL != queue_head->frqcb)
+ {
+ //application layer callback to notify sending of the response
+ queue_head->frqcb(queue_head->frqcb_cls, queue_head, SPDY_RESPONSE_RESULT_SUCCESS);
+ }
+
+ SPDYF_response_queue_destroy(queue_head);
+ }
+ }
+
+ if(SPDY_SESSION_STATUS_FLUSHING == session->status
+ && NULL == session->response_queue_head)
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+
+ //return i>0 ? SPDY_YES : SPDY_NO;
+ return session->fio_after_write(session, i>0 ? SPDY_YES : SPDY_NO);
+}
+
+
+int
+SPDYF_session_idle (struct SPDY_Session *session)
+{
+ size_t read_buffer_beginning;
+ size_t frame_length;
+ struct SPDYF_Control_Frame* control_frame;
+ struct SPDYF_Data_Frame *data_frame;
+
+ //prepare session for closing if timeout is used and already passed
+ if(SPDY_SESSION_STATUS_CLOSING != session->status
+ && session->daemon->session_timeout
+ && (session->last_activity + session->daemon->session_timeout < SPDYF_monotonic_time()))
+ {
+ session->status = SPDY_SESSION_STATUS_CLOSING;
+ //best effort for sending GOAWAY
+ SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_OK, true);
+ SPDYF_session_write(session,true);
+ }
+
+ switch(session->status)
+ {
+ //expect new frame to arrive
+ case SPDY_SESSION_STATUS_WAIT_FOR_HEADER:
+ session->current_stream_id = 0;
+ //check if the whole frame header is already here
+ //both frame types have the same length
+ if(session->read_buffer_offset - session->read_buffer_beginning
+ < sizeof(struct SPDYF_Control_Frame))
+ return SPDY_NO;
+
+ /* check the first bit to see if it is data or control frame
+ * and also if the version is supported */
+ if(0x80 == *(uint8_t *)(session->read_buffer + session->read_buffer_beginning)
+ && SPDY_VERSION == *((uint8_t *)session->read_buffer + session->read_buffer_beginning + 1))
+ {
+ //control frame
+ if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame))))
+ {
+ SPDYF_DEBUG("No memory");
+ return SPDY_NO;
+ }
+
+ //get frame headers
+ memcpy(control_frame,
+ session->read_buffer + session->read_buffer_beginning,
+ sizeof(struct SPDYF_Control_Frame));
+ session->read_buffer_beginning += sizeof(struct SPDYF_Control_Frame);
+ SPDYF_CONTROL_FRAME_NTOH(control_frame);
+
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER;
+ //assign different frame handler according to frame type
+ switch(control_frame->type){
+ case SPDY_CONTROL_FRAME_TYPES_SYN_STREAM:
+ session->frame_handler = &spdyf_handler_read_syn_stream;
+ break;
+ case SPDY_CONTROL_FRAME_TYPES_GOAWAY:
+ session->frame_handler = &spdyf_handler_read_goaway;
+ break;
+ case SPDY_CONTROL_FRAME_TYPES_RST_STREAM:
+ session->frame_handler = &spdyf_handler_read_rst_stream;
+ break;
+ default:
+ session->frame_handler = &SPDYF_handler_ignore_frame;
+ }
+ session->frame_handler_cls = control_frame;
+ //DO NOT break the outer case
+ }
+ else if(0 == *(uint8_t *)(session->read_buffer + session->read_buffer_beginning))
+ {
+ //needed for POST
+ //data frame
+ if(NULL == (data_frame = malloc(sizeof(struct SPDYF_Data_Frame))))
+ {
+ SPDYF_DEBUG("No memory");
+ return SPDY_NO;
+ }
+
+ //get frame headers
+ memcpy(data_frame,
+ session->read_buffer + session->read_buffer_beginning,
+ sizeof(struct SPDYF_Data_Frame));
+ session->read_buffer_beginning += sizeof(struct SPDYF_Data_Frame);
+ SPDYF_DATA_FRAME_NTOH(data_frame);
+
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY;
+ session->frame_handler = &spdyf_handler_read_data;
+ session->frame_handler_cls = data_frame;
+ //DO NOT brake the outer case
+ }
+ else
+ {
+ SPDYF_DEBUG("another protocol or version received!");
+
+ /* According to the draft the lib should send here
+ * RST_STREAM with status UNSUPPORTED_VERSION. I don't
+ * see any sense of keeping the session open since
+ * we don't know how many bytes is the bogus "frame".
+ * And the latter normally will be HTTP request.
+ *
+ */
+
+ //shutdown(session->socket_fd, SHUT_RD);
+ session->status = SPDY_SESSION_STATUS_FLUSHING;
+ SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_PROTOCOL_ERROR,false);
+ //SPDYF_session_write(session,false);
+ /* close connection since the client expects another
+ protocol from us */
+ //SPDYF_session_close(session);
+ return SPDY_YES;
+ }
+
+ //expect specific header fields after the standard header
+ case SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER:
+ if(NULL!=session->frame_handler)
+ {
+ read_buffer_beginning = session->read_buffer_beginning;
+ //if everything is ok, the "body" will also be processed
+ //by the handler
+ session->frame_handler(session);
+
+ if(SPDY_SESSION_STATUS_IGNORE_BYTES == session->status)
+ {
+ //check for larger than max supported frame
+ if(session->frame_handler != &spdyf_handler_read_data)
+ {
+ frame_length = ((struct SPDYF_Control_Frame *)session->frame_handler_cls)->length;
+ }
+ else
+ {
+ frame_length = ((struct SPDYF_Data_Frame *)session->frame_handler_cls)->length;
+ }
+
+ //if(SPDY_MAX_SUPPORTED_FRAME_SIZE < frame_length)
+ {
+ SPDYF_DEBUG("received frame with unsupported size: %zu", frame_length);
+ //the data being received must be ignored and
+ //RST_STREAM sent
+
+ //ignore bytes that will arive later
+ session->read_ignore_bytes = frame_length
+ + read_buffer_beginning
+ - session->read_buffer_offset;
+ //ignore what is already in read buffer
+ session->read_buffer_beginning = session->read_buffer_offset;
+
+ SPDYF_prepare_rst_stream(session,
+ session->current_stream_id > 0 ? session->streams_head : NULL, //may be 0 here which is not good
+ SPDY_RST_STREAM_STATUS_FRAME_TOO_LARGE);
+
+ //actually the read buffer can be bigger than the
+ //max supported size
+ session->status = session->read_ignore_bytes
+ ? SPDY_SESSION_STATUS_IGNORE_BYTES
+ : SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+
+ free(session->frame_handler_cls);
+ }
+ }
+ }
+
+ if(SPDY_SESSION_STATUS_IGNORE_BYTES != session->status)
+ {
+ break;
+ }
+
+ //ignoring data in read buffer
+ case SPDY_SESSION_STATUS_IGNORE_BYTES:
+ SPDYF_ASSERT(session->read_ignore_bytes > 0,
+ "Session is in wrong state");
+ if(session->read_ignore_bytes
+ > session->read_buffer_offset - session->read_buffer_beginning)
+ {
+ session->read_ignore_bytes -=
+ session->read_buffer_offset - session->read_buffer_beginning;
+ session->read_buffer_beginning = session->read_buffer_offset;
+ }
+ else
+ {
+ session->read_buffer_beginning += session->read_ignore_bytes;
+ session->read_ignore_bytes = 0;
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+ }
+ break;
+
+ //expect frame body (name/value pairs)
+ case SPDY_SESSION_STATUS_WAIT_FOR_BODY:
+ if(NULL!=session->frame_handler)
+ session->frame_handler(session);
+ break;
+
+ case SPDY_SESSION_STATUS_FLUSHING:
+
+ return SPDY_NO;
+
+ //because of error the session needs to be closed
+ case SPDY_SESSION_STATUS_CLOSING:
+ //error should be already sent to the client
+ SPDYF_session_close(session);
+ return SPDY_YES;
+ }
+
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_session_close (struct SPDY_Session *session)
+{
+ struct SPDY_Daemon *daemon = session->daemon;
+ int by_client = session->read_closed ? SPDY_YES : SPDY_NO;
+
+ //shutdown the tls and deinit the tls context
+ session->fio_close_session(session);
+ shutdown (session->socket_fd,
+ session->read_closed ? SHUT_WR : SHUT_RDWR);
+ session->read_closed = true;
+
+ //remove session from the list
+ DLL_remove (daemon->sessions_head,
+ daemon->sessions_tail,
+ session);
+ //add the session for the list for cleaning up
+ DLL_insert (daemon->cleanup_head,
+ daemon->cleanup_tail,
+ session);
+
+ //call callback for closed session
+ if(NULL != daemon->session_closed_cb)
+ {
+ daemon->session_closed_cb(daemon->cls, session, by_client);
+ }
+}
+
+
+int
+SPDYF_session_accept(struct SPDY_Daemon *daemon)
+{
+ int new_socket_fd;
+ int ret;
+ struct SPDY_Session *session = NULL;
+ socklen_t addr_len;
+ struct sockaddr *addr;
+
+#if HAVE_INET6
+ struct sockaddr_in6 addr6;
+
+ addr = (struct sockaddr *)&addr6;
+ addr_len = sizeof(addr6);
+#else
+ struct sockaddr_in addr4;
+
+ addr = (struct sockaddr *)&addr4;
+ addr_len = sizeof(addr6);
+#endif
+
+ new_socket_fd = accept (daemon->socket_fd, addr, &addr_len);
+
+ if(new_socket_fd < 1)
+ return SPDY_NO;
+
+ if (NULL == (session = malloc (sizeof (struct SPDY_Session))))
+ {
+ goto free_and_fail;
+ }
+ memset (session, 0, sizeof (struct SPDY_Session));
+
+ session->daemon = daemon;
+ session->socket_fd = new_socket_fd;
+ session->max_num_frames = daemon->max_num_frames;
+
+ ret = SPDYF_io_set_session(session, daemon->io_subsystem);
+ SPDYF_ASSERT(SPDY_YES == ret, "Somehow daemon->io_subsystem iswrong here");
+
+ //init TLS context, handshake will be done
+ if(SPDY_YES != session->fio_new_session(session))
+ {
+ goto free_and_fail;
+ }
+
+ //read buffer
+ session->read_buffer_size = SPDYF_BUFFER_SIZE;
+ if (NULL == (session->read_buffer = malloc (session->read_buffer_size)))
+ {
+ session->fio_close_session(session);
+ goto free_and_fail;
+ }
+
+ //address of the client
+ if (NULL == (session->addr = malloc (addr_len)))
+ {
+ session->fio_close_session(session);
+ goto free_and_fail;
+ }
+ memcpy (session->addr, addr, addr_len);
+
+ session->addr_len = addr_len;
+ session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER;
+
+ //init zlib context for the whole session
+ if(SPDY_YES != SPDYF_zlib_deflate_init(&session->zlib_send_stream))
+ {
+ session->fio_close_session(session);
+ goto free_and_fail;
+ }
+ if(SPDY_YES != SPDYF_zlib_inflate_init(&session->zlib_recv_stream))
+ {
+ session->fio_close_session(session);
+ SPDYF_zlib_deflate_end(&session->zlib_send_stream);
+ goto free_and_fail;
+ }
+
+ //add it to daemon's list
+ DLL_insert(daemon->sessions_head,daemon->sessions_tail,session);
+
+ session->last_activity = SPDYF_monotonic_time();
+
+ if(NULL != daemon->new_session_cb)
+ daemon->new_session_cb(daemon->cls, session);
+
+ return SPDY_YES;
+
+ //for GOTO
+ free_and_fail:
+ /* something failed, so shutdown, close and free memory */
+ shutdown (new_socket_fd, SHUT_RDWR);
+ (void)close (new_socket_fd);
+
+ if(NULL != session)
+ {
+ if(NULL != session->addr)
+ free (session->addr);
+ if(NULL != session->read_buffer)
+ free (session->read_buffer);
+ free (session);
+ }
+ return SPDY_NO;
+}
+
+
+void
+SPDYF_queue_response (struct SPDYF_Response_Queue *response_to_queue,
+ struct SPDY_Session *session,
+ int consider_priority)
+{
+ struct SPDYF_Response_Queue *pos;
+ struct SPDYF_Response_Queue *last;
+ uint8_t priority;
+
+ SPDYF_ASSERT(SPDY_YES != consider_priority || NULL != response_to_queue->stream,
+ "called with consider_priority but no stream provided");
+
+ last = response_to_queue;
+ while(NULL != last->next)
+ {
+ last = last->next;
+ }
+
+ if(SPDY_NO == consider_priority)
+ {
+ //put it at the end of the queue
+ response_to_queue->prev = session->response_queue_tail;
+ if (NULL == session->response_queue_head)
+ session->response_queue_head = response_to_queue;
+ else
+ session->response_queue_tail->next = response_to_queue;
+ session->response_queue_tail = last;
+ return;
+ }
+ else if(-1 == consider_priority)
+ {
+ //put it at the head of the queue
+ last->next = session->response_queue_head;
+ if (NULL == session->response_queue_tail)
+ session->response_queue_tail = last;
+ else
+ session->response_queue_head->prev = response_to_queue;
+ session->response_queue_head = response_to_queue;
+ return;
+ }
+
+ if(NULL == session->response_queue_tail)
+ {
+ session->response_queue_head = response_to_queue;
+ session->response_queue_tail = last;
+ return;
+ }
+
+ //search for the right position to put it
+ pos = session->response_queue_tail;
+ priority = response_to_queue->stream->priority;
+ while(NULL != pos
+ && pos->stream->priority > priority)
+ {
+ pos = pos->prev;
+ }
+
+ if(NULL == pos)
+ {
+ //put it on the head
+ session->response_queue_head->prev = last;
+ last->next = session->response_queue_head;
+ session->response_queue_head = response_to_queue;
+ }
+ else if(NULL == pos->next)
+ {
+ //put it at the end
+ response_to_queue->prev = pos;
+ pos->next = response_to_queue;
+ session->response_queue_tail = last;
+ }
+ else
+ {
+ response_to_queue->prev = pos;
+ last->next = pos->next;
+ pos->next = response_to_queue;
+ last->next->prev = last;
+ }
+}
+
+
+void
+SPDYF_session_destroy(struct SPDY_Session *session)
+{
+ struct SPDYF_Stream *stream;
+ struct SPDYF_Response_Queue *response_queue;
+
+ (void)close (session->socket_fd);
+ SPDYF_zlib_deflate_end(&session->zlib_send_stream);
+ SPDYF_zlib_inflate_end(&session->zlib_recv_stream);
+
+ //clean up unsent data in the output queue
+ while (NULL != (response_queue = session->response_queue_head))
+ {
+ DLL_remove (session->response_queue_head,
+ session->response_queue_tail,
+ response_queue);
+
+ if(NULL != response_queue->frqcb)
+ {
+ response_queue->frqcb(response_queue->frqcb_cls, response_queue, SPDY_RESPONSE_RESULT_SESSION_CLOSED);
+ }
+
+ SPDYF_response_queue_destroy(response_queue);
+ }
+
+ //clean up the streams belonging to this session
+ while (NULL != (stream = session->streams_head))
+ {
+ DLL_remove (session->streams_head,
+ session->streams_tail,
+ stream);
+
+ SPDYF_stream_destroy(stream);
+ }
+
+ free(session->addr);
+ free(session->read_buffer);
+ free(session->write_buffer);
+ free(session);
+}
+
+
+int
+SPDYF_prepare_goaway (struct SPDY_Session *session,
+ enum SPDY_GOAWAY_STATUS status,
+ bool in_front)
+{
+ struct SPDYF_Response_Queue *response_to_queue;
+ struct SPDYF_Control_Frame *control_frame;
+ uint32_t *data;
+
+ if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue))))
+ {
+ return SPDY_NO;
+ }
+ memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue));
+
+ if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame))))
+ {
+ free(response_to_queue);
+ return SPDY_NO;
+ }
+ memset(control_frame, 0, sizeof(struct SPDYF_Control_Frame));
+
+ if(NULL == (data = malloc(4)))
+ {
+ free(control_frame);
+ free(response_to_queue);
+ return SPDY_NO;
+ }
+ *(data) = htonl(status);
+
+ control_frame->control_bit = 1;
+ control_frame->version = SPDY_VERSION;
+ control_frame->type = SPDY_CONTROL_FRAME_TYPES_GOAWAY;
+ control_frame->flags = 0;
+
+ response_to_queue->control_frame = control_frame;
+ response_to_queue->process_response_handler = &SPDYF_handler_write_goaway;
+ response_to_queue->data = data;
+ response_to_queue->data_size = 4;
+
+ SPDYF_queue_response (response_to_queue,
+ session,
+ in_front ? -1 : SPDY_NO);
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_prepare_rst_stream (struct SPDY_Session *session,
+ struct SPDYF_Stream * stream,
+ enum SPDY_RST_STREAM_STATUS status)
+{
+ struct SPDYF_Response_Queue *response_to_queue;
+ struct SPDYF_Control_Frame *control_frame;
+ uint32_t *data;
+ uint32_t stream_id;
+
+ if(NULL == stream)
+ stream_id = 0;
+ else
+ stream_id = stream->stream_id;
+
+ if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue))))
+ {
+ return SPDY_NO;
+ }
+ memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue));
+
+ if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame))))
+ {
+ free(response_to_queue);
+ return SPDY_NO;
+ }
+ memset(control_frame, 0, sizeof(struct SPDYF_Control_Frame));
+
+ if(NULL == (data = malloc(8)))
+ {
+ free(control_frame);
+ free(response_to_queue);
+ return SPDY_NO;
+ }
+ *(data) = HTON31(stream_id);
+ *(data + 1) = htonl(status);
+
+ control_frame->control_bit = 1;
+ control_frame->version = SPDY_VERSION;
+ control_frame->type = SPDY_CONTROL_FRAME_TYPES_RST_STREAM;
+ control_frame->flags = 0;
+
+ response_to_queue->control_frame = control_frame;
+ response_to_queue->process_response_handler = &SPDYF_handler_write_rst_stream;
+ response_to_queue->data = data;
+ response_to_queue->data_size = 8;
+ response_to_queue->stream = stream;
+
+ SPDYF_queue_response (response_to_queue,
+ session,
+ -1);
+
+ return SPDY_YES;
+}
+
+
+int
+SPDYF_prepare_window_update (struct SPDY_Session *session,
+ struct SPDYF_Stream * stream,
+ int32_t delta_window_size)
+{
+ struct SPDYF_Response_Queue *response_to_queue;
+ struct SPDYF_Control_Frame *control_frame;
+ uint32_t *data;
+
+ SPDYF_ASSERT(NULL != stream, "stream cannot be NULL");
+
+ if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue))))
+ {
+ return SPDY_NO;
+ }
+ memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue));
+
+ if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame))))
+ {
+ free(response_to_queue);
+ return SPDY_NO;
+ }
+ memset(control_frame, 0, sizeof(struct SPDYF_Control_Frame));
+
+ if(NULL == (data = malloc(8)))
+ {
+ free(control_frame);
+ free(response_to_queue);
+ return SPDY_NO;
+ }
+ *(data) = HTON31(stream->stream_id);
+ *(data + 1) = HTON31(delta_window_size);
+
+ control_frame->control_bit = 1;
+ control_frame->version = SPDY_VERSION;
+ control_frame->type = SPDY_CONTROL_FRAME_TYPES_WINDOW_UPDATE;
+ control_frame->flags = 0;
+
+ response_to_queue->control_frame = control_frame;
+ response_to_queue->process_response_handler = &SPDYF_handler_write_window_update;
+ response_to_queue->data = data;
+ response_to_queue->data_size = 8;
+ response_to_queue->stream = stream;
+
+ SPDYF_queue_response (response_to_queue,
+ session,
+ -1);
+
+ return SPDY_YES;
+}
diff --git a/src/microspdy/session.h b/src/microspdy/session.h
new file mode 100644
index 0000000..29ab550
--- /dev/null
+++ b/src/microspdy/session.h
@@ -0,0 +1,281 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file session.h
+ * @brief TCP connection/SPDY session handling
+ * @author Andrey Uzunov
+ */
+
+#ifndef SESSION_H
+#define SESSION_H
+
+#include "platform.h"
+#include "structures.h"
+
+/**
+ * Called by the daemon when the socket for the session has available
+ * data to be read. Reads data from the TLS socket and puts it to the
+ * session's read buffer. The latte
+ *
+ * @param session SPDY_Session for which data will be read.
+ * @return SPDY_YES if something was read or session's status was
+ * changed. It is possible that error occurred but was handled
+ * and the status was therefore changed.
+ * SPDY_NO if nothing happened, e.g. the subsystem wants read/
+ * write to be called again.
+ */
+int
+SPDYF_session_read (struct SPDY_Session *session);
+
+
+/**
+ * Called by the daemon when the socket for the session is ready for some
+ * data to be written to it. For one or more objects on the response
+ * queue tries to fill in the write buffer, based on the frame on the
+ * queue, and to write data to the TLS socket.
+ *
+ * @param session SPDY_Session for which data will be written.
+ * @param only_one_frame when true, the function will write at most one
+ * SPDY frame to the underlying IO subsystem;
+ * when false, the function will write up to
+ * session->max_num_frames SPDY frames
+ * @return SPDY_YES if the session's internal writing state has changed:
+ * something was written and/or session's status was
+ * changed and/or response callback was called but did not provide
+ * data. It is possible that error occurred but was handled
+ * and the status was therefore changed.
+ * SPDY_NO if nothing happened. However, it is possible that some
+ * frames were discarded within the call, e.g. frames belonging
+ * to a closed stream.
+ */
+int
+SPDYF_session_write (struct SPDY_Session *session,
+ bool only_one_frame);
+
+
+/**
+ * Called by the daemon on SPDY_run to handle the data in the read and write
+ * buffer of a session. Based on the state and the content of the read
+ * buffer new frames are received and interpreted, appropriate user
+ * callbacks are called and maybe something is put on the response queue
+ * ready to be handled by session_write.
+ *
+ * @param session SPDY_Session which will be handled.
+ * @return SPDY_YES if something from the read buffers was processed,
+ * session's status was changed and/or the session was closed.
+ * SPDY_NO if nothing happened, e.g. the session is in a state,
+ * not allowing processing read buffers.
+ */
+int
+SPDYF_session_idle (struct SPDY_Session *session);
+
+
+/**
+ * This function shutdowns the socket, moves the session structure to
+ * daemon's queue for sessions to be cleaned up.
+ *
+ * @param session SPDY_Session which will be handled.
+ */
+void
+SPDYF_session_close (struct SPDY_Session *session);
+
+
+/**
+ * Called to accept new TCP connection and create SPDY session.
+ *
+ * @param daemon SPDY_Daemon whose listening socket is used.
+ * @return SPDY_NO on any kind of error while accepting new TCP connection
+ * and initializing new SPDY_Session.
+ * SPDY_YES otherwise.
+ */
+int
+SPDYF_session_accept(struct SPDY_Daemon *daemon);
+
+
+/**
+ * Puts SPDYF_Response_Queue object on the queue to be sent to the
+ * client later.
+ *
+ * @param response_to_queue linked list of objects containing SPDY
+ * frame and data to be added to the queue
+ * @param session SPDY session for which the response is sent
+ * @param consider_priority if SPDY_NO, the list will be added to the
+ * end of the queue.
+ * If SPDY_YES, the response will be added after
+ * the last previously added response with priority of the
+ * request grater or equal to that of the current one.
+ * If -1, the object will be put at the head of the queue.
+ */
+void
+SPDYF_queue_response (struct SPDYF_Response_Queue *response_to_queue,
+ struct SPDY_Session *session,
+ int consider_priority);
+
+
+/**
+ * Cleans up the TSL context for the session, closes the TCP connection,
+ * cleans up any data pointed by members of the session structure
+ * (buffers, queue of responses, etc.) and frees the memory allocated by
+ * the session itself.
+ */
+void
+SPDYF_session_destroy(struct SPDY_Session *session);
+
+
+/**
+ * Prepares GOAWAY frame to tell the client to stop creating new streams.
+ * The session should be closed soon after this call.
+ *
+ * @param session SPDY session
+ * @param status code for the GOAWAY frame
+ * @param in_front whether or not to put the frame in front of everything
+ * on the response queue
+ * @return SPDY_NO on error (not enough memory) or
+ * SPDY_YES on success
+ */
+int
+SPDYF_prepare_goaway (struct SPDY_Session *session,
+ enum SPDY_GOAWAY_STATUS status,
+ bool in_front);
+
+
+/**
+ * Prepares RST_STREAM frame to terminate a stream. This frame may or
+ * not indicate an error. The frame will be put at the head of the queue.
+ * This means that frames for this stream which are still in the queue
+ * will be discarded soon.
+ *
+ * @param session SPDY session
+ * @param stream stream to terminate
+ * @param status code for the RST_STREAM frame
+ * @return SPDY_NO on memory error or
+ * SPDY_YES on success
+ */
+int
+SPDYF_prepare_rst_stream (struct SPDY_Session *session,
+ struct SPDYF_Stream * stream,
+ enum SPDY_RST_STREAM_STATUS status);
+
+
+/**
+ * Prepares WINDOW_UPDATE frame to tell the other party that more
+ * data can be sent on the stream. The frame will be put at the head of
+ * the queue.
+ *
+ * @param session SPDY session
+ * @param stream stream to which the changed window will apply
+ * @param delta_window_size how much the window grows
+ * @return SPDY_NO on memory error or
+ * SPDY_YES on success
+ */
+int
+SPDYF_prepare_window_update (struct SPDY_Session *session,
+ struct SPDYF_Stream * stream,
+ int32_t delta_window_size);
+
+
+/**
+ * Handler called by session_write to fill the write buffer according to
+ * the data frame waiting in the response queue.
+ * When response data is given by user callback, the lib does not know
+ * how many frames are needed. In such case this call produces
+ * another ResponseQueue object and puts it on the queue while the the
+ * user callback says that there will be more data.
+ *
+ * @return SPDY_NO on error (not enough memory or the user calback for
+ * providing response data did something wrong). If
+ * the error is unrecoverable the handler changes session's
+ * status.
+ * SPDY_YES on success
+ */
+int
+SPDYF_handler_write_data (struct SPDY_Session *session);
+
+
+/**
+ * Handler called by session_write to fill the write buffer based on the
+ * control frame (SYN_REPLY) waiting in the response queue.
+ *
+ * @param session SPDY session
+ * @return SPDY_NO on error (zlib state is broken; the session MUST be
+ * closed). If
+ * the error is unrecoverable the handler changes session's
+ * status.
+ * SPDY_YES on success
+ */
+int
+SPDYF_handler_write_syn_reply (struct SPDY_Session *session);
+
+
+/**
+ * Handler called by session_write to fill the write buffer based on the
+ * control frame (GOAWAY) waiting in the response queue.
+ *
+ * @param session SPDY session
+ * @return SPDY_NO on error (not enough memory; by specification the
+ * session must be closed
+ * soon, thus there is no need to handle the error) or
+ * SPDY_YES on success
+ */
+int
+SPDYF_handler_write_goaway (struct SPDY_Session *session);
+
+
+/**
+ * Handler called by session_write to fill the write buffer based on the
+ * control frame (RST_STREAM) waiting in the response queue.
+ *
+ * @param session SPDY session
+ * @return SPDY_NO on error (not enough memory). If
+ * the error is unrecoverable the handler changes session's
+ * status.
+ * SPDY_YES on success
+ */
+int
+SPDYF_handler_write_rst_stream (struct SPDY_Session *session);
+
+
+/**
+ * Handler called by session_write to fill the write buffer based on the
+ * control frame (WINDOW_UPDATE) waiting in the response queue.
+ *
+ * @param session SPDY session
+ * @return SPDY_NO on error (not enough memory). If
+ * the error is unrecoverable the handler changes session's
+ * status.
+ * SPDY_YES on success
+ */
+int
+SPDYF_handler_write_window_update (struct SPDY_Session *session);
+
+
+/**
+ * Carefully ignore the full size of frames which are not yet supported
+ * by the lib.
+ * TODO Ignoring frames containing compressed bodies means that the
+ * compress state will be corrupted on next received frame. According to
+ * the draft the lib SHOULD try to decompress data also in corrupted
+ * frames just to keep right compression state.
+ *
+ * @param session SPDY_Session whose read buffer is used.
+ */
+void
+SPDYF_handler_ignore_frame (struct SPDY_Session *session);
+
+#endif
diff --git a/src/microspdy/stream.c b/src/microspdy/stream.c
new file mode 100644
index 0000000..9b6dc08
--- /dev/null
+++ b/src/microspdy/stream.c
@@ -0,0 +1,169 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file stream.c
+ * @brief SPDY streams handling
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "structures.h"
+#include "internal.h"
+#include "session.h"
+
+
+int
+SPDYF_stream_new (struct SPDY_Session *session)
+{
+ uint32_t stream_id;
+ uint32_t assoc_stream_id;
+ uint8_t priority;
+ uint8_t slot;
+ size_t buffer_pos = session->read_buffer_beginning;
+ struct SPDYF_Stream *stream;
+ struct SPDYF_Control_Frame *frame;
+
+ if((session->read_buffer_offset - session->read_buffer_beginning) < 10)
+ {
+ //not all fields are received to create new stream
+ return SPDY_NO;
+ }
+
+ frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls;
+
+ //get stream id of the new stream
+ memcpy(&stream_id, session->read_buffer + session->read_buffer_beginning, 4);
+ stream_id = NTOH31(stream_id);
+ session->read_buffer_beginning += 4;
+ if(stream_id <= session->last_in_stream_id || 0==(stream_id % 2))
+ {
+ //wrong stream id sent by client
+ //GOAWAY with PROTOCOL_ERROR MUST be sent
+ //TODO
+
+ //ignore frame
+ session->frame_handler = &SPDYF_handler_ignore_frame;
+ return SPDY_NO;
+ }
+ else if(session->is_goaway_sent)
+ {
+ //the client is not allowed to create new streams anymore
+ //we MUST ignore the frame
+ session->frame_handler = &SPDYF_handler_ignore_frame;
+ return SPDY_NO;
+ }
+
+ //set highest stream id for session
+ session->last_in_stream_id = stream_id;
+
+ //get assoc stream id of the new stream
+ //this value is used with SPDY PUSH, thus nothing to do with it here
+ memcpy(&assoc_stream_id, session->read_buffer + session->read_buffer_beginning, 4);
+ assoc_stream_id = NTOH31(assoc_stream_id);
+ session->read_buffer_beginning += 4;
+
+ //get stream priority (3 bits)
+ //after it there are 5 bits that are not used
+ priority = *(uint8_t *)(session->read_buffer + session->read_buffer_beginning) >> 5;
+ session->read_buffer_beginning++;
+
+ //get slot (see SPDY draft)
+ slot = *(uint8_t *)(session->read_buffer + session->read_buffer_beginning);
+ session->read_buffer_beginning++;
+
+ if(NULL == (stream = malloc(sizeof(struct SPDYF_Stream))))
+ {
+ SPDYF_DEBUG("No memory");
+ //revert buffer state
+ session->read_buffer_beginning = buffer_pos;
+ return SPDY_NO;
+ }
+ memset(stream,0, sizeof(struct SPDYF_Stream));
+ stream->session = session;
+ stream->stream_id = stream_id;
+ stream->assoc_stream_id = assoc_stream_id;
+ stream->priority = priority;
+ stream->slot = slot;
+ stream->is_in_closed = (frame->flags & SPDY_SYN_STREAM_FLAG_FIN) != 0;
+ stream->flag_unidirectional = (frame->flags & SPDY_SYN_STREAM_FLAG_UNIDIRECTIONAL) != 0;
+ stream->is_out_closed = stream->flag_unidirectional;
+ stream->is_server_initiator = false;
+ stream->window_size = SPDYF_INITIAL_WINDOW_SIZE;
+
+ //put the stream to the list of streams for the session
+ DLL_insert(session->streams_head, session->streams_tail, stream);
+
+ return SPDY_YES;
+}
+
+
+void
+SPDYF_stream_destroy(struct SPDYF_Stream *stream)
+{
+ SPDY_name_value_destroy(stream->headers);
+ free(stream);
+ stream = NULL;
+}
+
+
+void
+SPDYF_stream_set_flags_on_write(struct SPDYF_Response_Queue *response_queue)
+{
+ struct SPDYF_Stream * stream = response_queue->stream;
+
+ if(NULL != response_queue->data_frame)
+ {
+ stream->is_out_closed = (bool)(response_queue->data_frame->flags & SPDY_DATA_FLAG_FIN);
+ }
+ else if(NULL != response_queue->control_frame)
+ {
+ switch(response_queue->control_frame->type)
+ {
+ case SPDY_CONTROL_FRAME_TYPES_SYN_REPLY:
+ stream->is_out_closed = (bool)(response_queue->control_frame->flags & SPDY_SYN_REPLY_FLAG_FIN);
+ break;
+
+ case SPDY_CONTROL_FRAME_TYPES_RST_STREAM:
+ if(NULL != stream)
+ {
+ stream->is_out_closed = true;
+ stream->is_in_closed = true;
+ }
+ break;
+
+ }
+ }
+}
+
+
+//TODO add function *on_read
+
+
+struct SPDYF_Stream *
+SPDYF_stream_find(uint32_t stream_id, struct SPDY_Session * session)
+{
+ struct SPDYF_Stream * stream = session->streams_head;
+
+ while(NULL != stream && stream_id != stream->stream_id)
+ {
+ stream = stream->next;
+ }
+
+ return stream;
+}
diff --git a/src/microspdy/stream.h b/src/microspdy/stream.h
new file mode 100644
index 0000000..220231f
--- /dev/null
+++ b/src/microspdy/stream.h
@@ -0,0 +1,76 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file stream.h
+ * @brief SPDY streams handling
+ * @author Andrey Uzunov
+ */
+
+#ifndef STREAM_H
+#define STREAM_H
+
+#include "platform.h"
+
+
+/**
+ * Reads data from session's read buffer and tries to create a new SPDY
+ * stream. This function is called after control frame's header has been
+ * read from the buffer (after the length field). If bogus frame is
+ * received the function changes the read handler of the session and
+ * fails, i.e. there is no need of further error handling by the caller.
+ *
+ * @param session SPDY_Session whose read buffer is being read
+ * @return SPDY_YES if a new SPDY stream request was correctly received
+ * and handled. SPDY_NO if the whole SPDY frame was not yet
+ * received or memory error occurred.
+ */
+int
+SPDYF_stream_new (struct SPDY_Session *session);
+
+
+/**
+ * Destroys stream structure and whatever is in it.
+ *
+ * @param stream SPDY_Stream to destroy
+ */
+void
+SPDYF_stream_destroy(struct SPDYF_Stream *stream);
+
+
+/**
+ * Set stream flags if needed based on the type of the frame that was
+ * just sent (e.g., close stream if it was RST_STREAM).
+ *
+ * @param response_queue sent for this stream
+ */
+void
+SPDYF_stream_set_flags_on_write(struct SPDYF_Response_Queue *response_queue);
+
+
+/**
+ * Find and return a session's stream, based on stream's ID.
+ *
+ * @param stream_id to search for
+ * @param session whose streams are considered
+ * @return SPDY_Stream with the desired ID. Can be NULL.
+ */
+struct SPDYF_Stream *
+SPDYF_stream_find(uint32_t stream_id, struct SPDY_Session * session);
+
+#endif
diff --git a/src/microspdy/structures.c b/src/microspdy/structures.c
new file mode 100644
index 0000000..f00806b
--- /dev/null
+++ b/src/microspdy/structures.c
@@ -0,0 +1,638 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file structures.c
+ * @brief Functions for handling most of the structures in defined
+ * in structures.h
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "structures.h"
+#include "internal.h"
+#include "session.h"
+//TODO not for here?
+#include <ctype.h>
+
+
+int
+SPDYF_name_value_is_empty(struct SPDY_NameValue *container)
+{
+ SPDYF_ASSERT(NULL != container, "NULL is not an empty container!");
+ return (NULL == container->name && NULL == container->value) ? SPDY_YES : SPDY_NO;
+}
+
+struct SPDY_NameValue *
+SPDY_name_value_create ()
+{
+ struct SPDY_NameValue *pair;
+
+ if(NULL == (pair = malloc(sizeof(struct SPDY_NameValue))))
+ return NULL;
+
+ memset (pair, 0, sizeof (struct SPDY_NameValue));
+
+ return pair;
+}
+
+
+int
+SPDY_name_value_add (struct SPDY_NameValue *container,
+ const char *name,
+ const char *value)
+{
+ unsigned int i;
+ unsigned int len;
+ struct SPDY_NameValue *pair;
+ struct SPDY_NameValue *temp;
+ char **temp_value;
+ char *temp_string;
+
+ if(NULL == container || NULL == name || NULL == value || 0 == (len = strlen(name)))
+ return SPDY_INPUT_ERROR;
+ //TODO there is old code handling value==NULL
+ //update it to handle strlen(value)==0
+
+ for(i=0; i<len; ++i)
+ {
+ if(isupper((int) name[i]))
+ return SPDY_INPUT_ERROR;
+ }
+
+ if(SPDYF_name_value_is_empty(container))
+ {
+ //container is empty/just created
+ if (NULL == (container->name = strdup (name)))
+ {
+ return SPDY_NO;
+ }
+ if (NULL == (container->value = malloc(sizeof(char *))))
+ {
+ free(container->name);
+ return SPDY_NO;
+ }
+ /*if(NULL == value)
+ container->value[0] = NULL;
+ else */if (NULL == (container->value[0] = strdup (value)))
+ {
+ free(container->value);
+ free(container->name);
+ return SPDY_NO;
+ }
+ container->num_values = 1;
+ return SPDY_YES;
+ }
+
+ pair = container;
+ while(NULL != pair)
+ {
+ if(0 == strcmp(pair->name, name))
+ {
+ //the value will be added to this pair
+ break;
+ }
+ pair = pair->next;
+ }
+
+ if(NULL == pair)
+ {
+ //the name doesn't exist in container, add new pair
+ if(NULL == (pair = malloc(sizeof(struct SPDY_NameValue))))
+ return SPDY_NO;
+
+ memset(pair, 0, sizeof(struct SPDY_NameValue));
+
+ if (NULL == (pair->name = strdup (name)))
+ {
+ free(pair);
+ return SPDY_NO;
+ }
+ if (NULL == (pair->value = malloc(sizeof(char *))))
+ {
+ free(pair->name);
+ free(pair);
+ return SPDY_NO;
+ }
+ /*if(NULL == value)
+ pair->value[0] = NULL;
+ else */if (NULL == (pair->value[0] = strdup (value)))
+ {
+ free(pair->value);
+ free(pair->name);
+ free(pair);
+ return SPDY_NO;
+ }
+ pair->num_values = 1;
+
+ temp = container;
+ while(NULL != temp->next)
+ temp = temp->next;
+ temp->next = pair;
+ pair->prev = temp;
+
+ return SPDY_YES;
+ }
+
+ //check for duplication (case sensitive)
+ for(i=0; i<pair->num_values; ++i)
+ if(0 == strcmp(pair->value[i], value))
+ return SPDY_NO;
+
+ if(strlen(pair->value[0]) > 0)
+ {
+ //the value will be appended to the others for this name
+ if (NULL == (temp_value = malloc((pair->num_values + 1) * sizeof(char *))))
+ {
+ return SPDY_NO;
+ }
+ memcpy(temp_value, pair->value, pair->num_values * sizeof(char *));
+ if (NULL == (temp_value[pair->num_values] = strdup (value)))
+ {
+ free(temp_value);
+ return SPDY_NO;
+ }
+ free(pair->value);
+ pair->value = temp_value;
+ ++pair->num_values;
+ return SPDY_YES;
+ }
+
+ //just replace the empty value
+
+ if (NULL == (temp_string = strdup (value)))
+ {
+ return SPDY_NO;
+ }
+ free(pair->value[0]);
+ pair->value[0] = temp_string;
+
+ return SPDY_YES;
+}
+
+
+const char * const *
+SPDY_name_value_lookup (struct SPDY_NameValue *container,
+ const char *name,
+ int *num_values)
+{
+ struct SPDY_NameValue *temp = container;
+
+ if(NULL == container || NULL == name || NULL == num_values)
+ return NULL;
+ if(SPDYF_name_value_is_empty(container))
+ return NULL;
+
+ do
+ {
+ if(strcmp(name, temp->name) == 0)
+ {
+ *num_values = temp->num_values;
+ return (const char * const *)temp->value;
+ }
+
+ temp = temp->next;
+ }
+ while(NULL != temp);
+
+ return NULL;
+}
+
+
+void
+SPDY_name_value_destroy (struct SPDY_NameValue *container)
+{
+ unsigned int i;
+ struct SPDY_NameValue *temp = container;
+
+ while(NULL != temp)
+ {
+ container = container->next;
+ free(temp->name);
+ for(i=0; i<temp->num_values; ++i)
+ free(temp->value[i]);
+ free(temp->value);
+ free(temp);
+ temp=container;
+ }
+}
+
+
+int
+SPDY_name_value_iterate (struct SPDY_NameValue *container,
+ SPDY_NameValueIterator iterator,
+ void *iterator_cls)
+{
+ int count;
+ int ret;
+ struct SPDY_NameValue *temp = container;
+
+ if(NULL == container)
+ return SPDY_INPUT_ERROR;
+
+ //check if container is an empty struct
+ if(SPDYF_name_value_is_empty(container))
+ return 0;
+
+ count = 0;
+
+ if(NULL == iterator)
+ {
+ do
+ {
+ ++count;
+ temp=temp->next;
+ }
+ while(NULL != temp);
+
+ return count;
+ }
+
+ //code duplication for avoiding if here
+ do
+ {
+ ++count;
+ ret = iterator(iterator_cls, temp->name, (const char * const *)temp->value, temp->num_values);
+ temp=temp->next;
+ }
+ while(NULL != temp && SPDY_YES == ret);
+
+ return count;
+}
+
+void
+SPDY_destroy_response(struct SPDY_Response *response)
+{
+ if(NULL == response)
+ return;
+ free(response->data);
+ free(response->headers);
+ free(response);
+}
+
+
+struct SPDYF_Response_Queue *
+SPDYF_response_queue_create(bool is_data,
+ void *data,
+ size_t data_size,
+ struct SPDY_Response *response,
+ struct SPDYF_Stream *stream,
+ bool closestream,
+ SPDYF_ResponseQueueResultCallback frqcb,
+ void *frqcb_cls,
+ SPDY_ResponseResultCallback rrcb,
+ void *rrcb_cls)
+{
+ struct SPDYF_Response_Queue *head = NULL;
+ struct SPDYF_Response_Queue *prev;
+ struct SPDYF_Response_Queue *response_to_queue;
+ struct SPDYF_Control_Frame *control_frame;
+ struct SPDYF_Data_Frame *data_frame;
+ unsigned int i;
+ bool is_last;
+
+ SPDYF_ASSERT((! is_data)
+ || ((0 == data_size) && (NULL != response->rcb))
+ || ((0 < data_size) && (NULL == response->rcb)),
+ "either data or request->rcb must not be null");
+
+ if (is_data && (data_size > SPDY_MAX_SUPPORTED_FRAME_SIZE))
+ {
+ //separate the data in more frames and add them to the queue
+
+ prev=NULL;
+ for(i = 0; i < data_size; i += SPDY_MAX_SUPPORTED_FRAME_SIZE)
+ {
+ is_last = (i + SPDY_MAX_SUPPORTED_FRAME_SIZE) >= data_size;
+
+ if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue))))
+ goto free_and_fail;
+
+ memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue));
+ if(0 == i)
+ head = response_to_queue;
+
+ if(NULL == (data_frame = malloc(sizeof(struct SPDYF_Data_Frame))))
+ {
+ free(response_to_queue);
+ goto free_and_fail;
+ }
+ memset(data_frame, 0, sizeof(struct SPDYF_Data_Frame));
+ data_frame->control_bit = 0;
+ data_frame->stream_id = stream->stream_id;
+ if(is_last && closestream)
+ data_frame->flags |= SPDY_DATA_FLAG_FIN;
+
+ response_to_queue->data_frame = data_frame;
+ response_to_queue->process_response_handler = &SPDYF_handler_write_data;
+ response_to_queue->is_data = is_data;
+ response_to_queue->stream = stream;
+ if(is_last)
+ {
+ response_to_queue->frqcb = frqcb;
+ response_to_queue->frqcb_cls = frqcb_cls;
+ response_to_queue->rrcb = rrcb;
+ response_to_queue->rrcb_cls = rrcb_cls;
+ }
+ response_to_queue->data = data + i;
+ response_to_queue->data_size = is_last
+ ? (data_size - 1) % SPDY_MAX_SUPPORTED_FRAME_SIZE + 1
+ : SPDY_MAX_SUPPORTED_FRAME_SIZE;
+ response_to_queue->response = response;
+
+ response_to_queue->prev = prev;
+ if(NULL != prev)
+ prev->next = response_to_queue;
+ prev = response_to_queue;
+ }
+
+ return head;
+
+ //for GOTO
+ free_and_fail:
+ while(NULL != head)
+ {
+ response_to_queue = head;
+ head = head->next;
+ free(response_to_queue->data_frame);
+ free(response_to_queue);
+ }
+ return NULL;
+ }
+
+ //create only one frame for data, data with callback or control frame
+
+ if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue))))
+ {
+ return NULL;
+ }
+ memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue));
+
+ if(is_data)
+ {
+ if(NULL == (data_frame = malloc(sizeof(struct SPDYF_Data_Frame))))
+ {
+ free(response_to_queue);
+ return NULL;
+ }
+ memset(data_frame, 0, sizeof(struct SPDYF_Data_Frame));
+ data_frame->control_bit = 0;
+ data_frame->stream_id = stream->stream_id;
+ if(closestream && NULL == response->rcb)
+ data_frame->flags |= SPDY_DATA_FLAG_FIN;
+
+ response_to_queue->data_frame = data_frame;
+ response_to_queue->process_response_handler = &SPDYF_handler_write_data;
+ }
+ else
+ {
+ if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame))))
+ {
+ free(response_to_queue);
+ return NULL;
+ }
+ memset(control_frame, 0, sizeof(struct SPDYF_Control_Frame));
+ control_frame->control_bit = 1;
+ control_frame->version = SPDY_VERSION;
+ control_frame->type = SPDY_CONTROL_FRAME_TYPES_SYN_REPLY;
+ if(closestream)
+ control_frame->flags |= SPDY_SYN_REPLY_FLAG_FIN;
+
+ response_to_queue->control_frame = control_frame;
+ response_to_queue->process_response_handler = &SPDYF_handler_write_syn_reply;
+ }
+
+ response_to_queue->is_data = is_data;
+ response_to_queue->stream = stream;
+ response_to_queue->frqcb = frqcb;
+ response_to_queue->frqcb_cls = frqcb_cls;
+ response_to_queue->rrcb = rrcb;
+ response_to_queue->rrcb_cls = rrcb_cls;
+ response_to_queue->data = data;
+ response_to_queue->data_size = data_size;
+ response_to_queue->response = response;
+
+ return response_to_queue;
+}
+
+
+void
+SPDYF_response_queue_destroy(struct SPDYF_Response_Queue *response_queue)
+{
+ //data is not copied to the struct but only linked
+ //but this is not valid for GOAWAY and RST_STREAM
+ if(!response_queue->is_data
+ && (SPDY_CONTROL_FRAME_TYPES_RST_STREAM == response_queue->control_frame->type
+ || SPDY_CONTROL_FRAME_TYPES_GOAWAY == response_queue->control_frame->type))
+ {
+ free(response_queue->data);
+ }
+ if(response_queue->is_data)
+ free(response_queue->data_frame);
+ else
+ free(response_queue->control_frame);
+
+ free(response_queue);
+}
+
+
+/* Needed by testcase to be extern -- should this be
+ in the header? */
+_MHD_EXTERN ssize_t
+SPDYF_name_value_to_stream(struct SPDY_NameValue * container[],
+ int num_containers,
+ void **stream)
+{
+ size_t size;
+ int32_t num_pairs = 0;
+ int32_t value_size;
+ int32_t name_size;
+ int32_t temp;
+ unsigned int i;
+ unsigned int offset;
+ unsigned int value_offset;
+ struct SPDY_NameValue * iterator;
+ int j;
+
+ size = 4; //for num pairs
+
+ for(j=0; j<num_containers; ++j)
+ {
+ iterator = container[j];
+ while(iterator != NULL)
+ {
+ ++num_pairs;
+ size += 4 + strlen(iterator->name); //length + string
+
+ SPDYF_ASSERT(iterator->num_values>0, "num_values is 0");
+
+ size += 4; //value length
+
+ for(i=0; i<iterator->num_values; ++i)
+ {
+ //if(NULL == iterator->value[i])
+ // continue;
+ size += strlen(iterator->value[i]); // string
+ if(i/* || !strlen(iterator->value[i])*/) ++size; //NULL separator
+ }
+
+ iterator = iterator->next;
+ }
+ }
+
+ if(NULL == (*stream = malloc(size)))
+ {
+ return -1;
+ }
+
+ //put num_pairs to the stream
+ num_pairs = htonl(num_pairs);
+ memcpy(*stream, &num_pairs, 4);
+ offset = 4;
+
+ //put all other headers to the stream
+ for(j=0; j<num_containers; ++j)
+ {
+ iterator = container[j];
+ while(iterator != NULL)
+ {
+ name_size = strlen(iterator->name);
+ temp = htonl(name_size);
+ memcpy(*stream + offset, &temp, 4);
+ offset += 4;
+ strncpy(*stream + offset, iterator->name, name_size);
+ offset += name_size;
+
+ value_offset = offset;
+ offset += 4;
+ for(i=0; i<iterator->num_values; ++i)
+ {
+ if(i /*|| !strlen(iterator->value[0])*/)
+ {
+ memset(*stream + offset, 0, 1);
+ ++offset;
+ //if(!i) continue;
+ }
+ //else if(NULL != iterator->value[i])
+ //{
+ strncpy(*stream + offset, iterator->value[i], strlen(iterator->value[i]));
+ offset += strlen(iterator->value[i]);
+ //}
+ }
+ value_size = offset - value_offset - 4;
+ value_size = htonl(value_size);
+ memcpy(*stream + value_offset, &value_size, 4);
+
+ iterator = iterator->next;
+ }
+ }
+
+ SPDYF_ASSERT(offset == size,"offset is wrong");
+
+ return size;
+}
+
+
+/* Needed by testcase to be extern -- should this be
+ in the header? */
+_MHD_EXTERN int
+SPDYF_name_value_from_stream(void *stream,
+ size_t size,
+ struct SPDY_NameValue ** container)
+{
+ int32_t num_pairs;
+ int32_t value_size;
+ int32_t name_size;
+ int i;
+ unsigned int offset = 0;
+ unsigned int value_end_offset;
+ char *name;
+ char *value;
+
+ if(NULL == (*container = SPDY_name_value_create ()))
+ {
+ return SPDY_NO;
+ }
+
+ //get number of pairs
+ memcpy(&num_pairs, stream, 4);
+ offset = 4;
+ num_pairs = ntohl(num_pairs);
+
+ if(num_pairs > 0)
+ {
+ for(i = 0; i < num_pairs; ++i)
+ {
+ //get name size
+ memcpy(&name_size, stream + offset, 4);
+ offset += 4;
+ name_size = ntohl(name_size);
+ //get name
+ if(NULL == (name = strndup(stream + offset, name_size)))
+ {
+ SPDY_name_value_destroy(*container);
+ return SPDY_NO;
+ }
+ offset+=name_size;
+
+ //get value size
+ memcpy(&value_size, stream + offset, 4);
+ offset += 4;
+ value_size = ntohl(value_size);
+ value_end_offset = offset + value_size;
+ //get value
+ do
+ {
+ if(NULL == (value = strndup(stream + offset, value_size)))
+ {
+ free(name);
+ SPDY_name_value_destroy(*container);
+ return SPDY_NO;
+ }
+ offset += strlen(value);
+ if(offset < value_end_offset)
+ ++offset; //NULL separator
+
+ //add name/value to the struct
+ if(SPDY_YES != SPDY_name_value_add(*container, name, value))
+ {
+ free(name);
+ free(value);
+ SPDY_name_value_destroy(*container);
+ return SPDY_NO;
+ }
+ free(value);
+ }
+ while(offset < value_end_offset);
+
+ free(name);
+
+ if(offset != value_end_offset)
+ {
+ SPDY_name_value_destroy(*container);
+ return SPDY_INPUT_ERROR;
+ }
+ }
+ }
+
+ if(offset == size)
+ return SPDY_YES;
+
+ SPDY_name_value_destroy(*container);
+ return SPDY_INPUT_ERROR;
+}
diff --git a/src/microspdy/structures.h b/src/microspdy/structures.h
new file mode 100644
index 0000000..e1f8797
--- /dev/null
+++ b/src/microspdy/structures.h
@@ -0,0 +1,1246 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file structures.h
+ * @brief internal and public structures -- most of the structs used by
+ * the library are defined here
+ * @author Andrey Uzunov
+ */
+
+#ifndef STRUCTURES_H
+#define STRUCTURES_H
+
+#include "platform.h"
+#include "microspdy.h"
+#include "io.h"
+
+
+/**
+ * All possible SPDY control frame types. The number is used in the header
+ * of the control frame.
+ */
+enum SPDY_CONTROL_FRAME_TYPES
+{
+ /**
+ * The SYN_STREAM control frame allows the sender to asynchronously
+ * create a stream between the endpoints.
+ */
+ SPDY_CONTROL_FRAME_TYPES_SYN_STREAM = 1,
+
+ /**
+ * SYN_REPLY indicates the acceptance of a stream creation by
+ * the recipient of a SYN_STREAM frame.
+ */
+ SPDY_CONTROL_FRAME_TYPES_SYN_REPLY = 2,
+
+ /**
+ * The RST_STREAM frame allows for abnormal termination of a stream.
+ * When sent by the creator of a stream, it indicates the creator
+ * wishes to cancel the stream. When sent by the recipient of a
+ * stream, it indicates an error or that the recipient did not want
+ * to accept the stream, so the stream should be closed.
+ */
+ SPDY_CONTROL_FRAME_TYPES_RST_STREAM = 3,
+
+ /**
+ * A SETTINGS frame contains a set of id/value pairs for
+ * communicating configuration data about how the two endpoints may
+ * communicate. SETTINGS frames can be sent at any time by either
+ * endpoint, are optionally sent, and are fully asynchronous. When
+ * the server is the sender, the sender can request that
+ * configuration data be persisted by the client across SPDY
+ * sessions and returned to the server in future communications.
+ */
+ SPDY_CONTROL_FRAME_TYPES_SETTINGS = 4,
+
+ /**
+ * The PING control frame is a mechanism for measuring a minimal
+ * round-trip time from the sender. It can be sent from the client
+ * or the server. Recipients of a PING frame should send an
+ * identical frame to the sender as soon as possible (if there is
+ * other pending data waiting to be sent, PING should take highest
+ * priority). Each ping sent by a sender should use a unique ID.
+ */
+ SPDY_CONTROL_FRAME_TYPES_PING = 6,
+
+ /**
+ * The GOAWAY control frame is a mechanism to tell the remote side
+ * of the connection to stop creating streams on this session. It
+ * can be sent from the client or the server.
+ */
+ SPDY_CONTROL_FRAME_TYPES_GOAWAY = 7,
+
+ /**
+ * The HEADERS frame augments a stream with additional headers. It
+ * may be optionally sent on an existing stream at any time.
+ * Specific application of the headers in this frame is
+ * application-dependent. The name/value header block within this
+ * frame is compressed.
+ */
+ SPDY_CONTROL_FRAME_TYPES_HEADERS = 8,
+
+ /**
+ * The WINDOW_UPDATE control frame is used to implement per stream
+ * flow control in SPDY. Flow control in SPDY is per hop, that is,
+ * only between the two endpoints of a SPDY connection. If there are
+ * one or more intermediaries between the client and the origin
+ * server, flow control signals are not explicitly forwarded by the
+ * intermediaries.
+ */
+ SPDY_CONTROL_FRAME_TYPES_WINDOW_UPDATE = 9,
+
+ /**
+ * The CREDENTIAL control frame is used by the client to send
+ * additional client certificates to the server. A SPDY client may
+ * decide to send requests for resources from different origins on
+ * the same SPDY session if it decides that that server handles both
+ * origins. For example if the IP address associated with both
+ * hostnames matches and the SSL server certificate presented in the
+ * initial handshake is valid for both hostnames. However, because
+ * the SSL connection can contain at most one client certificate,
+ * the client needs a mechanism to send additional client
+ * certificates to the server.
+ */
+ SPDY_CONTROL_FRAME_TYPES_CREDENTIAL = 11
+};
+
+
+/**
+ * SPDY_SESSION_STATUS is used to show the current receiving state
+ * of each session, i.e. what is expected to come now, and how it should
+ * be handled.
+ */
+enum SPDY_SESSION_STATUS
+{
+ /**
+ * The session is in closing state, do not read read anything from
+ * it. Do not write anything to it.
+ */
+ SPDY_SESSION_STATUS_CLOSING = 0,
+
+ /**
+ * Wait for new SPDY frame to come.
+ */
+ SPDY_SESSION_STATUS_WAIT_FOR_HEADER = 1,
+
+ /**
+ * The standard 8 byte header of the SPDY frame was received and
+ * handled. Wait for the specific (sub)headers according to the
+ * frame type.
+ */
+ SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER = 2,
+
+ /**
+ * The specific (sub)headers were received and handled. Wait for the
+ * "body", i.e. wait for the name/value pairs compressed by zlib.
+ */
+ SPDY_SESSION_STATUS_WAIT_FOR_BODY = 3,
+
+ /**
+ * Ignore all the bytes read from the socket, e.g. larger frames.
+ */
+ SPDY_SESSION_STATUS_IGNORE_BYTES= 4,
+
+ /**
+ * The session is in pre-closing state, do not read read anything
+ * from it. In this state the output queue will be written to the
+ * socket.
+ */
+ SPDY_SESSION_STATUS_FLUSHING = 5,
+};
+
+
+/**
+ * Specific flags for the SYN_STREAM control frame.
+ */
+enum SPDY_SYN_STREAM_FLAG
+{
+ /**
+ * The sender won't send any more frames on this stream.
+ */
+ SPDY_SYN_STREAM_FLAG_FIN = 1,
+
+ /**
+ * The sender creates this stream as unidirectional.
+ */
+ SPDY_SYN_STREAM_FLAG_UNIDIRECTIONAL = 2
+};
+
+
+/**
+ * Specific flags for the SYN_REPLY control frame.
+ */
+enum SPDY_SYN_REPLY_FLAG
+{
+ /**
+ * The sender won't send any more frames on this stream.
+ */
+ SPDY_SYN_REPLY_FLAG_FIN = 1
+};
+
+
+/**
+ * Specific flags for the data frame.
+ */
+enum SPDY_DATA_FLAG
+{
+ /**
+ * The sender won't send any more frames on this stream.
+ */
+ SPDY_DATA_FLAG_FIN = 1,
+
+ /**
+ * The data in the frame is compressed.
+ * This flag appears only in the draft on ietf.org but not on
+ * chromium.org.
+ */
+ SPDY_DATA_FLAG_COMPRESS = 2
+};
+
+/**
+ * Status code within RST_STREAM control frame.
+ */
+enum SPDY_RST_STREAM_STATUS
+{
+ /**
+ * This is a generic error, and should only be used if a more
+ * specific error is not available.
+ */
+ SPDY_RST_STREAM_STATUS_PROTOCOL_ERROR = 1,
+
+ /**
+ * This is returned when a frame is received for a stream which is
+ * not active.
+ */
+ SPDY_RST_STREAM_STATUS_INVALID_STREAM = 2,
+
+ /**
+ * Indicates that the stream was refused before any processing has
+ * been done on the stream.
+ */
+ SPDY_RST_STREAM_STATUS_REFUSED_STREAM = 3,
+
+ /**
+ * Indicates that the recipient of a stream does not support the
+ * SPDY version requested.
+ */
+ SPDY_RST_STREAM_STATUS_UNSUPPORTED_VERSION = 4,
+
+ /**
+ * Used by the creator of a stream to indicate that the stream is
+ * no longer needed.
+ */
+ SPDY_RST_STREAM_STATUS_CANCEL = 5,
+
+ /**
+ * This is a generic error which can be used when the implementation
+ * has internally failed, not due to anything in the protocol.
+ */
+ SPDY_RST_STREAM_STATUS_INTERNAL_ERROR = 6,
+
+ /**
+ * The endpoint detected that its peer violated the flow control
+ * protocol.
+ */
+ SPDY_RST_STREAM_STATUS_FLOW_CONTROL_ERROR = 7,
+
+ /**
+ * The endpoint received a SYN_REPLY for a stream already open.
+ */
+ SPDY_RST_STREAM_STATUS_STREAM_IN_USE = 8,
+
+ /**
+ * The endpoint received a data or SYN_REPLY frame for a stream
+ * which is half closed.
+ */
+ SPDY_RST_STREAM_STATUS_STREAM_ALREADY_CLOSED = 9,
+
+ /**
+ * The server received a request for a resource whose origin does
+ * not have valid credentials in the client certificate vector.
+ */
+ SPDY_RST_STREAM_STATUS_INVALID_CREDENTIALS = 10,
+
+ /**
+ * The endpoint received a frame which this implementation could not
+ * support. If FRAME_TOO_LARGE is sent for a SYN_STREAM, HEADERS,
+ * or SYN_REPLY frame without fully processing the compressed
+ * portion of those frames, then the compression state will be
+ * out-of-sync with the other endpoint. In this case, senders of
+ * FRAME_TOO_LARGE MUST close the session.
+ */
+ SPDY_RST_STREAM_STATUS_FRAME_TOO_LARGE = 11
+};
+
+
+/**
+ * Status code within GOAWAY control frame.
+ */
+enum SPDY_GOAWAY_STATUS
+{
+ /**
+ * This is a normal session teardown.
+ */
+ SPDY_GOAWAY_STATUS_OK = 0,
+
+ /**
+ * This is a generic error, and should only be used if a more
+ * specific error is not available.
+ */
+ SPDY_GOAWAY_STATUS_PROTOCOL_ERROR = 1,
+
+ /**
+ * This is a generic error which can be used when the implementation
+ * has internally failed, not due to anything in the protocol.
+ */
+ SPDY_GOAWAY_STATUS_INTERNAL_ERROR = 11
+};
+
+
+struct SPDYF_Stream;
+
+struct SPDYF_Response_Queue;
+
+
+/**
+ * Callback for received new data chunk.
+ *
+ * @param cls client-defined closure
+ * @param stream handler
+ * @param buf data chunk from the data
+ * @param size the size of the data chunk 'buf' in bytes
+ * @param more false if this is the last frame received on this stream. Note:
+ * true does not mean that more data will come, exceptional
+ * situation is possible
+ * @return SPDY_YES to continue calling the function,
+ * SPDY_NO to stop calling the function for this stream
+ */
+typedef int
+(*SPDYF_NewDataCallback) (void * cls,
+ struct SPDYF_Stream *stream,
+ const void * buf,
+ size_t size,
+ bool more);
+
+
+/**
+ * Callback for new stream. To be used in the application layer of the
+ * lib.
+ *
+ * @param cls
+ * @param stream the new stream
+ * @return SPDY_YES on success,
+ * SPDY_NO if error occurs
+ */
+typedef int
+(*SPDYF_NewStreamCallback) (void *cls,
+ struct SPDYF_Stream * stream);
+
+
+/**
+ * Callback to be called when the response queue object was handled and
+ * the data was already sent.
+ *
+ * @param cls
+ * @param response_queue the SPDYF_Response_Queue structure which will
+ * be cleaned very soon
+ * @param status shows if actually the response was sent or it was
+ * discarded by the lib for any reason (e.g., closing session,
+ * closing stream, stopping daemon, etc.). It is possible that
+ * status indicates an error but part of the response (in one
+ * or several frames) was sent to the client.
+ */
+typedef void
+(*SPDYF_ResponseQueueResultCallback) (void * cls,
+ struct SPDYF_Response_Queue *response_queue,
+ enum SPDY_RESPONSE_RESULT status);
+
+
+/**
+ * Representation of the control frame's headers, which are common for
+ * all types.
+ */
+struct __attribute__((__packed__)) SPDYF_Control_Frame
+{
+ uint16_t version : 15;
+ uint16_t control_bit : 1; /* always 1 for control frames */
+ uint16_t type;
+ uint32_t flags : 8;
+ uint32_t length : 24;
+};
+
+
+/**
+ * Representation of the data frame's headers.
+ */
+struct __attribute__((__packed__)) SPDYF_Data_Frame
+{
+ uint32_t stream_id : 31;
+ uint32_t control_bit : 1; /* always 0 for data frames */
+ uint32_t flags : 8;
+ uint32_t length : 24;
+};
+
+
+/**
+ * Queue of the responses, to be handled (e.g. compressed) and sent later.
+ */
+struct SPDYF_Response_Queue
+{
+ /**
+ * This is a doubly-linked list.
+ */
+ struct SPDYF_Response_Queue *next;
+
+ /**
+ * This is a doubly-linked list.
+ */
+ struct SPDYF_Response_Queue *prev;
+
+ /**
+ * Stream (Request) for which is the response.
+ */
+ struct SPDYF_Stream *stream;
+
+ /**
+ * Response structure with all the data (uncompressed headers) to be sent.
+ */
+ struct SPDY_Response *response;
+
+ /**
+ * Control frame. The length field should be set after compressing
+ * the headers!
+ */
+ struct SPDYF_Control_Frame *control_frame;
+
+ /**
+ * Data frame. The length field should be set after compressing
+ * the body!
+ */
+ struct SPDYF_Data_Frame *data_frame;
+
+ /**
+ * Data to be sent: name/value pairs in control frames or body in data frames.
+ */
+ void *data;
+
+ /**
+ * Specific handler for different frame types.
+ */
+ int (* process_response_handler)(struct SPDY_Session *session);
+
+ /**
+ * Callback to be called when the last bytes from the response was sent
+ * to the client.
+ */
+ SPDYF_ResponseQueueResultCallback frqcb;
+
+ /**
+ * Closure for frqcb.
+ */
+ void *frqcb_cls;
+
+ /**
+ * Callback to be used by the application layer.
+ */
+ SPDY_ResponseResultCallback rrcb;
+
+ /**
+ * Closure for rcb.
+ */
+ void *rrcb_cls;
+
+ /**
+ * Data size.
+ */
+ size_t data_size;
+
+ /**
+ * True if data frame should be sent. False if control frame should
+ * be sent.
+ */
+ bool is_data;
+};
+
+
+
+/**
+ * Collection of HTTP headers used in requests and responses.
+ */
+struct SPDY_NameValue
+{
+ /**
+ * This is a doubly-linked list.
+ */
+ struct SPDY_NameValue *next;
+
+ /**
+ * This is a doubly-linked list.
+ */
+ struct SPDY_NameValue *prev;
+
+ /**
+ * Null terminated string for name.
+ */
+ char *name;
+
+ /**
+ * Array of Null terminated strings for value. num_values is the
+ * length of the array.
+ */
+ char **value;
+
+ /**
+ * Number of values, this is >= 0.
+ */
+ unsigned int num_values;
+};
+
+
+/**
+ * Represents a SPDY stream
+ */
+struct SPDYF_Stream
+{
+ /**
+ * This is a doubly-linked list.
+ */
+ struct SPDYF_Stream *next;
+
+ /**
+ * This is a doubly-linked list.
+ */
+ struct SPDYF_Stream *prev;
+
+ /**
+ * Reference to the SPDY_Session struct.
+ */
+ struct SPDY_Session *session;
+
+ /**
+ * Name value pairs, sent within the frame which created the stream.
+ */
+ struct SPDY_NameValue *headers;
+
+ /**
+ * Any object to be used by the application layer.
+ */
+ void *cls;
+
+ /**
+ * This stream's ID.
+ */
+ uint32_t stream_id;
+
+ /**
+ * Stream to which this one is associated.
+ */
+ uint32_t assoc_stream_id;
+
+ /**
+ * The window of the data within data frames.
+ */
+ uint32_t window_size;
+
+ /**
+ * Stream priority. 0 is the highest, 7 is the lowest.
+ */
+ uint8_t priority;
+
+ /**
+ * Integer specifying the index in the server's CREDENTIAL vector of
+ * the client certificate to be used for this request The value 0
+ * means no client certificate should be associated with this stream.
+ */
+ uint8_t slot;
+
+ /**
+ * If initially the stream was created as unidirectional.
+ */
+ bool flag_unidirectional;
+
+ /**
+ * If the stream won't be used for receiving frames anymore. The
+ * client has sent FLAG_FIN or the stream was terminated with
+ * RST_STREAM.
+ */
+ bool is_in_closed;
+
+ /**
+ * If the stream won't be used for sending out frames anymore. The
+ * server has sent FLAG_FIN or the stream was terminated with
+ * RST_STREAM.
+ */
+ bool is_out_closed;
+
+ /**
+ * Which entity (server/client) has created the stream.
+ */
+ bool is_server_initiator;
+};
+
+
+/**
+ * Represents a SPDY session which is just a TCP connection
+ */
+struct SPDY_Session
+{
+ /**
+ * zlib stream for decompressing all the name/pair values from the
+ * received frames. All the received compressed data must be
+ * decompressed within one context: this stream. Thus, it should be
+ * unique for the session and initialized at its creation.
+ */
+ z_stream zlib_recv_stream;
+
+ /**
+ * zlib stream for compressing all the name/pair values from the
+ * frames to be sent. All the sent compressed data must be
+ * compressed within one context: this stream. Thus, it should be
+ * unique for the session and initialized at its creation.
+ */
+ z_stream zlib_send_stream;
+
+ /**
+ * This is a doubly-linked list.
+ */
+ struct SPDY_Session *next;
+
+ /**
+ * This is a doubly-linked list.
+ */
+ struct SPDY_Session *prev;
+
+ /**
+ * Reference to the SPDY_Daemon struct.
+ */
+ struct SPDY_Daemon *daemon;
+
+ /**
+ * Foreign address (of length addr_len).
+ */
+ struct sockaddr *addr;
+
+ /**
+ * Head of doubly-linked list of the SPDY streams belonging to the
+ * session.
+ */
+ struct SPDYF_Stream *streams_head;
+
+ /**
+ * Tail of doubly-linked list of the streams.
+ */
+ struct SPDYF_Stream *streams_tail;
+
+ /**
+ * Unique IO context for the session. Initialized on each creation
+ * (actually when the TCP connection is established).
+ */
+ void *io_context;
+
+ /**
+ * Head of doubly-linked list of the responses.
+ */
+ struct SPDYF_Response_Queue *response_queue_head;
+
+ /**
+ * Tail of doubly-linked list of the responses.
+ */
+ struct SPDYF_Response_Queue *response_queue_tail;
+
+ /**
+ * Buffer for reading requests.
+ */
+ void *read_buffer;
+
+ /**
+ * Buffer for writing responses.
+ */
+ void *write_buffer;
+
+ /**
+ * Specific handler for the frame that is currently being received.
+ */
+ void (*frame_handler) (struct SPDY_Session * session);
+
+ /**
+ * Closure for frame_handler.
+ */
+ void *frame_handler_cls;
+
+ /**
+ * Extra field to be used by the user with set/get func for whatever
+ * purpose he wants.
+ */
+ void *user_cls;
+
+ /**
+ * Function to initialize the IO context for a new session.
+ */
+ SPDYF_IONewSession fio_new_session;
+
+ /**
+ * Function to deinitialize the IO context for a session.
+ */
+ SPDYF_IOCloseSession fio_close_session;
+
+ /**
+ * Function to read data from socket.
+ */
+ SPDYF_IORecv fio_recv;
+
+ /**
+ * Function to write data to socket.
+ */
+ SPDYF_IOSend fio_send;
+
+ /**
+ * Function to check for pending data in IO buffers.
+ */
+ SPDYF_IOIsPending fio_is_pending;
+
+ /**
+ * Function to call before writing set of frames.
+ */
+ SPDYF_IOBeforeWrite fio_before_write;
+
+ /**
+ * Function to call after writing set of frames.
+ */
+ SPDYF_IOAfterWrite fio_after_write;
+
+ /**
+ * Number of bytes that the lib must ignore immediately after they
+ * are read from the TLS socket without adding them to the read buf.
+ * This is needed, for instance, when receiving frame bigger than
+ * the buffer to avoid deadlock situations.
+ */
+ size_t read_ignore_bytes;
+
+ /**
+ * Size of read_buffer (in bytes). This value indicates
+ * how many bytes we're willing to read into the buffer;
+ * the real buffer is one byte longer to allow for
+ * adding zero-termination (when needed).
+ */
+ size_t read_buffer_size;
+
+ /**
+ * Position where we currently append data in
+ * read_buffer (last valid position).
+ */
+ size_t read_buffer_offset;
+
+ /**
+ * Position until where everything was already read
+ */
+ size_t read_buffer_beginning;
+
+ /**
+ * Size of write_buffer (in bytes). This value indicates
+ * how many bytes we're willing to prepare for writing.
+ */
+ size_t write_buffer_size;
+
+ /**
+ * Position where we currently append data in
+ * write_buffer (last valid position).
+ */
+ size_t write_buffer_offset;
+
+ /**
+ * Position until where everything was already written to the socket
+ */
+ size_t write_buffer_beginning;
+
+ /**
+ * Last time this connection had any activity
+ * (reading or writing). In milliseconds.
+ */
+ unsigned long long last_activity;
+
+ /**
+ * Socket for this connection. Set to -1 if
+ * this connection has died (daemon should clean
+ * up in that case).
+ */
+ int socket_fd;
+
+ /**
+ * Length of the foreign address.
+ */
+ socklen_t addr_len;
+
+ /**
+ * The biggest stream ID for this session for streams initiated
+ * by the client.
+ */
+ uint32_t last_in_stream_id;
+
+ /**
+ * The biggest stream ID for this session for streams initiated
+ * by the server.
+ */
+ uint32_t last_out_stream_id;
+
+ /**
+ * This value is updated whenever SYN_REPLY or RST_STREAM are sent
+ * and is used later in GOAWAY frame.
+ * TODO it is not clear in the draft what happens when streams are
+ * not answered in the order of their IDs. Moreover, why should we
+ * send GOAWAY with the ID of received bogus SYN_STREAM with huge ID?
+ */
+ uint32_t last_replied_to_stream_id;
+
+ /**
+ * Shows the stream id of the currently handled frame. This value is
+ * to be used when sending RST_STREAM in answer to a problematic
+ * frame, e.g. larger than supported.
+ */
+ uint32_t current_stream_id;
+
+ /**
+ * Maximum number of frames to be written to the socket at once. The
+ * library tries to send max_num_frames in a single call to SPDY_run
+ * for a single session. This means no requests can be received nor
+ * other sessions can send data as long the current one has enough
+ * frames to send and there is no error on writing.
+ */
+ uint32_t max_num_frames;
+
+ /**
+ * Shows the current receiving state the session, i.e. what is
+ * expected to come now, and how it shold be handled.
+ */
+ enum SPDY_SESSION_STATUS status;
+
+ /**
+ * Has this socket been closed for reading (i.e.
+ * other side closed the connection)? If so,
+ * we must completely close the connection once
+ * we are done sending our response (and stop
+ * trying to read from this socket).
+ */
+ bool read_closed;
+
+ /**
+ * If the server sends GOAWAY, it must ignore all SYN_STREAMS for
+ * this session. Normally the server will soon close the TCP session.
+ */
+ bool is_goaway_sent;
+
+ /**
+ * If the server receives GOAWAY, it must not send new SYN_STREAMS
+ * on this session. Normally the client will soon close the TCP
+ * session.
+ */
+ bool is_goaway_received;
+};
+
+
+/**
+ * State and settings kept for each SPDY daemon.
+ */
+struct SPDY_Daemon
+{
+
+ /**
+ * Tail of doubly-linked list of our current, active sessions.
+ */
+ struct SPDY_Session *sessions_head;
+
+ /**
+ * Tail of doubly-linked list of our current, active sessions.
+ */
+ struct SPDY_Session *sessions_tail;
+
+ /**
+ * Tail of doubly-linked list of connections to clean up.
+ */
+ struct SPDY_Session *cleanup_head;
+
+ /**
+ * Tail of doubly-linked list of connections to clean up.
+ */
+ struct SPDY_Session *cleanup_tail;
+
+ /**
+ * Unique IO context for the daemon. Initialized on daemon start.
+ */
+ void *io_context;
+
+ /**
+ * Certificate file of the server. File path is kept here.
+ */
+ char *certfile;
+
+ /**
+ * Key file for the certificate of the server. File path is
+ * kept here.
+ */
+ char *keyfile;
+
+
+ /**
+ * The address to which the listening socket is bound.
+ */
+ struct sockaddr *address;
+
+ /**
+ * Callback called when a new SPDY session is
+ * established by a client
+ */
+ SPDY_NewSessionCallback new_session_cb;
+
+ /**
+ * Callback called when a client closes the session
+ */
+ SPDY_SessionClosedCallback session_closed_cb;
+
+ /**
+ * Callback called when a client sends request
+ */
+ SPDY_NewRequestCallback new_request_cb;
+
+ /**
+ * Callback called when HTTP POST params are received
+ * after request. To be used by the application layer
+ */
+ SPDY_NewDataCallback received_data_cb;
+
+ /**
+ * Callback called when DATA frame is received.
+ */
+ SPDYF_NewDataCallback freceived_data_cb;
+
+ /**
+ * Closure argument for all the callbacks that can be used by the client.
+ */
+ void *cls;
+
+ /**
+ * Callback called when new stream is created.
+ */
+ SPDYF_NewStreamCallback fnew_stream_cb;
+
+ /**
+ * Closure argument for all the callbacks defined in the framing layer.
+ */
+ void *fcls;
+
+ /**
+ * Function to initialize the IO context for the daemon.
+ */
+ SPDYF_IOInit fio_init;
+
+ /**
+ * Function to deinitialize the IO context for the daemon.
+ */
+ SPDYF_IODeinit fio_deinit;
+
+ /**
+ * After how many milliseconds of inactivity should
+ * connections time out? Zero for no timeout.
+ */
+ unsigned long long session_timeout;
+
+ /**
+ * Listen socket.
+ */
+ int socket_fd;
+
+ /**
+ * This value is inherited by all sessions of the daemon.
+ * Maximum number of frames to be written to the socket at once. The
+ * library tries to send max_num_frames in a single call to SPDY_run
+ * for a single session. This means no requests can be received nor
+ * other sessions can send data as long the current one has enough
+ * frames to send and there is no error on writing.
+ */
+ uint32_t max_num_frames;
+
+ /**
+ * Daemon's options.
+ */
+ enum SPDY_DAEMON_OPTION options;
+
+ /**
+ * Daemon's flags.
+ */
+ enum SPDY_DAEMON_FLAG flags;
+
+ /**
+ * IO subsystem type used by daemon and all its sessions.
+ */
+ enum SPDY_IO_SUBSYSTEM io_subsystem;
+
+ /**
+ * Listen port.
+ */
+ uint16_t port;
+};
+
+
+/**
+ * Represents a SPDY response.
+ */
+struct SPDY_Response
+{
+ /**
+ * Raw uncompressed stream of the name/value pairs in SPDY frame
+ * used for the HTTP headers.
+ */
+ void *headers;
+
+ /**
+ * Raw stream of the data to be sent. Equivalent to the body in HTTP
+ * response.
+ */
+ void *data;
+
+ /**
+ * Callback function to be used when the response data is provided
+ * with callbacks. In this case data must be NULL and data_size must
+ * be 0.
+ */
+ SPDY_ResponseCallback rcb;
+
+ /**
+ * Extra argument to rcb.
+ */
+ void *rcb_cls;
+
+ /**
+ * Length of headers.
+ */
+ size_t headers_size;
+
+ /**
+ * Length of data.
+ */
+ size_t data_size;
+
+ /**
+ * The callback func will be called to get that amount of bytes to
+ * put them into a DATA frame. It is either user preffered or
+ * the maximum supported by the lib value.
+ */
+ uint32_t rcb_block_size;
+};
+
+
+/* Macros for handling data and structures */
+
+
+/**
+ * Insert an element at the head of a DLL. Assumes that head, tail and
+ * element are structs with prev and next fields.
+ *
+ * @param head pointer to the head of the DLL (struct ? *)
+ * @param tail pointer to the tail of the DLL (struct ? *)
+ * @param element element to insert (struct ? *)
+ */
+#define DLL_insert(head,tail,element) do { \
+ (element)->next = (head); \
+ (element)->prev = NULL; \
+ if ((tail) == NULL) \
+ (tail) = element; \
+ else \
+ (head)->prev = element; \
+ (head) = (element); } while (0)
+
+
+/**
+ * Remove an element from a DLL. Assumes
+ * that head, tail and element are structs
+ * with prev and next fields.
+ *
+ * @param head pointer to the head of the DLL (struct ? *)
+ * @param tail pointer to the tail of the DLL (struct ? *)
+ * @param element element to remove (struct ? *)
+ */
+#define DLL_remove(head,tail,element) do { \
+ if ((element)->prev == NULL) \
+ (head) = (element)->next; \
+ else \
+ (element)->prev->next = (element)->next; \
+ if ((element)->next == NULL) \
+ (tail) = (element)->prev; \
+ else \
+ (element)->next->prev = (element)->prev; \
+ (element)->next = NULL; \
+ (element)->prev = NULL; } while (0)
+
+
+/**
+ * Convert all integers in a SPDY control frame headers structure from
+ * host byte order to network byte order.
+ *
+ * @param frame input and output structure (struct SPDY_Control_Frame *)
+ */
+#if HAVE_BIG_ENDIAN
+#define SPDYF_CONTROL_FRAME_HTON(frame)
+#else
+#define SPDYF_CONTROL_FRAME_HTON(frame) do { \
+ (*((uint16_t *) frame )) = (*((uint8_t *) (frame) +1 )) | ((*((uint8_t *) frame ))<<8);\
+ (frame)->type = htons((frame)->type); \
+ (frame)->length = HTON24((frame)->length); \
+ } while (0)
+#endif
+
+
+/**
+ * Convert all integers in a SPDY control frame headers structure from
+ * network byte order to host byte order.
+ *
+ * @param frame input and output structure (struct SPDY_Control_Frame *)
+ */
+#if HAVE_BIG_ENDIAN
+#define SPDYF_CONTROL_FRAME_NTOH(frame)
+#else
+#define SPDYF_CONTROL_FRAME_NTOH(frame) do { \
+ (*((uint16_t *) frame )) = (*((uint8_t *) (frame) +1 )) | ((*((uint8_t *) frame ))<<8);\
+ (frame)->type = ntohs((frame)->type); \
+ (frame)->length = NTOH24((frame)->length); \
+ } while (0)
+#endif
+
+
+/**
+ * Convert all integers in a SPDY data frame headers structure from
+ * host byte order to network byte order.
+ *
+ * @param frame input and output structure (struct SPDY_Data_Frame *)
+ */
+#if HAVE_BIG_ENDIAN
+#define SPDYF_DATA_FRAME_HTON(frame)
+#else
+#define SPDYF_DATA_FRAME_HTON(frame) do { \
+ *((uint32_t *) frame ) = htonl(*((uint32_t *) frame ));\
+ (frame)->length = HTON24((frame)->length); \
+ } while (0)
+#endif
+
+
+/**
+ * Convert all integers in a SPDY data frame headers structure from
+ * network byte order to host byte order.
+ *
+ * @param frame input and output structure (struct SPDY_Data_Frame *)
+ */
+#if HAVE_BIG_ENDIAN
+#define SPDYF_DATA_FRAME_NTOH(frame)
+#else
+#define SPDYF_DATA_FRAME_NTOH(frame) do { \
+ *((uint32_t *) frame ) = ntohl(*((uint32_t *) frame ));\
+ (frame)->length = NTOH24((frame)->length); \
+ } while (0)
+#endif
+
+
+/**
+ * Creates one or more new SPDYF_Response_Queue object to be put on the
+ * response queue.
+ *
+ * @param is_data whether new data frame or new control frame will be
+ * crerated
+ * @param data the row stream which will be used as the body of the frame
+ * @param data_size length of data
+ * @param response object, part of which is the frame
+ * @param stream on which data is to be sent
+ * @param closestream TRUE if the frame must close the stream (with flag)
+ * @param frqcb callback to notify application layer when the frame
+ * has been sent or discarded
+ * @param frqcb_cls closure for frqcb
+ * @param rrcb callback used by the application layer to notify the
+ * application when the frame has been sent or discarded.
+ * frqcb will call it
+ * @param rrcb_cls closure for rrcb
+ * @return double linked list of SPDYF_Response_Queue structures: one or
+ * more frames are returned based on the size of the data
+ */
+struct SPDYF_Response_Queue *
+SPDYF_response_queue_create(bool is_data,
+ void *data,
+ size_t data_size,
+ struct SPDY_Response *response,
+ struct SPDYF_Stream *stream,
+ bool closestream,
+ SPDYF_ResponseQueueResultCallback frqcb,
+ void *frqcb_cls,
+ SPDY_ResponseResultCallback rrcb,
+ void *rrcb_cls);
+
+
+/**
+ * Destroys SPDYF_Response_Queue structure and whatever is in it.
+ *
+ * @param response_queue to destroy
+ */
+void
+SPDYF_response_queue_destroy(struct SPDYF_Response_Queue *response_queue);
+
+
+/**
+ * Checks if the container is empty, i.e. created but no values were
+ * added to it.
+ *
+ * @param container
+ * @return SPDY_YES if empty
+ * SPDY_NO if not
+ */
+int
+SPDYF_name_value_is_empty(struct SPDY_NameValue *container);
+
+
+/**
+ * Transforms raw binary decomressed stream of headers
+ * into SPDY_NameValue, containing all of the headers and values.
+ *
+ * @param stream that is to be transformed
+ * @param size length of the stream
+ * @param container will contain the newly created SPDY_NameValue
+ * container. Should point to NULL.
+ * @return SPDY_YES on success
+ * SPDY_NO on memory error
+ * SPDY_INPUT_ERROR if the provided stream is not valid
+ */
+int
+SPDYF_name_value_from_stream(void *stream,
+ size_t size,
+ struct SPDY_NameValue ** container);
+
+
+/**
+ * Transforms array of objects of name/values tuples, containing HTTP
+ * headers, into raw binary stream. The resulting stream is ready to
+ * be compressed and sent.
+ *
+ * @param container one or more SPDY_NameValue objects. Each object
+ * contains multiple number of name/value tuples.
+ * @param num_containers length of the array
+ * @param stream will contain the resulting stream. Should point to NULL.
+ * @return length of stream or value less than 0 indicating error
+ */
+ssize_t
+SPDYF_name_value_to_stream(struct SPDY_NameValue * container[],
+ int num_containers,
+ void **stream);
+
+#endif
diff --git a/src/platform/Makefile.am b/src/platform/Makefile.am
new file mode 100644
index 0000000..448efae
--- /dev/null
+++ b/src/platform/Makefile.am
@@ -0,0 +1,20 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include
+
+AM_CFLAGS = $(HIDDEN_VISIBILITY_CFLAGS)
+
+if USE_COVERAGE
+ AM_CFLAGS += --coverage
+endif
+
+if HAVE_W32
+noinst_LTLIBRARIES = \
+ libplatform_interface.la
+libplatform_interface_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DBUILDING_MHD_LIB=1
+libplatform_interface_la_SOURCES = \
+ w32functions.c
+endif
+
diff --git a/src/platform/Makefile.in b/src/platform/Makefile.in
new file mode 100644
index 0000000..31c85e8
--- /dev/null
+++ b/src/platform/Makefile.in
@@ -0,0 +1,658 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@USE_COVERAGE_TRUE@am__append_1 = --coverage
+subdir = src/platform
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libplatform_interface_la_LIBADD =
+am__libplatform_interface_la_SOURCES_DIST = w32functions.c
+@HAVE_W32_TRUE@am_libplatform_interface_la_OBJECTS = \
+@HAVE_W32_TRUE@ libplatform_interface_la-w32functions.lo
+libplatform_interface_la_OBJECTS = \
+ $(am_libplatform_interface_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+@HAVE_W32_TRUE@am_libplatform_interface_la_rpath =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libplatform_interface_la_SOURCES)
+DIST_SOURCES = $(am__libplatform_interface_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include
+
+AM_CFLAGS = $(HIDDEN_VISIBILITY_CFLAGS) $(am__append_1)
+@HAVE_W32_TRUE@noinst_LTLIBRARIES = \
+@HAVE_W32_TRUE@ libplatform_interface.la
+
+@HAVE_W32_TRUE@libplatform_interface_la_CPPFLAGS = \
+@HAVE_W32_TRUE@ $(AM_CPPFLAGS) \
+@HAVE_W32_TRUE@ -DBUILDING_MHD_LIB=1
+
+@HAVE_W32_TRUE@libplatform_interface_la_SOURCES = \
+@HAVE_W32_TRUE@ w32functions.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/platform/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/platform/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libplatform_interface.la: $(libplatform_interface_la_OBJECTS) $(libplatform_interface_la_DEPENDENCIES) $(EXTRA_libplatform_interface_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(am_libplatform_interface_la_rpath) $(libplatform_interface_la_OBJECTS) $(libplatform_interface_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libplatform_interface_la-w32functions.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+libplatform_interface_la-w32functions.lo: w32functions.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libplatform_interface_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libplatform_interface_la-w32functions.lo -MD -MP -MF $(DEPDIR)/libplatform_interface_la-w32functions.Tpo -c -o libplatform_interface_la-w32functions.lo `test -f 'w32functions.c' || echo '$(srcdir)/'`w32functions.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libplatform_interface_la-w32functions.Tpo $(DEPDIR)/libplatform_interface_la-w32functions.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='w32functions.c' object='libplatform_interface_la-w32functions.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libplatform_interface_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libplatform_interface_la-w32functions.lo `test -f 'w32functions.c' || echo '$(srcdir)/'`w32functions.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
+ clean-libtool clean-noinstLTLIBRARIES cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/platform/w32functions.c b/src/platform/w32functions.c
new file mode 100644
index 0000000..bc42fef
--- /dev/null
+++ b/src/platform/w32functions.c
@@ -0,0 +1,704 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2014 Karlson2k (Evgeny Grin)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library.
+ If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file platform/w32functions.h
+ * @brief internal functions for W32 systems
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "w32functions.h"
+#include <errno.h>
+#include <winsock2.h>
+#include <string.h>
+#include <stdint.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+
+/**
+ * Return errno equivalent of last winsock error
+ * @return errno equivalent of last winsock error
+ */
+int MHD_W32_errno_from_winsock_(void)
+{
+ switch(WSAGetLastError())
+ {
+ case 0: return 0;
+ case WSA_INVALID_HANDLE: return EBADF;
+ case WSA_NOT_ENOUGH_MEMORY: return ENOMEM;
+ case WSA_INVALID_PARAMETER: return EINVAL;
+ case WSAEINTR: return EINTR;
+ case WSAEWOULDBLOCK: return EWOULDBLOCK;
+ case WSAEINPROGRESS: return EINPROGRESS;
+ case WSAEALREADY: return EALREADY;
+ case WSAENOTSOCK: return ENOTSOCK;
+ case WSAEDESTADDRREQ: return EDESTADDRREQ;
+ case WSAEMSGSIZE: return EMSGSIZE;
+ case WSAEPROTOTYPE: return EPROTOTYPE;
+ case WSAENOPROTOOPT: return ENOPROTOOPT;
+ case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT;
+ case WSAESOCKTNOSUPPORT: return ESOCKTNOSUPPORT;
+ case WSAEOPNOTSUPP: return EOPNOTSUPP;
+ case WSAEPFNOSUPPORT: return EPFNOSUPPORT;
+ case WSAEAFNOSUPPORT: return EAFNOSUPPORT;
+ case WSAEADDRINUSE: return EADDRINUSE;
+ case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL;
+ case WSAENETDOWN: return ENETDOWN;
+ case WSAENETUNREACH: return ENETUNREACH;
+ case WSAENETRESET: return ENETRESET;
+ case WSAECONNABORTED: return ECONNABORTED;
+ case WSAECONNRESET: return ECONNRESET;
+ case WSAENOBUFS: return ENOBUFS;
+ case WSAEISCONN: return EISCONN;
+ case WSAENOTCONN: return ENOTCONN;
+ case WSAESHUTDOWN: return ESHUTDOWN;
+ case WSAETOOMANYREFS: return ETOOMANYREFS;
+ case WSAETIMEDOUT: return ETIMEDOUT;
+ case WSAECONNREFUSED: return ECONNREFUSED;
+ case WSAELOOP: return ELOOP;
+ case WSAENAMETOOLONG: return ENAMETOOLONG;
+ case WSAEHOSTDOWN: return EHOSTDOWN;
+ case WSAEHOSTUNREACH: return EHOSTUNREACH;
+ case WSAENOTEMPTY: return ENOTEMPTY;
+ case WSAEPROCLIM: return EPROCLIM;
+ case WSAEUSERS: return EUSERS;
+ case WSAEDQUOT: return EDQUOT;
+ case WSAESTALE: return ESTALE;
+ case WSAEREMOTE: return EREMOTE;
+ case WSAEINVAL: return EINVAL;
+ case WSAEFAULT: return EFAULT;
+ case WSANO_DATA: return ENODATA;
+ /* Rough equivalents */
+ case WSAEDISCON: return ECONNRESET;
+ case WSAEINVALIDPROCTABLE: return EFAULT;
+ case WSASYSNOTREADY:
+ case WSANOTINITIALISED:
+ case WSASYSCALLFAILURE: return ENOBUFS;
+ case WSAVERNOTSUPPORTED: return EOPNOTSUPP;
+ case WSAEREFUSED: return EIO;
+ }
+ return EINVAL;
+}
+
+/**
+ * Return pointer to string description of errnum error
+ * Works fine with both standard errno errnums
+ * and errnums from MHD_W32_errno_from_winsock_
+ * @param errnum the errno or value from MHD_W32_errno_from_winsock_()
+ * @return pointer to string description of error
+ */
+const char* MHD_W32_strerror_(int errnum)
+{
+ switch(errnum)
+ {
+ case 0:
+ return "No error";
+ case EWOULDBLOCK:
+ return "Operation would block";
+ case EINPROGRESS:
+ return "Connection already in progress";
+ case EALREADY:
+ return "Socket already connected";
+ case ENOTSOCK:
+ return "Socket operation on non-socket";
+ case EDESTADDRREQ:
+ return "Destination address required";
+ case EMSGSIZE:
+ return "Message too long";
+ case EPROTOTYPE:
+ return "Protocol wrong type for socket";
+ case ENOPROTOOPT:
+ return "Protocol not available";
+ case EPROTONOSUPPORT:
+ return "Unknown protocol";
+ case ESOCKTNOSUPPORT:
+ return "Socket type not supported";
+ case EOPNOTSUPP:
+ return "Operation not supported on socket";
+ case EPFNOSUPPORT:
+ return "Protocol family not supported";
+ case EAFNOSUPPORT:
+ return "Address family not supported by protocol family";
+ case EADDRINUSE:
+ return "Address already in use";
+ case EADDRNOTAVAIL:
+ return "Cannot assign requested address";
+ case ENETDOWN:
+ return "Network is down";
+ case ENETUNREACH:
+ return "Network is unreachable";
+ case ENETRESET:
+ return "Network dropped connection on reset";
+ case ECONNABORTED:
+ return "Software caused connection abort";
+ case ECONNRESET:
+ return "Connection reset by peer";
+ case ENOBUFS:
+ return "No system resources available";
+ case EISCONN:
+ return "Socket is already connected";
+ case ENOTCONN:
+ return "Socket is not connected";
+ case ESHUTDOWN:
+ return "Can't send after socket shutdown";
+ case ETOOMANYREFS:
+ return "Too many references: cannot splice";
+ case ETIMEDOUT:
+ return "Connection timed out";
+ case ECONNREFUSED:
+ return "Connection refused";
+ case ELOOP:
+ return "Cannot translate name";
+ case EHOSTDOWN:
+ return "Host is down";
+ case EHOSTUNREACH:
+ return "Host is unreachable";
+ case EPROCLIM:
+ return "Too many processes";
+ case EUSERS:
+ return "Too many users";
+ case EDQUOT:
+ return "Disk quota exceeded";
+ case ESTALE:
+ return "Stale file handle reference";
+ case EREMOTE:
+ return "Resource is remote";
+ case ENODATA:
+ return "No data available";
+ }
+ return strerror(errnum);
+}
+
+/**
+ * Return pointer to string description of last winsock error
+ * @return pointer to string description of last winsock error
+ */
+const char* MHD_W32_strerror_last_winsock_(void)
+{
+ switch (WSAGetLastError())
+ {
+ case 0:
+ return "No error";
+ case WSA_INVALID_HANDLE:
+ return "Specified event object handle is invalid";
+ case WSA_NOT_ENOUGH_MEMORY:
+ return "Insufficient memory available";
+ case WSA_INVALID_PARAMETER:
+ return "One or more parameters are invalid";
+ case WSA_OPERATION_ABORTED:
+ return "Overlapped operation aborted";
+ case WSA_IO_INCOMPLETE:
+ return "Overlapped I/O event object not in signaled state";
+ case WSA_IO_PENDING:
+ return "Overlapped operations will complete later";
+ case WSAEINTR:
+ return "Interrupted function call";
+ case WSAEBADF:
+ return "File handle is not valid";
+ case WSAEACCES:
+ return "Permission denied";
+ case WSAEFAULT:
+ return "Bad address";
+ case WSAEINVAL:
+ return "Invalid argument";
+ case WSAEMFILE:
+ return "Too many open files";
+ case WSAEWOULDBLOCK:
+ return "Resource temporarily unavailable";
+ case WSAEINPROGRESS:
+ return "Operation now in progress";
+ case WSAEALREADY:
+ return "Operation already in progress";
+ case WSAENOTSOCK:
+ return "Socket operation on nonsocket";
+ case WSAEDESTADDRREQ:
+ return "Destination address required";
+ case WSAEMSGSIZE:
+ return "Message too long";
+ case WSAEPROTOTYPE:
+ return "Protocol wrong type for socket";
+ case WSAENOPROTOOPT:
+ return "Bad protocol option";
+ case WSAEPROTONOSUPPORT:
+ return "Protocol not supported";
+ case WSAESOCKTNOSUPPORT:
+ return "Socket type not supported";
+ case WSAEOPNOTSUPP:
+ return "Operation not supported";
+ case WSAEPFNOSUPPORT:
+ return "Protocol family not supported";
+ case WSAEAFNOSUPPORT:
+ return "Address family not supported by protocol family";
+ case WSAEADDRINUSE:
+ return "Address already in use";
+ case WSAEADDRNOTAVAIL:
+ return "Cannot assign requested address";
+ case WSAENETDOWN:
+ return "Network is down";
+ case WSAENETUNREACH:
+ return "Network is unreachable";
+ case WSAENETRESET:
+ return "Network dropped connection on reset";
+ case WSAECONNABORTED:
+ return "Software caused connection abort";
+ case WSAECONNRESET:
+ return "Connection reset by peer";
+ case WSAENOBUFS:
+ return "No buffer space available";
+ case WSAEISCONN:
+ return "Socket is already connected";
+ case WSAENOTCONN:
+ return "Socket is not connected";
+ case WSAESHUTDOWN:
+ return "Cannot send after socket shutdown";
+ case WSAETOOMANYREFS:
+ return "Too many references";
+ case WSAETIMEDOUT:
+ return "Connection timed out";
+ case WSAECONNREFUSED:
+ return "Connection refused";
+ case WSAELOOP:
+ return "Cannot translate name";
+ case WSAENAMETOOLONG:
+ return "Name too long";
+ case WSAEHOSTDOWN:
+ return "Host is down";
+ case WSAEHOSTUNREACH:
+ return "No route to host";
+ case WSAENOTEMPTY:
+ return "Directory not empty";
+ case WSAEPROCLIM:
+ return "Too many processes";
+ case WSAEUSERS:
+ return "User quota exceeded";
+ case WSAEDQUOT:
+ return "Disk quota exceeded";
+ case WSAESTALE:
+ return "Stale file handle reference";
+ case WSAEREMOTE:
+ return "Item is remote";
+ case WSASYSNOTREADY:
+ return "Network subsystem is unavailable";
+ case WSAVERNOTSUPPORTED:
+ return "Winsock.dll version out of range";
+ case WSANOTINITIALISED:
+ return "Successful WSAStartup not yet performed";
+ case WSAEDISCON:
+ return "Graceful shutdown in progress";
+ case WSAENOMORE:
+ return "No more results";
+ case WSAECANCELLED:
+ return "Call has been canceled";
+ case WSAEINVALIDPROCTABLE:
+ return "Procedure call table is invalid";
+ case WSAEINVALIDPROVIDER:
+ return "Service provider is invalid";
+ case WSAEPROVIDERFAILEDINIT:
+ return "Service provider failed to initialize";
+ case WSASYSCALLFAILURE:
+ return "System call failure";
+ case WSASERVICE_NOT_FOUND:
+ return "Service not found";
+ case WSATYPE_NOT_FOUND:
+ return "Class type not found";
+ case WSA_E_NO_MORE:
+ return "No more results";
+ case WSA_E_CANCELLED:
+ return "Call was canceled";
+ case WSAEREFUSED:
+ return "Database query was refused";
+ case WSAHOST_NOT_FOUND:
+ return "Host not found";
+ case WSATRY_AGAIN:
+ return "Nonauthoritative host not found";
+ case WSANO_RECOVERY:
+ return "This is a nonrecoverable error";
+ case WSANO_DATA:
+ return "Valid name, no data record of requested type";
+ case WSA_QOS_RECEIVERS:
+ return "QoS receivers";
+ case WSA_QOS_SENDERS:
+ return "QoS senders";
+ case WSA_QOS_NO_SENDERS:
+ return "No QoS senders";
+ case WSA_QOS_NO_RECEIVERS:
+ return "QoS no receivers";
+ case WSA_QOS_REQUEST_CONFIRMED:
+ return "QoS request confirmed";
+ case WSA_QOS_ADMISSION_FAILURE:
+ return "QoS admission error";
+ case WSA_QOS_POLICY_FAILURE:
+ return "QoS policy failure";
+ case WSA_QOS_BAD_STYLE:
+ return "QoS bad style";
+ case WSA_QOS_BAD_OBJECT:
+ return "QoS bad object";
+ case WSA_QOS_TRAFFIC_CTRL_ERROR:
+ return "QoS traffic control error";
+ case WSA_QOS_GENERIC_ERROR:
+ return "QoS generic error";
+ case WSA_QOS_ESERVICETYPE:
+ return "QoS service type error";
+ case WSA_QOS_EFLOWSPEC:
+ return "QoS flowspec error";
+ case WSA_QOS_EPROVSPECBUF:
+ return "Invalid QoS provider buffer";
+ case WSA_QOS_EFILTERSTYLE:
+ return "Invalid QoS filter style";
+ case WSA_QOS_EFILTERTYPE:
+ return "Invalid QoS filter type";
+ case WSA_QOS_EFILTERCOUNT:
+ return "Incorrect QoS filter count";
+ case WSA_QOS_EOBJLENGTH:
+ return "Invalid QoS object length";
+ case WSA_QOS_EFLOWCOUNT:
+ return "Incorrect QoS flow count";
+ case WSA_QOS_EUNKOWNPSOBJ:
+ return "Unrecognized QoS object";
+ case WSA_QOS_EPOLICYOBJ:
+ return "Invalid QoS policy object";
+ case WSA_QOS_EFLOWDESC:
+ return "Invalid QoS flow descriptor";
+ case WSA_QOS_EPSFLOWSPEC:
+ return "Invalid QoS provider-specific flowspec";
+ case WSA_QOS_EPSFILTERSPEC:
+ return "Invalid QoS provider-specific filterspec";
+ case WSA_QOS_ESDMODEOBJ:
+ return "Invalid QoS shape discard mode object";
+ case WSA_QOS_ESHAPERATEOBJ:
+ return "Invalid QoS shaping rate object";
+ case WSA_QOS_RESERVED_PETYPE:
+ return "Reserved policy QoS element type";
+ }
+ return "Unknown winsock error";
+}
+
+/**
+ * Set last winsock error to equivalent of given errno value
+ * @param errnum the errno value to set
+ */
+void MHD_W32_set_last_winsock_error_(int errnum)
+{
+ switch (errnum)
+ {
+ case 0:
+ WSASetLastError(0);
+ break;
+ case EBADF:
+ WSASetLastError(WSA_INVALID_HANDLE);
+ break;
+ case ENOMEM:
+ WSASetLastError(WSA_NOT_ENOUGH_MEMORY);
+ break;
+ case EINVAL:
+ WSASetLastError(WSA_INVALID_PARAMETER);
+ break;
+ case EINTR:
+ WSASetLastError(WSAEINTR);
+ break;
+ case EWOULDBLOCK:
+ WSASetLastError(WSAEWOULDBLOCK);
+ break;
+ case EINPROGRESS:
+ WSASetLastError(WSAEINPROGRESS);
+ break;
+ case EALREADY:
+ WSASetLastError(WSAEALREADY);
+ break;
+ case ENOTSOCK:
+ WSASetLastError(WSAENOTSOCK);
+ break;
+ case EDESTADDRREQ:
+ WSASetLastError(WSAEDESTADDRREQ);
+ break;
+ case EMSGSIZE:
+ WSASetLastError(WSAEMSGSIZE);
+ break;
+ case EPROTOTYPE:
+ WSASetLastError(WSAEPROTOTYPE);
+ break;
+ case ENOPROTOOPT:
+ WSASetLastError(WSAENOPROTOOPT);
+ break;
+ case EPROTONOSUPPORT:
+ WSASetLastError(WSAEPROTONOSUPPORT);
+ break;
+ case ESOCKTNOSUPPORT:
+ WSASetLastError(WSAESOCKTNOSUPPORT);
+ break;
+ case EOPNOTSUPP:
+ WSASetLastError(WSAEOPNOTSUPP);
+ break;
+ case EPFNOSUPPORT:
+ WSASetLastError(WSAEPFNOSUPPORT);
+ break;
+ case EAFNOSUPPORT:
+ WSASetLastError(WSAEAFNOSUPPORT);
+ break;
+ case EADDRINUSE:
+ WSASetLastError(WSAEADDRINUSE);
+ break;
+ case EADDRNOTAVAIL:
+ WSASetLastError(WSAEADDRNOTAVAIL);
+ break;
+ case ENETDOWN:
+ WSASetLastError(WSAENETDOWN);
+ break;
+ case ENETUNREACH:
+ WSASetLastError(WSAENETUNREACH);
+ break;
+ case ENETRESET:
+ WSASetLastError(WSAENETRESET);
+ break;
+ case ECONNABORTED:
+ WSASetLastError(WSAECONNABORTED);
+ break;
+ case ECONNRESET:
+ WSASetLastError(WSAECONNRESET);
+ break;
+ case ENOBUFS:
+ WSASetLastError(WSAENOBUFS);
+ break;
+ case EISCONN:
+ WSASetLastError(WSAEISCONN);
+ break;
+ case ENOTCONN:
+ WSASetLastError(WSAENOTCONN);
+ break;
+ case ESHUTDOWN:
+ WSASetLastError(WSAESHUTDOWN);
+ break;
+ case ETOOMANYREFS:
+ WSASetLastError(WSAETOOMANYREFS);
+ break;
+ case ETIMEDOUT:
+ WSASetLastError(WSAETIMEDOUT);
+ break;
+ case ECONNREFUSED:
+ WSASetLastError(WSAECONNREFUSED);
+ break;
+ case ELOOP:
+ WSASetLastError(WSAELOOP);
+ break;
+ case ENAMETOOLONG:
+ WSASetLastError(WSAENAMETOOLONG);
+ break;
+ case EHOSTDOWN:
+ WSASetLastError(WSAEHOSTDOWN);
+ break;
+ case EHOSTUNREACH:
+ WSASetLastError(WSAEHOSTUNREACH);
+ break;
+ case ENOTEMPTY:
+ WSASetLastError(WSAENOTEMPTY);
+ break;
+ case EPROCLIM:
+ WSASetLastError(WSAEPROCLIM);
+ break;
+ case EUSERS:
+ WSASetLastError(WSAEUSERS);
+ break;
+ case EDQUOT:
+ WSASetLastError(WSAEDQUOT);
+ break;
+ case ESTALE:
+ WSASetLastError(WSAESTALE);
+ break;
+ case EREMOTE:
+ WSASetLastError(WSAEREMOTE);
+ break;
+ case EFAULT:
+ WSASetLastError(WSAEFAULT);
+ break;
+ case ENODATA:
+ WSASetLastError(WSANO_DATA);
+ break;
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+ WSASetLastError(WSAEWOULDBLOCK);
+ break;
+#endif
+ /* Rough equivalent */
+ case EIO:
+ WSASetLastError(WSAEREFUSED);
+ break;
+
+ default: /* Unmapped errors */
+ WSASetLastError(WSAENOBUFS);
+ break;
+ }
+}
+
+/**
+ * Create pair of mutually connected TCP/IP sockets on loopback address
+ * @param sockets_pair array to receive resulted sockets
+ * @return zero on success, -1 otherwise
+ */
+int MHD_W32_pair_of_sockets_(SOCKET sockets_pair[2])
+{
+ int i;
+ if (!sockets_pair)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+#define PAIRMAXTRYIES 800
+ for (i = 0; i < PAIRMAXTRYIES; i++)
+ {
+ struct sockaddr_in listen_addr;
+ SOCKET listen_s;
+ static const int c_addinlen = sizeof(struct sockaddr_in); /* help compiler to optimize */
+ int addr_len = c_addinlen;
+ int opt = 1;
+
+ listen_s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (INVALID_SOCKET == listen_s)
+ break; /* can't create even single socket */
+
+ listen_addr.sin_family = AF_INET;
+ listen_addr.sin_port = htons(0);
+ listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (0 == bind(listen_s, (struct sockaddr*) &listen_addr, c_addinlen)
+ && 0 == listen(listen_s, 1)
+ && 0 == getsockname(listen_s, (struct sockaddr*) &listen_addr,
+ &addr_len))
+ {
+ SOCKET client_s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (INVALID_SOCKET != client_s)
+ {
+ if (0 == ioctlsocket(client_s, FIONBIO, (u_long*) &opt)
+ && (0 == connect(client_s, (struct sockaddr*) &listen_addr, c_addinlen)
+ || WSAGetLastError() == WSAEWOULDBLOCK))
+ {
+ struct sockaddr_in accepted_from_addr;
+ addr_len = c_addinlen;
+ SOCKET server_s = accept(listen_s,
+ (struct sockaddr*) &accepted_from_addr, &addr_len);
+ if (INVALID_SOCKET != server_s)
+ {
+ struct sockaddr_in client_addr;
+ addr_len = c_addinlen;
+ opt = 0;
+ if (0 == getsockname(client_s, (struct sockaddr*) &client_addr, &addr_len)
+ && accepted_from_addr.sin_family == client_addr.sin_family
+ && accepted_from_addr.sin_port == client_addr.sin_port
+ && accepted_from_addr.sin_addr.s_addr == client_addr.sin_addr.s_addr
+ && 0 == ioctlsocket(client_s, FIONBIO, (u_long*) &opt)
+ && 0 == ioctlsocket(server_s, FIONBIO, (u_long*) &opt))
+ {
+ closesocket(listen_s);
+ sockets_pair[0] = client_s;
+ sockets_pair[1] = server_s;
+ return 0;
+ }
+ closesocket(server_s);
+ }
+ }
+ closesocket(client_s);
+ }
+ }
+ closesocket(listen_s);
+ }
+
+ sockets_pair[0] = INVALID_SOCKET;
+ sockets_pair[1] = INVALID_SOCKET;
+ return -1;
+}
+
+/**
+ * Static variable used by pseudo random number generator
+ */
+static int32_t rnd_val = 0;
+/**
+ * Generate 31-bit pseudo random number.
+ * Function initialize itself at first call to current time.
+ * @return 31-bit pseudo random number.
+ */
+int MHD_W32_random_(void)
+{
+ if (0 == rnd_val)
+ rnd_val = (int32_t)time(NULL);
+ /* stolen from winsup\cygwin\random.cc */
+ rnd_val = (16807 * (rnd_val % 127773) - 2836 * (rnd_val / 127773))
+ & 0x7fffffff;
+ return (int)rnd_val;
+}
+
+/* Emulate snprintf function on W32 */
+int W32_snprintf(char *__restrict s, size_t n, const char *__restrict format, ...)
+{
+ int ret;
+ va_list args;
+ if (0 != n && NULL != s )
+ {
+ va_start(args, format);
+ ret = _vsnprintf(s, n, format, args);
+ va_end(args);
+ if (n == ret)
+ s[n - 1] = 0;
+ if (ret >= 0)
+ return ret;
+ }
+ va_start(args, format);
+ ret = _vscprintf(format, args);
+ va_end(args);
+ if (0 <= ret && 0 != n && NULL == s)
+ return -1;
+
+ return ret;
+}
+
+#ifdef _MSC_FULL_VER
+/**
+ * Set thread name
+ * @param thread_id ID of thread, -1 for current thread
+ * @param thread_name name to set
+ */
+void W32_SetThreadName(const DWORD thread_id, const char *thread_name)
+{
+ static const DWORD VC_SETNAME_EXC = 0x406D1388;
+#pragma pack(push,8)
+ struct thread_info_struct
+ {
+ DWORD type; // Must be 0x1000.
+ LPCSTR name; // Pointer to name (in user address space).
+ DWORD ID; // Thread ID (-1=caller thread).
+ DWORD flags; // Reserved for future use, must be zero.
+ } thread_info;
+#pragma pack(pop)
+
+ if (NULL == thread_name)
+ return;
+
+ thread_info.type = 0x1000;
+ thread_info.name = thread_name;
+ thread_info.ID = thread_id;
+ thread_info.flags = 0;
+
+ __try
+ { /* This exception is intercepted by debugger */
+ RaiseException(VC_SETNAME_EXC, 0, sizeof(thread_info) / sizeof(ULONG_PTR), (ULONG_PTR*)&thread_info);
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {}
+}
+#endif /* _MSC_FULL_VER */
diff --git a/src/spdy2http/Makefile.am b/src/spdy2http/Makefile.am
new file mode 100644
index 0000000..f7432a9
--- /dev/null
+++ b/src/spdy2http/Makefile.am
@@ -0,0 +1,29 @@
+# This Makefile.am is in the public domain
+SUBDIRS = .
+
+AM_CFLAGS =
+
+if USE_COVERAGE
+ AM_CFLAGS += -fprofile-arcs -ftest-coverage
+endif
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/applicationlayer \
+ -DDATA_DIR=\"$(top_srcdir)/src/datadir/\" \
+ $(LIBCURL_CPPFLAGS)
+
+if !HAVE_W32
+PERF_GET_CONCURRENT=perf_get_concurrent
+endif
+
+bin_PROGRAMS = \
+ microspdy2http
+
+microspdy2http_SOURCES = \
+ proxy.c
+microspdy2http_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz \
+ $(LIBCURL)
diff --git a/src/spdy2http/Makefile.in b/src/spdy2http/Makefile.in
new file mode 100644
index 0000000..ef67ffc
--- /dev/null
+++ b/src/spdy2http/Makefile.in
@@ -0,0 +1,813 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@USE_COVERAGE_TRUE@am__append_1 = -fprofile-arcs -ftest-coverage
+bin_PROGRAMS = microspdy2http$(EXEEXT)
+subdir = src/spdy2http
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_microspdy2http_OBJECTS = proxy.$(OBJEXT)
+microspdy2http_OBJECTS = $(am_microspdy2http_OBJECTS)
+am__DEPENDENCIES_1 =
+microspdy2http_DEPENDENCIES = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(microspdy2http_SOURCES)
+DIST_SOURCES = $(microspdy2http_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+SUBDIRS = .
+AM_CFLAGS = $(am__append_1)
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/applicationlayer \
+ -DDATA_DIR=\"$(top_srcdir)/src/datadir/\" \
+ $(LIBCURL_CPPFLAGS)
+
+@HAVE_W32_FALSE@PERF_GET_CONCURRENT = perf_get_concurrent
+microspdy2http_SOURCES = \
+ proxy.c
+
+microspdy2http_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz \
+ $(LIBCURL)
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/spdy2http/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/spdy2http/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+microspdy2http$(EXEEXT): $(microspdy2http_OBJECTS) $(microspdy2http_DEPENDENCIES) $(EXTRA_microspdy2http_DEPENDENCIES)
+ @rm -f microspdy2http$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(microspdy2http_OBJECTS) $(microspdy2http_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proxy.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-binPROGRAMS clean-generic clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-binPROGRAMS install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/spdy2http/proxy.c b/src/spdy2http/proxy.c
new file mode 100644
index 0000000..02144ad
--- /dev/null
+++ b/src/spdy2http/proxy.c
@@ -0,0 +1,1411 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file proxy.c
+ * @brief Translates incoming SPDY requests to http server on localhost.
+ * Uses libcurl.
+ * No error handling for curl requests.
+ * TODO:
+ * - test all options!
+ * - don't abort on lack of memory
+ * - Correct recapitalizetion of header names before giving the headers
+ * to curl.
+ * - curl does not close sockets when connection is closed and no
+ * new sockets are opened (they stay in CLOSE_WAIT)
+ * - add '/' when a user requests http://example.com . Now this is a bad
+ * request
+ * - curl returns 0 or 1 ms for timeout even when nothing will be done;
+ * thus the loop uses CPU for nothing
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include "microspdy.h"
+#include <curl/curl.h>
+#include <assert.h>
+#include <getopt.h>
+#include <regex.h>
+
+#define ERROR_RESPONSE "502 Bad Gateway"
+
+
+struct global_options
+{
+ char *http_backend;
+ char *cert;
+ char *cert_key;
+ char *listen_host;
+ unsigned int timeout;
+ uint16_t listen_port;
+ bool verbose;
+ bool curl_verbose;
+ bool transparent;
+ bool http10;
+ bool notls;
+ bool nodelay;
+ bool ipv4;
+ bool ipv6;
+} glob_opt;
+
+
+struct URI
+{
+ char * full_uri;
+ char * scheme;
+ char * host_and_port;
+ //char * host_and_port_for_connecting;
+ char * host;
+ char * path;
+ char * path_and_more;
+ char * query;
+ char * fragment;
+ uint16_t port;
+};
+
+
+#define PRINT_INFO(msg) do{\
+ fprintf(stdout, "%i:%s\n", __LINE__, msg);\
+ fflush(stdout);\
+ }\
+ while(0)
+
+
+#define PRINT_INFO2(fmt, ...) do{\
+ fprintf(stdout, "%i\n", __LINE__);\
+ fprintf(stdout, fmt,##__VA_ARGS__);\
+ fprintf(stdout, "\n");\
+ fflush(stdout);\
+ }\
+ while(0)
+
+
+#define PRINT_VERBOSE(msg) do{\
+ if(glob_opt.verbose){\
+ fprintf(stdout, "%i:%s\n", __LINE__, msg);\
+ fflush(stdout);\
+ }\
+ }\
+ while(0)
+
+
+#define PRINT_VERBOSE2(fmt, ...) do{\
+ if(glob_opt.verbose){\
+ fprintf(stdout, "%i\n", __LINE__);\
+ fprintf(stdout, fmt,##__VA_ARGS__);\
+ fprintf(stdout, "\n");\
+ fflush(stdout);\
+ }\
+ }\
+ while(0)
+
+
+#define CURL_SETOPT(handle, opt, val) do{\
+ int ret; \
+ if(CURLE_OK != (ret = curl_easy_setopt(handle, opt, val))) \
+ { \
+ PRINT_INFO2("curl_easy_setopt failed (%i = %i)", opt, ret); \
+ abort(); \
+ } \
+ }\
+ while(0)
+
+
+#define DIE(msg) do{\
+ printf("FATAL ERROR (line %i): %s\n", __LINE__, msg);\
+ fflush(stdout);\
+ exit(EXIT_FAILURE);\
+ }\
+ while(0)
+
+
+static int loop = 1;
+
+static CURLM *multi_handle;
+
+static int still_running = 0; /* keep number of running handles */
+
+static regex_t uri_preg;
+
+static bool call_spdy_run;
+static bool call_curl_run;
+
+int debug_num_curls;
+
+
+struct Proxy
+{
+ char *url;
+ struct SPDY_Request *request;
+ struct SPDY_Response *response;
+ CURL *curl_handle;
+ struct curl_slist *curl_headers;
+ struct SPDY_NameValue *headers;
+ char *version;
+ char *status_msg;
+ void *http_body;
+ void *received_body;
+ bool *session_alive;
+ size_t http_body_size;
+ size_t received_body_size;
+ //ssize_t length;
+ int status;
+ //bool done;
+ bool receiving_done;
+ bool is_curl_read_paused;
+ bool is_with_body_data;
+ //bool error;
+ bool curl_done;
+ bool curl_error;
+ bool spdy_done;
+ bool spdy_error;
+};
+
+
+static void
+free_uri(struct URI * uri)
+{
+ if(NULL != uri)
+ {
+ free(uri->full_uri);
+ free(uri->scheme);
+ free(uri->host_and_port);
+ //free(uri->host_and_port_for_connecting);
+ free(uri->host);
+ free(uri->path);
+ free(uri->path_and_more);
+ free(uri->query);
+ free(uri->fragment);
+ uri->port = 0;
+ free(uri);
+ }
+}
+
+
+static int
+init_parse_uri(regex_t * preg)
+{
+ // RFC 2396
+ // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ /*
+ scheme = $2
+ authority = $4
+ path = $5
+ query = $7
+ fragment = $9
+ */
+
+ return regcomp(preg, "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?", REG_EXTENDED);
+}
+
+
+static void
+deinit_parse_uri(regex_t * preg)
+{
+ regfree(preg);
+}
+
+
+static int
+parse_uri(regex_t * preg, const char * full_uri, struct URI ** uri)
+{
+ //TODO memeory checks
+ int ret;
+ char *colon;
+ long long port;
+ size_t nmatch = 10;
+ regmatch_t pmatch[10];
+
+ if (0 != (ret = regexec(preg, full_uri, nmatch, pmatch, 0)))
+ return ret;
+
+ *uri = malloc(sizeof(struct URI));
+ if(NULL == *uri)
+ return -200;
+
+ (*uri)->full_uri = strdup(full_uri);
+
+ asprintf(&((*uri)->scheme),
+ "%.*s",
+ (int) (pmatch[2].rm_eo - pmatch[2].rm_so),
+ &full_uri[pmatch[2].rm_so]);
+ asprintf(&((*uri)->host_and_port), "%.*s",
+ (int) (pmatch[4].rm_eo - pmatch[4].rm_so),
+ &full_uri[pmatch[4].rm_so]);
+ asprintf(&((*uri)->path),
+ "%.*s",
+ (int) (pmatch[5].rm_eo - pmatch[5].rm_so),
+ &full_uri[pmatch[5].rm_so]);
+ asprintf(&((*uri)->path_and_more),
+ "%.*s",
+ (int) (pmatch[9].rm_eo - pmatch[5].rm_so),
+ &full_uri[pmatch[5].rm_so]);
+ asprintf(&((*uri)->query),
+ "%.*s",
+ (int) (pmatch[7].rm_eo - pmatch[7].rm_so),
+ &full_uri[pmatch[7].rm_so]);
+ asprintf(&((*uri)->fragment),
+ "%.*s",
+ (int) (pmatch[9].rm_eo - pmatch[9].rm_so),
+ &full_uri[pmatch[9].rm_so]);
+
+ colon = strrchr((*uri)->host_and_port, ':');
+ if(NULL == colon)
+ {
+ (*uri)->host = strdup((*uri)->host_and_port);
+ /*if(0 == strcasecmp("http", uri->scheme))
+ {
+ uri->port = 80;
+ asprintf(&(uri->host_and_port_for_connecting), "%s:80", uri->host_and_port);
+ }
+ else if(0 == strcasecmp("https", uri->scheme))
+ {
+ uri->port = 443;
+ asprintf(&(uri->host_and_port_for_connecting), "%s:443", uri->host_and_port);
+ }
+ else
+ {
+ PRINT_INFO("no standard scheme!");
+ */(*uri)->port = 0;
+ /*uri->host_and_port_for_connecting = strdup(uri->host_and_port);
+ }*/
+ return 0;
+ }
+
+ port = atoi(colon + 1);
+ if(port<1 || port >= 256 * 256)
+ {
+ free_uri(*uri);
+ return -100;
+ }
+ (*uri)->port = port;
+ asprintf(&((*uri)->host), "%.*s", (int)(colon - (*uri)->host_and_port), (*uri)->host_and_port);
+
+ return 0;
+}
+
+
+static bool
+store_in_buffer(const void *src, size_t src_size, void **dst, size_t *dst_size)
+{
+ if(0 == src_size)
+ return true;
+
+ if(NULL == *dst)
+ *dst = malloc(src_size);
+ else
+ *dst = realloc(*dst, src_size + *dst_size);
+ if(NULL == *dst)
+ return false;
+
+ memcpy(*dst + *dst_size, src, src_size);
+ *dst_size += src_size;
+
+ return true;
+}
+
+
+static ssize_t
+get_from_buffer(void **src, size_t *src_size, void *dst, size_t max_size)
+{
+ size_t ret;
+ void *newbody;
+
+ if(max_size >= *src_size)
+ {
+ ret = *src_size;
+ newbody = NULL;
+ }
+ else
+ {
+ ret = max_size;
+ if(NULL == (newbody = malloc(*src_size - max_size)))
+ return -1;
+ memcpy(newbody, *src + ret, *src_size - ret);
+ }
+ memcpy(dst, *src, ret);
+ free(*src);
+ *src = newbody;
+ *src_size -= ret;
+
+ return ret;
+}
+
+
+static void
+catch_signal(int signal)
+{
+ (void)signal;
+
+ loop = 0;
+}
+
+static void
+new_session_cb (void * cls,
+ struct SPDY_Session * session)
+{
+ (void)cls;
+
+ bool *session_alive;
+
+ PRINT_VERBOSE("new session");
+ //TODO clean this memory
+ if(NULL == (session_alive = malloc(sizeof(bool))))
+ {
+ DIE("no memory");
+ }
+ *session_alive = true;
+ SPDY_set_cls_to_session(session,
+ session_alive);
+}
+
+static void
+session_closed_cb (void * cls,
+ struct SPDY_Session * session,
+ int by_client)
+{
+ (void)cls;
+
+ bool *session_alive;
+
+ PRINT_VERBOSE2("session closed; by client: %i", by_client);
+
+ session_alive = SPDY_get_cls_from_session(session);
+ assert(NULL != session_alive);
+
+ *session_alive = false;
+}
+
+
+static int
+spdy_post_data_cb (void * cls,
+ struct SPDY_Request *request,
+ const void * buf,
+ size_t size,
+ bool more)
+{
+ (void)cls;
+ int ret;
+ struct Proxy *proxy = (struct Proxy *)SPDY_get_cls_from_request(request);
+
+ if(!store_in_buffer(buf, size, &proxy->received_body, &proxy->received_body_size))
+ {
+ PRINT_INFO("not enough memory (malloc/realloc returned NULL)");
+ return 0;
+ }
+
+ proxy->receiving_done = !more;
+
+ PRINT_VERBOSE2("POST bytes from SPDY: %zu", size);
+
+ call_curl_run = true;
+
+ if(proxy->is_curl_read_paused)
+ {
+ if(CURLE_OK != (ret = curl_easy_pause(proxy->curl_handle, CURLPAUSE_CONT)))
+ {
+ PRINT_INFO2("curl_easy_pause returned %i", ret);
+ abort();
+ }
+ PRINT_VERBOSE("curl_read_cb pause resumed");
+ }
+
+ return SPDY_YES;
+}
+
+
+ssize_t
+response_callback (void *cls,
+ void *buffer,
+ size_t max,
+ bool *more)
+{
+ ssize_t ret;
+ struct Proxy *proxy = (struct Proxy *)cls;
+
+ *more = true;
+
+ assert(!proxy->spdy_error);
+
+ if(proxy->curl_error)
+ {
+ PRINT_VERBOSE("tell spdy about the error");
+ return -1;
+ }
+
+ if(!proxy->http_body_size)//nothing to write now
+ {
+ PRINT_VERBOSE("nothing to write now");
+ if(proxy->curl_done || proxy->curl_error) *more = false;
+ return 0;
+ }
+
+ ret = get_from_buffer(&(proxy->http_body), &(proxy->http_body_size), buffer, max);
+ if(ret < 0)
+ {
+ PRINT_INFO("no memory");
+ //TODO error?
+ return -1;
+ }
+
+ if((proxy->curl_done || proxy->curl_error) && 0 == proxy->http_body_size) *more = false;
+
+ PRINT_VERBOSE2("given bytes to microspdy: %zd", ret);
+
+ return ret;
+}
+
+
+static void
+cleanup(struct Proxy *proxy)
+{
+ int ret;
+
+ //fprintf(stderr, "free proxy for %s\n", proxy->url);
+
+ if(CURLM_OK != (ret = curl_multi_remove_handle(multi_handle, proxy->curl_handle)))
+ {
+ PRINT_INFO2("curl_multi_remove_handle failed (%i)", ret);
+ DIE("bug in cleanup");
+ }
+ debug_num_curls--;
+ //TODO bug on ku6.com or amazon.cn
+ // after curl_multi_remove_handle returned CURLM_BAD_EASY_HANDLE
+ curl_slist_free_all(proxy->curl_headers);
+ curl_easy_cleanup(proxy->curl_handle);
+
+ free(proxy->url);
+ free(proxy);
+}
+
+
+static void
+response_done_callback(void *cls,
+ struct SPDY_Response *response,
+ struct SPDY_Request *request,
+ enum SPDY_RESPONSE_RESULT status,
+ bool streamopened)
+{
+ (void)streamopened;
+ struct Proxy *proxy = (struct Proxy *)cls;
+
+ if(SPDY_RESPONSE_RESULT_SUCCESS != status)
+ {
+ free(proxy->http_body);
+ proxy->http_body = NULL;
+ proxy->spdy_error = true;
+ }
+ cleanup(proxy);
+ SPDY_destroy_request(request);
+ SPDY_destroy_response(response);
+}
+
+
+static size_t
+curl_header_cb(void *ptr, size_t size, size_t nmemb, void *userp)
+{
+ size_t realsize = size * nmemb;
+ struct Proxy *proxy = (struct Proxy *)userp;
+ char *line = (char *)ptr;
+ char *name;
+ char *value;
+ char *status;
+ unsigned int i;
+ unsigned int pos;
+ int ret;
+ int num_values;
+ const char * const * values;
+ bool abort_it;
+
+ //printf("curl_header_cb %s\n", line);
+ if(!*(proxy->session_alive))
+ {
+ PRINT_VERBOSE("headers received, but session is dead");
+ proxy->spdy_error = true;
+ proxy->curl_error = true;
+ return 0;
+ }
+
+ //trailer
+ if(NULL != proxy->response) return 0;
+
+ if('\r' == line[0] || '\n' == line[0])
+ {
+ //all headers were already handled; prepare spdy frames
+ if(NULL == (proxy->response = SPDY_build_response_with_callback(proxy->status,
+ proxy->status_msg,
+ proxy->version,
+ proxy->headers,
+ &response_callback,
+ proxy,
+ 0)))
+ //256)))
+ DIE("no response");
+
+ SPDY_name_value_destroy(proxy->headers);
+ proxy->headers = NULL;
+ free(proxy->status_msg);
+ proxy->status_msg = NULL;
+ free(proxy->version);
+ proxy->version = NULL;
+
+ if(SPDY_YES != SPDY_queue_response(proxy->request,
+ proxy->response,
+ true,
+ false,
+ &response_done_callback,
+ proxy))
+ {
+ //DIE("no queue");
+ //TODO right?
+ proxy->spdy_error = true;
+ proxy->curl_error = true;
+ PRINT_VERBOSE2("no queue in curl_header_cb for %s", proxy->url);
+ SPDY_destroy_response(proxy->response);
+ proxy->response = NULL;
+ return 0;
+ }
+
+ call_spdy_run = true;
+
+ return realsize;
+ }
+
+ pos = 0;
+ if(NULL == proxy->version)
+ {
+ //first line from headers
+ //version
+ for(i=pos; i<realsize && ' '!=line[i]; ++i);
+ if(i == realsize)
+ DIE("error on parsing headers");
+ if(NULL == (proxy->version = strndup(line, i - pos)))
+ DIE("No memory");
+ pos = i+1;
+
+ //status (number)
+ for(i=pos; i<realsize && ' '!=line[i] && '\r'!=line[i] && '\n'!=line[i]; ++i);
+ if(NULL == (status = strndup(&(line[pos]), i - pos)))
+ DIE("No memory");
+ proxy->status = atoi(status);
+ free(status);
+ if(i<realsize && '\r'!=line[i] && '\n'!=line[i])
+ {
+ //status (message)
+ pos = i+1;
+ for(i=pos; i<realsize && '\r'!=line[i] && '\n'!=line[i]; ++i);
+ if(NULL == (proxy->status_msg = strndup(&(line[pos]), i - pos)))
+ DIE("No memory");
+ }
+ PRINT_VERBOSE2("Header line received '%s' '%i' '%s' ", proxy->version, proxy->status, proxy->status_msg);
+ return realsize;
+ }
+
+ //other lines
+ //header name
+ for(i=pos; i<realsize && ':'!=line[i] && '\r'!=line[i] && '\n'!=line[i]; ++i)
+ line[i] = tolower(line[i]); //spdy requires lower case
+ if(NULL == (name = strndup(line, i - pos)))
+ DIE("No memory");
+ if(0 == strcmp(SPDY_HTTP_HEADER_CONNECTION, name)
+ || 0 == strcmp(SPDY_HTTP_HEADER_KEEP_ALIVE, name)
+ || 0 == strcmp(SPDY_HTTP_HEADER_TRANSFER_ENCODING, name)
+ )
+ {
+ //forbidden in spdy, ignore
+ free(name);
+ return realsize;
+ }
+ if(i == realsize || '\r'==line[i] || '\n'==line[i])
+ {
+ //no value. is it possible?
+ if(SPDY_YES != SPDY_name_value_add(proxy->headers, name, ""))
+ DIE("SPDY_name_value_add failed");
+ return realsize;
+ }
+
+ //header value
+ pos = i+1;
+ while(pos<realsize && isspace(line[pos])) ++pos; //remove leading space
+ for(i=pos; i<realsize && '\r'!=line[i] && '\n'!=line[i]; ++i);
+ if(NULL == (value = strndup(&(line[pos]), i - pos)))
+ DIE("No memory");
+ PRINT_VERBOSE2("Adding header: '%s': '%s'", name, value);
+ if(SPDY_YES != (ret = SPDY_name_value_add(proxy->headers, name, value)))
+ {
+ abort_it=true;
+ if(NULL != (values = SPDY_name_value_lookup(proxy->headers, name, &num_values)))
+ for(i=0; i<(unsigned int)num_values; ++i)
+ if(0 == strcasecmp(value, values[i]))
+ {
+ abort_it=false;
+ PRINT_VERBOSE2("header appears more than once with same value '%s: %s'", name, value);
+ break;
+ }
+
+ if(abort_it)
+ {
+ PRINT_INFO2("SPDY_name_value_add failed (%i) for '%s'", ret, name);
+ abort();
+ }
+ }
+ free(name);
+ free(value);
+
+ return realsize;
+}
+
+
+static size_t
+curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp)
+{
+ size_t realsize = size * nmemb;
+ struct Proxy *proxy = (struct Proxy *)userp;
+
+ //printf("curl_write_cb %i\n", realsize);
+ if(!*(proxy->session_alive))
+ {
+ PRINT_VERBOSE("data received, but session is dead");
+ proxy->spdy_error = true;
+ proxy->curl_error = true;
+ return 0;
+ }
+
+ if(!store_in_buffer(contents, realsize, &proxy->http_body, &proxy->http_body_size))
+ {
+ PRINT_INFO("not enough memory (malloc/realloc returned NULL)");
+ proxy->curl_error = true;
+ return 0;
+ }
+ /*
+ if(NULL == proxy->http_body)
+ proxy->http_body = malloc(realsize);
+ else
+ proxy->http_body = realloc(proxy->http_body, proxy->http_body_size + realsize);
+ if(NULL == proxy->http_body)
+ {
+ PRINT_INFO("not enough memory (realloc returned NULL)");
+ return 0;
+ }
+
+ memcpy(proxy->http_body + proxy->http_body_size, contents, realsize);
+ proxy->http_body_size += realsize;
+ */
+
+ PRINT_VERBOSE2("received bytes from curl: %zu", realsize);
+
+ call_spdy_run = true;
+
+ return realsize;
+}
+
+
+static size_t
+curl_read_cb(void *ptr, size_t size, size_t nmemb, void *userp)
+{
+ ssize_t ret;
+ size_t max = size * nmemb;
+ struct Proxy *proxy = (struct Proxy *)userp;
+ //void *newbody;
+
+
+ if((proxy->receiving_done && !proxy->received_body_size) || !proxy->is_with_body_data || max < 1)
+ {
+ PRINT_VERBOSE("curl_read_cb last call");
+ return 0;
+ }
+
+ if(!*(proxy->session_alive))
+ {
+ PRINT_VERBOSE("POST is still being sent, but session is dead");
+ return CURL_READFUNC_ABORT;
+ }
+
+ if(!proxy->received_body_size)//nothing to write now
+ {
+ PRINT_VERBOSE("curl_read_cb called paused");
+ proxy->is_curl_read_paused = true;
+ return CURL_READFUNC_PAUSE;//TODO curl pause should be used
+ }
+
+ ret = get_from_buffer(&(proxy->received_body), &(proxy->received_body_size), ptr, max);
+ if(ret < 0)
+ {
+ PRINT_INFO("no memory");
+ return CURL_READFUNC_ABORT;
+ }
+
+ /*
+ if(max >= proxy->received_body_size)
+ {
+ ret = proxy->received_body_size;
+ newbody = NULL;
+ }
+ else
+ {
+ ret = max;
+ if(NULL == (newbody = malloc(proxy->received_body_size - max)))
+ {
+ PRINT_INFO("no memory");
+ return CURL_READFUNC_ABORT;
+ }
+ memcpy(newbody, proxy->received_body + max, proxy->received_body_size - max);
+ }
+ memcpy(ptr, proxy->received_body, ret);
+ free(proxy->received_body);
+ proxy->received_body = newbody;
+ proxy->received_body_size -= ret;
+ * */
+
+ PRINT_VERBOSE2("given POST bytes to curl: %zd", ret);
+
+ return ret;
+}
+
+
+static int
+iterate_cb (void *cls, const char *name, const char * const * value, int num_values)
+{
+ struct Proxy *proxy = (struct Proxy *)cls;
+ struct curl_slist **curl_headers = (&(proxy->curl_headers));
+ char *line;
+ int line_len = strlen(name) + 3; //+ ": \0"
+ int i;
+
+ for(i=0; i<num_values; ++i)
+ {
+ if(i) line_len += 2; //", "
+ line_len += strlen(value[i]);
+ }
+
+ if(NULL == (line = malloc(line_len)))//no recovery
+ DIE("No memory");
+ line[0] = 0;
+
+ strcat(line, name);
+ strcat(line, ": ");
+ //all spdy header names are lower case;
+ //for simplicity here we just capitalize the first letter
+ line[0] = toupper(line[0]);
+
+ for(i=0; i<num_values; ++i)
+ {
+ if(i) strcat(line, ", ");
+ PRINT_VERBOSE2("Adding request header: '%s' len %ld", value[i], strlen(value[i]));
+ strcat(line, value[i]);
+ }
+ PRINT_VERBOSE2("Adding request header: '%s'", line);
+ if(NULL == (*curl_headers = curl_slist_append(*curl_headers, line)))
+ DIE("curl_slist_append failed");
+ free(line);
+
+ return SPDY_YES;
+}
+
+
+static void
+standard_request_handler(void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers,
+ bool more)
+{
+ (void)cls;
+ (void)priority;
+ (void)host;
+ (void)scheme;
+
+ struct Proxy *proxy;
+ int ret;
+ struct URI *uri;
+ struct SPDY_Session *session;
+
+ proxy = SPDY_get_cls_from_request(request);
+ if(NULL != proxy)
+ {
+ //ignore trailers or more headers
+ return;
+ }
+
+ PRINT_VERBOSE2("received request for '%s %s %s'\n", method, path, version);
+
+ //TODO not freed once in a while
+ if(NULL == (proxy = malloc(sizeof(struct Proxy))))
+ DIE("No memory");
+ memset(proxy, 0, sizeof(struct Proxy));
+
+ //fprintf(stderr, "new proxy for %s\n", path);
+
+ session = SPDY_get_session_for_request(request);
+ assert(NULL != session);
+ proxy->session_alive = SPDY_get_cls_from_session(session);
+ assert(NULL != proxy->session_alive);
+
+ SPDY_set_cls_to_request(request, proxy);
+
+ proxy->request = request;
+ proxy->is_with_body_data = more;
+ if(NULL == (proxy->headers = SPDY_name_value_create()))
+ DIE("No memory");
+
+ if(glob_opt.transparent)
+ {
+ if(NULL != glob_opt.http_backend) //use always same host
+ ret = asprintf(&(proxy->url),"%s://%s%s", scheme, glob_opt.http_backend, path);
+ else //use host header
+ ret = asprintf(&(proxy->url),"%s://%s%s", scheme, host, path);
+ if(-1 == ret)
+ DIE("No memory");
+
+ ret = parse_uri(&uri_preg, proxy->url, &uri);
+ if(ret != 0)
+ DIE("parsing built uri failed");
+ }
+ else
+ {
+ ret = parse_uri(&uri_preg, path, &uri);
+ PRINT_INFO2("path %s '%s' '%s'", path, uri->scheme, uri->host);
+ if(ret != 0 || !strlen(uri->scheme) || !strlen(uri->host))
+ DIE("parsing received uri failed");
+
+ if(NULL != glob_opt.http_backend) //use backend host
+ {
+ ret = asprintf(&(proxy->url),"%s://%s%s", uri->scheme, glob_opt.http_backend, uri->path_and_more);
+ if(-1 == ret)
+ DIE("No memory");
+ }
+ else //use request path
+ if(NULL == (proxy->url = strdup(path)))
+ DIE("No memory");
+ }
+
+ free_uri(uri);
+
+ PRINT_VERBOSE2("curl will request '%s'", proxy->url);
+
+ SPDY_name_value_iterate(headers, &iterate_cb, proxy);
+
+ if(NULL == (proxy->curl_handle = curl_easy_init()))
+ {
+ PRINT_INFO("curl_easy_init failed");
+ abort();
+ }
+
+ if(glob_opt.curl_verbose)
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_VERBOSE, 1);
+
+ if(0 == strcmp(SPDY_HTTP_METHOD_POST,method))
+ {
+ if(NULL == (proxy->curl_headers = curl_slist_append(proxy->curl_headers, "Expect:")))
+ DIE("curl_slist_append failed");
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_POST, 1);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_READFUNCTION, curl_read_cb);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_READDATA, proxy);
+ }
+
+ if(glob_opt.timeout)
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_TIMEOUT, glob_opt.timeout);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_URL, proxy->url);
+ if(glob_opt.http10)
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEDATA, proxy);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERDATA, proxy);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_PRIVATE, proxy);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTPHEADER, proxy->curl_headers);
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);//TODO
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ if(glob_opt.ipv4 && !glob_opt.ipv6)
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+ else if(glob_opt.ipv6 && !glob_opt.ipv4)
+ CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
+
+ if(CURLM_OK != (ret = curl_multi_add_handle(multi_handle, proxy->curl_handle)))
+ {
+ PRINT_INFO2("curl_multi_add_handle failed (%i)", ret);
+ abort();
+ }
+ debug_num_curls++;
+
+ //~5ms additional latency for calling this
+ if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running))
+ && CURLM_CALL_MULTI_PERFORM != ret)
+ {
+ PRINT_INFO2("curl_multi_perform failed (%i)", ret);
+ abort();
+ }
+
+ call_curl_run = true;
+}
+
+
+static int
+run ()
+{
+ unsigned long long timeoutlong = 0;
+ unsigned long long timeout_spdy = 0;
+ long timeout_curl = -1;
+ struct timeval timeout;
+ int ret;
+ int ret_curl;
+ int ret_spdy;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int maxfd = -1;
+ int maxfd_curl = -1;
+ struct SPDY_Daemon *daemon;
+ CURLMsg *msg;
+ int msgs_left;
+ struct Proxy *proxy;
+ struct sockaddr_in *addr;
+ struct addrinfo hints;
+ char service[NI_MAXSERV];
+ struct addrinfo *gai;
+ enum SPDY_IO_SUBSYSTEM io = glob_opt.notls ? SPDY_IO_SUBSYSTEM_RAW : SPDY_IO_SUBSYSTEM_OPENSSL;
+ enum SPDY_DAEMON_FLAG flags = SPDY_DAEMON_FLAG_NO;
+ //struct SPDY_Response *error_response;
+ char *curl_private;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ if (signal(SIGINT, catch_signal) == SIG_ERR)
+ PRINT_VERBOSE("signal failed");
+
+ srand(time(NULL));
+ if(init_parse_uri(&uri_preg))
+ DIE("Regexp compilation failed");
+
+ SPDY_init();
+
+ if(glob_opt.nodelay)
+ flags |= SPDY_DAEMON_FLAG_NO_DELAY;
+
+ if(NULL == glob_opt.listen_host)
+ {
+ daemon = SPDY_start_daemon(glob_opt.listen_port,
+ glob_opt.cert,
+ glob_opt.cert_key,
+ &new_session_cb,
+ &session_closed_cb,
+ &standard_request_handler,
+ &spdy_post_data_cb,
+ NULL,
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
+ 1800,
+ SPDY_DAEMON_OPTION_IO_SUBSYSTEM,
+ io,
+ SPDY_DAEMON_OPTION_FLAGS,
+ flags,
+ SPDY_DAEMON_OPTION_END);
+ }
+ else
+ {
+ snprintf (service, sizeof(service), "%u", glob_opt.listen_port);
+ memset (&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ ret = getaddrinfo(glob_opt.listen_host, service, &hints, &gai);
+ if(ret != 0)
+ DIE("problem with specified host");
+
+ addr = (struct sockaddr_in *) gai->ai_addr;
+
+ daemon = SPDY_start_daemon(0,
+ glob_opt.cert,
+ glob_opt.cert_key,
+ &new_session_cb,
+ &session_closed_cb,
+ &standard_request_handler,
+ &spdy_post_data_cb,
+ NULL,
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
+ 1800,
+ SPDY_DAEMON_OPTION_IO_SUBSYSTEM,
+ io,
+ SPDY_DAEMON_OPTION_FLAGS,
+ flags,
+ SPDY_DAEMON_OPTION_SOCK_ADDR,
+ addr,
+ //SPDY_DAEMON_OPTION_MAX_NUM_FRAMES,
+ //1,
+ SPDY_DAEMON_OPTION_END);
+ }
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ multi_handle = curl_multi_init();
+ if(NULL==multi_handle)
+ DIE("no multi_handle");
+
+ timeout.tv_usec = 0;
+
+ do
+ {
+ FD_ZERO(&rs);
+ FD_ZERO(&ws);
+ FD_ZERO(&es);
+
+ PRINT_VERBOSE2("num curls %i", debug_num_curls);
+
+ ret_spdy = SPDY_get_timeout(daemon, &timeout_spdy);
+ if(SPDY_NO == ret_spdy || timeout_spdy > 5000)
+ timeoutlong = 5000;
+ else
+ timeoutlong = timeout_spdy;
+ PRINT_VERBOSE2("SPDY timeout %lld; %i", timeout_spdy, ret_spdy);
+
+ if(CURLM_OK != (ret_curl = curl_multi_timeout(multi_handle, &timeout_curl)))
+ {
+ PRINT_VERBOSE2("curl_multi_timeout failed (%i)", ret_curl);
+ //curl_timeo = timeoutlong;
+ }
+ else if(timeout_curl >= 0 && timeoutlong > (unsigned long)timeout_curl)
+ timeoutlong = (unsigned long)timeout_curl;
+
+ PRINT_VERBOSE2("curl timeout %ld", timeout_curl);
+
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+
+ maxfd = SPDY_get_fdset (daemon,
+ &rs,
+ &ws,
+ &es);
+ assert(-1 != maxfd);
+
+ if(CURLM_OK != (ret = curl_multi_fdset(multi_handle, &rs,
+ &ws,
+ &es, &maxfd_curl)))
+ {
+ PRINT_INFO2("curl_multi_fdset failed (%i)", ret);
+ abort();
+ }
+
+ if(maxfd_curl > maxfd)
+ maxfd = maxfd_curl;
+
+ PRINT_VERBOSE2("timeout before %lld %lld", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec);
+ ret = select(maxfd+1, &rs, &ws, &es, &timeout);
+ PRINT_VERBOSE2("timeout after %lld %lld; ret is %i", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec, ret);
+
+ /*switch(ret) {
+ case -1:
+ PRINT_INFO2("select error: %i", errno);
+ break;
+ case 0:
+ break;
+ default:*/
+
+ //the second part should not happen with current implementation
+ if(ret > 0 || (SPDY_YES == ret_spdy && 0 == timeout_spdy))
+ {
+ PRINT_VERBOSE("run spdy");
+ SPDY_run(daemon);
+ call_spdy_run = false;
+ }
+
+ //if(ret > 0 || (CURLM_OK == ret_curl && 0 == timeout_curl) || call_curl_run)
+ {
+ PRINT_VERBOSE("run curl");
+ if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running))
+ && CURLM_CALL_MULTI_PERFORM != ret)
+ {
+ PRINT_INFO2("curl_multi_perform failed (%i)", ret);
+ abort();
+ }
+ call_curl_run = false;
+ }
+ /*break;
+ }*/
+
+ while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
+ if (msg->msg == CURLMSG_DONE) {
+ PRINT_VERBOSE("A curl handler is done");
+ if(CURLE_OK != (ret = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &curl_private)))
+ {
+ PRINT_INFO2("err %i",ret);
+ abort();
+ }
+ assert(NULL != curl_private);
+ proxy = (struct Proxy *)curl_private;
+ if(CURLE_OK == msg->data.result)
+ {
+ proxy->curl_done = true;
+ call_spdy_run = true;
+ //TODO what happens with proxy when the client resets a stream
+ //and response_done is not yet set for the last frame? is it
+ //possible?
+ }
+ else
+ {
+ PRINT_VERBOSE2("bad curl result (%i) for '%s'", msg->data.result, proxy->url);
+ if(proxy->spdy_done || proxy->spdy_error || (NULL == proxy->response && !*(proxy->session_alive)))
+ {
+ PRINT_VERBOSE("cleaning");
+ SPDY_name_value_destroy(proxy->headers);
+ SPDY_destroy_request(proxy->request);
+ SPDY_destroy_response(proxy->response);
+ cleanup(proxy);
+ }
+ else if(NULL == proxy->response && *(proxy->session_alive))
+ {
+ //generate error for the client
+ PRINT_VERBOSE("will send Bad Gateway");
+ SPDY_name_value_destroy(proxy->headers);
+ proxy->headers = NULL;
+ if(NULL == (proxy->response = SPDY_build_response(SPDY_HTTP_BAD_GATEWAY,
+ NULL,
+ SPDY_HTTP_VERSION_1_1,
+ NULL,
+ ERROR_RESPONSE,
+ strlen(ERROR_RESPONSE))))
+ DIE("no response");
+ if(SPDY_YES != SPDY_queue_response(proxy->request,
+ proxy->response,
+ true,
+ false,
+ &response_done_callback,
+ proxy))
+ {
+ //clean and forget
+ PRINT_VERBOSE("cleaning");
+ SPDY_destroy_request(proxy->request);
+ SPDY_destroy_response(proxy->response);
+ cleanup(proxy);
+ }
+ }
+ else
+ {
+ proxy->curl_error = true;
+ }
+ call_spdy_run = true;
+ //TODO spdy should be notified to send RST_STREAM
+ }
+ }
+ else PRINT_INFO("shouldn't happen");
+ }
+
+ if(call_spdy_run)
+ {
+ PRINT_VERBOSE("second call to SPDY_run");
+ SPDY_run(daemon);
+ call_spdy_run = false;
+ }
+
+ if(glob_opt.verbose)
+ {
+
+#ifdef HAVE_CLOCK_GETTIME
+#ifdef CLOCK_MONOTONIC
+ struct timespec ts;
+
+ if (0 == clock_gettime(CLOCK_MONOTONIC, &ts))
+ PRINT_VERBOSE2 ("time now %lld %lld",
+ (unsigned long long) ts.tv_sec,
+ (unsigned long long) ts.tv_nsec);
+#endif
+#endif
+ }
+
+ }
+ while(loop);
+
+ SPDY_stop_daemon(daemon);
+
+ curl_multi_cleanup(multi_handle);
+
+ SPDY_deinit();
+
+ deinit_parse_uri(&uri_preg);
+
+ return 0;
+}
+
+
+static void
+display_usage()
+{
+ printf(
+ "Usage: microspdy2http -p <PORT> [-c <CERTIFICATE>] [-k <CERT-KEY>]\n"
+ " [-rvh0DtT] [-b <HTTP-SERVER>] [-l <HOST>]\n\n"
+ "OPTIONS:\n"
+ " -p, --port Listening port.\n"
+ " -l, --host Listening host. If not set, will listen on [::]\n"
+ " -c, --certificate Path to a certificate file. Requiered if\n"
+ " --no-tls is not set.\n"
+ " -k, --certificate-key Path to a key file for the certificate.\n"
+ " Requiered if --no-tls is not set.\n"
+ " -b, --backend-server If set, the proxy will connect always to it.\n"
+ " Otherwise the proxy will connect to the URL\n"
+ " which is specified in the path or 'Host:'.\n"
+ " -v, --verbose Print debug information.\n"
+ " -r, --no-tls Do not use TLS. Client must use SPDY/3.\n"
+ " -h, --curl-verbose Print debug information for curl.\n"
+ " -0, --http10 Prefer HTTP/1.0 connections to the next hop.\n"
+ " -D, --no-delay This makes sense only if --no-tls is used.\n"
+ " TCP_NODELAY will be used for all sessions' sockets.\n"
+ " -4, --curl-ipv4 Curl may use IPv4 to connect to the final destination.\n"
+ " -6, --curl-ipv6 Curl may use IPv6 to connect to the final destination.\n"
+ " If neither --curl-ipv4 nor --curl-ipv6 is set,\n"
+ " both will be used by default.\n"
+ " -T, --timeout Maximum time in seconds for each HTTP transfer.\n"
+ " Use 0 for no timeout; this is the default value.\n"
+ " -t, --transparent If set, the proxy will fetch an URL which\n"
+ " is based on 'Host:' header and requested path.\n"
+ " Otherwise, full URL in the requested path is required.\n\n"
+
+ );
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+
+ int getopt_ret;
+ int option_index;
+ struct option long_options[] = {
+ {"port", required_argument, 0, 'p'},
+ {"certificate", required_argument, 0, 'c'},
+ {"certificate-key", required_argument, 0, 'k'},
+ {"backend-server", required_argument, 0, 'b'},
+ {"no-tls", no_argument, 0, 'r'},
+ {"verbose", no_argument, 0, 'v'},
+ {"curl-verbose", no_argument, 0, 'h'},
+ {"http10", no_argument, 0, '0'},
+ {"no-delay", no_argument, 0, 'D'},
+ {"transparent", no_argument, 0, 't'},
+ {"curl-ipv4", no_argument, 0, '4'},
+ {"curl-ipv6", no_argument, 0, '6'},
+ {"timeout", required_argument, 0, 'T'},
+ {0, 0, 0, 0}
+ };
+
+ while (1)
+ {
+ getopt_ret = getopt_long( argc, argv, "p:l:c:k:b:rv0Dth46T:", long_options, &option_index);
+ if (getopt_ret == -1)
+ break;
+
+ switch(getopt_ret)
+ {
+ case 'p':
+ glob_opt.listen_port = atoi(optarg);
+ break;
+
+ case 'l':
+ glob_opt.listen_host= strdup(optarg);
+ if(NULL == glob_opt.listen_host)
+ return 1;
+ break;
+
+ case 'c':
+ glob_opt.cert = strdup(optarg);
+ break;
+
+ case 'k':
+ glob_opt.cert_key = strdup(optarg);
+ break;
+
+ case 'b':
+ glob_opt.http_backend = strdup(optarg);
+ if(NULL == glob_opt.http_backend)
+ return 1;
+ break;
+
+ case 'r':
+ glob_opt.notls = true;
+ break;
+
+ case 'v':
+ glob_opt.verbose = true;
+ break;
+
+ case 'h':
+ glob_opt.curl_verbose = true;
+ break;
+
+ case '0':
+ glob_opt.http10 = true;
+ break;
+
+ case 'D':
+ glob_opt.nodelay = true;
+ break;
+
+ case 't':
+ glob_opt.transparent = true;
+ break;
+
+ case '4':
+ glob_opt.ipv4 = true;
+ break;
+
+ case '6':
+ glob_opt.ipv6 = true;
+ break;
+
+ case 'T':
+ glob_opt.timeout = atoi(optarg);
+ break;
+
+ case 0:
+ PRINT_INFO("0 from getopt");
+ break;
+
+ case '?':
+ display_usage();
+ return 1;
+
+ default:
+ DIE("default from getopt");
+ }
+ }
+
+ if(
+ 0 == glob_opt.listen_port
+ || (!glob_opt.notls && (NULL == glob_opt.cert || NULL == glob_opt.cert_key))
+ //|| !glob_opt.transparent && NULL != glob_opt.http_backend
+ )
+ {
+ display_usage();
+ return 1;
+ }
+
+ return run();
+}
+
diff --git a/src/testcurl/Makefile.am b/src/testcurl/Makefile.am
new file mode 100644
index 0000000..eb165bd
--- /dev/null
+++ b/src/testcurl/Makefile.am
@@ -0,0 +1,310 @@
+# This Makefile.am is in the public domain
+SUBDIRS = .
+
+if USE_COVERAGE
+ AM_CFLAGS = -fprofile-arcs -ftest-coverage
+endif
+
+if ENABLE_HTTPS
+ SUBDIRS += https
+endif
+
+AM_CPPFLAGS = \
+-DCPU_COUNT=$(CPU_COUNT) \
+-I$(top_srcdir) \
+-I$(top_srcdir)/src/microhttpd \
+-I$(top_srcdir)/src/include \
+$(LIBCURL_CPPFLAGS)
+
+if !HAVE_W32
+PERF_GET_CONCURRENT=perf_get_concurrent
+TEST_CONCURRENT_STOP=test_concurrent_stop
+if HAVE_CURL_BINARY
+CURL_FORK_TEST = test_get_response_cleanup
+endif
+endif
+
+if HAVE_CURL
+check_PROGRAMS = \
+ test_start_stop \
+ test_get \
+ test_get_sendfile \
+ test_urlparse \
+ test_put \
+ $(TEST_CONCURRENT_STOP) \
+ test_process_headers \
+ test_process_arguments \
+ test_parse_cookies \
+ test_large_put \
+ test_get11 \
+ test_get_sendfile11 \
+ test_put11 \
+ test_large_put11 \
+ test_long_header \
+ test_long_header11 \
+ test_get_chunked \
+ test_put_chunked \
+ test_iplimit11 \
+ test_termination \
+ test_timeout \
+ test_callback \
+ $(CURL_FORK_TEST) \
+ perf_get $(PERF_GET_CONCURRENT)
+
+if HAVE_POSIX_THREADS
+check_PROGRAMS += \
+ test_quiesce
+endif
+
+if HAVE_POSTPROCESSOR
+ check_PROGRAMS += \
+ test_post \
+ test_postform \
+ test_post_loop \
+ test_post11 \
+ test_postform11 \
+ test_post_loop11
+endif
+
+noinst_PROGRAMS = \
+ test_options
+
+if ENABLE_DAUTH
+ check_PROGRAMS += \
+ test_digestauth test_digestauth_with_arguments
+endif
+
+TESTS = $(check_PROGRAMS)
+
+noinst_LIBRARIES = libcurl_version_check.a
+endif
+
+libcurl_version_check_a_SOURCES = \
+ curl_version_check.c
+
+test_start_stop_SOURCES = \
+ test_start_stop.c
+test_start_stop_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_concurrent_stop_SOURCES = \
+ test_concurrent_stop.c
+test_concurrent_stop_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_options_SOURCES = \
+ test_options.c
+test_options_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_get_SOURCES = \
+ test_get.c
+test_get_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_quiesce_SOURCES = \
+ test_quiesce.c
+test_quiesce_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+test_quiesce_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) @LIBCURL@
+
+test_callback_SOURCES = \
+ test_callback.c
+test_callback_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+perf_get_SOURCES = \
+ perf_get.c \
+ gauger.h
+perf_get_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+perf_get_concurrent_SOURCES = \
+ perf_get_concurrent.c \
+ gauger.h
+perf_get_concurrent_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_digestauth_SOURCES = \
+ test_digestauth.c
+test_digestauth_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_digestauth_with_arguments_SOURCES = \
+ test_digestauth_with_arguments.c
+test_digestauth_with_arguments_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_get_sendfile_SOURCES = \
+ test_get_sendfile.c
+test_get_sendfile_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+test_get_sendfile_DEPENDENCIES =
+
+if HAVE_W32
+test_get_sendfile_LDADD += \
+ $(top_builddir)/src/platform/libplatform_interface.la
+test_get_sendfile_DEPENDENCIES += \
+ $(top_builddir)/src/platform/libplatform_interface.la
+endif
+
+test_urlparse_SOURCES = \
+ test_urlparse.c
+test_urlparse_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get_response_cleanup_SOURCES = \
+ test_get_response_cleanup.c
+test_get_response_cleanup_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_get_chunked_SOURCES = \
+ test_get_chunked.c
+test_get_chunked_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post_SOURCES = \
+ test_post.c
+test_post_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_process_headers_SOURCES = \
+ test_process_headers.c
+test_process_headers_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_parse_cookies_SOURCES = \
+ test_parse_cookies.c
+test_parse_cookies_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_process_arguments_SOURCES = \
+ test_process_arguments.c
+test_process_arguments_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_postform_SOURCES = \
+ test_postform.c
+test_postform_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_post_loop_SOURCES = \
+ test_post_loop.c
+test_post_loop_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_SOURCES = \
+ test_put.c
+test_put_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_chunked_SOURCES = \
+ test_put_chunked.c
+test_put_chunked_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get11_SOURCES = \
+ test_get.c
+test_get11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get_sendfile11_SOURCES = \
+ test_get_sendfile.c
+test_get_sendfile11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+test_get_sendfile11_DEPENDENCIES =
+
+if HAVE_W32
+test_get_sendfile11_LDADD += \
+ $(top_builddir)/src/platform/libplatform_interface.la
+test_get_sendfile11_DEPENDENCIES += \
+ $(top_builddir)/src/platform/libplatform_interface.la
+endif
+
+test_post11_SOURCES = \
+ test_post.c
+test_post11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_postform11_SOURCES = \
+ test_postform.c
+test_postform11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_post_loop11_SOURCES = \
+ test_post_loop.c
+test_post_loop11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put11_SOURCES = \
+ test_put.c
+test_put11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_large_put_SOURCES = \
+ test_large_put.c
+test_large_put_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_large_put11_SOURCES = \
+ test_large_put.c
+test_large_put11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_long_header_SOURCES = \
+ test_long_header.c
+test_long_header_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_long_header11_SOURCES = \
+ test_long_header.c
+test_long_header11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_iplimit11_SOURCES = \
+ test_iplimit.c
+test_iplimit11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_termination_SOURCES = \
+ test_termination.c
+test_termination_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_timeout_SOURCES = \
+ test_timeout.c
+test_timeout_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
diff --git a/src/testcurl/Makefile.in b/src/testcurl/Makefile.in
new file mode 100644
index 0000000..937bf26
--- /dev/null
+++ b/src/testcurl/Makefile.in
@@ -0,0 +1,2059 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@ENABLE_HTTPS_TRUE@am__append_1 = https
+@HAVE_CURL_TRUE@check_PROGRAMS = test_start_stop$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_get$(EXEEXT) test_get_sendfile$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_urlparse$(EXEEXT) test_put$(EXEEXT) \
+@HAVE_CURL_TRUE@ $(am__EXEEXT_1) test_process_headers$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_process_arguments$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_parse_cookies$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_large_put$(EXEEXT) test_get11$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_get_sendfile11$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_put11$(EXEEXT) test_large_put11$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_long_header$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_long_header11$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_get_chunked$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_put_chunked$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_iplimit11$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_termination$(EXEEXT) \
+@HAVE_CURL_TRUE@ test_timeout$(EXEEXT) test_callback$(EXEEXT) \
+@HAVE_CURL_TRUE@ $(am__EXEEXT_2) perf_get$(EXEEXT) \
+@HAVE_CURL_TRUE@ $(am__EXEEXT_3) $(am__EXEEXT_4) \
+@HAVE_CURL_TRUE@ $(am__EXEEXT_5) $(am__EXEEXT_6)
+@HAVE_CURL_TRUE@@HAVE_POSIX_THREADS_TRUE@am__append_2 = \
+@HAVE_CURL_TRUE@@HAVE_POSIX_THREADS_TRUE@ test_quiesce
+
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@am__append_3 = \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_post \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_postform \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_post_loop \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_post11 \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_postform11 \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_post_loop11
+
+@HAVE_CURL_TRUE@noinst_PROGRAMS = test_options$(EXEEXT)
+@ENABLE_DAUTH_TRUE@@HAVE_CURL_TRUE@am__append_4 = \
+@ENABLE_DAUTH_TRUE@@HAVE_CURL_TRUE@ test_digestauth test_digestauth_with_arguments
+
+@HAVE_W32_TRUE@am__append_5 = \
+@HAVE_W32_TRUE@ $(top_builddir)/src/platform/libplatform_interface.la
+
+@HAVE_W32_TRUE@am__append_6 = \
+@HAVE_W32_TRUE@ $(top_builddir)/src/platform/libplatform_interface.la
+
+@HAVE_W32_TRUE@am__append_7 = \
+@HAVE_W32_TRUE@ $(top_builddir)/src/platform/libplatform_interface.la
+
+@HAVE_W32_TRUE@am__append_8 = \
+@HAVE_W32_TRUE@ $(top_builddir)/src/platform/libplatform_interface.la
+
+subdir = src/testcurl
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp $(top_srcdir)/test-driver
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libcurl_version_check_a_AR = $(AR) $(ARFLAGS)
+libcurl_version_check_a_LIBADD =
+am_libcurl_version_check_a_OBJECTS = curl_version_check.$(OBJEXT)
+libcurl_version_check_a_OBJECTS = \
+ $(am_libcurl_version_check_a_OBJECTS)
+@HAVE_W32_FALSE@am__EXEEXT_1 = test_concurrent_stop$(EXEEXT)
+@HAVE_CURL_BINARY_TRUE@@HAVE_W32_FALSE@am__EXEEXT_2 = test_get_response_cleanup$(EXEEXT)
+@HAVE_W32_FALSE@am__EXEEXT_3 = perf_get_concurrent$(EXEEXT)
+@HAVE_CURL_TRUE@@HAVE_POSIX_THREADS_TRUE@am__EXEEXT_4 = test_quiesce$(EXEEXT)
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@am__EXEEXT_5 = \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_post$(EXEEXT) \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_postform$(EXEEXT) \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_post_loop$(EXEEXT) \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_post11$(EXEEXT) \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_postform11$(EXEEXT) \
+@HAVE_CURL_TRUE@@HAVE_POSTPROCESSOR_TRUE@ test_post_loop11$(EXEEXT)
+@ENABLE_DAUTH_TRUE@@HAVE_CURL_TRUE@am__EXEEXT_6 = \
+@ENABLE_DAUTH_TRUE@@HAVE_CURL_TRUE@ test_digestauth$(EXEEXT) \
+@ENABLE_DAUTH_TRUE@@HAVE_CURL_TRUE@ test_digestauth_with_arguments$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am_perf_get_OBJECTS = perf_get.$(OBJEXT)
+perf_get_OBJECTS = $(am_perf_get_OBJECTS)
+perf_get_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_perf_get_concurrent_OBJECTS = perf_get_concurrent.$(OBJEXT)
+perf_get_concurrent_OBJECTS = $(am_perf_get_concurrent_OBJECTS)
+perf_get_concurrent_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_callback_OBJECTS = test_callback.$(OBJEXT)
+test_callback_OBJECTS = $(am_test_callback_OBJECTS)
+test_callback_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_concurrent_stop_OBJECTS = test_concurrent_stop.$(OBJEXT)
+test_concurrent_stop_OBJECTS = $(am_test_concurrent_stop_OBJECTS)
+test_concurrent_stop_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_digestauth_OBJECTS = test_digestauth.$(OBJEXT)
+test_digestauth_OBJECTS = $(am_test_digestauth_OBJECTS)
+test_digestauth_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_digestauth_with_arguments_OBJECTS = \
+ test_digestauth_with_arguments.$(OBJEXT)
+test_digestauth_with_arguments_OBJECTS = \
+ $(am_test_digestauth_with_arguments_OBJECTS)
+test_digestauth_with_arguments_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_get_OBJECTS = test_get.$(OBJEXT)
+test_get_OBJECTS = $(am_test_get_OBJECTS)
+test_get_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_get11_OBJECTS = test_get.$(OBJEXT)
+test_get11_OBJECTS = $(am_test_get11_OBJECTS)
+test_get11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_get_chunked_OBJECTS = test_get_chunked.$(OBJEXT)
+test_get_chunked_OBJECTS = $(am_test_get_chunked_OBJECTS)
+test_get_chunked_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_get_response_cleanup_OBJECTS = \
+ test_get_response_cleanup.$(OBJEXT)
+test_get_response_cleanup_OBJECTS = \
+ $(am_test_get_response_cleanup_OBJECTS)
+test_get_response_cleanup_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_get_sendfile_OBJECTS = test_get_sendfile.$(OBJEXT)
+test_get_sendfile_OBJECTS = $(am_test_get_sendfile_OBJECTS)
+am_test_get_sendfile11_OBJECTS = test_get_sendfile.$(OBJEXT)
+test_get_sendfile11_OBJECTS = $(am_test_get_sendfile11_OBJECTS)
+am_test_iplimit11_OBJECTS = test_iplimit.$(OBJEXT)
+test_iplimit11_OBJECTS = $(am_test_iplimit11_OBJECTS)
+test_iplimit11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_large_put_OBJECTS = test_large_put.$(OBJEXT)
+test_large_put_OBJECTS = $(am_test_large_put_OBJECTS)
+test_large_put_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_large_put11_OBJECTS = test_large_put.$(OBJEXT)
+test_large_put11_OBJECTS = $(am_test_large_put11_OBJECTS)
+test_large_put11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_long_header_OBJECTS = test_long_header.$(OBJEXT)
+test_long_header_OBJECTS = $(am_test_long_header_OBJECTS)
+test_long_header_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_long_header11_OBJECTS = test_long_header.$(OBJEXT)
+test_long_header11_OBJECTS = $(am_test_long_header11_OBJECTS)
+test_long_header11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_options_OBJECTS = test_options.$(OBJEXT)
+test_options_OBJECTS = $(am_test_options_OBJECTS)
+test_options_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_parse_cookies_OBJECTS = test_parse_cookies.$(OBJEXT)
+test_parse_cookies_OBJECTS = $(am_test_parse_cookies_OBJECTS)
+test_parse_cookies_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_post_OBJECTS = test_post.$(OBJEXT)
+test_post_OBJECTS = $(am_test_post_OBJECTS)
+test_post_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_post11_OBJECTS = test_post.$(OBJEXT)
+test_post11_OBJECTS = $(am_test_post11_OBJECTS)
+test_post11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_post_loop_OBJECTS = test_post_loop.$(OBJEXT)
+test_post_loop_OBJECTS = $(am_test_post_loop_OBJECTS)
+test_post_loop_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_post_loop11_OBJECTS = test_post_loop.$(OBJEXT)
+test_post_loop11_OBJECTS = $(am_test_post_loop11_OBJECTS)
+test_post_loop11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_postform_OBJECTS = test_postform.$(OBJEXT)
+test_postform_OBJECTS = $(am_test_postform_OBJECTS)
+test_postform_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_postform11_OBJECTS = test_postform.$(OBJEXT)
+test_postform11_OBJECTS = $(am_test_postform11_OBJECTS)
+test_postform11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_process_arguments_OBJECTS = test_process_arguments.$(OBJEXT)
+test_process_arguments_OBJECTS = $(am_test_process_arguments_OBJECTS)
+test_process_arguments_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_process_headers_OBJECTS = test_process_headers.$(OBJEXT)
+test_process_headers_OBJECTS = $(am_test_process_headers_OBJECTS)
+test_process_headers_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_put_OBJECTS = test_put.$(OBJEXT)
+test_put_OBJECTS = $(am_test_put_OBJECTS)
+test_put_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_put11_OBJECTS = test_put.$(OBJEXT)
+test_put11_OBJECTS = $(am_test_put11_OBJECTS)
+test_put11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_put_chunked_OBJECTS = test_put_chunked.$(OBJEXT)
+test_put_chunked_OBJECTS = $(am_test_put_chunked_OBJECTS)
+test_put_chunked_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_quiesce_OBJECTS = test_quiesce-test_quiesce.$(OBJEXT)
+test_quiesce_OBJECTS = $(am_test_quiesce_OBJECTS)
+am__DEPENDENCIES_1 =
+test_quiesce_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1)
+test_quiesce_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_quiesce_CFLAGS) \
+ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+am_test_start_stop_OBJECTS = test_start_stop.$(OBJEXT)
+test_start_stop_OBJECTS = $(am_test_start_stop_OBJECTS)
+test_start_stop_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_termination_OBJECTS = test_termination.$(OBJEXT)
+test_termination_OBJECTS = $(am_test_termination_OBJECTS)
+test_termination_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_timeout_OBJECTS = test_timeout.$(OBJEXT)
+test_timeout_OBJECTS = $(am_test_timeout_OBJECTS)
+test_timeout_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_urlparse_OBJECTS = test_urlparse.$(OBJEXT)
+test_urlparse_OBJECTS = $(am_test_urlparse_OBJECTS)
+test_urlparse_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libcurl_version_check_a_SOURCES) $(perf_get_SOURCES) \
+ $(perf_get_concurrent_SOURCES) $(test_callback_SOURCES) \
+ $(test_concurrent_stop_SOURCES) $(test_digestauth_SOURCES) \
+ $(test_digestauth_with_arguments_SOURCES) $(test_get_SOURCES) \
+ $(test_get11_SOURCES) $(test_get_chunked_SOURCES) \
+ $(test_get_response_cleanup_SOURCES) \
+ $(test_get_sendfile_SOURCES) $(test_get_sendfile11_SOURCES) \
+ $(test_iplimit11_SOURCES) $(test_large_put_SOURCES) \
+ $(test_large_put11_SOURCES) $(test_long_header_SOURCES) \
+ $(test_long_header11_SOURCES) $(test_options_SOURCES) \
+ $(test_parse_cookies_SOURCES) $(test_post_SOURCES) \
+ $(test_post11_SOURCES) $(test_post_loop_SOURCES) \
+ $(test_post_loop11_SOURCES) $(test_postform_SOURCES) \
+ $(test_postform11_SOURCES) $(test_process_arguments_SOURCES) \
+ $(test_process_headers_SOURCES) $(test_put_SOURCES) \
+ $(test_put11_SOURCES) $(test_put_chunked_SOURCES) \
+ $(test_quiesce_SOURCES) $(test_start_stop_SOURCES) \
+ $(test_termination_SOURCES) $(test_timeout_SOURCES) \
+ $(test_urlparse_SOURCES)
+DIST_SOURCES = $(libcurl_version_check_a_SOURCES) $(perf_get_SOURCES) \
+ $(perf_get_concurrent_SOURCES) $(test_callback_SOURCES) \
+ $(test_concurrent_stop_SOURCES) $(test_digestauth_SOURCES) \
+ $(test_digestauth_with_arguments_SOURCES) $(test_get_SOURCES) \
+ $(test_get11_SOURCES) $(test_get_chunked_SOURCES) \
+ $(test_get_response_cleanup_SOURCES) \
+ $(test_get_sendfile_SOURCES) $(test_get_sendfile11_SOURCES) \
+ $(test_iplimit11_SOURCES) $(test_large_put_SOURCES) \
+ $(test_large_put11_SOURCES) $(test_long_header_SOURCES) \
+ $(test_long_header11_SOURCES) $(test_options_SOURCES) \
+ $(test_parse_cookies_SOURCES) $(test_post_SOURCES) \
+ $(test_post11_SOURCES) $(test_post_loop_SOURCES) \
+ $(test_post_loop11_SOURCES) $(test_postform_SOURCES) \
+ $(test_postform11_SOURCES) $(test_process_arguments_SOURCES) \
+ $(test_process_headers_SOURCES) $(test_put_SOURCES) \
+ $(test_put11_SOURCES) $(test_put_chunked_SOURCES) \
+ $(test_quiesce_SOURCES) $(test_start_stop_SOURCES) \
+ $(test_termination_SOURCES) $(test_timeout_SOURCES) \
+ $(test_urlparse_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ check recheck distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red='[0;31m'; \
+ grn='[0;32m'; \
+ lgn='[1;32m'; \
+ blu='[1;34m'; \
+ mgn='[0;35m'; \
+ brg='[1m'; \
+ std='[m'; \
+ fi; \
+}
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+DIST_SUBDIRS = . https
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+SUBDIRS = . $(am__append_1)
+@USE_COVERAGE_TRUE@AM_CFLAGS = -fprofile-arcs -ftest-coverage
+AM_CPPFLAGS = \
+-DCPU_COUNT=$(CPU_COUNT) \
+-I$(top_srcdir) \
+-I$(top_srcdir)/src/microhttpd \
+-I$(top_srcdir)/src/include \
+$(LIBCURL_CPPFLAGS)
+
+@HAVE_W32_FALSE@PERF_GET_CONCURRENT = perf_get_concurrent
+@HAVE_W32_FALSE@TEST_CONCURRENT_STOP = test_concurrent_stop
+@HAVE_CURL_BINARY_TRUE@@HAVE_W32_FALSE@CURL_FORK_TEST = test_get_response_cleanup
+@HAVE_CURL_TRUE@TESTS = $(check_PROGRAMS)
+@HAVE_CURL_TRUE@noinst_LIBRARIES = libcurl_version_check.a
+libcurl_version_check_a_SOURCES = \
+ curl_version_check.c
+
+test_start_stop_SOURCES = \
+ test_start_stop.c
+
+test_start_stop_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_concurrent_stop_SOURCES = \
+ test_concurrent_stop.c
+
+test_concurrent_stop_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_options_SOURCES = \
+ test_options.c
+
+test_options_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_get_SOURCES = \
+ test_get.c
+
+test_get_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_quiesce_SOURCES = \
+ test_quiesce.c
+
+test_quiesce_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+
+test_quiesce_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) @LIBCURL@
+
+test_callback_SOURCES = \
+ test_callback.c
+
+test_callback_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+perf_get_SOURCES = \
+ perf_get.c \
+ gauger.h
+
+perf_get_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+perf_get_concurrent_SOURCES = \
+ perf_get_concurrent.c \
+ gauger.h
+
+perf_get_concurrent_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_digestauth_SOURCES = \
+ test_digestauth.c
+
+test_digestauth_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_digestauth_with_arguments_SOURCES = \
+ test_digestauth_with_arguments.c
+
+test_digestauth_with_arguments_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_get_sendfile_SOURCES = \
+ test_get_sendfile.c
+
+test_get_sendfile_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la @LIBCURL@ \
+ $(am__append_5)
+test_get_sendfile_DEPENDENCIES = $(am__append_6)
+test_urlparse_SOURCES = \
+ test_urlparse.c
+
+test_urlparse_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get_response_cleanup_SOURCES = \
+ test_get_response_cleanup.c
+
+test_get_response_cleanup_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_get_chunked_SOURCES = \
+ test_get_chunked.c
+
+test_get_chunked_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post_SOURCES = \
+ test_post.c
+
+test_post_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_process_headers_SOURCES = \
+ test_process_headers.c
+
+test_process_headers_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_parse_cookies_SOURCES = \
+ test_parse_cookies.c
+
+test_parse_cookies_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_process_arguments_SOURCES = \
+ test_process_arguments.c
+
+test_process_arguments_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_postform_SOURCES = \
+ test_postform.c
+
+test_postform_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_post_loop_SOURCES = \
+ test_post_loop.c
+
+test_post_loop_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_SOURCES = \
+ test_put.c
+
+test_put_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_chunked_SOURCES = \
+ test_put_chunked.c
+
+test_put_chunked_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get11_SOURCES = \
+ test_get.c
+
+test_get11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get_sendfile11_SOURCES = \
+ test_get_sendfile.c
+
+test_get_sendfile11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la @LIBCURL@ \
+ $(am__append_7)
+test_get_sendfile11_DEPENDENCIES = $(am__append_8)
+test_post11_SOURCES = \
+ test_post.c
+
+test_post11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_postform11_SOURCES = \
+ test_postform.c
+
+test_postform11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_post_loop11_SOURCES = \
+ test_post_loop.c
+
+test_post_loop11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put11_SOURCES = \
+ test_put.c
+
+test_put11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_large_put_SOURCES = \
+ test_large_put.c
+
+test_large_put_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_large_put11_SOURCES = \
+ test_large_put.c
+
+test_large_put11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_long_header_SOURCES = \
+ test_long_header.c
+
+test_long_header_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_long_header11_SOURCES = \
+ test_long_header.c
+
+test_long_header11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_iplimit11_SOURCES = \
+ test_iplimit.c
+
+test_iplimit11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_termination_SOURCES = \
+ test_termination.c
+
+test_termination_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_timeout_SOURCES = \
+ test_timeout.c
+
+test_timeout_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/testcurl/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/testcurl/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libcurl_version_check.a: $(libcurl_version_check_a_OBJECTS) $(libcurl_version_check_a_DEPENDENCIES) $(EXTRA_libcurl_version_check_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libcurl_version_check.a
+ $(AM_V_AR)$(libcurl_version_check_a_AR) libcurl_version_check.a $(libcurl_version_check_a_OBJECTS) $(libcurl_version_check_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libcurl_version_check.a
+
+clean-checkPROGRAMS:
+ @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+perf_get$(EXEEXT): $(perf_get_OBJECTS) $(perf_get_DEPENDENCIES) $(EXTRA_perf_get_DEPENDENCIES)
+ @rm -f perf_get$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(perf_get_OBJECTS) $(perf_get_LDADD) $(LIBS)
+
+perf_get_concurrent$(EXEEXT): $(perf_get_concurrent_OBJECTS) $(perf_get_concurrent_DEPENDENCIES) $(EXTRA_perf_get_concurrent_DEPENDENCIES)
+ @rm -f perf_get_concurrent$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(perf_get_concurrent_OBJECTS) $(perf_get_concurrent_LDADD) $(LIBS)
+
+test_callback$(EXEEXT): $(test_callback_OBJECTS) $(test_callback_DEPENDENCIES) $(EXTRA_test_callback_DEPENDENCIES)
+ @rm -f test_callback$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_callback_OBJECTS) $(test_callback_LDADD) $(LIBS)
+
+test_concurrent_stop$(EXEEXT): $(test_concurrent_stop_OBJECTS) $(test_concurrent_stop_DEPENDENCIES) $(EXTRA_test_concurrent_stop_DEPENDENCIES)
+ @rm -f test_concurrent_stop$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_concurrent_stop_OBJECTS) $(test_concurrent_stop_LDADD) $(LIBS)
+
+test_digestauth$(EXEEXT): $(test_digestauth_OBJECTS) $(test_digestauth_DEPENDENCIES) $(EXTRA_test_digestauth_DEPENDENCIES)
+ @rm -f test_digestauth$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_digestauth_OBJECTS) $(test_digestauth_LDADD) $(LIBS)
+
+test_digestauth_with_arguments$(EXEEXT): $(test_digestauth_with_arguments_OBJECTS) $(test_digestauth_with_arguments_DEPENDENCIES) $(EXTRA_test_digestauth_with_arguments_DEPENDENCIES)
+ @rm -f test_digestauth_with_arguments$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_digestauth_with_arguments_OBJECTS) $(test_digestauth_with_arguments_LDADD) $(LIBS)
+
+test_get$(EXEEXT): $(test_get_OBJECTS) $(test_get_DEPENDENCIES) $(EXTRA_test_get_DEPENDENCIES)
+ @rm -f test_get$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get_OBJECTS) $(test_get_LDADD) $(LIBS)
+
+test_get11$(EXEEXT): $(test_get11_OBJECTS) $(test_get11_DEPENDENCIES) $(EXTRA_test_get11_DEPENDENCIES)
+ @rm -f test_get11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get11_OBJECTS) $(test_get11_LDADD) $(LIBS)
+
+test_get_chunked$(EXEEXT): $(test_get_chunked_OBJECTS) $(test_get_chunked_DEPENDENCIES) $(EXTRA_test_get_chunked_DEPENDENCIES)
+ @rm -f test_get_chunked$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get_chunked_OBJECTS) $(test_get_chunked_LDADD) $(LIBS)
+
+test_get_response_cleanup$(EXEEXT): $(test_get_response_cleanup_OBJECTS) $(test_get_response_cleanup_DEPENDENCIES) $(EXTRA_test_get_response_cleanup_DEPENDENCIES)
+ @rm -f test_get_response_cleanup$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get_response_cleanup_OBJECTS) $(test_get_response_cleanup_LDADD) $(LIBS)
+
+test_get_sendfile$(EXEEXT): $(test_get_sendfile_OBJECTS) $(test_get_sendfile_DEPENDENCIES) $(EXTRA_test_get_sendfile_DEPENDENCIES)
+ @rm -f test_get_sendfile$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get_sendfile_OBJECTS) $(test_get_sendfile_LDADD) $(LIBS)
+
+test_get_sendfile11$(EXEEXT): $(test_get_sendfile11_OBJECTS) $(test_get_sendfile11_DEPENDENCIES) $(EXTRA_test_get_sendfile11_DEPENDENCIES)
+ @rm -f test_get_sendfile11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get_sendfile11_OBJECTS) $(test_get_sendfile11_LDADD) $(LIBS)
+
+test_iplimit11$(EXEEXT): $(test_iplimit11_OBJECTS) $(test_iplimit11_DEPENDENCIES) $(EXTRA_test_iplimit11_DEPENDENCIES)
+ @rm -f test_iplimit11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_iplimit11_OBJECTS) $(test_iplimit11_LDADD) $(LIBS)
+
+test_large_put$(EXEEXT): $(test_large_put_OBJECTS) $(test_large_put_DEPENDENCIES) $(EXTRA_test_large_put_DEPENDENCIES)
+ @rm -f test_large_put$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_large_put_OBJECTS) $(test_large_put_LDADD) $(LIBS)
+
+test_large_put11$(EXEEXT): $(test_large_put11_OBJECTS) $(test_large_put11_DEPENDENCIES) $(EXTRA_test_large_put11_DEPENDENCIES)
+ @rm -f test_large_put11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_large_put11_OBJECTS) $(test_large_put11_LDADD) $(LIBS)
+
+test_long_header$(EXEEXT): $(test_long_header_OBJECTS) $(test_long_header_DEPENDENCIES) $(EXTRA_test_long_header_DEPENDENCIES)
+ @rm -f test_long_header$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_long_header_OBJECTS) $(test_long_header_LDADD) $(LIBS)
+
+test_long_header11$(EXEEXT): $(test_long_header11_OBJECTS) $(test_long_header11_DEPENDENCIES) $(EXTRA_test_long_header11_DEPENDENCIES)
+ @rm -f test_long_header11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_long_header11_OBJECTS) $(test_long_header11_LDADD) $(LIBS)
+
+test_options$(EXEEXT): $(test_options_OBJECTS) $(test_options_DEPENDENCIES) $(EXTRA_test_options_DEPENDENCIES)
+ @rm -f test_options$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_options_OBJECTS) $(test_options_LDADD) $(LIBS)
+
+test_parse_cookies$(EXEEXT): $(test_parse_cookies_OBJECTS) $(test_parse_cookies_DEPENDENCIES) $(EXTRA_test_parse_cookies_DEPENDENCIES)
+ @rm -f test_parse_cookies$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_parse_cookies_OBJECTS) $(test_parse_cookies_LDADD) $(LIBS)
+
+test_post$(EXEEXT): $(test_post_OBJECTS) $(test_post_DEPENDENCIES) $(EXTRA_test_post_DEPENDENCIES)
+ @rm -f test_post$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_post_OBJECTS) $(test_post_LDADD) $(LIBS)
+
+test_post11$(EXEEXT): $(test_post11_OBJECTS) $(test_post11_DEPENDENCIES) $(EXTRA_test_post11_DEPENDENCIES)
+ @rm -f test_post11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_post11_OBJECTS) $(test_post11_LDADD) $(LIBS)
+
+test_post_loop$(EXEEXT): $(test_post_loop_OBJECTS) $(test_post_loop_DEPENDENCIES) $(EXTRA_test_post_loop_DEPENDENCIES)
+ @rm -f test_post_loop$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_post_loop_OBJECTS) $(test_post_loop_LDADD) $(LIBS)
+
+test_post_loop11$(EXEEXT): $(test_post_loop11_OBJECTS) $(test_post_loop11_DEPENDENCIES) $(EXTRA_test_post_loop11_DEPENDENCIES)
+ @rm -f test_post_loop11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_post_loop11_OBJECTS) $(test_post_loop11_LDADD) $(LIBS)
+
+test_postform$(EXEEXT): $(test_postform_OBJECTS) $(test_postform_DEPENDENCIES) $(EXTRA_test_postform_DEPENDENCIES)
+ @rm -f test_postform$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_postform_OBJECTS) $(test_postform_LDADD) $(LIBS)
+
+test_postform11$(EXEEXT): $(test_postform11_OBJECTS) $(test_postform11_DEPENDENCIES) $(EXTRA_test_postform11_DEPENDENCIES)
+ @rm -f test_postform11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_postform11_OBJECTS) $(test_postform11_LDADD) $(LIBS)
+
+test_process_arguments$(EXEEXT): $(test_process_arguments_OBJECTS) $(test_process_arguments_DEPENDENCIES) $(EXTRA_test_process_arguments_DEPENDENCIES)
+ @rm -f test_process_arguments$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_process_arguments_OBJECTS) $(test_process_arguments_LDADD) $(LIBS)
+
+test_process_headers$(EXEEXT): $(test_process_headers_OBJECTS) $(test_process_headers_DEPENDENCIES) $(EXTRA_test_process_headers_DEPENDENCIES)
+ @rm -f test_process_headers$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_process_headers_OBJECTS) $(test_process_headers_LDADD) $(LIBS)
+
+test_put$(EXEEXT): $(test_put_OBJECTS) $(test_put_DEPENDENCIES) $(EXTRA_test_put_DEPENDENCIES)
+ @rm -f test_put$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_put_OBJECTS) $(test_put_LDADD) $(LIBS)
+
+test_put11$(EXEEXT): $(test_put11_OBJECTS) $(test_put11_DEPENDENCIES) $(EXTRA_test_put11_DEPENDENCIES)
+ @rm -f test_put11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_put11_OBJECTS) $(test_put11_LDADD) $(LIBS)
+
+test_put_chunked$(EXEEXT): $(test_put_chunked_OBJECTS) $(test_put_chunked_DEPENDENCIES) $(EXTRA_test_put_chunked_DEPENDENCIES)
+ @rm -f test_put_chunked$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_put_chunked_OBJECTS) $(test_put_chunked_LDADD) $(LIBS)
+
+test_quiesce$(EXEEXT): $(test_quiesce_OBJECTS) $(test_quiesce_DEPENDENCIES) $(EXTRA_test_quiesce_DEPENDENCIES)
+ @rm -f test_quiesce$(EXEEXT)
+ $(AM_V_CCLD)$(test_quiesce_LINK) $(test_quiesce_OBJECTS) $(test_quiesce_LDADD) $(LIBS)
+
+test_start_stop$(EXEEXT): $(test_start_stop_OBJECTS) $(test_start_stop_DEPENDENCIES) $(EXTRA_test_start_stop_DEPENDENCIES)
+ @rm -f test_start_stop$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_start_stop_OBJECTS) $(test_start_stop_LDADD) $(LIBS)
+
+test_termination$(EXEEXT): $(test_termination_OBJECTS) $(test_termination_DEPENDENCIES) $(EXTRA_test_termination_DEPENDENCIES)
+ @rm -f test_termination$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_termination_OBJECTS) $(test_termination_LDADD) $(LIBS)
+
+test_timeout$(EXEEXT): $(test_timeout_OBJECTS) $(test_timeout_DEPENDENCIES) $(EXTRA_test_timeout_DEPENDENCIES)
+ @rm -f test_timeout$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_timeout_OBJECTS) $(test_timeout_LDADD) $(LIBS)
+
+test_urlparse$(EXEEXT): $(test_urlparse_OBJECTS) $(test_urlparse_DEPENDENCIES) $(EXTRA_test_urlparse_DEPENDENCIES)
+ @rm -f test_urlparse$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_urlparse_OBJECTS) $(test_urlparse_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/curl_version_check.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/perf_get.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/perf_get_concurrent.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_callback.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_concurrent_stop.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_digestauth.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_digestauth_with_arguments.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_get.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_get_chunked.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_get_response_cleanup.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_get_sendfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_iplimit.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_large_put.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_long_header.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_options.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_parse_cookies.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_post.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_post_loop.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_postform.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_process_arguments.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_process_headers.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_put.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_put_chunked.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_quiesce-test_quiesce.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_start_stop.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_termination.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_timeout.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_urlparse.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+test_quiesce-test_quiesce.o: test_quiesce.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_quiesce_CFLAGS) $(CFLAGS) -MT test_quiesce-test_quiesce.o -MD -MP -MF $(DEPDIR)/test_quiesce-test_quiesce.Tpo -c -o test_quiesce-test_quiesce.o `test -f 'test_quiesce.c' || echo '$(srcdir)/'`test_quiesce.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_quiesce-test_quiesce.Tpo $(DEPDIR)/test_quiesce-test_quiesce.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_quiesce.c' object='test_quiesce-test_quiesce.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_quiesce_CFLAGS) $(CFLAGS) -c -o test_quiesce-test_quiesce.o `test -f 'test_quiesce.c' || echo '$(srcdir)/'`test_quiesce.c
+
+test_quiesce-test_quiesce.obj: test_quiesce.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_quiesce_CFLAGS) $(CFLAGS) -MT test_quiesce-test_quiesce.obj -MD -MP -MF $(DEPDIR)/test_quiesce-test_quiesce.Tpo -c -o test_quiesce-test_quiesce.obj `if test -f 'test_quiesce.c'; then $(CYGPATH_W) 'test_quiesce.c'; else $(CYGPATH_W) '$(srcdir)/test_quiesce.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_quiesce-test_quiesce.Tpo $(DEPDIR)/test_quiesce-test_quiesce.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_quiesce.c' object='test_quiesce-test_quiesce.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_quiesce_CFLAGS) $(CFLAGS) -c -o test_quiesce-test_quiesce.obj `if test -f 'test_quiesce.c'; then $(CYGPATH_W) 'test_quiesce.c'; else $(CYGPATH_W) '$(srcdir)/test_quiesce.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ else \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all $(check_PROGRAMS)
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+test_start_stop.log: test_start_stop$(EXEEXT)
+ @p='test_start_stop$(EXEEXT)'; \
+ b='test_start_stop'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_get.log: test_get$(EXEEXT)
+ @p='test_get$(EXEEXT)'; \
+ b='test_get'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_get_sendfile.log: test_get_sendfile$(EXEEXT)
+ @p='test_get_sendfile$(EXEEXT)'; \
+ b='test_get_sendfile'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_urlparse.log: test_urlparse$(EXEEXT)
+ @p='test_urlparse$(EXEEXT)'; \
+ b='test_urlparse'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_put.log: test_put$(EXEEXT)
+ @p='test_put$(EXEEXT)'; \
+ b='test_put'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_concurrent_stop.log: test_concurrent_stop$(EXEEXT)
+ @p='test_concurrent_stop$(EXEEXT)'; \
+ b='test_concurrent_stop'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_process_headers.log: test_process_headers$(EXEEXT)
+ @p='test_process_headers$(EXEEXT)'; \
+ b='test_process_headers'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_process_arguments.log: test_process_arguments$(EXEEXT)
+ @p='test_process_arguments$(EXEEXT)'; \
+ b='test_process_arguments'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_parse_cookies.log: test_parse_cookies$(EXEEXT)
+ @p='test_parse_cookies$(EXEEXT)'; \
+ b='test_parse_cookies'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_large_put.log: test_large_put$(EXEEXT)
+ @p='test_large_put$(EXEEXT)'; \
+ b='test_large_put'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_get11.log: test_get11$(EXEEXT)
+ @p='test_get11$(EXEEXT)'; \
+ b='test_get11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_get_sendfile11.log: test_get_sendfile11$(EXEEXT)
+ @p='test_get_sendfile11$(EXEEXT)'; \
+ b='test_get_sendfile11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_put11.log: test_put11$(EXEEXT)
+ @p='test_put11$(EXEEXT)'; \
+ b='test_put11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_large_put11.log: test_large_put11$(EXEEXT)
+ @p='test_large_put11$(EXEEXT)'; \
+ b='test_large_put11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_long_header.log: test_long_header$(EXEEXT)
+ @p='test_long_header$(EXEEXT)'; \
+ b='test_long_header'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_long_header11.log: test_long_header11$(EXEEXT)
+ @p='test_long_header11$(EXEEXT)'; \
+ b='test_long_header11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_get_chunked.log: test_get_chunked$(EXEEXT)
+ @p='test_get_chunked$(EXEEXT)'; \
+ b='test_get_chunked'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_put_chunked.log: test_put_chunked$(EXEEXT)
+ @p='test_put_chunked$(EXEEXT)'; \
+ b='test_put_chunked'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_iplimit11.log: test_iplimit11$(EXEEXT)
+ @p='test_iplimit11$(EXEEXT)'; \
+ b='test_iplimit11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_termination.log: test_termination$(EXEEXT)
+ @p='test_termination$(EXEEXT)'; \
+ b='test_termination'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_timeout.log: test_timeout$(EXEEXT)
+ @p='test_timeout$(EXEEXT)'; \
+ b='test_timeout'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_callback.log: test_callback$(EXEEXT)
+ @p='test_callback$(EXEEXT)'; \
+ b='test_callback'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_get_response_cleanup.log: test_get_response_cleanup$(EXEEXT)
+ @p='test_get_response_cleanup$(EXEEXT)'; \
+ b='test_get_response_cleanup'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+perf_get.log: perf_get$(EXEEXT)
+ @p='perf_get$(EXEEXT)'; \
+ b='perf_get'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+perf_get_concurrent.log: perf_get_concurrent$(EXEEXT)
+ @p='perf_get_concurrent$(EXEEXT)'; \
+ b='perf_get_concurrent'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_quiesce.log: test_quiesce$(EXEEXT)
+ @p='test_quiesce$(EXEEXT)'; \
+ b='test_quiesce'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_post.log: test_post$(EXEEXT)
+ @p='test_post$(EXEEXT)'; \
+ b='test_post'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_postform.log: test_postform$(EXEEXT)
+ @p='test_postform$(EXEEXT)'; \
+ b='test_postform'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_post_loop.log: test_post_loop$(EXEEXT)
+ @p='test_post_loop$(EXEEXT)'; \
+ b='test_post_loop'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_post11.log: test_post11$(EXEEXT)
+ @p='test_post11$(EXEEXT)'; \
+ b='test_post11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_postform11.log: test_postform11$(EXEEXT)
+ @p='test_postform11$(EXEEXT)'; \
+ b='test_postform11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_post_loop11.log: test_post_loop11$(EXEEXT)
+ @p='test_post_loop11$(EXEEXT)'; \
+ b='test_post_loop11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_digestauth.log: test_digestauth$(EXEEXT)
+ @p='test_digestauth$(EXEEXT)'; \
+ b='test_digestauth'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_digestauth_with_arguments.log: test_digestauth_with_arguments$(EXEEXT)
+ @p='test_digestauth_with_arguments$(EXEEXT)'; \
+ b='test_digestauth_with_arguments'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(LIBRARIES) $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libtool \
+ clean-noinstLIBRARIES clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-TESTS check-am clean clean-checkPROGRAMS clean-generic \
+ clean-libtool clean-noinstLIBRARIES clean-noinstPROGRAMS \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am recheck tags tags-am uninstall \
+ uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/testcurl/curl_version_check.c b/src/testcurl/curl_version_check.c
new file mode 100644
index 0000000..518cb86
--- /dev/null
+++ b/src/testcurl/curl_version_check.c
@@ -0,0 +1,176 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file curl_version_check.c
+ * @brief verify required cURL version is available to run tests
+ * @author Sagie Amir
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+static int
+parse_version_number (const char **s)
+{
+ int i = 0;
+ char num[17];
+
+ while (i < 16 && ((**s >= '0') & (**s <= '9')))
+ {
+ num[i] = **s;
+ (*s)++;
+ i++;
+ }
+
+ num[i] = '\0';
+
+ return atoi (num);
+}
+
+const char *
+parse_version_string (const char *s, int *major, int *minor, int *micro)
+{
+ if (!s)
+ return NULL;
+ *major = parse_version_number (&s);
+ if (*s != '.')
+ return NULL;
+ s++;
+ *minor = parse_version_number (&s);
+ if (*s != '.')
+ return NULL;
+ s++;
+ *micro = parse_version_number (&s);
+ return s;
+}
+
+#if HTTPS_SUPPORT
+int
+curl_uses_nss_ssl()
+{
+ return (strstr(curl_version(), " NSS/") != NULL) ? 0 : -1;
+}
+#endif
+
+/*
+ * check local libcurl version matches required version
+ */
+int
+curl_check_version (const char *req_version)
+{
+ const char *ver;
+ const char *curl_ver;
+#if HTTPS_SUPPORT
+ const char *ssl_ver;
+ const char *req_ssl_ver;
+#endif
+
+ int loc_major, loc_minor, loc_micro;
+ int rq_major, rq_minor, rq_micro;
+
+ ver = curl_version ();
+#if HAVE_MESSAGES
+ fprintf (stderr, "curl version: %s\n", ver);
+#endif
+ /*
+ * this call relies on the cURL string to be of the exact following format :
+ * 'libcurl/7.16.4 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/0.6.5' OR
+ * 'libcurl/7.18.2 GnuTLS/2.4.0 zlib/1.2.3.3 libidn/0.6.5'
+ */
+ curl_ver = strchr (ver, '/');
+ if (curl_ver == NULL)
+ return -1;
+ curl_ver++;
+ /* Parse version numbers */
+ if ( (NULL == parse_version_string (req_version, &rq_major, &rq_minor, &rq_micro)) ||
+ (NULL == parse_version_string (curl_ver, &loc_major, &loc_minor, &loc_micro)) )
+ return -1;
+
+ /* Compare version numbers. */
+ if ((loc_major > rq_major
+ || (loc_major == rq_major && loc_minor > rq_minor)
+ || (loc_major == rq_major && loc_minor == rq_minor
+ && loc_micro > rq_micro) || (loc_major == rq_major
+ && loc_minor == rq_minor
+ && loc_micro == rq_micro)) == 0)
+ {
+ fprintf (stderr,
+ "Error: running curl test depends on local libcurl version > %s\n",
+ req_version);
+ return -1;
+ }
+
+ /*
+ * enforce required gnutls/openssl version.
+ * TODO use curl version string to assert use of gnutls
+ */
+#if HTTPS_SUPPORT
+ ssl_ver = strchr (curl_ver, ' ');
+ if (ssl_ver == NULL)
+ return -1;
+ ssl_ver++;
+ if (strncmp ("GnuTLS", ssl_ver, strlen ("GNUtls")) == 0)
+ {
+ ssl_ver = strchr (ssl_ver, '/');
+ req_ssl_ver = MHD_REQ_CURL_GNUTLS_VERSION;
+ }
+ else if (strncmp ("OpenSSL", ssl_ver, strlen ("OpenSSL")) == 0)
+ {
+ ssl_ver = strchr (ssl_ver, '/');
+ req_ssl_ver = MHD_REQ_CURL_OPENSSL_VERSION;
+ }
+ else if (strncmp ("NSS", ssl_ver, strlen ("NSS")) == 0)
+ {
+ ssl_ver = strchr (ssl_ver, '/');
+ req_ssl_ver = MHD_REQ_CURL_NSS_VERSION;
+ }
+ else
+ {
+ fprintf (stderr, "Error: unrecognized curl ssl library\n");
+ return -1;
+ }
+ if (ssl_ver == NULL)
+ return -1;
+ ssl_ver++;
+ if ( (NULL == parse_version_string (req_ssl_ver, &rq_major, &rq_minor, &rq_micro)) ||
+ (NULL == parse_version_string (ssl_ver, &loc_major, &loc_minor, &loc_micro)) )
+ return -1;
+
+ if ((loc_major > rq_major
+ || (loc_major == rq_major && loc_minor > rq_minor)
+ || (loc_major == rq_major && loc_minor == rq_minor
+ && loc_micro > rq_micro) || (loc_major == rq_major
+ && loc_minor == rq_minor
+ && loc_micro == rq_micro)) == 0)
+ {
+ fprintf (stderr,
+ "Error: running curl test depends on local libcurl SSL version > %s\n",
+ req_ssl_ver);
+ return -1;
+ }
+#endif
+ return 0;
+}
diff --git a/src/testcurl/gauger.h b/src/testcurl/gauger.h
new file mode 100644
index 0000000..3a0dd22
--- /dev/null
+++ b/src/testcurl/gauger.h
@@ -0,0 +1,86 @@
+/** ---------------------------------------------------------------------------
+ * This software is in the public domain, furnished "as is", without technical
+ * support, and with no warranty, express or implied, as to its usefulness for
+ * any purpose.
+ *
+ * gauger.h
+ * Interface for C programs to log remotely to a gauger server
+ *
+ * Author: Bartlomiej Polot
+ * -------------------------------------------------------------------------*/
+#ifndef __GAUGER_H__
+#define __GAUGER_H__
+
+#ifndef WINDOWS
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+#define GAUGER(category, counter, value, unit)\
+{\
+ const char * __gauger_v[10]; \
+ char __gauger_s[32];\
+ pid_t __gauger_p;\
+ if(!(__gauger_p=fork())){\
+ if(!fork()){ \
+ sprintf(__gauger_s,"%Lf", (long double) (value));\
+ __gauger_v[0] = "gauger";\
+ __gauger_v[1] = "-n";\
+ __gauger_v[2] = counter; \
+ __gauger_v[3] = "-d";\
+ __gauger_v[4] = __gauger_s;\
+ __gauger_v[5] = "-u";\
+ __gauger_v[6] = unit; \
+ __gauger_v[7] = "-c";\
+ __gauger_v[8] = category; \
+ __gauger_v[9] = (char *)NULL;\
+ execvp("gauger", (char*const*) __gauger_v); \
+ _exit(1);\
+ }else{\
+ _exit(0);\
+ }\
+ }else{\
+ waitpid(__gauger_p,NULL,0);\
+ }\
+}
+
+#define GAUGER_ID(category, counter, value, unit, id)\
+{\
+ char* __gauger_v[12];\
+ char __gauger_s[32];\
+ pid_t __gauger_p;\
+ if(!(__gauger_p=fork())){\
+ if(!fork()){\
+ sprintf(__gauger_s,"%Lf", (long double) (value));\
+ __gauger_v[0] = "gauger";\
+ __gauger_v[1] = "-n";\
+ __gauger_v[2] = counter;\
+ __gauger_v[3] = "-d";\
+ __gauger_v[4] = __gauger_s;\
+ __gauger_v[5] = "-u";\
+ __gauger_v[6] = unit;\
+ __gauger_v[7] = "-i";\
+ __gauger_v[8] = id;\
+ __gauger_v[9] = "-c";\
+ __gauger_v[10] = category;\
+ __gauger_v[11] = (char *)NULL;\
+ execvp("gauger",__gauger_v);\
+ perror("gauger");\
+ _exit(1);\
+ }else{\
+ _exit(0);\
+ }\
+ }else{\
+ waitpid(__gauger_p,NULL,0);\
+ }\
+}
+
+#else
+
+#define GAUGER_ID(category, counter, value, unit, id) {}
+#define GAUGER(category, counter, value, unit) {}
+
+#endif // WINDOWS
+
+#endif
diff --git a/src/testcurl/https/Makefile.am b/src/testcurl/https/Makefile.am
new file mode 100644
index 0000000..47deb70
--- /dev/null
+++ b/src/testcurl/https/Makefile.am
@@ -0,0 +1,153 @@
+# This Makefile.am is in the public domain
+SUBDIRS = .
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage
+endif
+
+if HAVE_GNUTLS_SNI
+ TEST_HTTPS_SNI = test_https_sni
+endif
+
+if HAVE_POSIX_THREADS
+ HTTPS_PARALLEL_TESTS = test_https_get_parallel \
+ test_https_get_parallel_threads
+endif
+
+CPU_COUNT_DEF = -DCPU_COUNT=$(CPU_COUNT)
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/microhttpd \
+ -I$(top_srcdir)/src/platform \
+ $(LIBCURL_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+
+check_PROGRAMS = \
+ test_tls_options \
+ test_tls_authentication \
+ test_https_multi_daemon \
+ test_https_get \
+ $(TEST_HTTPS_SNI) \
+ test_https_get_select \
+ $(HTTPS_PARALLEL_TESTS) \
+ test_https_session_info \
+ test_https_time_out \
+ test_empty_response
+
+EXTRA_DIST = cert.pem key.pem tls_test_keys.h tls_test_common.h \
+ host1.crt host1.key host2.crt host2.key
+
+TESTS = \
+ test_tls_options \
+ test_https_multi_daemon \
+ test_https_get \
+ $(TEST_HTTPS_SNI) \
+ test_https_get_select \
+ $(HTTPS_PARALLEL_TESTS) \
+ test_https_session_info \
+ test_https_time_out \
+ test_tls_authentication \
+ test_empty_response
+
+
+test_https_time_out_SOURCES = \
+ test_https_time_out.c \
+ tls_test_common.c
+test_https_time_out_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_tls_options_SOURCES = \
+ test_tls_options.c \
+ tls_test_common.c
+test_tls_options_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_get_parallel_SOURCES = \
+ test_https_get_parallel.c \
+ tls_test_common.c
+test_https_get_parallel_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+test_https_get_parallel_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+test_https_get_parallel_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_empty_response_SOURCES = \
+ test_empty_response.c \
+ tls_test_common.c
+test_empty_response_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_get_parallel_threads_SOURCES = \
+ test_https_get_parallel_threads.c \
+ tls_test_common.c
+test_https_get_parallel_threads_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+test_https_get_parallel_threads_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+test_https_get_parallel_threads_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_tls_authentication_SOURCES = \
+ test_tls_authentication.c \
+ tls_test_common.c
+test_tls_authentication_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_session_info_SOURCES = \
+ test_https_session_info.c \
+ tls_test_common.c
+test_https_session_info_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_multi_daemon_SOURCES = \
+ test_https_multi_daemon.c \
+ tls_test_common.c
+test_https_multi_daemon_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_get_SOURCES = \
+ test_https_get.c \
+ tls_test_common.c
+test_https_get_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+if HAVE_GNUTLS_SNI
+test_https_sni_SOURCES = \
+ test_https_sni.c \
+ tls_test_common.c
+test_https_sni_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DABS_SRCDIR=\"$(abs_srcdir)\"
+test_https_sni_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+endif
+
+test_https_get_select_SOURCES = \
+ test_https_get_select.c \
+ tls_test_common.c
+test_https_get_select_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
diff --git a/src/testcurl/https/Makefile.in b/src/testcurl/https/Makefile.in
new file mode 100644
index 0000000..8815b21
--- /dev/null
+++ b/src/testcurl/https/Makefile.in
@@ -0,0 +1,1578 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+check_PROGRAMS = test_tls_options$(EXEEXT) \
+ test_tls_authentication$(EXEEXT) \
+ test_https_multi_daemon$(EXEEXT) test_https_get$(EXEEXT) \
+ $(am__EXEEXT_1) test_https_get_select$(EXEEXT) $(am__EXEEXT_2) \
+ test_https_session_info$(EXEEXT) test_https_time_out$(EXEEXT) \
+ test_empty_response$(EXEEXT)
+TESTS = test_tls_options$(EXEEXT) test_https_multi_daemon$(EXEEXT) \
+ test_https_get$(EXEEXT) $(am__EXEEXT_1) \
+ test_https_get_select$(EXEEXT) $(am__EXEEXT_2) \
+ test_https_session_info$(EXEEXT) test_https_time_out$(EXEEXT) \
+ test_tls_authentication$(EXEEXT) test_empty_response$(EXEEXT)
+subdir = src/testcurl/https
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp $(top_srcdir)/test-driver
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GNUTLS_SNI_TRUE@am__EXEEXT_1 = test_https_sni$(EXEEXT)
+@HAVE_POSIX_THREADS_TRUE@am__EXEEXT_2 = \
+@HAVE_POSIX_THREADS_TRUE@ test_https_get_parallel$(EXEEXT) \
+@HAVE_POSIX_THREADS_TRUE@ test_https_get_parallel_threads$(EXEEXT)
+am_test_empty_response_OBJECTS = test_empty_response.$(OBJEXT) \
+ tls_test_common.$(OBJEXT)
+test_empty_response_OBJECTS = $(am_test_empty_response_OBJECTS)
+am__DEPENDENCIES_1 =
+test_empty_response_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_test_https_get_OBJECTS = test_https_get.$(OBJEXT) \
+ tls_test_common.$(OBJEXT)
+test_https_get_OBJECTS = $(am_test_https_get_OBJECTS)
+test_https_get_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_test_https_get_parallel_OBJECTS = \
+ test_https_get_parallel-test_https_get_parallel.$(OBJEXT) \
+ test_https_get_parallel-tls_test_common.$(OBJEXT)
+test_https_get_parallel_OBJECTS = \
+ $(am_test_https_get_parallel_OBJECTS)
+test_https_get_parallel_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+test_https_get_parallel_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(test_https_get_parallel_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_test_https_get_parallel_threads_OBJECTS = test_https_get_parallel_threads-test_https_get_parallel_threads.$(OBJEXT) \
+ test_https_get_parallel_threads-tls_test_common.$(OBJEXT)
+test_https_get_parallel_threads_OBJECTS = \
+ $(am_test_https_get_parallel_threads_OBJECTS)
+test_https_get_parallel_threads_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+test_https_get_parallel_threads_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+am_test_https_get_select_OBJECTS = test_https_get_select.$(OBJEXT) \
+ tls_test_common.$(OBJEXT)
+test_https_get_select_OBJECTS = $(am_test_https_get_select_OBJECTS)
+test_https_get_select_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_test_https_multi_daemon_OBJECTS = \
+ test_https_multi_daemon.$(OBJEXT) tls_test_common.$(OBJEXT)
+test_https_multi_daemon_OBJECTS = \
+ $(am_test_https_multi_daemon_OBJECTS)
+test_https_multi_daemon_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_test_https_session_info_OBJECTS = \
+ test_https_session_info.$(OBJEXT) tls_test_common.$(OBJEXT)
+test_https_session_info_OBJECTS = \
+ $(am_test_https_session_info_OBJECTS)
+test_https_session_info_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am__test_https_sni_SOURCES_DIST = test_https_sni.c tls_test_common.c
+@HAVE_GNUTLS_SNI_TRUE@am_test_https_sni_OBJECTS = \
+@HAVE_GNUTLS_SNI_TRUE@ test_https_sni-test_https_sni.$(OBJEXT) \
+@HAVE_GNUTLS_SNI_TRUE@ test_https_sni-tls_test_common.$(OBJEXT)
+test_https_sni_OBJECTS = $(am_test_https_sni_OBJECTS)
+@HAVE_GNUTLS_SNI_TRUE@test_https_sni_DEPENDENCIES = $(top_builddir)/src/testcurl/libcurl_version_check.a \
+@HAVE_GNUTLS_SNI_TRUE@ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+@HAVE_GNUTLS_SNI_TRUE@ $(am__DEPENDENCIES_1) \
+@HAVE_GNUTLS_SNI_TRUE@ $(am__DEPENDENCIES_1)
+am_test_https_time_out_OBJECTS = test_https_time_out.$(OBJEXT) \
+ tls_test_common.$(OBJEXT)
+test_https_time_out_OBJECTS = $(am_test_https_time_out_OBJECTS)
+test_https_time_out_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_test_tls_authentication_OBJECTS = \
+ test_tls_authentication.$(OBJEXT) tls_test_common.$(OBJEXT)
+test_tls_authentication_OBJECTS = \
+ $(am_test_tls_authentication_OBJECTS)
+test_tls_authentication_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_test_tls_options_OBJECTS = test_tls_options.$(OBJEXT) \
+ tls_test_common.$(OBJEXT)
+test_tls_options_OBJECTS = $(am_test_tls_options_OBJECTS)
+test_tls_options_DEPENDENCIES = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(test_empty_response_SOURCES) $(test_https_get_SOURCES) \
+ $(test_https_get_parallel_SOURCES) \
+ $(test_https_get_parallel_threads_SOURCES) \
+ $(test_https_get_select_SOURCES) \
+ $(test_https_multi_daemon_SOURCES) \
+ $(test_https_session_info_SOURCES) $(test_https_sni_SOURCES) \
+ $(test_https_time_out_SOURCES) \
+ $(test_tls_authentication_SOURCES) $(test_tls_options_SOURCES)
+DIST_SOURCES = $(test_empty_response_SOURCES) \
+ $(test_https_get_SOURCES) $(test_https_get_parallel_SOURCES) \
+ $(test_https_get_parallel_threads_SOURCES) \
+ $(test_https_get_select_SOURCES) \
+ $(test_https_multi_daemon_SOURCES) \
+ $(test_https_session_info_SOURCES) \
+ $(am__test_https_sni_SOURCES_DIST) \
+ $(test_https_time_out_SOURCES) \
+ $(test_tls_authentication_SOURCES) $(test_tls_options_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ check recheck distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red='[0;31m'; \
+ grn='[0;32m'; \
+ lgn='[1;32m'; \
+ blu='[1;34m'; \
+ mgn='[0;35m'; \
+ brg='[1m'; \
+ std='[m'; \
+ fi; \
+}
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+SUBDIRS = .
+@USE_COVERAGE_TRUE@AM_CFLAGS = --coverage
+@HAVE_GNUTLS_SNI_TRUE@TEST_HTTPS_SNI = test_https_sni
+@HAVE_POSIX_THREADS_TRUE@HTTPS_PARALLEL_TESTS = test_https_get_parallel \
+@HAVE_POSIX_THREADS_TRUE@ test_https_get_parallel_threads
+
+CPU_COUNT_DEF = -DCPU_COUNT=$(CPU_COUNT)
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/microhttpd \
+ -I$(top_srcdir)/src/platform \
+ $(LIBCURL_CPPFLAGS) $(GNUTLS_CPPFLAGS)
+
+EXTRA_DIST = cert.pem key.pem tls_test_keys.h tls_test_common.h \
+ host1.crt host1.key host2.crt host2.key
+
+test_https_time_out_SOURCES = \
+ test_https_time_out.c \
+ tls_test_common.c
+
+test_https_time_out_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_tls_options_SOURCES = \
+ test_tls_options.c \
+ tls_test_common.c
+
+test_tls_options_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_get_parallel_SOURCES = \
+ test_https_get_parallel.c \
+ tls_test_common.c
+
+test_https_get_parallel_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+
+test_https_get_parallel_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+
+test_https_get_parallel_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_empty_response_SOURCES = \
+ test_empty_response.c \
+ tls_test_common.c
+
+test_empty_response_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_get_parallel_threads_SOURCES = \
+ test_https_get_parallel_threads.c \
+ tls_test_common.c
+
+test_https_get_parallel_threads_CPPFLAGS = \
+ $(AM_CPPFLAGS) $(CPU_COUNT_DEF)
+
+test_https_get_parallel_threads_CFLAGS = \
+ $(PTHREAD_CFLAGS) $(AM_CFLAGS)
+
+test_https_get_parallel_threads_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(PTHREAD_LIBS) $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_tls_authentication_SOURCES = \
+ test_tls_authentication.c \
+ tls_test_common.c
+
+test_tls_authentication_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_session_info_SOURCES = \
+ test_https_session_info.c \
+ tls_test_common.c
+
+test_https_session_info_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_multi_daemon_SOURCES = \
+ test_https_multi_daemon.c \
+ tls_test_common.c
+
+test_https_multi_daemon_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_get_SOURCES = \
+ test_https_get.c \
+ tls_test_common.c
+
+test_https_get_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+@HAVE_GNUTLS_SNI_TRUE@test_https_sni_SOURCES = \
+@HAVE_GNUTLS_SNI_TRUE@ test_https_sni.c \
+@HAVE_GNUTLS_SNI_TRUE@ tls_test_common.c
+
+@HAVE_GNUTLS_SNI_TRUE@test_https_sni_CPPFLAGS = \
+@HAVE_GNUTLS_SNI_TRUE@ $(AM_CPPFLAGS) \
+@HAVE_GNUTLS_SNI_TRUE@ -DABS_SRCDIR=\"$(abs_srcdir)\"
+
+@HAVE_GNUTLS_SNI_TRUE@test_https_sni_LDADD = \
+@HAVE_GNUTLS_SNI_TRUE@ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+@HAVE_GNUTLS_SNI_TRUE@ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+@HAVE_GNUTLS_SNI_TRUE@ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+test_https_get_select_SOURCES = \
+ test_https_get_select.c \
+ tls_test_common.c
+
+test_https_get_select_LDADD = \
+ $(top_builddir)/src/testcurl/libcurl_version_check.a \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ $(GNUTLS_LDFLAGS) $(GNUTLS_LIBS) @LIBGCRYPT_LIBS@ @LIBCURL@
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/testcurl/https/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/testcurl/https/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-checkPROGRAMS:
+ @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+test_empty_response$(EXEEXT): $(test_empty_response_OBJECTS) $(test_empty_response_DEPENDENCIES) $(EXTRA_test_empty_response_DEPENDENCIES)
+ @rm -f test_empty_response$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_empty_response_OBJECTS) $(test_empty_response_LDADD) $(LIBS)
+
+test_https_get$(EXEEXT): $(test_https_get_OBJECTS) $(test_https_get_DEPENDENCIES) $(EXTRA_test_https_get_DEPENDENCIES)
+ @rm -f test_https_get$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_https_get_OBJECTS) $(test_https_get_LDADD) $(LIBS)
+
+test_https_get_parallel$(EXEEXT): $(test_https_get_parallel_OBJECTS) $(test_https_get_parallel_DEPENDENCIES) $(EXTRA_test_https_get_parallel_DEPENDENCIES)
+ @rm -f test_https_get_parallel$(EXEEXT)
+ $(AM_V_CCLD)$(test_https_get_parallel_LINK) $(test_https_get_parallel_OBJECTS) $(test_https_get_parallel_LDADD) $(LIBS)
+
+test_https_get_parallel_threads$(EXEEXT): $(test_https_get_parallel_threads_OBJECTS) $(test_https_get_parallel_threads_DEPENDENCIES) $(EXTRA_test_https_get_parallel_threads_DEPENDENCIES)
+ @rm -f test_https_get_parallel_threads$(EXEEXT)
+ $(AM_V_CCLD)$(test_https_get_parallel_threads_LINK) $(test_https_get_parallel_threads_OBJECTS) $(test_https_get_parallel_threads_LDADD) $(LIBS)
+
+test_https_get_select$(EXEEXT): $(test_https_get_select_OBJECTS) $(test_https_get_select_DEPENDENCIES) $(EXTRA_test_https_get_select_DEPENDENCIES)
+ @rm -f test_https_get_select$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_https_get_select_OBJECTS) $(test_https_get_select_LDADD) $(LIBS)
+
+test_https_multi_daemon$(EXEEXT): $(test_https_multi_daemon_OBJECTS) $(test_https_multi_daemon_DEPENDENCIES) $(EXTRA_test_https_multi_daemon_DEPENDENCIES)
+ @rm -f test_https_multi_daemon$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_https_multi_daemon_OBJECTS) $(test_https_multi_daemon_LDADD) $(LIBS)
+
+test_https_session_info$(EXEEXT): $(test_https_session_info_OBJECTS) $(test_https_session_info_DEPENDENCIES) $(EXTRA_test_https_session_info_DEPENDENCIES)
+ @rm -f test_https_session_info$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_https_session_info_OBJECTS) $(test_https_session_info_LDADD) $(LIBS)
+
+test_https_sni$(EXEEXT): $(test_https_sni_OBJECTS) $(test_https_sni_DEPENDENCIES) $(EXTRA_test_https_sni_DEPENDENCIES)
+ @rm -f test_https_sni$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_https_sni_OBJECTS) $(test_https_sni_LDADD) $(LIBS)
+
+test_https_time_out$(EXEEXT): $(test_https_time_out_OBJECTS) $(test_https_time_out_DEPENDENCIES) $(EXTRA_test_https_time_out_DEPENDENCIES)
+ @rm -f test_https_time_out$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_https_time_out_OBJECTS) $(test_https_time_out_LDADD) $(LIBS)
+
+test_tls_authentication$(EXEEXT): $(test_tls_authentication_OBJECTS) $(test_tls_authentication_DEPENDENCIES) $(EXTRA_test_tls_authentication_DEPENDENCIES)
+ @rm -f test_tls_authentication$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_tls_authentication_OBJECTS) $(test_tls_authentication_LDADD) $(LIBS)
+
+test_tls_options$(EXEEXT): $(test_tls_options_OBJECTS) $(test_tls_options_DEPENDENCIES) $(EXTRA_test_tls_options_DEPENDENCIES)
+ @rm -f test_tls_options$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_tls_options_OBJECTS) $(test_tls_options_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_empty_response.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_get.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_get_parallel-test_https_get_parallel.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_get_parallel-tls_test_common.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_get_parallel_threads-test_https_get_parallel_threads.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_get_parallel_threads-tls_test_common.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_get_select.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_multi_daemon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_session_info.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_sni-test_https_sni.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_sni-tls_test_common.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_https_time_out.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_tls_authentication.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_tls_options.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tls_test_common.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+test_https_get_parallel-test_https_get_parallel.o: test_https_get_parallel.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_CFLAGS) $(CFLAGS) -MT test_https_get_parallel-test_https_get_parallel.o -MD -MP -MF $(DEPDIR)/test_https_get_parallel-test_https_get_parallel.Tpo -c -o test_https_get_parallel-test_https_get_parallel.o `test -f 'test_https_get_parallel.c' || echo '$(srcdir)/'`test_https_get_parallel.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_get_parallel-test_https_get_parallel.Tpo $(DEPDIR)/test_https_get_parallel-test_https_get_parallel.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_https_get_parallel.c' object='test_https_get_parallel-test_https_get_parallel.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_CFLAGS) $(CFLAGS) -c -o test_https_get_parallel-test_https_get_parallel.o `test -f 'test_https_get_parallel.c' || echo '$(srcdir)/'`test_https_get_parallel.c
+
+test_https_get_parallel-test_https_get_parallel.obj: test_https_get_parallel.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_CFLAGS) $(CFLAGS) -MT test_https_get_parallel-test_https_get_parallel.obj -MD -MP -MF $(DEPDIR)/test_https_get_parallel-test_https_get_parallel.Tpo -c -o test_https_get_parallel-test_https_get_parallel.obj `if test -f 'test_https_get_parallel.c'; then $(CYGPATH_W) 'test_https_get_parallel.c'; else $(CYGPATH_W) '$(srcdir)/test_https_get_parallel.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_get_parallel-test_https_get_parallel.Tpo $(DEPDIR)/test_https_get_parallel-test_https_get_parallel.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_https_get_parallel.c' object='test_https_get_parallel-test_https_get_parallel.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_CFLAGS) $(CFLAGS) -c -o test_https_get_parallel-test_https_get_parallel.obj `if test -f 'test_https_get_parallel.c'; then $(CYGPATH_W) 'test_https_get_parallel.c'; else $(CYGPATH_W) '$(srcdir)/test_https_get_parallel.c'; fi`
+
+test_https_get_parallel-tls_test_common.o: tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_CFLAGS) $(CFLAGS) -MT test_https_get_parallel-tls_test_common.o -MD -MP -MF $(DEPDIR)/test_https_get_parallel-tls_test_common.Tpo -c -o test_https_get_parallel-tls_test_common.o `test -f 'tls_test_common.c' || echo '$(srcdir)/'`tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_get_parallel-tls_test_common.Tpo $(DEPDIR)/test_https_get_parallel-tls_test_common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tls_test_common.c' object='test_https_get_parallel-tls_test_common.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_CFLAGS) $(CFLAGS) -c -o test_https_get_parallel-tls_test_common.o `test -f 'tls_test_common.c' || echo '$(srcdir)/'`tls_test_common.c
+
+test_https_get_parallel-tls_test_common.obj: tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_CFLAGS) $(CFLAGS) -MT test_https_get_parallel-tls_test_common.obj -MD -MP -MF $(DEPDIR)/test_https_get_parallel-tls_test_common.Tpo -c -o test_https_get_parallel-tls_test_common.obj `if test -f 'tls_test_common.c'; then $(CYGPATH_W) 'tls_test_common.c'; else $(CYGPATH_W) '$(srcdir)/tls_test_common.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_get_parallel-tls_test_common.Tpo $(DEPDIR)/test_https_get_parallel-tls_test_common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tls_test_common.c' object='test_https_get_parallel-tls_test_common.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_CFLAGS) $(CFLAGS) -c -o test_https_get_parallel-tls_test_common.obj `if test -f 'tls_test_common.c'; then $(CYGPATH_W) 'tls_test_common.c'; else $(CYGPATH_W) '$(srcdir)/tls_test_common.c'; fi`
+
+test_https_get_parallel_threads-test_https_get_parallel_threads.o: test_https_get_parallel_threads.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_threads_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) -MT test_https_get_parallel_threads-test_https_get_parallel_threads.o -MD -MP -MF $(DEPDIR)/test_https_get_parallel_threads-test_https_get_parallel_threads.Tpo -c -o test_https_get_parallel_threads-test_https_get_parallel_threads.o `test -f 'test_https_get_parallel_threads.c' || echo '$(srcdir)/'`test_https_get_parallel_threads.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_get_parallel_threads-test_https_get_parallel_threads.Tpo $(DEPDIR)/test_https_get_parallel_threads-test_https_get_parallel_threads.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_https_get_parallel_threads.c' object='test_https_get_parallel_threads-test_https_get_parallel_threads.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_threads_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) -c -o test_https_get_parallel_threads-test_https_get_parallel_threads.o `test -f 'test_https_get_parallel_threads.c' || echo '$(srcdir)/'`test_https_get_parallel_threads.c
+
+test_https_get_parallel_threads-test_https_get_parallel_threads.obj: test_https_get_parallel_threads.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_threads_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) -MT test_https_get_parallel_threads-test_https_get_parallel_threads.obj -MD -MP -MF $(DEPDIR)/test_https_get_parallel_threads-test_https_get_parallel_threads.Tpo -c -o test_https_get_parallel_threads-test_https_get_parallel_threads.obj `if test -f 'test_https_get_parallel_threads.c'; then $(CYGPATH_W) 'test_https_get_parallel_threads.c'; else $(CYGPATH_W) '$(srcdir)/test_https_get_parallel_threads.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_get_parallel_threads-test_https_get_parallel_threads.Tpo $(DEPDIR)/test_https_get_parallel_threads-test_https_get_parallel_threads.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_https_get_parallel_threads.c' object='test_https_get_parallel_threads-test_https_get_parallel_threads.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_threads_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) -c -o test_https_get_parallel_threads-test_https_get_parallel_threads.obj `if test -f 'test_https_get_parallel_threads.c'; then $(CYGPATH_W) 'test_https_get_parallel_threads.c'; else $(CYGPATH_W) '$(srcdir)/test_https_get_parallel_threads.c'; fi`
+
+test_https_get_parallel_threads-tls_test_common.o: tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_threads_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) -MT test_https_get_parallel_threads-tls_test_common.o -MD -MP -MF $(DEPDIR)/test_https_get_parallel_threads-tls_test_common.Tpo -c -o test_https_get_parallel_threads-tls_test_common.o `test -f 'tls_test_common.c' || echo '$(srcdir)/'`tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_get_parallel_threads-tls_test_common.Tpo $(DEPDIR)/test_https_get_parallel_threads-tls_test_common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tls_test_common.c' object='test_https_get_parallel_threads-tls_test_common.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_threads_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) -c -o test_https_get_parallel_threads-tls_test_common.o `test -f 'tls_test_common.c' || echo '$(srcdir)/'`tls_test_common.c
+
+test_https_get_parallel_threads-tls_test_common.obj: tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_threads_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) -MT test_https_get_parallel_threads-tls_test_common.obj -MD -MP -MF $(DEPDIR)/test_https_get_parallel_threads-tls_test_common.Tpo -c -o test_https_get_parallel_threads-tls_test_common.obj `if test -f 'tls_test_common.c'; then $(CYGPATH_W) 'tls_test_common.c'; else $(CYGPATH_W) '$(srcdir)/tls_test_common.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_get_parallel_threads-tls_test_common.Tpo $(DEPDIR)/test_https_get_parallel_threads-tls_test_common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tls_test_common.c' object='test_https_get_parallel_threads-tls_test_common.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_get_parallel_threads_CPPFLAGS) $(CPPFLAGS) $(test_https_get_parallel_threads_CFLAGS) $(CFLAGS) -c -o test_https_get_parallel_threads-tls_test_common.obj `if test -f 'tls_test_common.c'; then $(CYGPATH_W) 'tls_test_common.c'; else $(CYGPATH_W) '$(srcdir)/tls_test_common.c'; fi`
+
+test_https_sni-test_https_sni.o: test_https_sni.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_sni_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_https_sni-test_https_sni.o -MD -MP -MF $(DEPDIR)/test_https_sni-test_https_sni.Tpo -c -o test_https_sni-test_https_sni.o `test -f 'test_https_sni.c' || echo '$(srcdir)/'`test_https_sni.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_sni-test_https_sni.Tpo $(DEPDIR)/test_https_sni-test_https_sni.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_https_sni.c' object='test_https_sni-test_https_sni.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_sni_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_https_sni-test_https_sni.o `test -f 'test_https_sni.c' || echo '$(srcdir)/'`test_https_sni.c
+
+test_https_sni-test_https_sni.obj: test_https_sni.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_sni_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_https_sni-test_https_sni.obj -MD -MP -MF $(DEPDIR)/test_https_sni-test_https_sni.Tpo -c -o test_https_sni-test_https_sni.obj `if test -f 'test_https_sni.c'; then $(CYGPATH_W) 'test_https_sni.c'; else $(CYGPATH_W) '$(srcdir)/test_https_sni.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_sni-test_https_sni.Tpo $(DEPDIR)/test_https_sni-test_https_sni.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test_https_sni.c' object='test_https_sni-test_https_sni.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_sni_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_https_sni-test_https_sni.obj `if test -f 'test_https_sni.c'; then $(CYGPATH_W) 'test_https_sni.c'; else $(CYGPATH_W) '$(srcdir)/test_https_sni.c'; fi`
+
+test_https_sni-tls_test_common.o: tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_sni_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_https_sni-tls_test_common.o -MD -MP -MF $(DEPDIR)/test_https_sni-tls_test_common.Tpo -c -o test_https_sni-tls_test_common.o `test -f 'tls_test_common.c' || echo '$(srcdir)/'`tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_sni-tls_test_common.Tpo $(DEPDIR)/test_https_sni-tls_test_common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tls_test_common.c' object='test_https_sni-tls_test_common.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_sni_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_https_sni-tls_test_common.o `test -f 'tls_test_common.c' || echo '$(srcdir)/'`tls_test_common.c
+
+test_https_sni-tls_test_common.obj: tls_test_common.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_sni_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_https_sni-tls_test_common.obj -MD -MP -MF $(DEPDIR)/test_https_sni-tls_test_common.Tpo -c -o test_https_sni-tls_test_common.obj `if test -f 'tls_test_common.c'; then $(CYGPATH_W) 'tls_test_common.c'; else $(CYGPATH_W) '$(srcdir)/tls_test_common.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_https_sni-tls_test_common.Tpo $(DEPDIR)/test_https_sni-tls_test_common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tls_test_common.c' object='test_https_sni-tls_test_common.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_https_sni_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_https_sni-tls_test_common.obj `if test -f 'tls_test_common.c'; then $(CYGPATH_W) 'tls_test_common.c'; else $(CYGPATH_W) '$(srcdir)/tls_test_common.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ else \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all $(check_PROGRAMS)
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+test_tls_options.log: test_tls_options$(EXEEXT)
+ @p='test_tls_options$(EXEEXT)'; \
+ b='test_tls_options'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_https_multi_daemon.log: test_https_multi_daemon$(EXEEXT)
+ @p='test_https_multi_daemon$(EXEEXT)'; \
+ b='test_https_multi_daemon'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_https_get.log: test_https_get$(EXEEXT)
+ @p='test_https_get$(EXEEXT)'; \
+ b='test_https_get'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_https_sni.log: test_https_sni$(EXEEXT)
+ @p='test_https_sni$(EXEEXT)'; \
+ b='test_https_sni'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_https_get_select.log: test_https_get_select$(EXEEXT)
+ @p='test_https_get_select$(EXEEXT)'; \
+ b='test_https_get_select'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_https_get_parallel.log: test_https_get_parallel$(EXEEXT)
+ @p='test_https_get_parallel$(EXEEXT)'; \
+ b='test_https_get_parallel'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_https_get_parallel_threads.log: test_https_get_parallel_threads$(EXEEXT)
+ @p='test_https_get_parallel_threads$(EXEEXT)'; \
+ b='test_https_get_parallel_threads'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_https_session_info.log: test_https_session_info$(EXEEXT)
+ @p='test_https_session_info$(EXEEXT)'; \
+ b='test_https_session_info'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_https_time_out.log: test_https_time_out$(EXEEXT)
+ @p='test_https_time_out$(EXEEXT)'; \
+ b='test_https_time_out'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_tls_authentication.log: test_tls_authentication$(EXEEXT)
+ @p='test_tls_authentication$(EXEEXT)'; \
+ b='test_tls_authentication'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_empty_response.log: test_empty_response$(EXEEXT)
+ @p='test_empty_response$(EXEEXT)'; \
+ b='test_empty_response'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-TESTS check-am clean clean-checkPROGRAMS clean-generic \
+ clean-libtool cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am recheck tags tags-am \
+ uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/testcurl/https/cert.pem b/src/testcurl/https/cert.pem
new file mode 100644
index 0000000..2c766df
--- /dev/null
+++ b/src/testcurl/https/cert.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICpjCCAZCgAwIBAgIESEPtjjALBgkqhkiG9w0BAQUwADAeFw0wODA2MDIxMjU0
+MzhaFw0wOTA2MDIxMjU0NDZaMAAwggEfMAsGCSqGSIb3DQEBAQOCAQ4AMIIBCQKC
+AQC03TyUvK5HmUAirRp067taIEO4bibh5nqolUoUdo/LeblMQV+qnrv/RNAMTx5X
+fNLZ45/kbM9geF8qY0vsPyQvP4jumzK0LOJYuIwmHaUm9vbXnYieILiwCuTgjaud
+3VkZDoQ9fteIo+6we9UTpVqZpxpbLulBMh/VsvX0cPJ1VFC7rT59o9hAUlFf9jX/
+GmKdYI79MtgVx0OPBjmmSD6kicBBfmfgkO7bIGwlRtsIyMznxbHu6VuoX/eVxrTv
+rmCwgEXLWRZ6ru8MQl5YfqeGXXRVwMeXU961KefbuvmEPccgCxm8FZ1C1cnDHFXh
+siSgAzMBjC/b6KVhNQ4KnUdZAgMBAAGjLzAtMAwGA1UdEwEB/wQCMAAwHQYDVR0O
+BBYEFJcUvpjvE5fF/yzUshkWDpdYiQh/MAsGCSqGSIb3DQEBBQOCAQEARP7eKSB2
+RNd6XjEjK0SrxtoTnxS3nw9sfcS7/qD1+XHdObtDFqGNSjGYFB3Gpx8fpQhCXdoN
+8QUs3/5ZVa5yjZMQewWBgz8kNbnbH40F2y81MHITxxCe1Y+qqHWwVaYLsiOTqj2/
+0S3QjEJ9tvklmg7JX09HC4m5QRYfWBeQLD1u8ZjA1Sf1xJriomFVyRLI2VPO2bNe
+JDMXWuP+8kMC7gEvUnJ7A92Y2yrhu3QI3bjPk8uSpHea19Q77tul1UVBJ5g+zpH3
+OsF5p0MyaVf09GTzcLds5nE/osTdXGUyHJapWReVmPm3Zn6gqYlnzD99z+DPIgIV
+RhZvQx74NQnS6g==
+-----END CERTIFICATE-----
diff --git a/src/testcurl/https/host1.crt b/src/testcurl/https/host1.crt
new file mode 100644
index 0000000..d9b8b99
--- /dev/null
+++ b/src/testcurl/https/host1.crt
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICWTCCAcICCQDc4McLp7j56DANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa
+WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MTEZMBcGCSqGSIb3DQEJARYKdGVzdEBo
+b3N0MTAgFw0xMzExMTcxNTE2MzdaGA8yMTEzMTAyNDE1MTYzN1owcDELMAkGA1UE
+BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
+ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDExGTAXBgkqhkiG9w0BCQEWCnRl
+c3RAaG9zdDEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKxYiRUzfQnekQn3
+6e+hP/mt/JEkiFzX5TV+E19ue2v4tc7lf+SoLEk2dVt5tGQkHjIGeFFNwCLrgXoi
+h3KfP4R1IYe7NFbM+lFVwPceF3inJ75dZD80BxaXQANeh0yC/DhaVJUFNaof2S4+
+7xd8zTL6M11gME+XmR8uaDvW7EBtAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAf62m
+Nstj9p9u8T5A5fRnJWfoglH/zfm7IHzht0Wi047O3NFZJ0pOPqV97HuErUA5oBGg
+qswnyRGyGMcvL08Bki7Q6NkY7K0ON3lq+ofTkIAHlOKMF+Y/otbjuIDHBfo63tmE
+uOcr8XDQGu9R0cfh+qLgicJQd/8cFBhxsL0ls6I=
+-----END CERTIFICATE-----
diff --git a/src/testcurl/https/host1.key b/src/testcurl/https/host1.key
new file mode 100644
index 0000000..f549a4d
--- /dev/null
+++ b/src/testcurl/https/host1.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQCsWIkVM30J3pEJ9+nvoT/5rfyRJIhc1+U1fhNfbntr+LXO5X/k
+qCxJNnVbebRkJB4yBnhRTcAi64F6Iodynz+EdSGHuzRWzPpRVcD3Hhd4pye+XWQ/
+NAcWl0ADXodMgvw4WlSVBTWqH9kuPu8XfM0y+jNdYDBPl5kfLmg71uxAbQIDAQAB
+AoGBAJvq9QmjLSnymtCj4pYSEai2iNpebKdiAlEkoC4j67DArupgohWhN398ryt0
+rYgzTMYBKHSVnI969AYkmtlNzM1yNckRQb/G/tWrkl9re28y2nbAExtHbvLoTk2C
+a/EEl1Op+JZNzLoSje7IQMVZoArD3d4aUbfux4XzlO2eRNmZAkEA2pV49QgcOTOJ
+PrR5cgekonNdeMtkbZm9dhxgDk9IsYkC0iOxjn/IbeCQN3wuTQ5/yLoiiQ/CQ8w5
+JndF/XpICwJBAMnY37BSRb+XKZeJWP0yjqyFJwzHXkh6IsoSF2OOXSixdiMpthLh
+IPzvo6Qxsnha4VvwuDxljHzQFPgMT//CTGcCQQDMs9S+LKU50JDEX4Goj43X8RBl
+cp0Poz3yYap3XDqowLYalADRgcvzUq3cuHgoA98Z3W9ASrjUg2o2ItcyBhV3AkAK
+bCBgwl7Hnc6P/I+Tw2CKl/WEO2cq5uOU+4opodg9maw39JdqMiW56cXRXJ+Sh17L
+mIpq0/OFHll21WvsEORRAkAnDDn/vmW25PSxPVY7tKKJCCkmtBeLQpySfpDgBF+O
+QvvokKs2COivc50rmOYNvD1WSsAOspdaSoZUgFw5ikti
+-----END RSA PRIVATE KEY-----
diff --git a/src/testcurl/https/host2.crt b/src/testcurl/https/host2.crt
new file mode 100644
index 0000000..ac28b0e
--- /dev/null
+++ b/src/testcurl/https/host2.crt
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICWTCCAcICCQCJ9nhDYTUBKjANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa
+WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MjEZMBcGCSqGSIb3DQEJARYKdGVzdEBo
+b3N0MjAgFw0xMzExMTcxNTE2NDNaGA8yMTEzMTAyNDE1MTY0M1owcDELMAkGA1UE
+BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
+ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDIxGTAXBgkqhkiG9w0BCQEWCnRl
+c3RAaG9zdDIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVK8QKMvU96iNL2
+66PKm6xXw9NPHDn+o1TLF1CQRxXMrBYUrObk0961+3n3Z3BXOFHKfSV4E55CpVyz
+D1Wcadlt3B9z3ke3HOi0lEa1xNJTMQK/QT3Fx/NURmNg5s9HAsqY4ocb9KHaF5Ex
+0TgC0L0aRP0cK1x2TgPEHBNcgGl9AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAEXOi
+9rSmVrTN5olIdowctr1vWbGwRCjCnAFXDsqakcDASNthr15LB5kr/mrA3olJjbZh
+o+JDvWMY6FN8r1QXW0RL9/obbHxtJpwvAmYVMY9jrR8Rpo38p4RfXlN85g3q9PVx
+5IGLaOqLf4hSnKArFL/fzXwxX9b5HBCKlXfiuqM=
+-----END CERTIFICATE-----
diff --git a/src/testcurl/https/host2.key b/src/testcurl/https/host2.key
new file mode 100644
index 0000000..4bbd617
--- /dev/null
+++ b/src/testcurl/https/host2.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQC1SvECjL1PeojS9uujypusV8PTTxw5/qNUyxdQkEcVzKwWFKzm
+5NPetft592dwVzhRyn0leBOeQqVcsw9VnGnZbdwfc95HtxzotJRGtcTSUzECv0E9
+xcfzVEZjYObPRwLKmOKHG/Sh2heRMdE4AtC9GkT9HCtcdk4DxBwTXIBpfQIDAQAB
+AoGAR5Do6TfDt69IefdNeCAQKg2PWUg+fUpfEacGciAyX5GnUSQiSReF58HxHumi
+ZL+ZlPgZRQRMwknO23Q4FnSjd66A3E9iHLqkWxRFJWME6E7zgtBrIjctnNu9uYM9
+cw4R6qmXOL7C5sK00KXF2ep8+s+JjrZz61o85QnGGRYA94ECQQDbG6f1B8NKY9T1
+1GDR/++rJbdTVQlZQcKSXMumpU6V3mEV0O9GkYaZzoYvWa3kx6c0np4karrm3QWa
+u5E0q1YdAkEA09FPcmzVvIR0+sMWca8QJ/tJUxD6qYo8vLOpO4wt4iTPhGBEU+Q5
+cgXmde3/plVsp0vYxK/NG5XZkoC1fbuC4QJATRGxRlLwsl3jLoUBeVxY5Q5jKYCj
+xS2ITwss5vUGa1jJNW9EesH9YmRudoFI1UwU2EFixtRz4Xik3ARV0vzhUQJAfabT
+50ASxqMYtczW2peMEPurMqCG4d4ES7iUMqPkcBuAErn8rntbbH19igWmOyi/rLp8
+m6jiFnQdPiAmCbEbYQJAFAKiQl2ZOe3gkSh8MaQilD8Ppog6rod4SQiSmRNsDWPi
+IxqXneaGDWhzynC9xr4SwuJ9D5VxW1phNyiveDuYXw==
+-----END RSA PRIVATE KEY-----
diff --git a/src/testcurl/https/key.pem b/src/testcurl/https/key.pem
new file mode 100644
index 0000000..a5848ee
--- /dev/null
+++ b/src/testcurl/https/key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAtN08lLyuR5lAIq0adOu7WiBDuG4m4eZ6qJVKFHaPy3m5TEFf
+qp67/0TQDE8eV3zS2eOf5GzPYHhfKmNL7D8kLz+I7psytCziWLiMJh2lJvb2152I
+niC4sArk4I2rnd1ZGQ6EPX7XiKPusHvVE6VamacaWy7pQTIf1bL19HDydVRQu60+
+faPYQFJRX/Y1/xpinWCO/TLYFcdDjwY5pkg+pInAQX5n4JDu2yBsJUbbCMjM58Wx
+7ulbqF/3lca0765gsIBFy1kWeq7vDEJeWH6nhl10VcDHl1PetSnn27r5hD3HIAsZ
+vBWdQtXJwxxV4bIkoAMzAYwv2+ilYTUOCp1HWQIDAQABAoIBAArOQv3R7gmqDspj
+lDaTFOz0C4e70QfjGMX0sWnakYnDGn6DU19iv3GnX1S072ejtgc9kcJ4e8VUO79R
+EmqpdRR7k8dJr3RTUCyjzf/C+qiCzcmhCFYGN3KRHA6MeEnkvRuBogX4i5EG1k5l
+/5t+YBTZBnqXKWlzQLKoUAiMLPg0eRWh+6q7H4N7kdWWBmTpako7TEqpIwuEnPGx
+u3EPuTR+LN6lF55WBePbCHccUHUQaXuav18NuDkcJmCiMArK9SKb+h0RqLD6oMI/
+dKD6n8cZXeMBkK+C8U/K0sN2hFHACsu30b9XfdnljgP9v+BP8GhnB0nCB6tNBCPo
+32srOwECgYEAxWh3iBT4lWqL6bZavVbnhmvtif4nHv2t2/hOs/CAq8iLAw0oWGZc
++JEZTUDMvFRlulr0kcaWra+4fN3OmJnjeuFXZq52lfMgXBIKBmoSaZpIh2aDY1Rd
+RbEse7nQl9hTEPmYspiXLGtnAXW7HuWqVfFFP3ya8rUS3t4d07Hig8ECgYEA6ou6
+OHiBRTbtDqLIv8NghARc/AqwNWgEc9PelCPe5bdCOLBEyFjqKiT2MttnSSUc2Zob
+XhYkHC6zN1Mlq30N0e3Q61YK9LxMdU1vsluXxNq2rfK1Scb1oOlOOtlbV3zA3VRF
+hV3t1nOA9tFmUrwZi0CUMWJE/zbPAyhwWotKyZkCgYEAh0kFicPdbABdrCglXVae
+SnfSjVwYkVuGd5Ze0WADvjYsVkYBHTvhgRNnRJMg+/vWz3Sf4Ps4rgUbqK8Vc20b
+AU5G6H6tlCvPRGm0ZxrwTWDHTcuKRVs+pJE8C/qWoklE/AAhjluWVoGwUMbPGuiH
+6Gf1bgHF6oj/Sq7rv/VLZ8ECgYBeq7ml05YyLuJutuwa4yzQ/MXfghzv4aVyb0F3
+QCdXR6o2IYgR6jnSewrZKlA9aPqFJrwHNR6sNXlnSmt5Fcf/RWO/qgJQGLUv3+rG
+7kuLTNDR05azSdiZc7J89ID3Bkb+z2YkV+6JUiPq/Ei1+nDBEXb/m+/HqALU/nyj
+P3gXeQKBgBusb8Rbd+KgxSA0hwY6aoRTPRt8LNvXdsB9vRcKKHUFQvxUWiUSS+L9
+/Qu1sJbrUquKOHqksV5wCnWnAKyJNJlhHuBToqQTgKXjuNmVdYSe631saiI7PHyC
+eRJ6DxULPxABytJrYCRrNqmXi5TCiqR2mtfalEMOPxz8rUU8dYyx
+-----END RSA PRIVATE KEY-----
diff --git a/src/testcurl/https/test_empty_response.c b/src/testcurl/https/test_empty_response.c
new file mode 100644
index 0000000..f9f8001
--- /dev/null
+++ b/src/testcurl/https/test_empty_response.c
@@ -0,0 +1,201 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2013 Christian Grothoff
+
+ libmicrohttpd 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 3, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * @file test_empty_response.c
+ * @brief Testcase for libmicrohttpd HTTPS GET operations with emtpy reply
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "microhttpd.h"
+#include <limits.h>
+#include <sys/stat.h>
+#include <curl/curl.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+extern const char srv_signed_cert_pem[];
+extern const char srv_signed_key_pem[];
+
+static int oneone;
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ struct MHD_Response *response;
+ int ret;
+
+ response = MHD_create_response_from_buffer (0, NULL,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testInternalSelectGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_SSL | MHD_USE_SELECT_INTERNALLY,
+ 1082, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+
+ char *aes256_sha = "AES256-SHA";
+ if (curl_uses_nss_ssl() == 0)
+ {
+ aes256_sha = "rsa_aes_256_sha";
+ }
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "https://127.0.0.1:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ /* TLS options */
+ curl_easy_setopt (c, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
+ curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, aes256_sha);
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != 0)
+ return 8192;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error: %s\n", strerror (errno));
+ return -1;
+ }
+ if (0 != (errorCount = testInternalSelectGet ()))
+ fprintf (stderr, "Fail: %d\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/test_https_get.c b/src/testcurl/https/test_https_get.c
new file mode 100644
index 0000000..f7957c3
--- /dev/null
+++ b/src/testcurl/https/test_https_get.c
@@ -0,0 +1,130 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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 3, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_https_get.c
+ * @brief Testcase for libmicrohttpd HTTPS GET operations
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <limits.h>
+#include <sys/stat.h>
+#include <curl/curl.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+extern const char srv_signed_cert_pem[];
+extern const char srv_signed_key_pem[];
+
+
+static int
+test_cipher_option (FILE * test_fd,
+ const char *cipher_suite,
+ int proto_version)
+{
+
+ int ret;
+ struct MHD_Daemon *d;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL |
+ MHD_USE_DEBUG, 4233,
+ NULL, NULL, &http_ahc, NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_END);
+
+ if (d == NULL)
+ {
+ fprintf (stderr, MHD_E_SERVER_INIT);
+ return -1;
+ }
+
+ ret = test_https_transfer (test_fd, cipher_suite, proto_version);
+
+ MHD_stop_daemon (d);
+ return ret;
+}
+
+
+/* perform a HTTP GET request via SSL/TLS */
+static int
+test_secure_get (FILE * test_fd,
+ const char *cipher_suite,
+ int proto_version)
+{
+ int ret;
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL |
+ MHD_USE_DEBUG, 4233,
+ NULL, NULL, &http_ahc, NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem,
+ MHD_OPTION_END);
+
+ if (d == NULL)
+ {
+ fprintf (stderr, MHD_E_SERVER_INIT);
+ return -1;
+ }
+
+ ret = test_https_transfer (test_fd, cipher_suite, proto_version);
+
+ MHD_stop_daemon (d);
+ return ret;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ const char *aes256_sha_tlsv1 = "AES256-SHA";
+ const char *des_cbc3_sha_tlsv1 = "DES-CBC3-SHA";
+
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error: %s\n", strerror (errno));
+ return -1;
+ }
+
+ if (curl_uses_nss_ssl() == 0)
+ {
+ aes256_sha_tlsv1 = "rsa_aes_256_sha";
+ des_cbc3_sha_tlsv1 = "rsa_aes_128_sha";
+ }
+
+ errorCount +=
+ test_secure_get (NULL, aes256_sha_tlsv1, CURL_SSLVERSION_TLSv1);
+ errorCount +=
+ test_cipher_option (NULL, des_cbc3_sha_tlsv1, CURL_SSLVERSION_TLSv1);
+ print_test_result (errorCount, argv[0]);
+
+ curl_global_cleanup ();
+
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/test_https_get_parallel.c b/src/testcurl/https/test_https_get_parallel.c
new file mode 100644
index 0000000..166e400
--- /dev/null
+++ b/src/testcurl/https/test_https_get_parallel.c
@@ -0,0 +1,185 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_https_get_parallel.c
+ * @brief Testcase for libmicrohttpd HTTPS GET operations
+ * @author Sagie Amir
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <sys/stat.h>
+#include <limits.h>
+#include <curl/curl.h>
+#include <pthread.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 4
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 4
+#endif
+
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+
+int curl_check_version (const char *req_version, ...);
+
+
+/**
+ * used when spawning multiple threads executing curl server requests
+ *
+ */
+static void *
+https_transfer_thread_adapter (void *args)
+{
+ static int nonnull;
+ struct https_test_data *cargs = args;
+ int ret;
+
+ /* time spread incomming requests */
+ usleep ((useconds_t) 10.0 * ((double) rand ()) / ((double) RAND_MAX));
+ ret = test_https_transfer (NULL,
+ cargs->cipher_suite, cargs->proto_version);
+ if (ret == 0)
+ return NULL;
+ return &nonnull;
+}
+
+
+/**
+ * Test non-parallel requests.
+ *
+ * @return: 0 upon all client requests returning '0', -1 otherwise.
+ *
+ * TODO : make client_count a parameter - number of curl client threads to spawn
+ */
+static int
+test_single_client (void *cls, const char *cipher_suite,
+ int curl_proto_version)
+{
+ void *client_thread_ret;
+ struct https_test_data client_args =
+ { NULL, cipher_suite, curl_proto_version };
+
+ client_thread_ret = https_transfer_thread_adapter (&client_args);
+ if (client_thread_ret != NULL)
+ return -1;
+ return 0;
+}
+
+
+/**
+ * Test parallel request handling.
+ *
+ * @return: 0 upon all client requests returning '0', -1 otherwise.
+ *
+ * TODO : make client_count a parameter - numver of curl client threads to spawn
+ */
+static int
+test_parallel_clients (void * cls, const char *cipher_suite,
+ int curl_proto_version)
+{
+ int i;
+ int client_count = (CPU_COUNT - 1);
+ void *client_thread_ret;
+ pthread_t client_arr[client_count];
+ struct https_test_data client_args =
+ { NULL, cipher_suite, curl_proto_version };
+
+ for (i = 0; i < client_count; ++i)
+ {
+ if (pthread_create (&client_arr[i], NULL,
+ &https_transfer_thread_adapter, &client_args) != 0)
+ {
+ fprintf (stderr, "Error: failed to spawn test client threads.\n");
+ return -1;
+ }
+ }
+
+ /* check all client requests fulfilled correctly */
+ for (i = 0; i < client_count; ++i)
+ {
+ if ((pthread_join (client_arr[i], &client_thread_ret) != 0) ||
+ (client_thread_ret != NULL))
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ const char *aes256_sha = "AES256-SHA";
+
+ /* initialize random seed used by curl clients */
+ unsigned int iseed = (unsigned int) time (NULL);
+ srand (iseed);
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error: %s\n", strerror (errno));
+ return -1;
+ }
+
+ if (curl_uses_nss_ssl() == 0)
+ aes256_sha = "rsa_aes_256_sha";
+#if EPOLL_SUPPORT
+ errorCount +=
+ test_wrap ("single threaded daemon, single client, epoll", &test_single_client,
+ NULL,
+ MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL | MHD_USE_DEBUG | MHD_USE_EPOLL_LINUX_ONLY,
+ aes256_sha, CURL_SSLVERSION_TLSv1, MHD_OPTION_HTTPS_MEM_KEY,
+ srv_key_pem, MHD_OPTION_HTTPS_MEM_CERT,
+ srv_self_signed_cert_pem, MHD_OPTION_END);
+#endif
+ errorCount +=
+ test_wrap ("single threaded daemon, single client", &test_single_client,
+ NULL,
+ MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL | MHD_USE_DEBUG,
+ aes256_sha, CURL_SSLVERSION_TLSv1, MHD_OPTION_HTTPS_MEM_KEY,
+ srv_key_pem, MHD_OPTION_HTTPS_MEM_CERT,
+ srv_self_signed_cert_pem, MHD_OPTION_END);
+#if EPOLL_SUPPORT
+ errorCount +=
+ test_wrap ("single threaded daemon, parallel clients, epoll",
+ &test_parallel_clients, NULL,
+ MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL | MHD_USE_DEBUG | MHD_USE_EPOLL_LINUX_ONLY,
+ aes256_sha, CURL_SSLVERSION_TLSv1, MHD_OPTION_HTTPS_MEM_KEY,
+ srv_key_pem, MHD_OPTION_HTTPS_MEM_CERT,
+ srv_self_signed_cert_pem, MHD_OPTION_END);
+#endif
+ errorCount +=
+ test_wrap ("single threaded daemon, parallel clients",
+ &test_parallel_clients, NULL,
+ MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL | MHD_USE_DEBUG,
+ aes256_sha, CURL_SSLVERSION_TLSv1, MHD_OPTION_HTTPS_MEM_KEY,
+ srv_key_pem, MHD_OPTION_HTTPS_MEM_CERT,
+ srv_self_signed_cert_pem, MHD_OPTION_END);
+
+ curl_global_cleanup ();
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/test_https_get_parallel_threads.c b/src/testcurl/https/test_https_get_parallel_threads.c
new file mode 100644
index 0000000..4cb2128
--- /dev/null
+++ b/src/testcurl/https/test_https_get_parallel_threads.c
@@ -0,0 +1,191 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file tls_thread_mode_test.c
+ * @brief Testcase for libmicrohttpd HTTPS GET operations
+ * @author Sagie Amir
+ * @author Christian Grothoff
+ *
+ * TODO: add test for external select!
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <sys/stat.h>
+#include <limits.h>
+#include <curl/curl.h>
+#include <pthread.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 4
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 4
+#endif
+
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+
+int curl_check_version (const char *req_version, ...);
+
+/**
+ * used when spawning multiple threads executing curl server requests
+ *
+ */
+static void *
+https_transfer_thread_adapter (void *args)
+{
+ static int nonnull;
+ struct https_test_data *cargs = args;
+ int ret;
+
+ /* time spread incomming requests */
+ usleep ((useconds_t) 10.0 * ((double) rand ()) / ((double) RAND_MAX));
+ ret = test_https_transfer (cargs->cls,
+ cargs->cipher_suite, cargs->proto_version);
+ if (ret == 0)
+ return NULL;
+ return &nonnull;
+}
+
+/**
+ * Test non-parallel requests.
+ *
+ * @return: 0 upon all client requests returning '0', -1 otherwise.
+ *
+ * TODO : make client_count a parameter - numver of curl client threads to spawn
+ */
+static int
+test_single_client (void *cls, const char *cipher_suite,
+ int curl_proto_version)
+{
+ void *client_thread_ret;
+ struct https_test_data client_args =
+ { NULL, cipher_suite, curl_proto_version };
+
+ client_thread_ret = https_transfer_thread_adapter (&client_args);
+ if (client_thread_ret != NULL)
+ return -1;
+ return 0;
+}
+
+
+/**
+ * Test parallel request handling.
+ *
+ * @return: 0 upon all client requests returning '0', -1 otherwise.
+ *
+ * TODO : make client_count a parameter - numver of curl client threads to spawn
+ */
+static int
+test_parallel_clients (void *cls, const char *cipher_suite,
+ int curl_proto_version)
+{
+ int i;
+ int client_count = (CPU_COUNT - 1);
+ void *client_thread_ret;
+ pthread_t client_arr[client_count];
+ struct https_test_data client_args =
+ { NULL, cipher_suite, curl_proto_version };
+
+ for (i = 0; i < client_count; ++i)
+ {
+ if (pthread_create (&client_arr[i], NULL,
+ &https_transfer_thread_adapter, &client_args) != 0)
+ {
+ fprintf (stderr, "Error: failed to spawn test client threads.\n");
+
+ return -1;
+ }
+ }
+
+ /* check all client requests fulfilled correctly */
+ for (i = 0; i < client_count; ++i)
+ {
+ if ((pthread_join (client_arr[i], &client_thread_ret) != 0) ||
+ (client_thread_ret != NULL))
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ const char *ssl_version;
+
+ /* initialize random seed used by curl clients */
+ unsigned int iseed = (unsigned int) time (NULL);
+
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+ srand (iseed);
+ ssl_version = curl_version_info (CURLVERSION_NOW)->ssl_version;
+ if (NULL == ssl_version)
+ {
+ fprintf (stderr, "Curl does not support SSL. Cannot run the test.\n");
+ return 0;
+ }
+ if (0 != strncmp (ssl_version, "GnuTLS", 6))
+ {
+ fprintf (stderr, "This test can be run only with libcurl-gnutls.\n");
+ return 0;
+ }
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error: %s\n", strerror (errno));
+ return -1;
+ }
+
+ char *aes256_sha = "AES256-SHA";
+ if (curl_uses_nss_ssl() == 0)
+ {
+ aes256_sha = "rsa_aes_256_sha";
+ }
+
+ errorCount +=
+ test_wrap ("multi threaded daemon, single client", &test_single_client,
+ NULL,
+ MHD_USE_SSL | MHD_USE_DEBUG | MHD_USE_THREAD_PER_CONNECTION,
+ aes256_sha, CURL_SSLVERSION_TLSv1, MHD_OPTION_HTTPS_MEM_KEY,
+ srv_key_pem, MHD_OPTION_HTTPS_MEM_CERT,
+ srv_self_signed_cert_pem, MHD_OPTION_END);
+
+ errorCount +=
+ test_wrap ("multi threaded daemon, parallel client",
+ &test_parallel_clients, NULL,
+ MHD_USE_SSL | MHD_USE_DEBUG | MHD_USE_THREAD_PER_CONNECTION,
+ aes256_sha, CURL_SSLVERSION_TLSv1, MHD_OPTION_HTTPS_MEM_KEY,
+ srv_key_pem, MHD_OPTION_HTTPS_MEM_CERT,
+ srv_self_signed_cert_pem, MHD_OPTION_END);
+
+ if (errorCount != 0)
+ fprintf (stderr, "Failed test: %s.\n", argv[0]);
+
+ curl_global_cleanup ();
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/test_https_get_select.c b/src/testcurl/https/test_https_get_select.c
new file mode 100644
index 0000000..9f9ba99
--- /dev/null
+++ b/src/testcurl/https/test_https_get_select.c
@@ -0,0 +1,228 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * @file test_https_get_select.c
+ * @brief Testcase for libmicrohttpd HTTPS GET operations
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <limits.h>
+#include <sys/stat.h>
+#include <curl/curl.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+extern const char srv_signed_cert_pem[];
+extern const char srv_signed_key_pem[];
+
+static int oneone;
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testExternalGet (int flags)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+ const char *aes256_sha = "AES256-SHA";
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_SSL | flags,
+ 1082, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+
+ if (curl_uses_nss_ssl() == 0)
+ aes256_sha = "rsa_aes_256_sha";
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "https://127.0.0.1:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ /* TLS options */
+ curl_easy_setopt (c, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
+ curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, aes256_sha);
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error: %s\n", strerror (errno));
+ return -1;
+ }
+#if EPOLL_SUPPORT
+ if (0 != (errorCount = testExternalGet (MHD_USE_EPOLL_LINUX_ONLY)))
+ fprintf (stderr, "Fail: %d\n", errorCount);
+#endif
+ if (0 != (errorCount = testExternalGet (0)))
+ fprintf (stderr, "Fail: %d\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/test_https_multi_daemon.c b/src/testcurl/https/test_https_multi_daemon.c
new file mode 100644
index 0000000..293aff4
--- /dev/null
+++ b/src/testcurl/https/test_https_multi_daemon.c
@@ -0,0 +1,134 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * @file mhds_multi_daemon_test.c
+ * @brief Testcase for libmicrohttpd multiple HTTPS daemon scenario
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <curl/curl.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+extern int curl_check_version (const char *req_version, ...);
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+
+/*
+ * assert initiating two separate daemons and having one shut down
+ * doesn't affect the other
+ */
+static int
+test_concurent_daemon_pair (void *cls,
+ const char *cipher_suite,
+ int proto_version)
+{
+
+ int ret;
+ struct MHD_Daemon *d1;
+ struct MHD_Daemon *d2;
+
+ d1 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL |
+ MHD_USE_DEBUG, DEAMON_TEST_PORT,
+ NULL, NULL, &http_ahc, NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_END);
+
+ if (d1 == NULL)
+ {
+ fprintf (stderr, MHD_E_SERVER_INIT);
+ return -1;
+ }
+
+ d2 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL |
+ MHD_USE_DEBUG, DEAMON_TEST_PORT + 1,
+ NULL, NULL, &http_ahc, NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_END);
+
+ if (d2 == NULL)
+ {
+ MHD_stop_daemon (d1);
+ fprintf (stderr, MHD_E_SERVER_INIT);
+ return -1;
+ }
+
+ ret =
+ test_daemon_get (NULL, cipher_suite, proto_version, DEAMON_TEST_PORT, 0);
+ ret +=
+ test_daemon_get (NULL, cipher_suite, proto_version,
+ DEAMON_TEST_PORT + 1, 0);
+
+ MHD_stop_daemon (d2);
+ ret +=
+ test_daemon_get (NULL, cipher_suite, proto_version, DEAMON_TEST_PORT, 0);
+ MHD_stop_daemon (d1);
+ return ret;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ FILE *cert;
+
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error (code: %u). l:%d f:%s\n", errorCount, __LINE__,
+ __FUNCTION__);
+ return -1;
+ }
+ if ((cert = setup_ca_cert ()) == NULL)
+ {
+ fprintf (stderr, MHD_E_TEST_FILE_CREAT);
+ return -1;
+ }
+
+ const char *aes256_sha = "AES256-SHA";
+ if (curl_uses_nss_ssl() == 0)
+ {
+ aes256_sha = "rsa_aes_256_sha";
+ }
+
+ errorCount +=
+ test_concurent_daemon_pair (NULL, aes256_sha, CURL_SSLVERSION_TLSv1);
+
+ print_test_result (errorCount, "concurent_daemon_pair");
+
+ curl_global_cleanup ();
+ fclose (cert);
+ if (0 != remove (ca_cert_file_name))
+ fprintf (stderr,
+ "Failed to remove `%s'\n",
+ ca_cert_file_name);
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/test_https_session_info.c b/src/testcurl/https/test_https_session_info.c
new file mode 100644
index 0000000..8dac253
--- /dev/null
+++ b/src/testcurl/https/test_https_session_info.c
@@ -0,0 +1,186 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * @file mhds_session_info_test.c
+ * @brief Testcase for libmicrohttpd HTTPS connection querying operations
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <curl/curl.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+extern int curl_check_version (const char *req_version, ...);
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+
+struct MHD_Daemon *d;
+
+/*
+ * HTTP access handler call back
+ * used to query negotiated security parameters
+ */
+static int
+query_session_ahc (void *cls, struct MHD_Connection *connection,
+ const char *url, const char *method,
+ const char *upload_data, const char *version,
+ size_t *upload_data_size, void **ptr)
+{
+ struct MHD_Response *response;
+ int ret;
+
+ if (NULL == *ptr)
+ {
+ *ptr = &query_session_ahc;
+ return MHD_YES;
+ }
+
+ if (GNUTLS_TLS1_1 !=
+ (ret = MHD_get_connection_info
+ (connection,
+ MHD_CONNECTION_INFO_PROTOCOL)->protocol))
+ {
+ if (GNUTLS_TLS1_2 == ret)
+ {
+ /* as usual, TLS implementations sometimes don't
+ quite do what was asked, just mildly complain... */
+ fprintf (stderr,
+ "Warning: requested TLS 1.1, got TLS 1.2\n");
+ }
+ else
+ {
+ /* really different version... */
+ fprintf (stderr,
+ "Error: requested protocol mismatch (wanted %d, got %d)\n",
+ GNUTLS_TLS1_1,
+ ret);
+ return -1;
+ }
+ }
+
+ response = MHD_create_response_from_buffer (strlen (EMPTY_PAGE),
+ (void *) EMPTY_PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * negotiate a secure connection with server & query negotiated security parameters
+ */
+static int
+test_query_session ()
+{
+ CURL *c;
+ struct CBC cbc;
+ CURLcode errornum;
+ char url[256];
+
+ if (NULL == (cbc.buf = malloc (sizeof (char) * 255)))
+ return 16;
+ cbc.size = 255;
+ cbc.pos = 0;
+
+ gen_test_file_url (url, DEAMON_TEST_PORT);
+
+ /* setup test */
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL |
+ MHD_USE_DEBUG, DEAMON_TEST_PORT,
+ NULL, NULL, &query_session_ahc, NULL,
+ MHD_OPTION_HTTPS_PRIORITIES, "NORMAL:+ARCFOUR-128",
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_END);
+
+ if (d == NULL)
+ return 2;
+
+ const char *aes256_sha = "AES256-SHA";
+ if (curl_uses_nss_ssl() == 0)
+ {
+ aes256_sha = "rsa_aes_256_sha";
+ }
+
+ c = curl_easy_init ();
+#if DEBUG_HTTPS_TEST
+ curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
+#endif
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_FILE, &cbc);
+ /* TLS options */
+ curl_easy_setopt (c, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1);
+ curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, aes256_sha);
+ /* currently skip any peer authentication */
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr, "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+
+ MHD_stop_daemon (d);
+ curl_easy_cleanup (c);
+ free (cbc.buf);
+ return -1;
+ }
+
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ free (cbc.buf);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ return -1;
+ }
+ errorCount += test_query_session ();
+ print_test_result (errorCount, argv[0]);
+ curl_global_cleanup ();
+ if (errorCount > 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ return errorCount;
+}
diff --git a/src/testcurl/https/test_https_sni.c b/src/testcurl/https/test_https_sni.c
new file mode 100644
index 0000000..8c4dea6
--- /dev/null
+++ b/src/testcurl/https/test_https_sni.c
@@ -0,0 +1,291 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2013 Christian Grothoff
+
+ libmicrohttpd 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 3, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_https_sni.c
+ * @brief Testcase for libmicrohttpd HTTPS with SNI operations
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "microhttpd.h"
+#include <limits.h>
+#include <sys/stat.h>
+#include <curl/curl.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+#include <gnutls/gnutls.h>
+
+/* This test only works with GnuTLS >= 3.0 */
+#if GNUTLS_VERSION_MAJOR >= 3
+
+#include <gnutls/abstract.h>
+
+/**
+ * A hostname, server key and certificate.
+ */
+struct Hosts
+{
+ struct Hosts *next;
+ const char *hostname;
+ gnutls_pcert_st pcrt;
+ gnutls_privkey_t key;
+};
+
+
+/**
+ * Linked list of supported TLDs and respective certificates.
+ */
+static struct Hosts *hosts;
+
+/* Load the certificate and the private key.
+ * (This code is largely taken from GnuTLS).
+ */
+static void
+load_keys(const char *hostname,
+ const char *CERT_FILE,
+ const char *KEY_FILE)
+{
+ int ret;
+ gnutls_datum_t data;
+ struct Hosts *host;
+
+ host = malloc (sizeof (struct Hosts));
+ if (NULL == host)
+ abort ();
+ host->hostname = hostname;
+ host->next = hosts;
+ hosts = host;
+
+ ret = gnutls_load_file (CERT_FILE, &data);
+ if (ret < 0)
+ {
+ fprintf (stderr,
+ "*** Error loading certificate file %s.\n",
+ CERT_FILE);
+ exit (1);
+ }
+ ret =
+ gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
+ 0);
+ if (ret < 0)
+ {
+ fprintf (stderr,
+ "*** Error loading certificate file: %s\n",
+ gnutls_strerror (ret));
+ exit (1);
+ }
+ gnutls_free (data.data);
+
+ ret = gnutls_load_file (KEY_FILE, &data);
+ if (ret < 0)
+ {
+ fprintf (stderr,
+ "*** Error loading key file %s.\n",
+ KEY_FILE);
+ exit (1);
+ }
+
+ gnutls_privkey_init (&host->key);
+ ret =
+ gnutls_privkey_import_x509_raw (host->key,
+ &data, GNUTLS_X509_FMT_PEM,
+ NULL, 0);
+ if (ret < 0)
+ {
+ fprintf (stderr,
+ "*** Error loading key file: %s\n",
+ gnutls_strerror (ret));
+ exit (1);
+ }
+ gnutls_free (data.data);
+}
+
+
+
+/**
+ * @param session the session we are giving a cert for
+ * @param req_ca_dn NULL on server side
+ * @param nreqs length of req_ca_dn, and thus 0 on server side
+ * @param pk_algos NULL on server side
+ * @param pk_algos_length 0 on server side
+ * @param pcert list of certificates (to be set)
+ * @param pcert_length length of pcert (to be set)
+ * @param pkey the private key (to be set)
+ */
+static int
+sni_callback (gnutls_session_t session,
+ const gnutls_datum_t* req_ca_dn,
+ int nreqs,
+ const gnutls_pk_algorithm_t* pk_algos,
+ int pk_algos_length,
+ gnutls_pcert_st** pcert,
+ unsigned int *pcert_length,
+ gnutls_privkey_t * pkey)
+{
+ char name[256];
+ size_t name_len;
+ struct Hosts *host;
+ unsigned int type;
+
+ name_len = sizeof (name);
+ if (GNUTLS_E_SUCCESS !=
+ gnutls_server_name_get (session,
+ name,
+ &name_len,
+ &type,
+ 0 /* index */))
+ return -1;
+ for (host = hosts; NULL != host; host = host->next)
+ if (0 == strncmp (name, host->hostname, name_len))
+ break;
+ if (NULL == host)
+ {
+ fprintf (stderr,
+ "Need certificate for %.*s\n",
+ (int) name_len,
+ name);
+ return -1;
+ }
+#if 0
+ fprintf (stderr,
+ "Returning certificate for %.*s\n",
+ (int) name_len,
+ name);
+#endif
+ *pkey = host->key;
+ *pcert_length = 1;
+ *pcert = &host->pcrt;
+ return 0;
+}
+
+
+/* perform a HTTP GET request via SSL/TLS */
+static int
+do_get (const char *url)
+{
+ CURL *c;
+ struct CBC cbc;
+ CURLcode errornum;
+ size_t len;
+ struct curl_slist *dns_info;
+
+ len = strlen (test_data);
+ if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
+ {
+ fprintf (stderr, MHD_E_MEM);
+ return -1;
+ }
+ cbc.size = len;
+ cbc.pos = 0;
+
+ c = curl_easy_init ();
+#if DEBUG_HTTPS_TEST
+ curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
+#endif
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_FILE, &cbc);
+
+ /* perform peer authentication */
+ /* TODO merge into send_curl_req */
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 2);
+ dns_info = curl_slist_append (NULL, "host1:4233:127.0.0.1");
+ dns_info = curl_slist_append (dns_info, "host2:4233:127.0.0.1");
+ curl_easy_setopt (c, CURLOPT_RESOLVE, dns_info);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr, "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ free (cbc.buf);
+ curl_slist_free_all (dns_info);
+ return errornum;
+ }
+
+ curl_easy_cleanup (c);
+ curl_slist_free_all (dns_info);
+ if (memcmp (cbc.buf, test_data, len) != 0)
+ {
+ fprintf (stderr, "Error: local file & received file differ.\n");
+ free (cbc.buf);
+ return -1;
+ }
+
+ free (cbc.buf);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int error_count = 0;
+ struct MHD_Daemon *d;
+
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error: %s\n", strerror (errno));
+ return -1;
+ }
+ load_keys ("host1", ABS_SRCDIR "/host1.crt", ABS_SRCDIR "/host1.key");
+ load_keys ("host2", ABS_SRCDIR "/host2.crt", ABS_SRCDIR "/host2.key");
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL | MHD_USE_DEBUG,
+ 4233,
+ NULL, NULL,
+ &http_ahc, NULL,
+ MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
+ MHD_OPTION_END);
+ if (d == NULL)
+ {
+ fprintf (stderr, MHD_E_SERVER_INIT);
+ return -1;
+ }
+ error_count += do_get ("https://host1:4233/");
+ error_count += do_get ("https://host2:4233/");
+
+ MHD_stop_daemon (d);
+ curl_global_cleanup ();
+ return error_count != 0;
+}
+
+
+#else
+
+int main ()
+{
+ fprintf (stderr,
+ "SNI not supported by GnuTLS < 3.0\n");
+ return 0;
+}
+#endif
diff --git a/src/testcurl/https/test_https_time_out.c b/src/testcurl/https/test_https_time_out.c
new file mode 100644
index 0000000..124a280
--- /dev/null
+++ b/src/testcurl/https/test_https_time_out.c
@@ -0,0 +1,146 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * @file mhds_get_test.c
+ * @brief: daemon TLS alert response test-case
+ *
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include "internal.h"
+#include "tls_test_common.h"
+#include <gcrypt.h>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+
+static const int TIME_OUT = 3;
+
+static int
+test_tls_session_time_out (gnutls_session_t session)
+{
+ int ret;
+ MHD_socket sd;
+ struct sockaddr_in sa;
+
+ sd = socket (AF_INET, SOCK_STREAM, 0);
+ if (sd == -1)
+ {
+ fprintf (stderr, "Failed to create socket: %s\n", strerror (errno));
+ return -1;
+ }
+
+ memset (&sa, '\0', sizeof (struct sockaddr_in));
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons (DEAMON_TEST_PORT);
+ sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+
+ gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) (intptr_t) sd);
+
+ ret = connect (sd, &sa, sizeof (struct sockaddr_in));
+
+ if (ret < 0)
+ {
+ fprintf (stderr, "Error: %s\n", MHD_E_FAILED_TO_CONNECT);
+ close (sd);
+ return -1;
+ }
+
+ ret = gnutls_handshake (session);
+ if (ret < 0)
+ {
+ fprintf (stderr, "Handshake failed\n");
+ close (sd);
+ return -1;
+ }
+
+ sleep (TIME_OUT + 1);
+
+ /* check that server has closed the connection */
+ /* TODO better RST trigger */
+ if (send (sd, "", 1, 0) == 0)
+ {
+ fprintf (stderr, "Connection failed to time-out\n");
+ close (sd);
+ return -1;
+ }
+
+ close (sd);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ int errorCount = 0;;
+ struct MHD_Daemon *d;
+ gnutls_session_t session;
+ gnutls_datum_t key;
+ gnutls_datum_t cert;
+ gnutls_certificate_credentials_t xcred;
+
+
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+ gnutls_global_init ();
+ gnutls_global_set_log_level (11);
+
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL |
+ MHD_USE_DEBUG, DEAMON_TEST_PORT,
+ NULL, NULL, &http_dummy_ahc, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, TIME_OUT,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_END);
+
+ if (d == NULL)
+ {
+ fprintf (stderr, MHD_E_SERVER_INIT);
+ return -1;
+ }
+
+ if (0 != setup_session (&session, &key, &cert, &xcred))
+ {
+ fprintf (stderr, "failed to setup session\n");
+ return 1;
+ }
+ errorCount += test_tls_session_time_out (session);
+ teardown_session (session, &key, &cert, xcred);
+
+ print_test_result (errorCount, argv[0]);
+
+ MHD_stop_daemon (d);
+ gnutls_global_deinit ();
+
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/test_tls_authentication.c b/src/testcurl/https/test_tls_authentication.c
new file mode 100644
index 0000000..cc9c76d
--- /dev/null
+++ b/src/testcurl/https/test_tls_authentication.c
@@ -0,0 +1,110 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * @file tls_authentication_test.c
+ * @brief Testcase for libmicrohttpd HTTPS GET operations
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <curl/curl.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+extern int curl_check_version (const char *req_version, ...);
+extern const char test_file_data[];
+
+extern const char ca_key_pem[];
+extern const char ca_cert_pem[];
+extern const char srv_signed_cert_pem[];
+extern const char srv_signed_key_pem[];
+
+
+
+/* perform a HTTP GET request via SSL/TLS */
+static int
+test_secure_get (void * cls, char *cipher_suite, int proto_version)
+{
+ int ret;
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL |
+ MHD_USE_DEBUG, DEAMON_TEST_PORT,
+ NULL, NULL, &http_ahc, NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem,
+ MHD_OPTION_END);
+
+ if (d == NULL)
+ {
+ fprintf (stderr, MHD_E_SERVER_INIT);
+ return -1;
+ }
+
+ ret = test_daemon_get (NULL, cipher_suite, proto_version, DEAMON_TEST_PORT, 0);
+
+ MHD_stop_daemon (d);
+ return ret;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+ if (setup_ca_cert () == NULL)
+ {
+ fprintf (stderr, MHD_E_TEST_FILE_CREAT);
+ return -1;
+ }
+
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ return -1;
+ }
+
+ char *aes256_sha = "AES256-SHA";
+ if (curl_uses_nss_ssl() == 0)
+ {
+ aes256_sha = "rsa_aes_256_sha";
+ }
+
+ errorCount +=
+ test_secure_get (NULL, aes256_sha, CURL_SSLVERSION_TLSv1);
+
+ print_test_result (errorCount, argv[0]);
+
+ curl_global_cleanup ();
+ if (0 != remove (ca_cert_file_name))
+ fprintf (stderr,
+ "Failed to remove `%s'\n",
+ ca_cert_file_name);
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/test_tls_options.c b/src/testcurl/https/test_tls_options.c
new file mode 100644
index 0000000..7dd01a7
--- /dev/null
+++ b/src/testcurl/https/test_tls_options.c
@@ -0,0 +1,155 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2010 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file tls_daemon_options_test.c
+ * @brief Testcase for libmicrohttpd HTTPS GET operations
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <sys/stat.h>
+#include <limits.h>
+#include <gcrypt.h>
+#include "tls_test_common.h"
+
+extern const char srv_key_pem[];
+extern const char srv_self_signed_cert_pem[];
+
+int curl_check_version (const char *req_version, ...);
+
+/**
+ * test server refuses to negotiate connections with unsupported protocol versions
+ *
+ */
+static int
+test_unmatching_ssl_version (void * cls, const char *cipher_suite,
+ int curl_req_ssl_version)
+{
+ struct CBC cbc;
+ if (NULL == (cbc.buf = malloc (sizeof (char) * 256)))
+ {
+ fprintf (stderr, "Error: failed to allocate: %s\n",
+ strerror (errno));
+ return -1;
+ }
+ cbc.size = 256;
+ cbc.pos = 0;
+
+ char url[255];
+ if (gen_test_file_url (url, DEAMON_TEST_PORT))
+ {
+ free (cbc.buf);
+ fprintf (stderr, "Internal error in gen_test_file_url\n");
+ return -1;
+ }
+
+ /* assert daemon *rejected* request */
+ if (CURLE_OK ==
+ send_curl_req (url, &cbc, cipher_suite, curl_req_ssl_version))
+ {
+ free (cbc.buf);
+ fprintf (stderr, "cURL failed to reject request despite SSL version missmatch!\n");
+ return -1;
+ }
+
+ free (cbc.buf);
+ return 0;
+}
+
+
+/* setup a temporary transfer test file */
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ const char *ssl_version;
+ int daemon_flags =
+ MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL | MHD_USE_DEBUG;
+
+ gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+ if (curl_check_version (MHD_REQ_CURL_VERSION))
+ {
+ return 0;
+ }
+ ssl_version = curl_version_info (CURLVERSION_NOW)->ssl_version;
+ if (NULL == ssl_version)
+ {
+ fprintf (stderr, "Curl does not support SSL. Cannot run the test.\n");
+ return 0;
+ }
+ if (0 != strncmp (ssl_version, "GnuTLS", 6))
+ {
+ fprintf (stderr, "This test can be run only with libcurl-gnutls.\n");
+ return 0;
+ }
+
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ {
+ fprintf (stderr, "Error: %s\n", strerror (errno));
+ return 0;
+ }
+
+ const char *aes128_sha = "AES128-SHA";
+ const char *aes256_sha = "AES256-SHA";
+ if (curl_uses_nss_ssl() == 0)
+ {
+ aes128_sha = "rsa_aes_128_sha";
+ aes256_sha = "rsa_aes_256_sha";
+ }
+
+
+ if (0 !=
+ test_wrap ("TLS1.0-AES-SHA1",
+ &test_https_transfer, NULL, daemon_flags,
+ aes128_sha,
+ CURL_SSLVERSION_TLSv1,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_HTTPS_PRIORITIES, "NONE:+VERS-TLS1.0:+AES-128-CBC:+SHA1:+RSA:+COMP-NULL",
+ MHD_OPTION_END))
+ {
+ fprintf (stderr, "TLS1.0-AES-SHA1 test failed\n");
+ errorCount++;
+ }
+ fprintf (stderr,
+ "The following handshake should fail (and print an error message)...\n");
+ if (0 !=
+ test_wrap ("TLS1.0 vs SSL3",
+ &test_unmatching_ssl_version, NULL, daemon_flags,
+ aes256_sha,
+ CURL_SSLVERSION_SSLv3,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_self_signed_cert_pem,
+ MHD_OPTION_HTTPS_PRIORITIES, "NONE:+VERS-TLS1.0:+AES-256-CBC:+SHA1:+RSA:+COMP-NULL",
+ MHD_OPTION_END))
+ {
+ fprintf (stderr, "TLS1.0 vs SSL3 test failed\n");
+ errorCount++;
+ }
+ curl_global_cleanup ();
+
+ return errorCount != 0;
+}
diff --git a/src/testcurl/https/tls_test_common.c b/src/testcurl/https/tls_test_common.c
new file mode 100644
index 0000000..f917582
--- /dev/null
+++ b/src/testcurl/https/tls_test_common.c
@@ -0,0 +1,484 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * @file tls_test_common.c
+ * @brief Common tls test functions
+ * @author Sagie Amir
+ */
+#include "tls_test_common.h"
+#include "tls_test_keys.h"
+
+
+int curl_check_version (const char *req_version, ...);
+
+FILE *
+setup_ca_cert ()
+{
+ FILE *cert_fd;
+
+ if (NULL == (cert_fd = fopen (ca_cert_file_name, "wb+")))
+ {
+ fprintf (stderr, "Error: failed to open `%s': %s\n",
+ ca_cert_file_name, strerror (errno));
+ return NULL;
+ }
+ if (fwrite (ca_cert_pem, sizeof (char), strlen (ca_cert_pem) + 1, cert_fd)
+ != strlen (ca_cert_pem) + 1)
+ {
+ fprintf (stderr, "Error: failed to write `%s. %s'\n",
+ ca_cert_file_name, strerror (errno));
+ fclose (cert_fd);
+ return NULL;
+ }
+ if (fflush (cert_fd))
+ {
+ fprintf (stderr, "Error: failed to flush ca cert file stream. %s\n",
+ strerror (errno));
+ fclose (cert_fd);
+ return NULL;
+ }
+ return cert_fd;
+}
+
+
+/*
+ * test HTTPS transfer
+ */
+int
+test_daemon_get (void *cls,
+ const char *cipher_suite, int proto_version,
+ int port,
+ int ver_peer)
+{
+ CURL *c;
+ struct CBC cbc;
+ CURLcode errornum;
+ char url[255];
+ size_t len;
+
+ len = strlen (test_data);
+ if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
+ {
+ fprintf (stderr, MHD_E_MEM);
+ return -1;
+ }
+ cbc.size = len;
+ cbc.pos = 0;
+
+ /* construct url - this might use doc_path */
+ gen_test_file_url (url, port);
+
+ c = curl_easy_init ();
+#if DEBUG_HTTPS_TEST
+ curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
+#endif
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_FILE, &cbc);
+
+ /* TLS options */
+ curl_easy_setopt (c, CURLOPT_SSLVERSION, proto_version);
+ curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, cipher_suite);
+
+ /* perform peer authentication */
+ /* TODO merge into send_curl_req */
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, ver_peer);
+ curl_easy_setopt (c, CURLOPT_CAINFO, ca_cert_file_name);
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr, "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ free (cbc.buf);
+ return errornum;
+ }
+
+ curl_easy_cleanup (c);
+
+ if (memcmp (cbc.buf, test_data, len) != 0)
+ {
+ fprintf (stderr, "Error: local file & received file differ.\n");
+ free (cbc.buf);
+ return -1;
+ }
+
+ free (cbc.buf);
+ return 0;
+}
+
+
+void
+print_test_result (int test_outcome, char *test_name)
+{
+#if 0
+ if (test_outcome != 0)
+ fprintf (stderr, "running test: %s [fail]\n", test_name);
+ else
+ fprintf (stdout, "running test: %s [pass]\n", test_name);
+#endif
+}
+
+size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+/**
+ * HTTP access handler call back
+ */
+int
+http_ahc (void *cls, struct MHD_Connection *connection,
+ const char *url, const char *method, const char *upload_data,
+ const char *version, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ *ptr = NULL; /* reset when done */
+ response = MHD_create_response_from_buffer (strlen (test_data),
+ (void *) test_data,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+/* HTTP access handler call back */
+int
+http_dummy_ahc (void *cls, struct MHD_Connection *connection,
+ const char *url, const char *method, const char *upload_data,
+ const char *version, size_t *upload_data_size,
+ void **ptr)
+{
+ return 0;
+}
+
+/**
+ * send a test http request to the daemon
+ * @param url
+ * @param cbc - may be null
+ * @param cipher_suite
+ * @param proto_version
+ * @return
+ */
+/* TODO have test wrap consider a NULL cbc */
+int
+send_curl_req (char *url, struct CBC * cbc, const char *cipher_suite,
+ int proto_version)
+{
+ CURL *c;
+ CURLcode errornum;
+ c = curl_easy_init ();
+#if DEBUG_HTTPS_TEST
+ curl_easy_setopt (c, CURLOPT_VERBOSE, CURL_VERBOS_LEVEL);
+#endif
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 60L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 60L);
+
+ if (cbc != NULL)
+ {
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_FILE, cbc);
+ }
+
+ /* TLS options */
+ curl_easy_setopt (c, CURLOPT_SSLVERSION, proto_version);
+ curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, cipher_suite);
+
+ /* currently skip any peer authentication */
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
+
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr, "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ return errornum;
+ }
+ curl_easy_cleanup (c);
+
+ return CURLE_OK;
+}
+
+
+/**
+ * compile test file url pointing to the current running directory path
+ *
+ * @param url - char buffer into which the url is compiled
+ * @param port port to use for the test
+ * @return -1 on error
+ */
+int
+gen_test_file_url (char *url, int port)
+{
+ int ret = 0;
+ char *doc_path;
+ size_t doc_path_len;
+ /* setup test file path, url */
+ doc_path_len = PATH_MAX > 4096 ? 4096 : PATH_MAX;
+ if (NULL == (doc_path = malloc (doc_path_len)))
+ {
+ fprintf (stderr, MHD_E_MEM);
+ return -1;
+ }
+ if (getcwd (doc_path, doc_path_len) == NULL)
+ {
+ fprintf (stderr, "Error: failed to get working directory. %s\n",
+ strerror (errno));
+ ret = -1;
+ }
+#ifdef WINDOWS
+ {
+ int i;
+ for (i = 0; i < doc_path_len; i++)
+ {
+ if (doc_path[i] == 0)
+ break;
+ if (doc_path[i] == '\\')
+ {
+ doc_path[i] = '/';
+ }
+ if (doc_path[i] != ':')
+ continue;
+ if (i == 0)
+ break;
+ doc_path[i] = doc_path[i - 1];
+ doc_path[i - 1] = '/';
+ }
+ }
+#endif
+ /* construct url - this might use doc_path */
+ if (sprintf (url, "%s:%d%s/%s", "https://127.0.0.1", port,
+ doc_path, "urlpath") < 0)
+ ret = -1;
+
+ free (doc_path);
+ return ret;
+}
+
+/**
+ * test HTTPS file transfer
+ */
+int
+test_https_transfer (void *cls, const char *cipher_suite, int proto_version)
+{
+ int len;
+ int ret = 0;
+ struct CBC cbc;
+ char url[255];
+
+ len = strlen (test_data);
+ if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
+ {
+ fprintf (stderr, MHD_E_MEM);
+ return -1;
+ }
+ cbc.size = len;
+ cbc.pos = 0;
+
+ if (gen_test_file_url (url, DEAMON_TEST_PORT))
+ {
+ ret = -1;
+ goto cleanup;
+ }
+
+ if (CURLE_OK != send_curl_req (url, &cbc, cipher_suite, proto_version))
+ {
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* compare test file & daemon responce */
+ if ( (len != strlen (test_data)) ||
+ (memcmp (cbc.buf,
+ test_data,
+ len) != 0) )
+ {
+ fprintf (stderr, "Error: local file & received file differ.\n");
+ ret = -1;
+ }
+cleanup:
+ free (cbc.buf);
+ return ret;
+}
+
+/**
+ * setup test case
+ *
+ * @param d
+ * @param daemon_flags
+ * @param arg_list
+ * @return
+ */
+int
+setup_testcase (struct MHD_Daemon **d, int daemon_flags, va_list arg_list)
+{
+ *d = MHD_start_daemon_va (daemon_flags, DEAMON_TEST_PORT,
+ NULL, NULL, &http_ahc, NULL, arg_list);
+
+ if (*d == NULL)
+ {
+ fprintf (stderr, MHD_E_SERVER_INIT);
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+teardown_testcase (struct MHD_Daemon *d)
+{
+ MHD_stop_daemon (d);
+}
+
+int
+setup_session (gnutls_session_t * session,
+ gnutls_datum_t * key,
+ gnutls_datum_t * cert,
+ gnutls_certificate_credentials_t * xcred)
+{
+ int ret;
+ const char *err_pos;
+
+ gnutls_certificate_allocate_credentials (xcred);
+ key->size = strlen (srv_key_pem) + 1;
+ key->data = malloc (key->size);
+ if (NULL == key->data)
+ {
+ gnutls_certificate_free_credentials (*xcred);
+ return -1;
+ }
+ memcpy (key->data, srv_key_pem, key->size);
+ cert->size = strlen (srv_self_signed_cert_pem) + 1;
+ cert->data = malloc (cert->size);
+ if (NULL == cert->data)
+ {
+ gnutls_certificate_free_credentials (*xcred);
+ free (key->data);
+ return -1;
+ }
+ memcpy (cert->data, srv_self_signed_cert_pem, cert->size);
+ gnutls_certificate_set_x509_key_mem (*xcred, cert, key,
+ GNUTLS_X509_FMT_PEM);
+ gnutls_init (session, GNUTLS_CLIENT);
+ ret = gnutls_priority_set_direct (*session,
+ "NORMAL", &err_pos);
+ if (ret < 0)
+ {
+ gnutls_deinit (*session);
+ gnutls_certificate_free_credentials (*xcred);
+ free (key->data);
+ return -1;
+ }
+ gnutls_credentials_set (*session,
+ GNUTLS_CRD_CERTIFICATE,
+ *xcred);
+ return 0;
+}
+
+int
+teardown_session (gnutls_session_t session,
+ gnutls_datum_t * key,
+ gnutls_datum_t * cert,
+ gnutls_certificate_credentials_t xcred)
+{
+ free (key->data);
+ key->data = NULL;
+ key->size = 0;
+ free (cert->data);
+ cert->data = NULL;
+ cert->size = 0;
+ gnutls_deinit (session);
+ gnutls_certificate_free_credentials (xcred);
+ return 0;
+}
+
+/* TODO test_wrap: change sig to (setup_func, test, va_list test_arg) */
+int
+test_wrap (const char *test_name, int
+ (*test_function) (void * cls, const char *cipher_suite,
+ int proto_version), void * cls,
+ int daemon_flags, const char *cipher_suite, int proto_version, ...)
+{
+ int ret;
+ va_list arg_list;
+ struct MHD_Daemon *d;
+
+ va_start (arg_list, proto_version);
+ if (setup_testcase (&d, daemon_flags, arg_list) != 0)
+ {
+ va_end (arg_list);
+ fprintf (stderr, "Failed to setup testcase %s\n", test_name);
+ return -1;
+ }
+#if 0
+ fprintf (stdout, "running test: %s ", test_name);
+#endif
+ ret = test_function (NULL, cipher_suite, proto_version);
+#if 0
+ if (ret == 0)
+ {
+ fprintf (stdout, "[pass]\n");
+ }
+ else
+ {
+ fprintf (stdout, "[fail]\n");
+ }
+#endif
+ teardown_testcase (d);
+ va_end (arg_list);
+ return ret;
+}
diff --git a/src/testcurl/https/tls_test_common.h b/src/testcurl/https/tls_test_common.h
new file mode 100644
index 0000000..20198f7
--- /dev/null
+++ b/src/testcurl/https/tls_test_common.h
@@ -0,0 +1,141 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+#ifndef TLS_TEST_COMMON_H_
+#define TLS_TEST_COMMON_H_
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <curl/curl.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <gnutls/gnutls.h>
+
+/* this enables verbos CURL version checking */
+#define DEBUG_HTTPS_TEST 0
+#define CURL_VERBOS_LEVEL 0
+
+#define DEAMON_TEST_PORT 4233
+
+#define test_data "Hello World\n"
+#define ca_cert_file_name "tmp_ca_cert.pem"
+
+#define EMPTY_PAGE "<html><head><title>Empty page</title></head><body>Empty page</body></html>"
+#define PAGE_NOT_FOUND "<html><head><title>File not found</title></head><body>File not found</body></html>"
+
+#define MHD_E_MEM "Error: memory error\n"
+#define MHD_E_SERVER_INIT "Error: failed to start server\n"
+#define MHD_E_TEST_FILE_CREAT "Error: failed to setup test file\n"
+#define MHD_E_CERT_FILE_CREAT "Error: failed to setup test certificate\n"
+#define MHD_E_KEY_FILE_CREAT "Error: failed to setup test certificate\n"
+#define MHD_E_FAILED_TO_CONNECT "Error: server connection could not be established\n"
+
+/* TODO rm if unused */
+struct https_test_data
+{
+ void *cls;
+ const char *cipher_suite;
+ int proto_version;
+};
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+struct CipherDef
+{
+ int options[2];
+ char *curlname;
+};
+
+
+int curl_check_version (const char *req_version, ...);
+int curl_uses_nss_ssl ();
+
+
+FILE *
+setup_ca_cert ();
+
+/**
+ * perform cURL request for file
+ */
+int
+test_daemon_get (void * cls,
+ const char *cipher_suite, int proto_version,
+ int port, int ver_peer);
+
+void print_test_result (int test_outcome, char *test_name);
+
+size_t copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx);
+
+int
+http_ahc (void *cls, struct MHD_Connection *connection,
+ const char *url, const char *method, const char *upload_data,
+ const char *version, size_t *upload_data_size, void **ptr);
+
+int
+http_dummy_ahc (void *cls, struct MHD_Connection *connection,
+ const char *url, const char *method, const char *upload_data,
+ const char *version, size_t *upload_data_size,
+ void **ptr);
+
+
+/**
+ * compile test file url pointing to the current running directory path
+ *
+ * @param url - char buffer into which the url is compiled
+ * @param port port to use for the test
+ * @return -1 on error
+ */
+int gen_test_file_url (char *url, int port);
+
+int
+send_curl_req (char *url, struct CBC *cbc, const char *cipher_suite,
+ int proto_version);
+
+int
+test_https_transfer (void *cls, const char *cipher_suite, int proto_version);
+
+int
+setup_testcase (struct MHD_Daemon **d, int daemon_flags, va_list arg_list);
+
+void teardown_testcase (struct MHD_Daemon *d);
+
+int
+setup_session (gnutls_session_t * session,
+ gnutls_datum_t * key,
+ gnutls_datum_t * cert,
+ gnutls_certificate_credentials_t * xcred);
+
+int
+teardown_session (gnutls_session_t session,
+ gnutls_datum_t * key,
+ gnutls_datum_t * cert,
+ gnutls_certificate_credentials_t xcred);
+
+int
+test_wrap (const char *test_name, int
+ (*test_function) (void * cls, const char *cipher_suite,
+ int proto_version), void *test_function_cls,
+ int daemon_flags, const char *cipher_suite, int proto_version, ...);
+#endif /* TLS_TEST_COMMON_H_ */
diff --git a/src/testcurl/https/tls_test_keys.h b/src/testcurl/https/tls_test_keys.h
new file mode 100644
index 0000000..b6e37ee
--- /dev/null
+++ b/src/testcurl/https/tls_test_keys.h
@@ -0,0 +1,173 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2006, 2007, 2008 Christian Grothoff (and other contributing authors)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef MHD_TLS_TEST_KEYS_H
+#define MHD_TLS_TEST_KEYS_H
+
+/* Test Certificates */
+
+/* Certificate Authority key */
+const char ca_key_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEowIBAAKCAQEA0EdlP613rjFvEj93tGo9fzBoKWU3CW+AbbfcJ397C89MyZ9J\n"
+ "rlxyLGfa6qVX7CFVNmzgWWfcl2tHlw/fZmWtf/SFgrlkldvuGyY8H3n2HuMsWz/E\n"
+ "h7n5VgwBX8NsP4eZNmikepxpr1mYx25K8FjnsKjAR9jGUSV8UfZ7VLIY0x/yqe+3\n"
+ "32oqc4D/wJbV1AwwvC5Xf9rvHJwcZg57eqbDCL/4GDDk7d9Gark4XK6ZG+FnnxQn\n"
+ "4a4jIdf4FoPp9s0EieHrHwYzs/uBqmfCSF4wXiaO8bmEwtbAsVbZH74Le7ggUbEe\n"
+ "o+jan9XK0dE88AIImGzgoBnlic/Rr7J8OWA+iwIDAQABAoIBAEICZqXkUdpsw2F6\n"
+ "qPMOergNPO3lrKg6ZO8hBs6j2fj3tcPuzljK5sqJDboxNejZ9Zo+rmnXf3Oj5fgL\n"
+ "6UcYMYEsm4W/QRA3uEJ1fzeQnT7Ty9KNprlHaSzquCLEGlIWJSo3xu0vFlWjJUcL\n"
+ "fwemfaOhD/OVUeEU6s5FOngwy6pZUsOajs3fNRtwBGuuXjniKZZlpSf2Wqu3xpHZ\n"
+ "31OF1V0ycUCGPPFtpmUCtnZhS9L8QBTkNtfTIdXv6SfoBRFm0oXb0uL5HGft6yc7\n"
+ "eYRXIscllQciqG3ymJ/y9o0E3A0YsBVauQyi7OEk+Kg8uoYOBkZCIY69hoN2Znlk\n"
+ "OY5S5Z0CgYEA3j8pRAJzvc827KcX4vJf05HYD4aCyaI80fNmx1DgXfglTSGLQ361\n"
+ "6i05YW8WtIvgkma3wF+jJOckBCW/7iq8wAX7Kz75WKGRyyTEb0wSfjx0G8grxX4d\n"
+ "7sTIAAOnQj5WT6E/bkqxQZAYnVtIPxKtSlwts0H/bjPVYwSFchHK7t8CgYEA7+ks\n"
+ "C0EMjF8CDeCfvbOUGiiqAvU3G20LEC3WlJM3AU+J9Jzp6AMkgaIA8J5oNdsbFBn4\n"
+ "N12JPOO+7WRUk6Av8bsh4faE36ThnHohgAL8guRU7jIXvsFyO5yiY7/o/0lES0/V\n"
+ "6xkh/Epj4MReuCGkiD9ifCVAo+dhHskeE9qbYdUCgYA4yBpa7eV0UUTPIcHQkew5\n"
+ "ucFh9hPkQDcZzP4tXlR0rbmaAz/5dp4zvmoyopdCeZpezS+VTtn3y7Y/+QUYbILc\n"
+ "7KpHWkeKhX0iUbp+VQlEh12C25mTU62CG3SdzFEnc5XJsoDqRNsUzSP80B2dP8BW\n"
+ "h0aFzg7csRGLwtP1WOZoMQKBgQCrgsKd+Q8Dexh421DXyX3jhZalLrEKxlXWZy60\n"
+ "YNo98aLqYRNHbpe2pR6O5nARsGYXZMlyq0flY9um0sc0Epyz79g1NoufZrxzpUw1\n"
+ "u+zRlnKxJtaa5KjJvRzKuvPTLYnJXXXM8Na/Cl+E3F3qvQJm9QlvPyKLCmsAGz+J\n"
+ "agsTUQKBgC0wqqJ6b1tbrAD8AVeeAn/IiP1rxYpc3x2s6ikFO2FMHXHC9wgrRPOc\n"
+ "mkokV+DrUOv3I/7jG8wQA/FmBUPy562a1bObIKzg6CPXzrN68AmNnOIVU+H8fdxI\n"
+ "iGyfT8WNpcRmtN11v34qXHwOWGQhpyyk2yNa8VIBSpkShq/EseZ1\n"
+ "-----END RSA PRIVATE KEY-----\n";
+
+/* Certificate Authority cert */
+const char ca_cert_pem[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIC6DCCAdKgAwIBAgIES0KCvTALBgkqhkiG9w0BAQUwFzEVMBMGA1UEAxMMdGVz\n"
+ "dF9jYV9jZXJ0MB4XDTEwMDEwNTAwMDcyNVoXDTQ1MDMxMjAwMDcyNVowFzEVMBMG\n"
+ "A1UEAxMMdGVzdF9jYV9jZXJ0MIIBHzALBgkqhkiG9w0BAQEDggEOADCCAQkCggEA\n"
+ "0EdlP613rjFvEj93tGo9fzBoKWU3CW+AbbfcJ397C89MyZ9JrlxyLGfa6qVX7CFV\n"
+ "NmzgWWfcl2tHlw/fZmWtf/SFgrlkldvuGyY8H3n2HuMsWz/Eh7n5VgwBX8NsP4eZ\n"
+ "Nmikepxpr1mYx25K8FjnsKjAR9jGUSV8UfZ7VLIY0x/yqe+332oqc4D/wJbV1Aww\n"
+ "vC5Xf9rvHJwcZg57eqbDCL/4GDDk7d9Gark4XK6ZG+FnnxQn4a4jIdf4FoPp9s0E\n"
+ "ieHrHwYzs/uBqmfCSF4wXiaO8bmEwtbAsVbZH74Le7ggUbEeo+jan9XK0dE88AII\n"
+ "mGzgoBnlic/Rr7J8OWA+iwIDAQABo0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud\n"
+ "DwEB/wQFAwMHBAAwHQYDVR0OBBYEFP2olB4s2T/xuoQ5pT2RKojFwZo2MAsGCSqG\n"
+ "SIb3DQEBBQOCAQEAebD5m+vZkVXa8y+QZ5GtsiR9gpH+LKtdWBjk1kmfSgvQI/xA\n"
+ "aDCV/9BhdNGIBOTYGkln8urWd7g2Mj3TwKEAfNTUFpAsrBAlSSLTGYCSt72S2NsS\n"
+ "L/qUxmj1W6X95UHXCo49mSZx3LlaY3mz1L87gq/kK0XpzA3g2uF25jt84RvshsXy\n"
+ "clOc+eRrVETqFZqer96WB7kzFTv+qmROQKmW8X4a2A5r5Jl4vRwOz5/rEeB9Qs0K\n"
+ "rmK8+5HgvWd80WB8BtfFtZfoY/hHVM8nLD3ELVJrOKiTeIACunQFyT5lV0QkdmSA\n"
+ "CGInU7jzs8nu+s2avf6j+eVZUbVJ+dFMApTJgg==\n"
+ "-----END CERTIFICATE-----\n";
+
+/* test server CA signed certificates */
+const char srv_signed_cert_pem[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIDGzCCAgWgAwIBAgIES0KCvTALBgkqhkiG9w0BAQUwFzEVMBMGA1UEAxMMdGVz\n"
+ "dF9jYV9jZXJ0MB4XDTEwMDEwNTAwMDcyNVoXDTQ1MDMxMjAwMDcyNVowFzEVMBMG\n"
+ "A1UEAxMMdGVzdF9jYV9jZXJ0MIIBHzALBgkqhkiG9w0BAQEDggEOADCCAQkCggEA\n"
+ "vfTdv+3fgvVTKRnP/HVNG81cr8TrUP/iiyuve/THMzvFXhCW+K03KwEku55QvnUn\n"
+ "dwBfU/ROzLlv+5hotgiDRNFT3HxurmhouySBrJNJv7qWp8ILq4sw32vo0fbMu5BZ\n"
+ "F49bUXK9L3kW2PdhTtSQPWHEzNrCxO+YgCilKHkY3vQNfdJ020Q5EAAEseD1YtWC\n"
+ "IpRvJzYlZMpjYB1ubTl24kwrgOKUJYKqM4jmF4DVQp4oOK/6QYGGh1QmHRPAy3CB\n"
+ "II6sbb+sZT9cAqU6GYQVB35lm4XAgibXV6KgmpVxVQQ69U6xyoOl204xuekZOaG9\n"
+ "RUPId74Rtmwfi1TLbBzo2wIDAQABo3YwdDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM\n"
+ "MAoGCCsGAQUFBwMBMA8GA1UdDwEB/wQFAwMHIAAwHQYDVR0OBBYEFOFi4ilKOP1d\n"
+ "XHlWCMwmVKr7mgy8MB8GA1UdIwQYMBaAFP2olB4s2T/xuoQ5pT2RKojFwZo2MAsG\n"
+ "CSqGSIb3DQEBBQOCAQEAHVWPxazupbOkG7Did+dY9z2z6RjTzYvurTtEKQgzM2Vz\n"
+ "GQBA+3pZ3c5mS97fPIs9hZXfnQeelMeZ2XP1a+9vp35bJjZBBhVH+pqxjCgiUflg\n"
+ "A3Zqy0XwwVCgQLE2HyaU3DLUD/aeIFK5gJaOSdNTXZLv43K8kl4cqDbMeRpVTbkt\n"
+ "YmG4AyEOYRNKGTqMEJXJoxD5E3rBUNrVI/XyTjYrulxbNPcMWEHKNeeqWpKDYTFo\n"
+ "Bb01PCthGXiq/4A2RLAFosadzRa8SBpoSjPPfZ0b2w4MJpReHqKbR5+T2t6hzml6\n"
+ "4ToyOKPDmamiTuN5KzLN3cw7DQlvWMvqSOChPLnA3Q==\n"
+ "-----END CERTIFICATE-----\n";
+
+/* test server key */
+const char srv_signed_key_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEowIBAAKCAQEAvfTdv+3fgvVTKRnP/HVNG81cr8TrUP/iiyuve/THMzvFXhCW\n"
+ "+K03KwEku55QvnUndwBfU/ROzLlv+5hotgiDRNFT3HxurmhouySBrJNJv7qWp8IL\n"
+ "q4sw32vo0fbMu5BZF49bUXK9L3kW2PdhTtSQPWHEzNrCxO+YgCilKHkY3vQNfdJ0\n"
+ "20Q5EAAEseD1YtWCIpRvJzYlZMpjYB1ubTl24kwrgOKUJYKqM4jmF4DVQp4oOK/6\n"
+ "QYGGh1QmHRPAy3CBII6sbb+sZT9cAqU6GYQVB35lm4XAgibXV6KgmpVxVQQ69U6x\n"
+ "yoOl204xuekZOaG9RUPId74Rtmwfi1TLbBzo2wIDAQABAoIBADu09WSICNq5cMe4\n"
+ "+NKCLlgAT1NiQpLls1gKRbDhKiHU9j8QWNvWWkJWrCya4QdUfLCfeddCMeiQmv3K\n"
+ "lJMvDs+5OjJSHFoOsGiuW2Ias7IjnIojaJalfBml6frhJ84G27IXmdz6gzOiTIer\n"
+ "DjeAgcwBaKH5WwIay2TxIaScl7AwHBauQkrLcyb4hTmZuQh6ArVIN6+pzoVuORXM\n"
+ "bpeNWl2l/HSN3VtUN6aCAKbN/X3o0GavCCMn5Fa85uJFsab4ss/uP+2PusU71+zP\n"
+ "sBm6p/2IbGvF5k3VPDA7X5YX61sukRjRBihY8xSnNYx1UcoOsX6AiPnbhifD8+xQ\n"
+ "Tlf8oJUCgYEA0BTfzqNpr9Wxw5/QXaSdw7S/0eP5a0C/nwURvmfSzuTD4equzbEN\n"
+ "d+dI/s2JMxrdj/I4uoAfUXRGaabevQIjFzC9uyE3LaOyR2zhuvAzX+vVcs6bSXeU\n"
+ "pKpCAcN+3Z3evMaX2f+z/nfSUAl2i4J2R+/LQAWJW4KwRky/m+cxpfUCgYEA6bN1\n"
+ "b73bMgM8wpNt6+fcmS+5n0iZihygQ2U2DEud8nZJL4Nrm1dwTnfZfJBnkGj6+0Q0\n"
+ "cOwj2KS0/wcEdJBP0jucU4v60VMhp75AQeHqidIde0bTViSRo3HWKXHBIFGYoU3T\n"
+ "LyPyKndbqsOObnsFXHn56Nwhr2HLf6nw4taGQY8CgYBoSW36FLCNbd6QGvLFXBGt\n"
+ "2lMhEM8az/K58kJ4WXSwOLtr6MD/WjNT2tkcy0puEJLm6BFCd6A6pLn9jaKou/92\n"
+ "SfltZjJPb3GUlp9zn5tAAeSSi7YMViBrfuFiHObij5LorefBXISLjuYbMwL03MgH\n"
+ "Ocl2JtA2ywMp2KFXs8GQWQKBgFyIVv5ogQrbZ0pvj31xr9HjqK6d01VxIi+tOmpB\n"
+ "4ocnOLEcaxX12BzprW55ytfOCVpF1jHD/imAhb3YrHXu0fwe6DXYXfZV4SSG2vB7\n"
+ "IB9z14KBN5qLHjNGFpMQXHSMek+b/ftTU0ZnPh9uEM5D3YqRLVd7GcdUhHvG8P8Q\n"
+ "C9aXAoGBAJtID6h8wOGMP0XYX5YYnhlC7dOLfk8UYrzlp3xhqVkzKthTQTj6wx9R\n"
+ "GtC4k7U1ki8oJsfcIlBNXd768fqDVWjYju5rzShMpo8OCTS6ipAblKjCxPPVhIpv\n"
+ "tWPlbSn1qj6wylstJ5/3Z+ZW5H4wIKp5jmLiioDhcP0L/Ex3Zx8O\n"
+ "-----END RSA PRIVATE KEY-----\n";
+
+/* test server self signed certificates */
+const char srv_self_signed_cert_pem[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIC+jCCAeSgAwIBAgIES0KCvTALBgkqhkiG9w0BAQUwFzEVMBMGA1UEAxMMdGVz\n"
+ "dF9jYV9jZXJ0MB4XDTEwMDEwNTAwMDcyNVoXDTQ1MDMxMjAwMDcyNVowFzEVMBMG\n"
+ "A1UEAxMMdGVzdF9jYV9jZXJ0MIIBHzALBgkqhkiG9w0BAQEDggEOADCCAQkCggEA\n"
+ "tDEagv3p9OUhUL55jMucxjNK9N5cuozhcnrwDfBSU6oVrqm5kPqO1I7Cggzw68Y5\n"
+ "jhTcBi4FXmYOZppm1R3MhSJ5JSi/67Q7X4J5rnJLXYGN27qjMpnoGQ/2xmsNG/is\n"
+ "i+h/2vbtPU+WP9SEJnTfPLLpZ7KqCAk7FUUzKsuLx3/SOKtdkrWxPKwYTgnDEN6D\n"
+ "JL7tEzCnG5DFc4mQ7YW9PaRdC3rS1T8PvQ3jB2BUnohM0cFvKRuiU35tU7h7CPbL\n"
+ "4L66VglXoiwqmgcrwI2U968bD0+wRQ5c5bzNoshJOzN6CTMh1IhbklSh/Z6FA/e8\n"
+ "hj0yVo2tdllXuJGVs3PIEwIDAQABo1UwUzAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM\n"
+ "MAoGCCsGAQUFBwMBMA8GA1UdDwEB/wQFAwMHIAAwHQYDVR0OBBYEFDfU7pAv9LYn\n"
+ "n7jb4WHl4+Vgi2FnMAsGCSqGSIb3DQEBBQOCAQEAkaembPQMmv6OOjbIod8zTatr\n"
+ "x5Bwkwp3TOE1NRyy2OytzFIYRUkNrZYlcmrxcbNNycIK41CNVXbriFCF8gcmIq9y\n"
+ "vaKZn8Gcy+vGggv+1BP9IAPBGKRwSi0wmq9JoGE8hx+qqTpRSdfbM/cps/09hicO\n"
+ "0EIR7kWEbvnpMBcMKYOtYE9Gce7rdSMWVAsKc174xn8vW6TxCUvmWFv5DPg5HG1v\n"
+ "y1SUX73qafRo+W6FN4UC/DHfwRhF8RSKEnVbmgDVCs6GHdKBjU2qRgYyj6nWZqK1\n"
+ "XFUTWgia+Fl3D9vlsXaFcSZKA0Bq1eojl0B0AfeYAxTFwPWXscKvt/bXZfH8bg==\n"
+ "-----END CERTIFICATE-----\n";
+
+/* test server key */
+const char srv_key_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEpAIBAAKCAQEAtDEagv3p9OUhUL55jMucxjNK9N5cuozhcnrwDfBSU6oVrqm5\n"
+ "kPqO1I7Cggzw68Y5jhTcBi4FXmYOZppm1R3MhSJ5JSi/67Q7X4J5rnJLXYGN27qj\n"
+ "MpnoGQ/2xmsNG/isi+h/2vbtPU+WP9SEJnTfPLLpZ7KqCAk7FUUzKsuLx3/SOKtd\n"
+ "krWxPKwYTgnDEN6DJL7tEzCnG5DFc4mQ7YW9PaRdC3rS1T8PvQ3jB2BUnohM0cFv\n"
+ "KRuiU35tU7h7CPbL4L66VglXoiwqmgcrwI2U968bD0+wRQ5c5bzNoshJOzN6CTMh\n"
+ "1IhbklSh/Z6FA/e8hj0yVo2tdllXuJGVs3PIEwIDAQABAoIBAAEtcg+LFLGtoxjq\n"
+ "b+tFttBJfbRcfdG6ocYqBGmUXF+MgFs573DHX3sHNOQxlaNHtSgIclF1eYgNZFFt\n"
+ "VLIoBFTzfEQXoFosPUDoEuqVMeXLttmD7P2jwL780XJLZ4Xj6GY07npq1iGBcEZf\n"
+ "yCcdoyGkr9jgc5Auyis8DStGg/jfUBC4NBvF0GnuuNPAdYRPKUpKw9EatI+FdMjy\n"
+ "BuroD90fhdkK8EwMEVb9P17bdIc1MCIZFpUE9YHjVdK/oxCUhQ8KRfdbI4JU5Zh3\n"
+ "UtO6Jm2wFuP3VmeVpPvE/C2rxI70pyl6HMSiFGNc0rhJYCQ+yhohWj7nZ67H4vLx\n"
+ "plv5LxkCgYEAz7ewou8oFafDAMNoxaqKudvUg+lxXewdLDKaYBF5ACi9uAPCJ+v7\n"
+ "M5c/fvPFn/XHzo7xaXbtTAH3Z5xzBs+80OsvL+e1Ut4xR+ELRkybknh/s2wQeABk\n"
+ "Kb0vA59ukQGj12LV5phZMaVoXe6KJ7hZnN62d3K6m1wGE/k58i4pPLUCgYEA3hN8\n"
+ "G95zW7g0jVdSr+KUeVmephph9yh8Yb+3I3ojwOIv6d45TopGx8pFZlnBAMZf1ZQx\n"
+ "DIhzJNnaqZy/4w7RNaOGWnPA/5f+MIoHBiLGEEmfHC3lt087Yp9OuwDUHwpETYdV\n"
+ "o+KBCvVh60Et3bZUgF/1k/3YXxn8J5dsmJsjNqcCgYBLflyRa1BrRnTGMz9CEDCp\n"
+ "Si9b3h1Y4Hbd2GppHhCXMTd6yMrpDYhYANGQB3M9Juv+s88j4JhwNoq/uonH4Pqk\n"
+ "B8Y3qAQr4RuSH0WkwDUOsALhqBX4N1QwI1USAQEDbNAqeP5698X7GD3tXcQSmZrg\n"
+ "O8WfdjBCRNjkq4EW9xX/vQKBgQDONtmwJ0iHiu2BseyeVo/4fzfKlgUSNQ4K1rOA\n"
+ "xhIdMeu8Bxa/z7caHsGC4SVPSuYCtbE2Kh6BwapChcPJXCD45fgEViiJLuJiwEj1\n"
+ "caTpyvNsf1IoffJvCe9ZxtMyX549P8ZOgC3Dt0hN5CBrGLwu2Ox5l+YrqT10pi+5\n"
+ "JZX1UQKBgQCrcXrdkkDAc/a4+PxNRpJRLcU4fhv8/lr+UWItE8eUe7bd25bTQfQm\n"
+ "VpNKc/kAJ66PjIED6fy3ADhd2y4naT2a24uAgQ/M494J68qLnGh6K4JU/09uxR2v\n"
+ "1i2q/4FNLdFFk1XP4iNnTHRLZ+NYr2p5Y9RcvQfTjOauz8Ahav0lyg==\n"
+ "-----END RSA PRIVATE KEY-----\n";
+
+#endif
diff --git a/src/testcurl/perf_get.c b/src/testcurl/perf_get.c
new file mode 100644
index 0000000..1e42f7d
--- /dev/null
+++ b/src/testcurl/perf_get.c
@@ -0,0 +1,532 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009, 2011 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file perf_get.c
+ * @brief benchmark simple GET operations (sequential access).
+ * Note that we run libcurl in the same process at the
+ * same time, so the execution time given is the combined
+ * time for both MHD and libcurl; it is quite possible
+ * that more time is spend with libcurl than with MHD,
+ * so the performance scores calculated with this code
+ * should NOT be used to compare with other HTTP servers
+ * (since MHD is actually better); only the relative
+ * scores between MHD versions are meaningful.
+ * Furthermore, this code ONLY tests MHD processing
+ * a single request at a time. This is again
+ * not universally meaningful (i.e. when comparing
+ * multithreaded vs. single-threaded or select/poll).
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "gauger.h"
+
+#ifndef WINDOWS
+#include <unistd.h>
+#include <sys/socket.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+/**
+ * How many rounds of operations do we do for each
+ * test?
+ */
+#define ROUNDS 500
+
+/**
+ * Do we use HTTP 1.1?
+ */
+static int oneone;
+
+/**
+ * Response to return (re-used).
+ */
+static struct MHD_Response *response;
+
+/**
+ * Time this round was started.
+ */
+static unsigned long long start_time;
+
+
+/**
+ * Get the current timestamp
+ *
+ * @return current time in ms
+ */
+static unsigned long long
+now ()
+{
+ struct timeval tv;
+
+ gettimeofday (&tv, NULL);
+ return (((unsigned long long) tv.tv_sec * 1000LL) +
+ ((unsigned long long) tv.tv_usec / 1000LL));
+}
+
+
+/**
+ * Start the timer.
+ */
+static void
+start_timer()
+{
+ start_time = now ();
+}
+
+
+/**
+ * Stop the timer and report performance
+ *
+ * @param desc description of the threading mode we used
+ */
+static void
+stop (const char *desc)
+{
+ double rps = ((double) (ROUNDS * 1000)) / ((double) (now() - start_time));
+
+ fprintf (stderr,
+ "Sequential GETs using %s: %f %s\n",
+ desc,
+ rps,
+ "requests/s");
+ GAUGER (desc,
+ "Sequential GETs",
+ rps,
+ "requests/s");
+}
+
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+
+static size_t
+copyBuffer (void *ptr,
+ size_t size, size_t nmemb,
+ void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testInternalGet (int port, int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ unsigned int i;
+ char url[64];
+
+ sprintf(url, "http://127.0.0.1:%d/hello_world", port);
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ start_timer ();
+ for (i=0;i<ROUNDS;i++)
+ {
+ cbc.pos = 0;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ }
+ stop (poll_flag == MHD_USE_POLL ? "internal poll" :
+ poll_flag == MHD_USE_EPOLL_LINUX_ONLY ? "internal epoll" : "internal select");
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+
+static int
+testMultithreadedGet (int port, int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ unsigned int i;
+ char url[64];
+
+ sprintf(url, "http://127.0.0.1:%d/hello_world", port);
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | poll_flag,
+ port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ start_timer ();
+ for (i=0;i<ROUNDS;i++)
+ {
+ cbc.pos = 0;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ }
+ stop ((poll_flag & MHD_USE_POLL) ? "thread with poll" :
+ (poll_flag & MHD_USE_EPOLL_LINUX_ONLY) ? "thread with epoll" : "thread with select");
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testMultithreadedPoolGet (int port, int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ unsigned int i;
+ char url[64];
+
+ sprintf(url, "http://127.0.0.1:%d/hello_world", port);
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ port, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ start_timer ();
+ for (i=0;i<ROUNDS;i++)
+ {
+ cbc.pos = 0;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ }
+ stop (0 != (poll_flag & MHD_USE_POLL) ? "thread pool with poll" :
+ 0 != (poll_flag & MHD_USE_EPOLL_LINUX_ONLY) ? "thread pool with epoll" : "thread pool with select");
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testExternalGet (int port)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+ unsigned int i;
+ char url[64];
+
+ sprintf(url, "http://127.0.0.1:%d/hello_world", port);
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ start_timer ();
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ for (i=0;i<ROUNDS;i++)
+ {
+ cbc.pos = 0;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (c != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ }
+ /* two possibilities here; as select sets are
+ tiny, this makes virtually no difference
+ in actual runtime right now, even though the
+ number of select calls is virtually cut in half
+ (and 'select' is the most expensive of our system
+ calls according to 'strace') */
+ if (0)
+ MHD_run (d);
+ else
+ MHD_run_from_select (d, &rs, &ws, &es);
+ }
+ if (NULL != c)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ fprintf (stderr, "Timeout!?\n");
+ }
+ }
+ stop ("external select");
+ if (multi != NULL)
+ {
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ int port = 1081;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ response = MHD_create_response_from_buffer (strlen ("/hello_world"),
+ "/hello_world",
+ MHD_RESPMEM_MUST_COPY);
+ errorCount += testExternalGet (port++);
+ errorCount += testInternalGet (port++, 0);
+ errorCount += testMultithreadedGet (port++, 0);
+ errorCount += testMultithreadedPoolGet (port++, 0);
+ if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_POLL))
+ {
+ errorCount += testInternalGet(port++, MHD_USE_POLL);
+ errorCount += testMultithreadedGet(port++, MHD_USE_POLL);
+ errorCount += testMultithreadedPoolGet(port++, MHD_USE_POLL);
+ }
+ if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_EPOLL))
+ {
+ errorCount += testInternalGet(port++, MHD_USE_EPOLL_LINUX_ONLY);
+ errorCount += testMultithreadedPoolGet(port++, MHD_USE_EPOLL_LINUX_ONLY);
+ }
+ MHD_destroy_response (response);
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/perf_get_concurrent.c b/src/testcurl/perf_get_concurrent.c
new file mode 100644
index 0000000..28559ee
--- /dev/null
+++ b/src/testcurl/perf_get_concurrent.c
@@ -0,0 +1,364 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009, 2011 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file perf_get_concurrent.c
+ * @brief benchmark concurrent GET operations
+ * Note that we run libcurl on the machine at the
+ * same time, so the execution time may be influenced
+ * by the concurrent activity; it is quite possible
+ * that more time is spend with libcurl than with MHD,
+ * so the performance scores calculated with this code
+ * should NOT be used to compare with other HTTP servers
+ * (since MHD is actually better); only the relative
+ * scores between MHD versions are meaningful.
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "gauger.h"
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+/**
+ * How many rounds of operations do we do for each
+ * test (total number of requests will be ROUNDS * PAR).
+ */
+#define ROUNDS 500
+
+/**
+ * How many requests do we do in parallel?
+ */
+#define PAR CPU_COUNT
+
+/**
+ * Do we use HTTP 1.1?
+ */
+static int oneone;
+
+/**
+ * Response to return (re-used).
+ */
+static struct MHD_Response *response;
+
+/**
+ * Time this round was started.
+ */
+static unsigned long long start_time;
+
+
+/**
+ * Get the current timestamp
+ *
+ * @return current time in ms
+ */
+static unsigned long long
+now ()
+{
+ struct timeval tv;
+
+ gettimeofday (&tv, NULL);
+ return (((unsigned long long) tv.tv_sec * 1000LL) +
+ ((unsigned long long) tv.tv_usec / 1000LL));
+}
+
+
+/**
+ * Start the timer.
+ */
+static void
+start_timer()
+{
+ start_time = now ();
+}
+
+
+/**
+ * Stop the timer and report performance
+ *
+ * @param desc description of the threading mode we used
+ */
+static void
+stop (const char *desc)
+{
+ double rps = ((double) (PAR * ROUNDS * 1000)) / ((double) (now() - start_time));
+
+ fprintf (stderr,
+ "Parallel GETs using %s: %f %s\n",
+ desc,
+ rps,
+ "requests/s");
+ GAUGER (desc,
+ "Parallel GETs",
+ rps,
+ "requests/s");
+}
+
+
+static size_t
+copyBuffer (void *ptr,
+ size_t size, size_t nmemb,
+ void *ctx)
+{
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static pid_t
+do_gets (int port)
+{
+ pid_t ret;
+ CURL *c;
+ CURLcode errornum;
+ unsigned int i;
+ unsigned int j;
+ pid_t par[PAR];
+ char url[64];
+
+ sprintf(url, "http://127.0.0.1:%d/hello_world", port);
+
+ ret = fork ();
+ if (ret == -1) abort ();
+ if (ret != 0)
+ return ret;
+ for (j=0;j<PAR;j++)
+ {
+ par[j] = fork ();
+ if (par[j] == 0)
+ {
+ for (i=0;i<ROUNDS;i++)
+ {
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, NULL);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ _exit (1);
+ }
+ curl_easy_cleanup (c);
+ }
+ _exit (0);
+ }
+ }
+ for (j=0;j<PAR;j++)
+ waitpid (par[j], NULL, 0);
+ _exit (0);
+}
+
+
+static void
+join_gets (pid_t pid)
+{
+ int status;
+
+ status = 1;
+ waitpid (pid, &status, 0);
+ if (0 != status)
+ abort ();
+}
+
+
+static int
+testInternalGet (int port, int poll_flag)
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ start_timer ();
+ join_gets (do_gets (port));
+ stop (poll_flag ? "internal poll" : "internal select");
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+static int
+testMultithreadedGet (int port, int poll_flag)
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | poll_flag,
+ port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ start_timer ();
+ join_gets (do_gets (port));
+ stop (poll_flag ? "thread with poll" : "thread with select");
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedPoolGet (int port, int poll_flag)
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ port, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ start_timer ();
+ join_gets (do_gets (port));
+ stop (poll_flag ? "thread pool with poll" : "thread pool with select");
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testExternalGet (int port)
+{
+ struct MHD_Daemon *d;
+ pid_t pid;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ struct timeval tv;
+ MHD_UNSIGNED_LONG_LONG tt;
+ int tret;
+
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ start_timer ();
+ pid = do_gets (port);
+ while (0 == waitpid (pid, NULL, WNOHANG))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tret = MHD_get_timeout (d, &tt);
+ if (MHD_YES != tret) tt = 1;
+ tv.tv_sec = tt / 1000;
+ tv.tv_usec = 1000 * (tt % 1000);
+ if (-1 == select (max + 1, &rs, &ws, &es, &tv))
+ {
+ if (EINTR == errno)
+ continue;
+ fprintf (stderr,
+ "select failed: %s\n",
+ strerror (errno));
+ break;
+ }
+ MHD_run (d);
+ }
+ stop ("external select");
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ int port = 1081;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ response = MHD_create_response_from_buffer (strlen ("/hello_world"),
+ "/hello_world",
+ MHD_RESPMEM_MUST_COPY);
+ errorCount += testInternalGet (port++, 0);
+ errorCount += testMultithreadedGet (port++, 0);
+ errorCount += testMultithreadedPoolGet (port++, 0);
+ errorCount += testExternalGet (port++);
+#ifndef WINDOWS
+ errorCount += testInternalGet (port++, MHD_USE_POLL);
+ errorCount += testMultithreadedGet (port++, MHD_USE_POLL);
+ errorCount += testMultithreadedPoolGet (port++, MHD_USE_POLL);
+#endif
+#if EPOLL_SUPPORT
+ errorCount += testInternalGet (port++, MHD_USE_EPOLL_LINUX_ONLY);
+ errorCount += testMultithreadedPoolGet (port++, MHD_USE_EPOLL_LINUX_ONLY);
+#endif
+ MHD_destroy_response (response);
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_callback.c b/src/testcurl/test_callback.c
new file mode 100644
index 0000000..83233e6
--- /dev/null
+++ b/src/testcurl/test_callback.c
@@ -0,0 +1,189 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009, 2011 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_callback.c
+ * @brief Testcase for MHD not calling the callback too often
+ * @author Jan Seeger
+ * @author Christian Grothoff
+ */
+
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+
+struct callback_closure {
+ unsigned int called;
+};
+
+
+static ssize_t
+called_twice(void *cls, uint64_t pos, char *buf, size_t max)
+{
+ struct callback_closure *cls2 = cls;
+
+ if (cls2->called == 0)
+ {
+ memset(buf, 0, max);
+ strcat(buf, "test");
+ cls2->called = 1;
+ return strlen(buf);
+ }
+ if (cls2->called == 1)
+ {
+ cls2->called = 2;
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+ fprintf(stderr,
+ "Handler called after returning END_OF_STREAM!\n");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+}
+
+
+static int
+callback(void *cls, struct MHD_Connection *connection, const char *url,
+ const char *method, const char *version, const char *upload_data,
+ size_t *upload_data_size, void **con_cls) {
+ struct callback_closure *cbc = calloc(1, sizeof(struct callback_closure));
+ struct MHD_Response *r;
+
+ r = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, 1024,
+ &called_twice, cbc,
+ &free);
+ MHD_queue_response(connection, 200, r);
+ MHD_destroy_response(r);
+ return MHD_YES;
+}
+
+
+static size_t
+discard_buffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ return size * nmemb;
+}
+
+
+int main(int argc, char **argv)
+{
+ struct MHD_Daemon *d;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ CURL *c;
+ CURLM *multi;
+ CURLMcode mret;
+ struct CURLMsg *msg;
+ int running;
+ struct timeval tv;
+ int extra;
+
+ d = MHD_start_daemon(0,
+ 8000,
+ NULL,
+ NULL,
+ callback,
+ NULL,
+ MHD_OPTION_END);
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:8000/");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &discard_buffer);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ extra = 10;
+ while ( (c != NULL) || (--extra > 0) )
+ {
+ max = MHD_INVALID_SOCKET;
+ FD_ZERO(&ws);
+ FD_ZERO(&rs);
+ FD_ZERO(&es);
+ curl_multi_perform (multi, &running);
+ if (NULL != multi)
+ {
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 3;
+ }
+ }
+ if (MHD_YES !=
+ MHD_get_fdset(d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select(max + 1, &rs, &ws, &es, &tv);
+ if (NULL != multi)
+ {
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ }
+ MHD_run(d);
+ }
+ MHD_stop_daemon(d);
+ return 0;
+}
diff --git a/src/testcurl/test_concurrent_stop.c b/src/testcurl/test_concurrent_stop.c
new file mode 100644
index 0000000..60cc98d
--- /dev/null
+++ b/src/testcurl/test_concurrent_stop.c
@@ -0,0 +1,228 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009, 2011, 2015 Christian Grothoff
+
+ libmicrohttpd 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 3, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_concurrent_stop.c
+ * @brief test stopping server while concurrent GETs are ongoing
+ * @author Christian Grothoff
+ */
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "gauger.h"
+
+#ifdef CPU_COUNT
+#undef CPU_COUNT
+#endif
+#define CPU_COUNT 40
+
+
+/**
+ * How many rounds of operations do we do for each
+ * test (total number of requests will be ROUNDS * PAR).
+ */
+#define ROUNDS 50000
+
+/**
+ * How many requests do we do in parallel?
+ */
+#define PAR CPU_COUNT
+
+/**
+ * Do we use HTTP 1.1?
+ */
+static int oneone;
+
+/**
+ * Response to return (re-used).
+ */
+static struct MHD_Response *response;
+
+
+static size_t
+copyBuffer (void *ptr,
+ size_t size, size_t nmemb,
+ void *ctx)
+{
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static pid_t
+do_gets (int port)
+{
+ pid_t ret;
+ CURL *c;
+ CURLcode errornum;
+ unsigned int i;
+ unsigned int j;
+ pid_t par[PAR];
+ char url[64];
+
+ sprintf(url, "http://127.0.0.1:%d/hello_world", port);
+
+ ret = fork ();
+ if (ret == -1) abort ();
+ if (ret != 0)
+ return ret;
+ for (j=0;j<PAR;j++)
+ {
+ par[j] = fork ();
+ if (par[j] == 0)
+ {
+ for (i=0;i<ROUNDS;i++)
+ {
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, NULL);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ curl_easy_cleanup (c);
+ _exit (1);
+ }
+ curl_easy_cleanup (c);
+ }
+ _exit (0);
+ }
+ }
+ for (j=0;j<PAR;j++)
+ waitpid (par[j], NULL, 0);
+ _exit (0);
+}
+
+
+static void
+join_gets (pid_t pid)
+{
+ int status;
+
+ status = 1;
+ waitpid (pid, &status, 0);
+ if (0 != status)
+ abort ();
+}
+
+
+static int
+testMultithreadedGet (int port, int poll_flag)
+{
+ struct MHD_Daemon *d;
+ pid_t p;
+
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | poll_flag,
+ port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ p = do_gets (port);
+ sleep (1);
+ MHD_stop_daemon (d);
+ join_gets (p);
+ return 0;
+}
+
+
+static int
+testMultithreadedPoolGet (int port, int poll_flag)
+{
+ struct MHD_Daemon *d;
+ pid_t p;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ port,
+ NULL, NULL,
+ &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ p = do_gets (port);
+ sleep (1);
+ MHD_stop_daemon (d);
+ join_gets (p);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ int port = 1081;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ response = MHD_create_response_from_buffer (strlen ("/hello_world"),
+ "/hello_world",
+ MHD_RESPMEM_MUST_COPY);
+ errorCount += testMultithreadedGet (port++, 0);
+ errorCount += testMultithreadedPoolGet (port++, 0);
+ MHD_destroy_response (response);
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_digestauth.c b/src/testcurl/test_digestauth.c
new file mode 100644
index 0000000..255e086
--- /dev/null
+++ b/src/testcurl/test_digestauth.c
@@ -0,0 +1,247 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2010 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_digestauth.c
+ * @brief Testcase for libmicrohttpd Digest Auth
+ * @author Amr Ali
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#ifdef HAVE_GCRYPT_H
+#include <gcrypt.h>
+#endif
+
+#ifndef WINDOWS
+#include <sys/socket.h>
+#include <unistd.h>
+#else
+#include <wincrypt.h>
+#endif
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>Access granted</body></html>"
+
+#define DENIED "<html><head><title>libmicrohttpd demo</title></head><body>Access denied</body></html>"
+
+#define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4"
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ struct MHD_Response *response;
+ char *username;
+ const char *password = "testpass";
+ const char *realm = "test@example.com";
+ int ret;
+
+ username = MHD_digest_auth_get_username(connection);
+ if ( (username == NULL) ||
+ (0 != strcmp (username, "testuser")) )
+ {
+ response = MHD_create_response_from_buffer(strlen (DENIED),
+ DENIED,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_auth_fail_response(connection, realm,
+ MY_OPAQUE,
+ response,
+ MHD_NO);
+ MHD_destroy_response(response);
+ return ret;
+ }
+ ret = MHD_digest_auth_check(connection, realm,
+ username,
+ password,
+ 300);
+ free(username);
+ if ( (ret == MHD_INVALID_NONCE) ||
+ (ret == MHD_NO) )
+ {
+ response = MHD_create_response_from_buffer(strlen (DENIED),
+ DENIED,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ return MHD_NO;
+ ret = MHD_queue_auth_fail_response(connection, realm,
+ MY_OPAQUE,
+ response,
+ (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO);
+ MHD_destroy_response(response);
+ return ret;
+ }
+ response = MHD_create_response_from_buffer(strlen(PAGE), PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+ return ret;
+}
+
+
+static int
+testDigestAuth ()
+{
+ int fd;
+ CURL *c;
+ CURLcode errornum;
+ struct MHD_Daemon *d;
+ struct CBC cbc;
+ size_t len;
+ size_t off = 0;
+ char buf[2048];
+ char rnd[8];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+#ifndef WINDOWS
+ fd = open("/dev/urandom", O_RDONLY);
+ if (-1 == fd)
+ {
+ fprintf(stderr, "Failed to open `%s': %s\n",
+ "/dev/urandom",
+ strerror(errno));
+ return 1;
+ }
+ while (off < 8)
+ {
+ len = read(fd, rnd, 8);
+ if (len == -1)
+ {
+ fprintf(stderr, "Failed to read `%s': %s\n",
+ "/dev/urandom",
+ strerror(errno));
+ (void) close(fd);
+ return 1;
+ }
+ off += len;
+ }
+ (void) close(fd);
+#else
+ {
+ HCRYPTPROV cc;
+ BOOL b;
+ b = CryptAcquireContext (&cc, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
+ if (b == 0)
+ {
+ fprintf (stderr, "Failed to acquire crypto provider context: %lu\n",
+ GetLastError ());
+ return 1;
+ }
+ b = CryptGenRandom (cc, 8, rnd);
+ if (b == 0)
+ {
+ fprintf (stderr, "Failed to generate 8 random bytes: %lu\n",
+ GetLastError ());
+ }
+ CryptReleaseContext (cc, 0);
+ if (b == 0)
+ return 1;
+ }
+#endif
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1337, NULL, NULL, &ahc_echo, PAGE,
+ MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof (rnd), rnd,
+ MHD_OPTION_NONCE_NC_SIZE, 300,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1337/");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
+ curl_easy_setopt (c, CURLOPT_USERPWD, "testuser:testpass");
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen (PAGE))
+ return 4;
+ if (0 != strncmp (PAGE, cbc.buf, strlen (PAGE)))
+ return 8;
+ return 0;
+}
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+#ifdef HAVE_GCRYPT_H
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+#endif
+if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testDigestAuth ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_digestauth_with_arguments.c b/src/testcurl/test_digestauth_with_arguments.c
new file mode 100644
index 0000000..51868ab
--- /dev/null
+++ b/src/testcurl/test_digestauth_with_arguments.c
@@ -0,0 +1,245 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2010, 2012 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_digestauth_with_arguments.c
+ * @brief Testcase for libmicrohttpd Digest Auth with arguments
+ * @author Amr Ali
+ */
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#ifdef HAVE_GCRYPT_H
+#include <gcrypt.h>
+#endif
+
+#ifndef WINDOWS
+#include <sys/socket.h>
+#include <unistd.h>
+#else
+#include <wincrypt.h>
+#endif
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>Access granted</body></html>"
+
+#define DENIED "<html><head><title>libmicrohttpd demo</title></head><body>Access denied</body></html>"
+
+#define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4"
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ struct MHD_Response *response;
+ char *username;
+ const char *password = "testpass";
+ const char *realm = "test@example.com";
+ int ret;
+
+ username = MHD_digest_auth_get_username(connection);
+ if ( (username == NULL) ||
+ (0 != strcmp (username, "testuser")) )
+ {
+ response = MHD_create_response_from_buffer(strlen (DENIED),
+ DENIED,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_auth_fail_response(connection, realm,
+ MY_OPAQUE,
+ response,
+ MHD_NO);
+ MHD_destroy_response(response);
+ return ret;
+ }
+ ret = MHD_digest_auth_check(connection, realm,
+ username,
+ password,
+ 300);
+ free(username);
+ if ( (ret == MHD_INVALID_NONCE) ||
+ (ret == MHD_NO) )
+ {
+ response = MHD_create_response_from_buffer(strlen (DENIED),
+ DENIED,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ return MHD_NO;
+ ret = MHD_queue_auth_fail_response(connection, realm,
+ MY_OPAQUE,
+ response,
+ (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO);
+ MHD_destroy_response(response);
+ return ret;
+ }
+ response = MHD_create_response_from_buffer(strlen(PAGE), PAGE,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+ return ret;
+}
+
+
+static int
+testDigestAuth ()
+{
+ int fd;
+ CURL *c;
+ CURLcode errornum;
+ struct MHD_Daemon *d;
+ struct CBC cbc;
+ size_t len;
+ size_t off = 0;
+ char buf[2048];
+ char rnd[8];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+#ifndef WINDOWS
+ fd = open("/dev/urandom", O_RDONLY);
+ if (-1 == fd)
+ {
+ fprintf(stderr, "Failed to open `%s': %s\n",
+ "/dev/urandom",
+ strerror(errno));
+ return 1;
+ }
+ while (off < 8)
+ {
+ len = read(fd, rnd, 8);
+ if (len == -1)
+ {
+ fprintf(stderr, "Failed to read `%s': %s\n",
+ "/dev/urandom",
+ strerror(errno));
+ (void) close(fd);
+ return 1;
+ }
+ off += len;
+ }
+ (void) close(fd);
+#else
+ {
+ HCRYPTPROV cc;
+ BOOL b;
+ b = CryptAcquireContext (&cc, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
+ if (b == 0)
+ {
+ fprintf (stderr, "Failed to acquire crypto provider context: %lu\n",
+ GetLastError ());
+ return 1;
+ }
+ b = CryptGenRandom (cc, 8, rnd);
+ if (b == 0)
+ {
+ fprintf (stderr, "Failed to generate 8 random bytes: %lu\n",
+ GetLastError ());
+ }
+ CryptReleaseContext (cc, 0);
+ if (b == 0)
+ return 1;
+ }
+#endif
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1337, NULL, NULL, &ahc_echo, PAGE,
+ MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof (rnd), rnd,
+ MHD_OPTION_NONCE_NC_SIZE, 300,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1337/foo?key=value");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
+ curl_easy_setopt (c, CURLOPT_USERPWD, "testuser:testpass");
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen (PAGE))
+ return 4;
+ if (0 != strncmp (PAGE, cbc.buf, strlen (PAGE)))
+ return 8;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+#ifdef HAVE_GCRYPT_H
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+#endif
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testDigestAuth ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_get.c b/src/testcurl/test_get.c
new file mode 100644
index 0000000..e4c531d
--- /dev/null
+++ b/src/testcurl/test_get.c
@@ -0,0 +1,640 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009, 2011 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_get.c
+ * @brief Testcase for libmicrohttpd GET operations
+ * TODO: test parsing of query
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include "platform_interface.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+#ifndef WINDOWS
+#include <unistd.h>
+#include <sys/socket.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testInternalGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+
+static int
+testMultithreadedGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | poll_flag,
+ 1081, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+
+static int
+testMultithreadedPoolGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ 1081, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+static int
+testUnknownPortGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+ const union MHD_DaemonInfo *di;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ struct sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = 0;
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ 1, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_SOCK_ADDR, &addr,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 32768;
+
+ di = MHD_get_daemon_info (d, MHD_DAEMON_INFO_LISTEN_FD);
+ if (di == NULL)
+ return 65536;
+
+ if (0 != getsockname(di->listen_fd, (struct sockaddr *) &addr, &addr_len))
+ return 131072;
+
+ if (addr.sin_family != AF_INET)
+ return 26214;
+
+ snprintf(buf, sizeof(buf), "http://127.0.0.1:%hu/hello_world",
+ ntohs(addr.sin_port));
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, buf);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 524288;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 1048576;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 2097152;
+ return 0;
+}
+
+
+static int
+testStopRace (int poll_flag)
+{
+ struct sockaddr_in sin;
+ MHD_socket fd;
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | poll_flag,
+ 1081, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_CONNECTION_TIMEOUT, 5, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+
+ fd = socket (PF_INET, SOCK_STREAM, 0);
+ if (fd == MHD_INVALID_SOCKET)
+ {
+ fprintf(stderr, "socket error\n");
+ return 256;
+ }
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(1081);
+ sin.sin_addr.s_addr = htonl(0x7f000001);
+
+ if (connect (fd, (struct sockaddr *)(&sin), sizeof(sin)) < 0)
+ {
+ fprintf(stderr, "connect error\n");
+ MHD_socket_close_ (fd);
+ return 512;
+ }
+
+ /* printf("Waiting\n"); */
+ /* Let the thread get going. */
+ usleep(500000);
+
+ /* printf("Stopping daemon\n"); */
+ MHD_stop_daemon (d);
+
+ MHD_socket_close_ (fd);
+
+ /* printf("good\n"); */
+ return 0;
+}
+
+
+static int
+ahc_empty (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp ("GET", method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ response = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+curlExcessFound(CURL *c, curl_infotype type, char *data, size_t size, void *cls)
+{
+ static const char *excess_found = "Excess found";
+ const size_t str_size = strlen (excess_found);
+
+ if (CURLINFO_TEXT == type
+ && size >= str_size
+ && 0 == strncmp(excess_found, data, str_size))
+ *(int *)cls = 1;
+ return 0;
+}
+
+
+static int
+testEmptyGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ int excess_found = 0;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ 11081, NULL, NULL, &ahc_empty, NULL, MHD_OPTION_END);
+ if (d == NULL)
+ return 4194304;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_DEBUGFUNCTION, &curlExcessFound);
+ curl_easy_setopt (c, CURLOPT_DEBUGDATA, &excess_found);
+ curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 8388608;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != 0)
+ return 16777216;
+ if (excess_found)
+ return 33554432;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalGet (0);
+ errorCount += testMultithreadedGet (0);
+ errorCount += testMultithreadedPoolGet (0);
+ errorCount += testUnknownPortGet (0);
+ errorCount += testStopRace (0);
+ errorCount += testExternalGet ();
+ errorCount += testEmptyGet (0);
+ if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_POLL))
+ {
+ errorCount += testInternalGet(MHD_USE_POLL);
+ errorCount += testMultithreadedGet(MHD_USE_POLL);
+ errorCount += testMultithreadedPoolGet(MHD_USE_POLL);
+ errorCount += testUnknownPortGet(MHD_USE_POLL);
+ errorCount += testStopRace(MHD_USE_POLL);
+ errorCount += testEmptyGet(MHD_USE_POLL);
+ }
+ if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_EPOLL))
+ {
+ errorCount += testInternalGet(MHD_USE_EPOLL_LINUX_ONLY);
+ errorCount += testMultithreadedPoolGet(MHD_USE_EPOLL_LINUX_ONLY);
+ errorCount += testUnknownPortGet(MHD_USE_EPOLL_LINUX_ONLY);
+ errorCount += testEmptyGet(MHD_USE_EPOLL_LINUX_ONLY);
+ }
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_get_chunked.c b/src/testcurl/test_get_chunked.c
new file mode 100644
index 0000000..3b8bf39
--- /dev/null
+++ b/src/testcurl/test_get_chunked.c
@@ -0,0 +1,414 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_get_chunked.c
+ * @brief Testcase for libmicrohttpd GET operations with chunked content encoding
+ * TODO:
+ * - how to test that chunking was actually used?
+ * - use CURLOPT_HEADERFUNCTION to validate
+ * footer was sent
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+/**
+ * MHD content reader callback that returns
+ * data in chunks.
+ */
+static ssize_t
+crc (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ struct MHD_Response **responseptr = cls;
+
+ if (pos == 128 * 10)
+ {
+ MHD_add_response_header (*responseptr, "Footer", "working");
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+ if (max < 128)
+ abort (); /* should not happen in this testcase... */
+ memset (buf, 'A' + (pos / 128), 128);
+ return 128;
+}
+
+/**
+ * Dummy function that does nothing.
+ */
+static void
+crcf (void *ptr)
+{
+ free (ptr);
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ struct MHD_Response **responseptr;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ responseptr = malloc (sizeof (struct MHD_Response *));
+ if (responseptr == NULL)
+ return MHD_NO;
+ response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ 1024,
+ &crc, responseptr, &crcf);
+ *responseptr = response;
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+static int
+validate (struct CBC cbc, int ebase)
+{
+ int i;
+ char buf[128];
+
+ if (cbc.pos != 128 * 10)
+ return ebase;
+
+ for (i = 0; i < 10; i++)
+ {
+ memset (buf, 'A' + i, 128);
+ if (0 != memcmp (buf, &cbc.buf[i * 128], 128))
+ {
+ fprintf (stderr,
+ "Got `%.*s'\nWant `%.*s'\n",
+ 128, buf, 128, &cbc.buf[i * 128]);
+ return ebase * 2;
+ }
+ }
+ return 0;
+}
+
+static int
+testInternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return validate (cbc, 4);
+}
+
+static int
+testMultithreadedGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return validate (cbc, 64);
+}
+
+static int
+testMultithreadedPoolGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return validate (cbc, 64);
+}
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ return validate (cbc, 8192);
+}
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalGet ();
+ errorCount += testMultithreadedGet ();
+ errorCount += testMultithreadedPoolGet ();
+ errorCount += testExternalGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_get_response_cleanup.c b/src/testcurl/test_get_response_cleanup.c
new file mode 100644
index 0000000..f881746
--- /dev/null
+++ b/src/testcurl/test_get_response_cleanup.c
@@ -0,0 +1,302 @@
+/* DO NOT CHANGE THIS LINE */
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_get_response_cleanup.c
+ * @brief Testcase for libmicrohttpd response cleanup
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+
+#ifndef WINDOWS
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+#define TESTSTR "/* DO NOT CHANGE THIS LINE */"
+
+static int oneone;
+
+static int ok;
+
+
+static pid_t
+fork_curl (const char *url)
+{
+ pid_t ret;
+
+ ret = fork();
+ if (ret != 0)
+ return ret;
+ execlp ("curl", "curl", "-s", "-N", "-o", "/dev/null", "-GET", url, NULL);
+ fprintf (stderr,
+ "Failed to exec curl: %s\n",
+ strerror (errno));
+ _exit (-1);
+}
+
+static void
+kill_curl (pid_t pid)
+{
+ int status;
+
+ //fprintf (stderr, "Killing curl\n");
+ kill (pid, SIGTERM);
+ waitpid (pid, &status, 0);
+}
+
+
+static ssize_t
+push_callback (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ if (max == 0)
+ return 0;
+ buf[0] = 'd';
+ return 1;
+}
+
+static void
+push_free_callback (void *cls)
+{
+ int *ok = cls;
+
+ //fprintf (stderr, "Cleanup callback called!\n");
+ *ok = 0;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ //fprintf (stderr, "In CB: %s!\n", method);
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ 32 * 1024,
+ &push_callback,
+ &ok,
+ &push_free_callback);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testInternalGet ()
+{
+ struct MHD_Daemon *d;
+ pid_t curl;
+
+ ok = 1;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ curl = fork_curl ("http://127.0.0.1:11080/");
+ sleep (1);
+ kill_curl (curl);
+ sleep (1);
+ // fprintf (stderr, "Stopping daemon!\n");
+ MHD_stop_daemon (d);
+ if (ok != 0)
+ return 2;
+ return 0;
+}
+
+static int
+testMultithreadedGet ()
+{
+ struct MHD_Daemon *d;
+ pid_t curl;
+
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 2,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ ok = 1;
+ //fprintf (stderr, "Forking cURL!\n");
+ curl = fork_curl ("http://127.0.0.1:1081/");
+ sleep (1);
+ kill_curl (curl);
+ sleep (1);
+ curl = fork_curl ("http://127.0.0.1:1081/");
+ sleep (1);
+ if (ok != 0)
+ {
+ kill_curl (curl);
+ MHD_stop_daemon (d);
+ return 64;
+ }
+ kill_curl (curl);
+ sleep (1);
+ //fprintf (stderr, "Stopping daemon!\n");
+ MHD_stop_daemon (d);
+ if (ok != 0)
+ return 32;
+
+ return 0;
+}
+
+static int
+testMultithreadedPoolGet ()
+{
+ struct MHD_Daemon *d;
+ pid_t curl;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 64;
+ ok = 1;
+ curl = fork_curl ("http://127.0.0.1:1081/");
+ sleep (1);
+ kill_curl (curl);
+ sleep (1);
+ //fprintf (stderr, "Stopping daemon!\n");
+ MHD_stop_daemon (d);
+ if (ok != 0)
+ return 128;
+ return 0;
+}
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ time_t start;
+ struct timeval tv;
+ pid_t curl;
+
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ curl = fork_curl ("http://127.0.0.1:1082/");
+
+ start = time (NULL);
+ while ((time (NULL) - start < 2))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ MHD_run (d);
+ }
+ kill_curl (curl);
+ start = time (NULL);
+ while ((time (NULL) - start < 2))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ MHD_run (d);
+ }
+ // fprintf (stderr, "Stopping daemon!\n");
+ MHD_stop_daemon (d);
+ if (ok != 0)
+ return 1024;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ errorCount += testInternalGet ();
+ errorCount += testMultithreadedGet ();
+ errorCount += testMultithreadedPoolGet ();
+ errorCount += testExternalGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_get_sendfile.c b/src/testcurl/test_get_sendfile.c
new file mode 100644
index 0000000..f0853b2
--- /dev/null
+++ b/src/testcurl/test_get_sendfile.c
@@ -0,0 +1,500 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_get_sendfile.c
+ * @brief Testcase for libmicrohttpd response from FD
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include "platform_interface.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#ifndef WINDOWS
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+#define TESTSTR "This is the content of the test file we are sending using sendfile (if available)"
+
+char *sourcefile;
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+ int fd;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ fd = open (sourcefile, O_RDONLY);
+ if (fd == -1)
+ {
+ fprintf (stderr, "Failed to open `%s': %s\n",
+ sourcefile,
+ MHD_strerror_ (errno));
+ exit (1);
+ }
+ response = MHD_create_response_from_fd (strlen (TESTSTR), fd);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testInternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11080/");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen (TESTSTR))
+ return 4;
+ if (0 != strncmp (TESTSTR, cbc.buf, strlen (TESTSTR)))
+ return 8;
+ return 0;
+}
+
+static int
+testMultithreadedGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen (TESTSTR))
+ return 64;
+ if (0 != strncmp (TESTSTR, cbc.buf, strlen (TESTSTR)))
+ return 128;
+ return 0;
+}
+
+static int
+testMultithreadedPoolGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen (TESTSTR))
+ return 64;
+ if (0 != strncmp (TESTSTR, cbc.buf, strlen (TESTSTR)))
+ return 128;
+ return 0;
+}
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen (TESTSTR))
+ return 8192;
+ if (0 != strncmp (TESTSTR, cbc.buf, strlen (TESTSTR)))
+ return 16384;
+ return 0;
+}
+
+static int
+testUnknownPortGet ()
+{
+ struct MHD_Daemon *d;
+ const union MHD_DaemonInfo *di;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ struct sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = 0;
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_SOCK_ADDR, &addr,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 32768;
+
+ di = MHD_get_daemon_info (d, MHD_DAEMON_INFO_LISTEN_FD);
+ if (di == NULL)
+ return 65536;
+
+ if (0 != getsockname(di->listen_fd, (struct sockaddr *) &addr, &addr_len))
+ return 131072;
+
+ if (addr.sin_family != AF_INET)
+ return 26214;
+
+ snprintf(buf, sizeof(buf), "http://127.0.0.1:%hu/",
+ ntohs(addr.sin_port));
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, buf);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 524288;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen (TESTSTR))
+ return 1048576;
+ if (0 != strncmp (TESTSTR, cbc.buf, strlen (TESTSTR)))
+ return 2097152;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+ const char *tmp;
+ FILE *f;
+
+ if ( (NULL == (tmp = getenv ("TMPDIR"))) &&
+ (NULL == (tmp = getenv ("TMP"))) &&
+ (NULL == (tmp = getenv ("TEMP"))) )
+ tmp = "/tmp";
+ sourcefile = malloc (strlen (tmp) + 32);
+ sprintf (sourcefile,
+ "%s/%s",
+ tmp,
+ "test-mhd-sendfile");
+ f = fopen (sourcefile, "w");
+ if (NULL == f)
+ {
+ fprintf (stderr, "failed to write test file\n");
+ free (sourcefile);
+ return 1;
+ }
+ fwrite (TESTSTR, strlen (TESTSTR), 1, f);
+ fclose (f);
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalGet ();
+ errorCount += testMultithreadedGet ();
+ errorCount += testMultithreadedPoolGet ();
+ errorCount += testExternalGet ();
+ errorCount += testUnknownPortGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ unlink (sourcefile);
+ free (sourcefile);
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_iplimit.c b/src/testcurl/test_iplimit.c
new file mode 100644
index 0000000..813fc8b
--- /dev/null
+++ b/src/testcurl/test_iplimit.c
@@ -0,0 +1,313 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_iplimit.c
+ * @brief Testcase for libmicrohttpd GET operations
+ * TODO: test parsing of query
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+static int
+testMultithreadedGet ()
+{
+ struct MHD_Daemon *d;
+ char buf[2048];
+ int k;
+ unsigned int success;
+ unsigned int failure;
+
+ /* Test only valid for HTTP/1.1 (uses persistent connections) */
+ if (!oneone)
+ return 0;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081, NULL, NULL,
+ &ahc_echo, "GET",
+ MHD_OPTION_PER_IP_CONNECTION_LIMIT, 2,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+
+ for (k = 0; k < 3; ++k)
+ {
+ struct CBC cbc[3];
+ CURL *cenv[3];
+ int i;
+
+ success = 0;
+ failure = 0;
+ for (i = 0; i < 3; ++i)
+ {
+ CURL *c;
+ CURLcode errornum;
+
+ cenv[i] = c = curl_easy_init ();
+ cbc[i].buf = buf;
+ cbc[i].size = 2048;
+ cbc[i].pos = 0;
+
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc[i]);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_FORBID_REUSE, 0L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+ errornum = curl_easy_perform (c);
+ if (CURLE_OK == errornum)
+ success++;
+ else
+ failure++;
+ }
+
+ /* Cleanup the environments */
+ for (i = 0; i < 3; ++i)
+ curl_easy_cleanup (cenv[i]);
+ if ( (2 != success) ||
+ (1 != failure) )
+ {
+ fprintf (stderr,
+ "Unexpected number of success (%u) or failure (%u)\n",
+ success,
+ failure);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+
+ sleep(2);
+
+ for (i = 0; i < 2; ++i)
+ {
+ if (cbc[i].pos != strlen ("/hello_world"))
+ {
+ MHD_stop_daemon (d);
+ return 64;
+ }
+ if (0 != strncmp ("/hello_world", cbc[i].buf, strlen ("/hello_world")))
+ {
+ MHD_stop_daemon (d);
+ return 128;
+ }
+ }
+ }
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedPoolGet ()
+{
+ struct MHD_Daemon *d;
+ char buf[2048];
+ int k;
+
+ /* Test only valid for HTTP/1.1 (uses persistent connections) */
+ if (!oneone)
+ return 0;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_PER_IP_CONNECTION_LIMIT, 2,
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+
+ for (k = 0; k < 3; ++k)
+ {
+ struct CBC cbc[3];
+ CURL *cenv[3];
+ int i;
+
+ for (i = 0; i < 3; ++i)
+ {
+ CURL *c;
+ CURLcode errornum;
+
+ cenv[i] = c = curl_easy_init ();
+ cbc[i].buf = buf;
+ cbc[i].size = 2048;
+ cbc[i].pos = 0;
+
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc[i]);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_FORBID_REUSE, 0L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+ errornum = curl_easy_perform (c);
+ if ( ( (CURLE_OK != errornum) && (i < 2) ) ||
+ ( (CURLE_OK == errornum) && (i == 2) ) )
+ {
+ int j;
+
+ /* First 2 should succeed */
+ if (i < 2)
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+
+ /* Last request should have failed */
+ else
+ fprintf (stderr,
+ "No error on IP address over limit\n");
+
+ for (j = 0; j < i; ++j)
+ curl_easy_cleanup (cenv[j]);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ }
+
+ /* Cleanup the environments */
+ for (i = 0; i < 3; ++i)
+ curl_easy_cleanup (cenv[i]);
+
+ sleep(2);
+
+ for (i = 0; i < 2; ++i)
+ {
+ if (cbc[i].pos != strlen ("/hello_world"))
+ {
+ MHD_stop_daemon (d);
+ return 64;
+ }
+ if (0 != strncmp ("/hello_world", cbc[i].buf, strlen ("/hello_world")))
+ {
+ MHD_stop_daemon (d);
+ return 128;
+ }
+ }
+
+
+ }
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount |= testMultithreadedGet ();
+ errorCount |= testMultithreadedPoolGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_large_put.c b/src/testcurl/test_large_put.c
new file mode 100644
index 0000000..ec0022e
--- /dev/null
+++ b/src/testcurl/test_large_put.c
@@ -0,0 +1,479 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_large_put.c
+ * @brief Testcase for libmicrohttpd PUT operations
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+static int oneone;
+
+/**
+ * Do not make this much larger since we will hit the
+ * MHD default buffer limit and the test code is not
+ * written for incremental upload processing...
+ * (larger values will likely cause MHD to generate
+ * an internal server error -- which would be avoided
+ * by writing the putBuffer method in a more general
+ * fashion).
+ */
+#define PUT_SIZE (256 * 1024)
+
+static char *put_buffer;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
+{
+ unsigned int *pos = ptr;
+ unsigned int wrt;
+
+ wrt = size * nmemb;
+ if (wrt > PUT_SIZE - (*pos))
+ wrt = PUT_SIZE - (*pos);
+ memcpy (stream, &put_buffer[*pos], wrt);
+ (*pos) += wrt;
+ return wrt;
+}
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ int *done = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp ("PUT", method))
+ return MHD_NO; /* unexpected method */
+ if ((*done) == 0)
+ {
+ if (*upload_data_size != PUT_SIZE)
+ {
+#if 0
+ fprintf (stderr,
+ "Waiting for more data (%u/%u)...\n",
+ *upload_data_size, PUT_SIZE);
+#endif
+ return MHD_YES; /* not yet ready */
+ }
+ if (0 == memcmp (upload_data, put_buffer, PUT_SIZE))
+ {
+ *upload_data_size = 0;
+ }
+ else
+ {
+ printf ("Invalid upload data!\n");
+ return MHD_NO;
+ }
+ *done = 1;
+ return MHD_YES;
+ }
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testInternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+ char buf[2048];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (1024*1024),
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+static int
+testMultithreadedPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+ char buf[2048];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (1024*1024),
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ {
+ fprintf (stderr, "Got invalid response `%.*s'\n", (int)cbc.pos, cbc.buf);
+ return 64;
+ }
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testMultithreadedPoolPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+ char buf[2048];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT,
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (1024*1024),
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ {
+ fprintf (stderr, "Got invalid response `%.*s'\n", (int)cbc.pos, cbc.buf);
+ return 64;
+ }
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testExternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ char buf[2048];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ multi = NULL;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT,
+ (size_t) (PUT_SIZE * 4), MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ {
+ fprintf (stderr, "Got invalid response `%.*s'\n", (int)cbc.pos, cbc.buf);
+ return 8192;
+ }
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ put_buffer = malloc (PUT_SIZE);
+ if (NULL == put_buffer) return 1;
+ memset (put_buffer, 1, PUT_SIZE);
+ errorCount += testInternalPut ();
+ errorCount += testMultithreadedPut ();
+ errorCount += testMultithreadedPoolPut ();
+ errorCount += testExternalPut ();
+ free (put_buffer);
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_long_header.c b/src/testcurl/test_long_header.c
new file mode 100644
index 0000000..e1e024b
--- /dev/null
+++ b/src/testcurl/test_long_header.c
@@ -0,0 +1,253 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_long_header.c
+ * @brief Testcase for libmicrohttpd handling of very long headers
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+/**
+ * We will set the memory available per connection to
+ * half of this value, so the actual value does not have
+ * to be big at all...
+ */
+#define VERY_LONG (1024*10)
+
+static int oneone;
+
+static int
+apc_all (void *cls, const struct sockaddr *addr, socklen_t addrlen)
+{
+ return MHD_YES;
+}
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testLongUrlGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ char *url;
+ long code;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 1080,
+ &apc_all,
+ NULL,
+ &ahc_echo,
+ "GET",
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT,
+ (size_t) (VERY_LONG / 2), MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ url = malloc (VERY_LONG);
+ if (url == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 1;
+ }
+ memset (url, 'a', VERY_LONG);
+ url[VERY_LONG - 1] = '\0';
+ memcpy (url, "http://127.0.0.1:1080/", strlen ("http://127.0.0.1:1080/"));
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK == curl_easy_perform (c))
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ free (url);
+ return 2;
+ }
+ if (CURLE_OK != curl_easy_getinfo (c, CURLINFO_RESPONSE_CODE, &code))
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ free (url);
+ return 4;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ free (url);
+ if (code != MHD_HTTP_REQUEST_URI_TOO_LONG)
+ return 8;
+ return 0;
+}
+
+
+static int
+testLongHeaderGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ char *url;
+ long code;
+ struct curl_slist *header = NULL;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 1080,
+ &apc_all,
+ NULL,
+ &ahc_echo,
+ "GET",
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT,
+ (size_t) (VERY_LONG / 2), MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ url = malloc (VERY_LONG);
+ if (url == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 16;
+ }
+ memset (url, 'a', VERY_LONG);
+ url[VERY_LONG - 1] = '\0';
+ url[VERY_LONG / 2] = ':';
+ url[VERY_LONG / 2 + 1] = ' ';
+ header = curl_slist_append (header, url);
+
+ curl_easy_setopt (c, CURLOPT_HTTPHEADER, header);
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK == curl_easy_perform (c))
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ curl_slist_free_all (header);
+ free (url);
+ return 32;
+ }
+ if (CURLE_OK != curl_easy_getinfo (c, CURLINFO_RESPONSE_CODE, &code))
+ {
+ curl_slist_free_all (header);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ free (url);
+ return 64;
+ }
+ curl_slist_free_all (header);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ free (url);
+ if (code != MHD_HTTP_REQUEST_ENTITY_TOO_LARGE)
+ return 128;
+ return 0;
+}
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testLongUrlGet ();
+ errorCount += testLongHeaderGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_options.c b/src/testcurl/test_options.c
new file mode 100644
index 0000000..4afe3d4
--- /dev/null
+++ b/src/testcurl/test_options.c
@@ -0,0 +1,124 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * @file mhds_get_test.c
+ * @brief Testcase for libmicrohttpd HTTPS GET operations
+ * @author Sagie Amir
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+
+#define MHD_E_MEM "Error: memory error\n"
+#define MHD_E_SERVER_INIT "Error: failed to start server\n"
+
+const int DEBUG_GNUTLS_LOG_LEVEL = 0;
+const char *test_file_name = "https_test_file";
+const char test_file_data[] = "Hello World\n";
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ return 0;
+}
+
+int
+test_wrap (char *test_name, int (*test) (void))
+{
+ int ret;
+
+ fprintf (stdout, "running test: %s ", test_name);
+ ret = test ();
+ if (ret == 0)
+ {
+ fprintf (stdout, "[pass]\n");
+ }
+ else
+ {
+ fprintf (stdout, "[fail]\n");
+ }
+ return ret;
+}
+
+
+/**
+ * Test daemon initialization with the MHD_OPTION_SOCK_ADDR option
+ */
+static int
+test_ip_addr_option ()
+{
+ struct MHD_Daemon *d;
+ struct sockaddr_in daemon_ip_addr;
+#if HAVE_INET6
+ struct sockaddr_in6 daemon_ip_addr6;
+#endif
+
+ memset (&daemon_ip_addr, 0, sizeof (struct sockaddr_in));
+ daemon_ip_addr.sin_family = AF_INET;
+ daemon_ip_addr.sin_port = htons (4233);
+ daemon_ip_addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+
+#if HAVE_INET6
+ memset (&daemon_ip_addr6, 0, sizeof (struct sockaddr_in6));
+ daemon_ip_addr6.sin6_family = AF_INET6;
+ daemon_ip_addr6.sin6_port = htons (4233);
+ daemon_ip_addr6.sin6_addr = in6addr_loopback;
+#endif
+
+ d = MHD_start_daemon (MHD_USE_DEBUG, 4233,
+ NULL, NULL, &ahc_echo, NULL, MHD_OPTION_SOCK_ADDR,
+ &daemon_ip_addr, MHD_OPTION_END);
+
+ if (d == 0)
+ return -1;
+
+ MHD_stop_daemon (d);
+
+#if HAVE_INET6
+ d = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_IPv6, 4233,
+ NULL, NULL, &ahc_echo, NULL, MHD_OPTION_SOCK_ADDR,
+ &daemon_ip_addr6, MHD_OPTION_END);
+
+ if (d == 0)
+ return -1;
+
+ MHD_stop_daemon (d);
+#endif
+
+ return 0;
+}
+
+/* setup a temporary transfer test file */
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ errorCount += test_wrap ("ip addr option", &test_ip_addr_option);
+
+ return errorCount != 0;
+}
diff --git a/src/testcurl/test_parse_cookies.c b/src/testcurl/test_parse_cookies.c
new file mode 100644
index 0000000..7d70867
--- /dev/null
+++ b/src/testcurl/test_parse_cookies.c
@@ -0,0 +1,250 @@
+
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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 3, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_parse_cookies.c
+ * @brief Testcase for HTTP cookie parsing
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+ const char *hdr;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ ret = 0;
+
+ hdr = MHD_lookup_connection_value (connection, MHD_COOKIE_KIND, "name1");
+ if ((hdr == NULL) || (0 != strcmp (hdr, "var1")))
+ abort ();
+ hdr = MHD_lookup_connection_value (connection, MHD_COOKIE_KIND, "name2");
+ if ((hdr == NULL) || (0 != strcmp (hdr, "var2")))
+ abort ();
+ hdr = MHD_lookup_connection_value (connection, MHD_COOKIE_KIND, "name3");
+ if ((hdr == NULL) || (0 != strcmp (hdr, "")))
+ abort ();
+ hdr = MHD_lookup_connection_value (connection, MHD_COOKIE_KIND, "name4");
+ if ((hdr == NULL) || (0 != strcmp (hdr, "var4 with spaces")))
+ abort ();
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 21080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:21080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ /* note that the string below intentionally uses the
+ various ways cookies can be specified to exercise the
+ parser! Do not change! */
+ curl_easy_setopt (c, CURLOPT_COOKIE,
+ "name1=var1; name2=var2,name3 ;name4=\"var4 with spaces\";");
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testExternalGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_post.c b/src/testcurl/test_post.c
new file mode 100644
index 0000000..49bf30a
--- /dev/null
+++ b/src/testcurl/test_post.c
@@ -0,0 +1,654 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_post.c
+ * @brief Testcase for libmicrohttpd POST operations using URL-encoding
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+#define POST_DATA "name=daniel&project=curl"
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+
+static void
+completed_cb (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct MHD_PostProcessor *pp = *con_cls;
+
+ if (NULL != pp)
+ MHD_destroy_post_processor (pp);
+ *con_cls = NULL;
+}
+
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+
+/**
+ * Note that this post_iterator is not perfect
+ * in that it fails to support incremental processing.
+ * (to be fixed in the future)
+ */
+static int
+post_iterator (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *value, uint64_t off, size_t size)
+{
+ int *eok = cls;
+
+ if ((0 == strcmp (key, "name")) &&
+ (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size)))
+ (*eok) |= 1;
+ if ((0 == strcmp (key, "project")) &&
+ (size == strlen ("curl")) && (0 == strncmp (value, "curl", size)))
+ (*eok) |= 2;
+ return MHD_YES;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int eok;
+ struct MHD_Response *response;
+ struct MHD_PostProcessor *pp;
+ int ret;
+
+ if (0 != strcmp ("POST", method))
+ {
+ printf ("METHOD: %s\n", method);
+ return MHD_NO; /* unexpected method */
+ }
+ pp = *unused;
+ if (pp == NULL)
+ {
+ eok = 0;
+ pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok);
+ *unused = pp;
+ }
+ MHD_post_process (pp, upload_data, *upload_data_size);
+ if ((eok == 3) && (0 == *upload_data_size))
+ {
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ MHD_destroy_post_processor (pp);
+ *unused = NULL;
+ return ret;
+ }
+ *upload_data_size = 0;
+ return MHD_YES;
+}
+
+
+static int
+testInternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+static int
+testMultithreadedPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testMultithreadedPoolPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testExternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+static int
+ahc_cancel (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp ("POST", method))
+ {
+ fprintf (stderr,
+ "Unexpected method `%s'\n", method);
+ return MHD_NO;
+ }
+
+ if (*unused == NULL)
+ {
+ *unused = "wibble";
+ /* We don't want the body. Send a 500. */
+ response = MHD_create_response_from_buffer (0, NULL,
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response(connection, 500, response);
+ if (ret != MHD_YES)
+ fprintf(stderr, "Failed to queue response\n");
+ MHD_destroy_response(response);
+ return ret;
+ }
+ else
+ {
+ fprintf(stderr,
+ "In ahc_cancel again. This should not happen.\n");
+ return MHD_NO;
+ }
+}
+
+struct CRBC
+{
+ const char *buffer;
+ size_t size;
+ size_t pos;
+};
+
+
+static size_t
+readBuffer(void *p, size_t size, size_t nmemb, void *opaque)
+{
+ struct CRBC *data = opaque;
+ size_t required = size * nmemb;
+ size_t left = data->size - data->pos;
+
+ if (required > left)
+ required = left;
+
+ memcpy(p, data->buffer + data->pos, required);
+ data->pos += required;
+
+ return required/size;
+}
+
+
+static size_t
+slowReadBuffer(void *p, size_t size, size_t nmemb, void *opaque)
+{
+ sleep(1);
+ return readBuffer(p, size, nmemb, opaque);
+}
+
+
+#define FLAG_EXPECT_CONTINUE 1
+#define FLAG_CHUNKED 2
+#define FLAG_FORM_DATA 4
+#define FLAG_SLOW_READ 8
+#define FLAG_COUNT 16
+
+
+static int
+testMultithreadedPostCancelPart(int flags)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ struct curl_slist *headers = NULL;
+ long response_code;
+ CURLcode cc;
+ int result = 0;
+ struct CRBC crbc;
+
+ /* Don't test features that aren't available with HTTP/1.0 in
+ * HTTP/1.0 mode. */
+ if (!oneone && (flags & (FLAG_EXPECT_CONTINUE | FLAG_CHUNKED)))
+ return 0;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_cancel, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 32768;
+
+ crbc.buffer = "Test content";
+ crbc.size = strlen(crbc.buffer);
+ crbc.pos = 0;
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, (flags & FLAG_SLOW_READ) ? &slowReadBuffer : &readBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &crbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, NULL);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, crbc.size);
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+ if (flags & FLAG_CHUNKED)
+ headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+ if (!(flags & FLAG_FORM_DATA))
+ headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
+ if (flags & FLAG_EXPECT_CONTINUE)
+ headers = curl_slist_append(headers, "Expect: 100-Continue");
+ curl_easy_setopt(c, CURLOPT_HTTPHEADER, headers);
+
+ if (CURLE_HTTP_RETURNED_ERROR != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "flibbet curl_easy_perform didn't fail as expected: `%s' %d\n",
+ curl_easy_strerror (errornum), errornum);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ curl_slist_free_all(headers);
+ return 65536;
+ }
+
+ if (CURLE_OK != (cc = curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code)))
+ {
+ fprintf(stderr, "curl_easy_getinfo failed: '%s'\n", curl_easy_strerror(errornum));
+ result = 65536;
+ }
+
+ if (!result && (response_code != 500))
+ {
+ fprintf(stderr, "Unexpected response code: %ld\n", response_code);
+ result = 131072;
+ }
+
+ if (!result && (cbc.pos != 0))
+ result = 262144;
+
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ curl_slist_free_all(headers);
+ return result;
+}
+
+
+static int
+testMultithreadedPostCancel()
+{
+ int result = 0;
+ int flags;
+ for(flags = 0; flags < FLAG_COUNT; ++flags)
+ result |= testMultithreadedPostCancelPart(flags);
+ return result;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testMultithreadedPostCancel ();
+ errorCount += testInternalPost ();
+ errorCount += testMultithreadedPost ();
+ errorCount += testMultithreadedPoolPost ();
+ errorCount += testExternalPost ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_post_loop.c b/src/testcurl/test_post_loop.c
new file mode 100644
index 0000000..fe8d25c
--- /dev/null
+++ b/src/testcurl/test_post_loop.c
@@ -0,0 +1,531 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_post_loop.c
+ * @brief Testcase for libmicrohttpd POST operations using URL-encoding
+ * @author Christian Grothoff (inspired by bug report #1296)
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <gauger.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+#define POST_DATA "<?xml version='1.0' ?>\n<xml>\n<data-id>1</data-id>\n</xml>\n"
+
+#define LOOPCOUNT 1000
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **mptr)
+{
+ static int marker;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp ("POST", method))
+ {
+ printf ("METHOD: %s\n", method);
+ return MHD_NO; /* unexpected method */
+ }
+ if ((*mptr != NULL) && (0 == *upload_data_size))
+ {
+ if (*mptr != &marker)
+ abort ();
+ response = MHD_create_response_from_buffer (2, "OK",
+ MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ *mptr = NULL;
+ return ret;
+ }
+ if (strlen (POST_DATA) != *upload_data_size)
+ return MHD_YES;
+ *upload_data_size = 0;
+ *mptr = ▮
+ return MHD_YES;
+}
+
+
+static int
+testInternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ int i;
+ char url[1024];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ for (i = 0; i < LOOPCOUNT; i++)
+ {
+ if (99 == i % 100)
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ cbc.pos = 0;
+ buf[0] = '\0';
+ sprintf (url, "http://127.0.0.1:1080/hw%d", i);
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ if ((buf[0] != 'O') || (buf[1] != 'K'))
+ {
+ MHD_stop_daemon (d);
+ return 4;
+ }
+ }
+ MHD_stop_daemon (d);
+ if (LOOPCOUNT >= 99)
+ fprintf (stderr, "\n");
+ return 0;
+}
+
+static int
+testMultithreadedPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ int i;
+ char url[1024];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ for (i = 0; i < LOOPCOUNT; i++)
+ {
+ if (99 == i % 100)
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ cbc.pos = 0;
+ buf[0] = '\0';
+ sprintf (url, "http://127.0.0.1:1081/hw%d", i);
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ if ((buf[0] != 'O') || (buf[1] != 'K'))
+ {
+ MHD_stop_daemon (d);
+ return 64;
+ }
+ }
+ MHD_stop_daemon (d);
+ if (LOOPCOUNT >= 99)
+ fprintf (stderr, "\n");
+ return 0;
+}
+
+static int
+testMultithreadedPoolPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ int i;
+ char url[1024];
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ for (i = 0; i < LOOPCOUNT; i++)
+ {
+ if (99 == i % 100)
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ cbc.pos = 0;
+ buf[0] = '\0';
+ sprintf (url, "http://127.0.0.1:1081/hw%d", i);
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ if ((buf[0] != 'O') || (buf[1] != 'K'))
+ {
+ MHD_stop_daemon (d);
+ return 64;
+ }
+ }
+ MHD_stop_daemon (d);
+ if (LOOPCOUNT >= 99)
+ fprintf (stderr, "\n");
+ return 0;
+}
+
+static int
+testExternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+ int i;
+ unsigned long long timeout;
+ long ctimeout;
+ char url[1024];
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ for (i = 0; i < LOOPCOUNT; i++)
+ {
+ if (99 == i % 100)
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ cbc.pos = 0;
+ buf[0] = '\0';
+ sprintf (url, "http://127.0.0.1:1082/hw%d", i);
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ while (CURLM_CALL_MULTI_PERFORM ==
+ curl_multi_perform (multi, &running));
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ if (MHD_NO == MHD_get_timeout (d, &timeout))
+ timeout = 100; /* 100ms == INFTY -- CURL bug... */
+ if ((CURLM_OK == curl_multi_timeout (multi, &ctimeout)) &&
+ (ctimeout < timeout) && (ctimeout >= 0))
+ timeout = ctimeout;
+ if ( (c == NULL) || (running == 0) )
+ timeout = 0; /* terminate quickly... */
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ if (-1 == select (max + 1, &rs, &ws, &es, &tv))
+ {
+ if (EINTR == errno)
+ continue;
+ fprintf (stderr,
+ "select failed: %s\n",
+ strerror (errno));
+ break;
+ }
+ while (CURLM_CALL_MULTI_PERFORM ==
+ curl_multi_perform (multi, &running));
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (c != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ }
+ if ((buf[0] != 'O') || (buf[1] != 'K'))
+ {
+ curl_multi_cleanup (multi);
+ MHD_stop_daemon (d);
+ return 8192;
+ }
+ }
+ curl_multi_cleanup (multi);
+ MHD_stop_daemon (d);
+ if (LOOPCOUNT >= 99)
+ fprintf (stderr, "\n");
+ return 0;
+}
+
+
+/**
+ * Time this round was started.
+ */
+static unsigned long long start_time;
+
+
+/**
+ * Get the current timestamp
+ *
+ * @return current time in ms
+ */
+static unsigned long long
+now ()
+{
+ struct timeval tv;
+
+ gettimeofday (&tv, NULL);
+ return (((unsigned long long) tv.tv_sec * 1000LL) +
+ ((unsigned long long) tv.tv_usec / 1000LL));
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ start_time = now();
+ errorCount += testInternalPost ();
+ fprintf (stderr,
+ oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n",
+ "internal select",
+ (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0));
+ GAUGER ("internal select",
+ oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)",
+ (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0),
+ "requests/s");
+ start_time = now();
+ errorCount += testMultithreadedPost ();
+ fprintf (stderr,
+ oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n",
+ "multithreaded post",
+ (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0));
+ GAUGER ("Multithreaded select",
+ oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)",
+ (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0),
+ "requests/s");
+ start_time = now();
+ errorCount += testMultithreadedPoolPost ();
+ fprintf (stderr,
+ oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n",
+ "thread with pool",
+ (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0));
+ GAUGER ("thread with pool",
+ oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)",
+ (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0),
+ "requests/s");
+ start_time = now();
+ errorCount += testExternalPost ();
+ fprintf (stderr,
+ oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n",
+ "external select",
+ (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0));
+ GAUGER ("external select",
+ oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)",
+ (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0),
+ "requests/s");
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_postform.c b/src/testcurl/test_postform.c
new file mode 100644
index 0000000..450eb6f
--- /dev/null
+++ b/src/testcurl/test_postform.c
@@ -0,0 +1,498 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_postform.c
+ * @brief Testcase for libmicrohttpd POST operations using multipart/postform data
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#ifdef HAVE_GCRYPT_H
+#include <gcrypt.h>
+#endif
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+
+static void
+completed_cb (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct MHD_PostProcessor *pp = *con_cls;
+
+ if (NULL != pp)
+ MHD_destroy_post_processor (pp);
+ *con_cls = NULL;
+}
+
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+
+/**
+ * Note that this post_iterator is not perfect
+ * in that it fails to support incremental processing.
+ * (to be fixed in the future)
+ */
+static int
+post_iterator (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *value, uint64_t off, size_t size)
+{
+ int *eok = cls;
+
+#if 0
+ fprintf (stderr, "PI sees %s-%.*s\n", key, size, value);
+#endif
+ if ((0 == strcmp (key, "name")) &&
+ (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size)))
+ (*eok) |= 1;
+ if ((0 == strcmp (key, "project")) &&
+ (size == strlen ("curl")) && (0 == strncmp (value, "curl", size)))
+ (*eok) |= 2;
+ return MHD_YES;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int eok;
+ struct MHD_Response *response;
+ struct MHD_PostProcessor *pp;
+ int ret;
+
+ if (0 != strcmp ("POST", method))
+ {
+ printf ("METHOD: %s\n", method);
+ return MHD_NO; /* unexpected method */
+ }
+ pp = *unused;
+ if (pp == NULL)
+ {
+ eok = 0;
+ pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok);
+ if (pp == NULL)
+ abort ();
+ *unused = pp;
+ }
+ MHD_post_process (pp, upload_data, *upload_data_size);
+ if ((eok == 3) && (0 == *upload_data_size))
+ {
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ MHD_destroy_post_processor (pp);
+ *unused = NULL;
+ return ret;
+ }
+ *upload_data_size = 0;
+ return MHD_YES;
+}
+
+static struct curl_httppost *
+make_form ()
+{
+ struct curl_httppost *post = NULL;
+ struct curl_httppost *last = NULL;
+
+ curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
+ CURLFORM_COPYCONTENTS, "daniel", CURLFORM_END);
+ curl_formadd (&post, &last, CURLFORM_COPYNAME, "project",
+ CURLFORM_COPYCONTENTS, "curl", CURLFORM_END);
+ return post;
+}
+
+
+static int
+testInternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ struct curl_httppost *pd;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ pd = make_form ();
+ curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+static int
+testMultithreadedPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ struct curl_httppost *pd;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ pd = make_form ();
+ curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testMultithreadedPoolPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ struct curl_httppost *pd;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ pd = make_form ();
+ curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testExternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+ struct curl_httppost *pd;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ pd = make_form ();
+ curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_formfree (pd);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ curl_formfree (pd);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ curl_formfree (pd);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+#ifdef HAVE_GCRYPT_H
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+#ifdef GCRYCTL_INITIALIZATION_FINISHED
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+#endif
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalPost ();
+ errorCount += testMultithreadedPost ();
+ errorCount += testMultithreadedPoolPost ();
+ errorCount += testExternalPost ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_process_arguments.c b/src/testcurl/test_process_arguments.c
new file mode 100644
index 0000000..a3e5841
--- /dev/null
+++ b/src/testcurl/test_process_arguments.c
@@ -0,0 +1,249 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2013 Christian Grothoff
+
+ libmicrohttpd 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 3, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_process_arguments.c
+ * @brief Testcase for HTTP URI arguments
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+ const char *hdr;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ hdr = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "k");
+ if ((hdr == NULL) || (0 != strcmp (hdr, "v x")))
+ abort ();
+ hdr = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND, "hash");
+ if ((hdr == NULL) || (0 != strcmp (hdr, "#foo")))
+ abort ();
+ hdr = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND, "space");
+ if ((hdr == NULL) || (0 != strcmp (hdr, "\240bar")))
+ abort ();
+ if (3 != MHD_get_connection_values (connection,
+ MHD_GET_ARGUMENT_KIND,
+ NULL, NULL))
+ abort ();
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 21080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL,
+ "http://127.0.0.1:21080/hello+world?k=v+x&hash=%23foo&space=%A0bar");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello+world"))
+ return 8192;
+ if (0 != strncmp ("/hello+world", cbc.buf, strlen ("/hello+world")))
+ return 16384;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testExternalGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_process_headers.c b/src/testcurl/test_process_headers.c
new file mode 100644
index 0000000..4a219fb
--- /dev/null
+++ b/src/testcurl/test_process_headers.c
@@ -0,0 +1,434 @@
+
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_process_headers.c
+ * @brief Testcase for HTTP header access
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+kv_cb (void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
+{
+ if ((0 == strcmp (key, MHD_HTTP_HEADER_HOST)) &&
+ (0 == strcmp (value, "127.0.0.1:21080")) && (kind == MHD_HEADER_KIND))
+ {
+ *((int *) cls) = 1;
+ return MHD_NO;
+ }
+ return MHD_YES;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+ const char *hdr;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ ret = 0;
+ MHD_get_connection_values (connection, MHD_HEADER_KIND, &kv_cb, &ret);
+ if (ret != 1)
+ abort ();
+ hdr = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "NotFound");
+ if (hdr != NULL)
+ abort ();
+ hdr = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT);
+ if ((hdr == NULL) || (0 != strcmp (hdr, "*/*")))
+ abort ();
+ hdr = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND, MHD_HTTP_HEADER_HOST);
+ if ((hdr == NULL) || (0 != strcmp (hdr, "127.0.0.1:21080")))
+ abort ();
+ MHD_set_connection_value (connection,
+ MHD_HEADER_KIND, "FakeHeader", "NowPresent");
+ hdr = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND, "FakeHeader");
+ if ((hdr == NULL) || (0 != strcmp (hdr, "NowPresent")))
+ abort ();
+
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ MHD_add_response_header (response, "MyHeader", "MyValue");
+ hdr = MHD_get_response_header (response, "MyHeader");
+ if (0 != strcmp ("MyValue", hdr))
+ abort ();
+ MHD_add_response_header (response, "MyHeader", "MyValueToo");
+ if (MHD_YES != MHD_del_response_header (response, "MyHeader", "MyValue"))
+ abort ();
+ hdr = MHD_get_response_header (response, "MyHeader");
+ if (0 != strcmp ("MyValueToo", hdr))
+ abort ();
+ if (1 != MHD_get_response_headers (response, NULL, NULL))
+ abort ();
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testInternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 21080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:21080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+static int
+testMultithreadedGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 21080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:21080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testMultithreadedPoolGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 21080, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:21080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+ return 0;
+}
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 21080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:21080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system! */
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ errorCount += testMultithreadedGet ();
+ errorCount += testMultithreadedPoolGet ();
+ errorCount += testExternalGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_put.c b/src/testcurl/test_put.c
new file mode 100644
index 0000000..fe1d8db
--- /dev/null
+++ b/src/testcurl/test_put.c
@@ -0,0 +1,440 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_put.c
+ * @brief Testcase for libmicrohttpd PUT operations
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
+{
+ unsigned int *pos = ptr;
+ unsigned int wrt;
+
+ wrt = size * nmemb;
+ if (wrt > 8 - (*pos))
+ wrt = 8 - (*pos);
+ memcpy (stream, &("Hello123"[*pos]), wrt);
+ (*pos) += wrt;
+ return wrt;
+}
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ int *done = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp ("PUT", method))
+ return MHD_NO; /* unexpected method */
+ if ((*done) == 0)
+ {
+ if (*upload_data_size != 8)
+ return MHD_YES; /* not yet ready */
+ if (0 == memcmp (upload_data, "Hello123", 8))
+ {
+ *upload_data_size = 0;
+ }
+ else
+ {
+ printf ("Invalid upload data `%8s'!\n", upload_data);
+ return MHD_NO;
+ }
+ *done = 1;
+ return MHD_YES;
+ }
+ response = MHD_create_response_from_buffer (strlen (url), (void*) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testInternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+static int
+testMultithreadedPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 1081,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+
+ return 0;
+}
+
+static int
+testMultithreadedPoolPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1081,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+
+ return 0;
+}
+
+
+static int
+testExternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+ unsigned int pos = 0;
+ int done_flag = 0;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalPut ();
+ errorCount += testMultithreadedPut ();
+ errorCount += testMultithreadedPoolPut ();
+ errorCount += testExternalPut ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_put_chunked.c b/src/testcurl/test_put_chunked.c
new file mode 100644
index 0000000..b2fa322
--- /dev/null
+++ b/src/testcurl/test_put_chunked.c
@@ -0,0 +1,448 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_put_chunked.c
+ * @brief Testcase for libmicrohttpd PUT operations with chunked encoding
+ * for the upload data
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
+{
+ unsigned int *pos = ptr;
+ unsigned int wrt;
+
+ wrt = size * nmemb;
+ if (wrt > 8 - (*pos))
+ wrt = 8 - (*pos);
+ if (wrt > 4)
+ wrt = 4; /* only send half at first => force multiple chunks! */
+ memcpy (stream, &("Hello123"[*pos]), wrt);
+ (*pos) += wrt;
+ return wrt;
+}
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ int *done = cls;
+ struct MHD_Response *response;
+ int ret;
+ int have;
+
+ if (0 != strcmp ("PUT", method))
+ return MHD_NO; /* unexpected method */
+ if ((*done) < 8)
+ {
+ have = *upload_data_size;
+ if (have + *done > 8)
+ {
+ printf ("Invalid upload data `%8s'!\n", upload_data);
+ return MHD_NO;
+ }
+ if (0 == memcmp (upload_data, &"Hello123"[*done], have))
+ {
+ *done += have;
+ *upload_data_size = 0;
+ }
+ else
+ {
+ printf ("Invalid upload data `%8s'!\n", upload_data);
+ return MHD_NO;
+ }
+#if 0
+ fprintf (stderr, "Not ready for response: %u/%u\n", *done, 8);
+#endif
+ return MHD_YES;
+ }
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testInternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 11080,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ /*
+ // by not giving the file size, we force chunking!
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ */
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+static int
+testMultithreadedPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 11081,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ /*
+ // by not giving the file size, we force chunking!
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ */
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+
+ return 0;
+}
+
+static int
+testMultithreadedPoolPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 11081,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ /*
+ // by not giving the file size, we force chunking!
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ */
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+
+ return 0;
+}
+
+
+static int
+testExternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+ unsigned int pos = 0;
+ int done_flag = 0;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 11082,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ /*
+ // by not giving the file size, we force chunking!
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ */
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalPut ();
+ errorCount += testMultithreadedPut ();
+ errorCount += testMultithreadedPoolPut ();
+ errorCount += testExternalPut ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_quiesce.c b/src/testcurl/test_quiesce.c
new file mode 100644
index 0000000..17adcb2
--- /dev/null
+++ b/src/testcurl/test_quiesce.c
@@ -0,0 +1,457 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2013, 2015 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+/**
+ * @file test_quiesce.c
+ * @brief Testcase for libmicrohttpd quiescing
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include "platform_interface.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <pthread.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#include <sys/socket.h>
+#endif
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static void
+request_completed (void *cls, struct MHD_Connection *connection,
+ void **con_cls, enum MHD_RequestTerminationCode code)
+{
+ int *done = (int *)cls;
+ *done = 1;
+}
+
+
+static void *
+ServeOneRequest(void *param)
+{
+ struct MHD_Daemon *d;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket fd, max;
+ time_t start;
+ struct timeval tv;
+ int done = 0;
+
+ fd = (MHD_socket) (intptr_t) param;
+
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_LISTEN_SOCKET, fd,
+ MHD_OPTION_NOTIFY_COMPLETED, &request_completed, &done,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return "MHD_start_daemon() failed";
+
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && done == 0)
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ MHD_stop_daemon (d);
+ MHD_socket_close_(fd);
+ return "MHD_get_fdset() failed";
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ MHD_SYS_select_ (max + 1, &rs, &ws, &es, &tv);
+ MHD_run (d);
+ }
+ MHD_stop_daemon (d);
+ MHD_socket_close_(fd);
+ return NULL;
+}
+
+
+static CURL *
+setupCURL (void *cbc)
+{
+ CURL *c;
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+ return c;
+}
+
+
+static int
+testGet (int type, int pool_count, int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+ MHD_socket fd;
+ pthread_t thrd;
+ const char *thrdRet;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ if (pool_count > 0) {
+ d = MHD_start_daemon (type | MHD_USE_DEBUG | MHD_USE_PIPE_FOR_SHUTDOWN | poll_flag,
+ 11080, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, pool_count, MHD_OPTION_END);
+
+ } else {
+ d = MHD_start_daemon (type | MHD_USE_DEBUG | MHD_USE_PIPE_FOR_SHUTDOWN | poll_flag,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ }
+ if (d == NULL)
+ return 1;
+
+ c = setupCURL(&cbc);
+
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+
+ if (cbc.pos != strlen ("/hello_world")) {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4;
+ }
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 8;
+ }
+
+ fd = MHD_quiesce_daemon (d);
+ if (0 != pthread_create(&thrd, NULL, &ServeOneRequest, (void*)(intptr_t) fd))
+ {
+ fprintf (stderr, "pthread_create failed\n");
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 16;
+ }
+
+ cbc.pos = 0;
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+
+ if (0 != pthread_join(thrd, (void**)&thrdRet))
+ {
+ fprintf (stderr, "pthread_join failed\n");
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 16;
+ }
+ if (NULL != thrdRet)
+ {
+ fprintf (stderr, "ServeOneRequest() error: %s\n", thrdRet);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 16;
+ }
+
+ if (cbc.pos != strlen ("/hello_world"))
+ {
+ fprintf(stderr, "%s\n", cbc.buf);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ MHD_socket_close_(fd);
+ return 4;
+ }
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ {
+ fprintf(stderr, "%s\n", cbc.buf);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ MHD_socket_close_(fd);
+ return 8;
+ }
+
+ /* at this point, the forked server quit, and the new
+ * server has quiesced, so new requests should fail
+ */
+ if (CURLE_OK == (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr, "curl_easy_perform should fail\n");
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ MHD_socket_close_(fd);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ MHD_socket_close_(fd);
+
+ return 0;
+}
+
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ MHD_socket max;
+ int running;
+ struct CURLMsg *msg;
+ time_t start;
+ struct timeval tv;
+ int i;
+ MHD_socket fd;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ c = setupCURL(&cbc);
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+
+ for (i = 0; i < 2; i++) {
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (multi != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ msg = curl_multi_info_read (multi, &running);
+ if (msg == NULL)
+ break;
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (i == 0 && msg->data.result != CURLE_OK)
+ printf ("%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__, curl_easy_strerror (msg->data.result));
+ else if (i == 1 && msg->data.result == CURLE_OK)
+ printf ("%s should have failed at %s:%d\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__);
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ c = NULL;
+ multi = NULL;
+ }
+ }
+ MHD_run (d);
+ }
+
+ if (i == 0) {
+ /* quiesce the daemon on the 1st iteration, so the 2nd should fail */
+ fd = MHD_quiesce_daemon(d);
+ if (MHD_INVALID_SOCKET == fd)
+ abort ();
+ MHD_socket_close_ (fd);
+ c = setupCURL (&cbc);
+ multi = curl_multi_init ();
+ mret = curl_multi_add_handle (multi, c);
+ }
+ }
+ if (multi != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ curl_multi_cleanup (multi);
+ }
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 8192;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 16384;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testGet (MHD_USE_SELECT_INTERNALLY, 0, 0);
+ errorCount += testGet (MHD_USE_THREAD_PER_CONNECTION, 0, 0);
+ errorCount += testGet (MHD_USE_SELECT_INTERNALLY, CPU_COUNT, 0);
+ errorCount += testExternalGet ();
+ if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_POLL))
+ {
+ errorCount += testGet(MHD_USE_SELECT_INTERNALLY, 0, MHD_USE_POLL);
+ errorCount += testGet (MHD_USE_THREAD_PER_CONNECTION, 0, MHD_USE_POLL);
+ errorCount += testGet (MHD_USE_SELECT_INTERNALLY, CPU_COUNT, MHD_USE_POLL);
+ }
+ if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_EPOLL))
+ {
+ errorCount += testGet (MHD_USE_SELECT_INTERNALLY, 0, MHD_USE_EPOLL_LINUX_ONLY);
+ errorCount += testGet (MHD_USE_SELECT_INTERNALLY, CPU_COUNT, MHD_USE_EPOLL_LINUX_ONLY);
+ }
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_start_stop.c b/src/testcurl/test_start_stop.c
new file mode 100644
index 0000000..a708341
--- /dev/null
+++ b/src/testcurl/test_start_stop.c
@@ -0,0 +1,129 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2011 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_start_stop.c
+ * @brief test for #1901 (start+stop)
+ * @author Christian Grothoff
+ */
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+
+#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
+#undef CPU_COUNT
+#endif
+#if !defined(CPU_COUNT)
+#define CPU_COUNT 2
+#endif
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ return MHD_NO;
+}
+
+
+static int
+testInternalGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | poll_flag,
+ 1081, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 2;
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedPoolGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ 1081, NULL, NULL, &ahc_echo, "GET",
+ MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
+ if (d == NULL)
+ return 4;
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 8;
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ errorCount += testInternalGet (0);
+ errorCount += testMultithreadedGet (0);
+ errorCount += testMultithreadedPoolGet (0);
+ errorCount += testExternalGet ();
+ if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_POLL))
+ {
+ errorCount += testInternalGet(MHD_USE_POLL);
+ errorCount += testMultithreadedGet(MHD_USE_POLL);
+ errorCount += testMultithreadedPoolGet(MHD_USE_POLL);
+ }
+ if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_EPOLL))
+ {
+ errorCount += testInternalGet(MHD_USE_EPOLL_LINUX_ONLY);
+ errorCount += testMultithreadedPoolGet(MHD_USE_EPOLL_LINUX_ONLY);
+ }
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testcurl/test_termination.c b/src/testcurl/test_termination.c
new file mode 100644
index 0000000..25f9e61
--- /dev/null
+++ b/src/testcurl/test_termination.c
@@ -0,0 +1,125 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2009 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_termination.c
+ * @brief Testcase for libmicrohttpd tolerating client not closing immediately
+ * @author hollosig
+ */
+#define PORT 12345
+
+#include "platform.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <microhttpd.h>
+#include <unistd.h>
+#include <curl/curl.h>
+
+#ifndef __MINGW32__
+#include <sys/select.h>
+#include <sys/socket.h>
+#endif
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+static int
+connection_handler (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t * upload_data_size,
+ void **ptr)
+{
+ static int i;
+
+ if (*ptr == NULL)
+ {
+ *ptr = &i;
+ return MHD_YES;
+ }
+
+ if (*upload_data_size != 0)
+ {
+ (*upload_data_size) = 0;
+ return MHD_YES;
+ }
+
+ struct MHD_Response *response =
+ MHD_create_response_from_buffer (strlen ("Response"), "Response",
+ MHD_RESPMEM_PERSISTENT);
+ int ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+
+ return ret;
+}
+
+static size_t
+write_data (void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ return size * nmemb;
+}
+
+int
+main ()
+{
+ struct MHD_Daemon *daemon;
+
+ daemon = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ PORT,
+ NULL,
+ NULL, connection_handler, NULL, MHD_OPTION_END);
+
+ if (daemon == NULL)
+ {
+ fprintf (stderr, "Daemon cannot be started!");
+ exit (1);
+ }
+
+ CURL *curl = curl_easy_init ();
+ //curl_easy_setopt(curl, CURLOPT_POST, 1L);
+ char url[255];
+ sprintf (url, "http://127.0.0.1:%d", PORT);
+ curl_easy_setopt (curl, CURLOPT_URL, url);
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data);
+
+ CURLcode success = curl_easy_perform (curl);
+ if (success != 0)
+ {
+ fprintf (stderr, "CURL Error");
+ exit (1);
+ }
+ /* CPU used to go crazy here */
+ sleep (1);
+
+ curl_easy_cleanup (curl);
+ MHD_stop_daemon (daemon);
+
+ return 0;
+}
diff --git a/src/testcurl/test_timeout.c b/src/testcurl/test_timeout.c
new file mode 100644
index 0000000..006c1a8
--- /dev/null
+++ b/src/testcurl/test_timeout.c
@@ -0,0 +1,294 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_timeout.c
+ * @brief Testcase for libmicrohttpd PUT operations
+ * @author Matthias Wachs
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+static int oneone;
+
+static int withTimeout = 1;
+
+static int withoutTimeout = 1;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+
+static void
+termination_cb (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ int *test = cls;
+
+ switch (toe)
+ {
+ case MHD_REQUEST_TERMINATED_COMPLETED_OK :
+ if (test == &withoutTimeout)
+ {
+ withoutTimeout = 0;
+ }
+ break;
+ case MHD_REQUEST_TERMINATED_WITH_ERROR :
+ case MHD_REQUEST_TERMINATED_READ_ERROR :
+ break;
+ case MHD_REQUEST_TERMINATED_TIMEOUT_REACHED :
+ if (test == &withTimeout)
+ {
+ withTimeout = 0;
+ }
+ break;
+ case MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN:
+ break;
+ case MHD_REQUEST_TERMINATED_CLIENT_ABORT:
+ break;
+ }
+}
+
+
+static size_t
+putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
+{
+ unsigned int *pos = ptr;
+ unsigned int wrt;
+
+ wrt = size * nmemb;
+ if (wrt > 8 - (*pos))
+ wrt = 8 - (*pos);
+ memcpy (stream, &("Hello123"[*pos]), wrt);
+ (*pos) += wrt;
+ return wrt;
+}
+
+
+static size_t
+putBuffer_fail (void *stream, size_t size, size_t nmemb, void *ptr)
+{
+ return 0;
+}
+
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ int *done = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp ("PUT", method))
+ return MHD_NO; /* unexpected method */
+ if ((*done) == 0)
+ {
+ if (*upload_data_size != 8)
+ return MHD_YES; /* not yet ready */
+ if (0 == memcmp (upload_data, "Hello123", 8))
+ {
+ *upload_data_size = 0;
+ }
+ else
+ {
+ printf ("Invalid upload data `%8s'!\n", upload_data);
+ return MHD_NO;
+ }
+ *done = 1;
+ return MHD_YES;
+ }
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testWithoutTimeout ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_CONNECTION_TIMEOUT, 2,
+ MHD_OPTION_NOTIFY_COMPLETED, &termination_cb, &withTimeout,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ return 0;
+}
+
+static int
+testWithTimeout ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 1080,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_CONNECTION_TIMEOUT, 2,
+ MHD_OPTION_NOTIFY_COMPLETED, &termination_cb, &withoutTimeout,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer_fail);
+ curl_easy_setopt (c, CURLOPT_READDATA, &testWithTimeout);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (errornum == CURLE_GOT_NOTHING)
+ /* mhd had the timeout */
+ return 0;
+ else
+ /* curl had the timeout first */
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 64;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 16;
+ errorCount += testWithoutTimeout ();
+ errorCount += testWithTimeout ();
+ if (errorCount != 0)
+ fprintf (stderr,
+ "Error during test execution (code: %u)\n",
+ errorCount);
+ curl_global_cleanup ();
+ if ((withTimeout == 0) && (withoutTimeout == 0))
+ return 0;
+ else
+ return errorCount; /* 0 == pass */
+}
diff --git a/src/testcurl/test_urlparse.c b/src/testcurl/test_urlparse.c
new file mode 100644
index 0000000..c48bff2
--- /dev/null
+++ b/src/testcurl/test_urlparse.c
@@ -0,0 +1,191 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2009, 2011 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_urlparse.c
+ * @brief Testcase for libmicrohttpd url parsing
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+#ifndef WINDOWS
+#include <unistd.h>
+#include <sys/socket.h>
+#endif
+
+static int oneone;
+
+static int matches;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+test_values (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *value)
+{
+ if ( (0 == strcmp (key, "a")) &&
+ (0 == strcmp (value, "b")) )
+ matches += 1;
+ if ( (0 == strcmp (key, "c")) &&
+ (0 == strcmp (value, "")) )
+ matches += 2;
+ if ( (0 == strcmp (key, "d")) &&
+ (NULL == value) )
+ matches += 4;
+ return MHD_YES;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ MHD_get_connection_values (connection,
+ MHD_GET_ARGUMENT_KIND,
+ &test_values,
+ NULL);
+ *unused = NULL;
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testInternalGet (int poll_flag)
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11080/hello_world?a=b&c=&d");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ /* NOTE: use of CONNECTTIMEOUT without also
+ setting NOSIGNAL results in really weird
+ crashes on my system!*/
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 2;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 4;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 8;
+ if (matches != 7)
+ return 16;
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalGet (0);
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testspdy/Makefile.am b/src/testspdy/Makefile.am
new file mode 100644
index 0000000..9d78bec
--- /dev/null
+++ b/src/testspdy/Makefile.am
@@ -0,0 +1,115 @@
+# This Makefile.am is in the public domain
+SUBDIRS = .
+
+AM_CFLAGS = -DDATA_DIR=\"$(top_srcdir)/src/datadir/\"
+
+if USE_COVERAGE
+ AM_CFLAGS += -fprofile-arcs -ftest-coverage
+endif
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/applicationlayer \
+ $(LIBCURL_CPPFLAGS)
+
+if !HAVE_W32
+PERF_GET_CONCURRENT=perf_get_concurrent
+endif
+
+if ENABLE_SPDY
+if HAVE_OPENSSL
+check_PROGRAMS = \
+ test_daemon_start_stop \
+ test_daemon_start_stop_many \
+ test_struct_namevalue
+
+if HAVE_SPDYLAY
+check_PROGRAMS += \
+ test_new_connection \
+ test_request_response \
+ test_notls \
+ test_request_response_with_callback \
+ test_misc \
+ test_session_timeout
+ #test_requests_with_assets
+if HAVE_CURL_BINARY
+check_PROGRAMS += \
+ test_proxies
+endif
+endif
+endif
+endif
+
+
+TESTS = $(check_PROGRAMS)
+
+
+SPDY_SOURCES= \
+ common.h common.c
+
+SPDY_LDADD= \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz
+
+test_daemon_start_stop_SOURCES = \
+ test_daemon_start_stop.c \
+ $(SPDY_SOURCES)
+test_daemon_start_stop_LDADD = $(SPDY_LDADD)
+
+test_daemon_start_stop_many_SOURCES = \
+ test_daemon_start_stop_many.c \
+ $(SPDY_SOURCES)
+test_daemon_start_stop_many_LDADD = $(SPDY_LDADD)
+
+test_struct_namevalue_SOURCES = \
+ test_struct_namevalue.c \
+ $(SPDY_SOURCES)
+test_struct_namevalue_LDADD = $(SPDY_LDADD)
+
+if HAVE_SPDYLAY
+test_new_connection_SOURCES = \
+ test_new_connection.c \
+ $(SPDY_SOURCES)
+test_new_connection_LDADD = $(SPDY_LDADD) \
+ -lspdylay
+
+test_request_response_SOURCES = \
+ test_request_response.c \
+ $(SPDY_SOURCES)
+test_request_response_LDADD = $(SPDY_LDADD) \
+ -lspdylay
+
+test_notls_SOURCES = \
+ test_notls.c \
+ $(SPDY_SOURCES)
+test_notls_LDADD = $(SPDY_LDADD) \
+ -lspdylay
+
+test_request_response_with_callback_SOURCES = \
+ test_request_response_with_callback.c \
+ $(SPDY_SOURCES)
+test_request_response_with_callback_LDADD = $(SPDY_LDADD)
+
+#test_requests_with_assets_SOURCES = \
+# test_requests_with_assets.c \
+# $(SPDY_SOURCES)
+#test_requests_with_assets_LDADD = $(SPDY_LDADD)
+
+test_misc_SOURCES = \
+ test_misc.c \
+ $(SPDY_SOURCES)
+test_misc_LDADD = $(SPDY_LDADD)
+
+test_session_timeout_SOURCES = \
+ test_session_timeout.c \
+ $(SPDY_SOURCES)
+test_session_timeout_LDADD = $(SPDY_LDADD)
+
+
+test_proxies_SOURCES = \
+ test_proxies.c \
+ $(SPDY_SOURCES)
+test_proxies_LDADD = $(SPDY_LDADD)
+
+endif
diff --git a/src/testspdy/Makefile.in b/src/testspdy/Makefile.in
new file mode 100644
index 0000000..937e793
--- /dev/null
+++ b/src/testspdy/Makefile.in
@@ -0,0 +1,1402 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@USE_COVERAGE_TRUE@am__append_1 = -fprofile-arcs -ftest-coverage
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@check_PROGRAMS = test_daemon_start_stop$(EXEEXT) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@ test_daemon_start_stop_many$(EXEEXT) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@ test_struct_namevalue$(EXEEXT) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@ $(am__EXEEXT_1) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@ $(am__EXEEXT_2)
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@am__append_2 = \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_new_connection \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_request_response \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_notls \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_request_response_with_callback \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_misc \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_session_timeout
+
+@ENABLE_SPDY_TRUE@@HAVE_CURL_BINARY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@am__append_3 = \
+@ENABLE_SPDY_TRUE@@HAVE_CURL_BINARY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_proxies
+
+subdir = src/testspdy
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp $(top_srcdir)/test-driver
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@am__EXEEXT_1 = test_new_connection$(EXEEXT) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_request_response$(EXEEXT) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_notls$(EXEEXT) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_request_response_with_callback$(EXEEXT) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_misc$(EXEEXT) \
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ test_session_timeout$(EXEEXT)
+@ENABLE_SPDY_TRUE@@HAVE_CURL_BINARY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@am__EXEEXT_2 = test_proxies$(EXEEXT)
+am__objects_1 = common.$(OBJEXT)
+am_test_daemon_start_stop_OBJECTS = test_daemon_start_stop.$(OBJEXT) \
+ $(am__objects_1)
+test_daemon_start_stop_OBJECTS = $(am_test_daemon_start_stop_OBJECTS)
+am__DEPENDENCIES_1 = $(top_builddir)/src/microspdy/libmicrospdy.la
+test_daemon_start_stop_DEPENDENCIES = $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_test_daemon_start_stop_many_OBJECTS = \
+ test_daemon_start_stop_many.$(OBJEXT) $(am__objects_1)
+test_daemon_start_stop_many_OBJECTS = \
+ $(am_test_daemon_start_stop_many_OBJECTS)
+test_daemon_start_stop_many_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__test_misc_SOURCES_DIST = test_misc.c common.h common.c
+@HAVE_SPDYLAY_TRUE@am_test_misc_OBJECTS = test_misc.$(OBJEXT) \
+@HAVE_SPDYLAY_TRUE@ $(am__objects_1)
+test_misc_OBJECTS = $(am_test_misc_OBJECTS)
+@HAVE_SPDYLAY_TRUE@test_misc_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__test_new_connection_SOURCES_DIST = test_new_connection.c common.h \
+ common.c
+@HAVE_SPDYLAY_TRUE@am_test_new_connection_OBJECTS = \
+@HAVE_SPDYLAY_TRUE@ test_new_connection.$(OBJEXT) \
+@HAVE_SPDYLAY_TRUE@ $(am__objects_1)
+test_new_connection_OBJECTS = $(am_test_new_connection_OBJECTS)
+@HAVE_SPDYLAY_TRUE@test_new_connection_DEPENDENCIES = \
+@HAVE_SPDYLAY_TRUE@ $(am__DEPENDENCIES_1)
+am__test_notls_SOURCES_DIST = test_notls.c common.h common.c
+@HAVE_SPDYLAY_TRUE@am_test_notls_OBJECTS = test_notls.$(OBJEXT) \
+@HAVE_SPDYLAY_TRUE@ $(am__objects_1)
+test_notls_OBJECTS = $(am_test_notls_OBJECTS)
+@HAVE_SPDYLAY_TRUE@test_notls_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__test_proxies_SOURCES_DIST = test_proxies.c common.h common.c
+@HAVE_SPDYLAY_TRUE@am_test_proxies_OBJECTS = test_proxies.$(OBJEXT) \
+@HAVE_SPDYLAY_TRUE@ $(am__objects_1)
+test_proxies_OBJECTS = $(am_test_proxies_OBJECTS)
+@HAVE_SPDYLAY_TRUE@test_proxies_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__test_request_response_SOURCES_DIST = test_request_response.c \
+ common.h common.c
+@HAVE_SPDYLAY_TRUE@am_test_request_response_OBJECTS = \
+@HAVE_SPDYLAY_TRUE@ test_request_response.$(OBJEXT) \
+@HAVE_SPDYLAY_TRUE@ $(am__objects_1)
+test_request_response_OBJECTS = $(am_test_request_response_OBJECTS)
+@HAVE_SPDYLAY_TRUE@test_request_response_DEPENDENCIES = \
+@HAVE_SPDYLAY_TRUE@ $(am__DEPENDENCIES_1)
+am__test_request_response_with_callback_SOURCES_DIST = \
+ test_request_response_with_callback.c common.h common.c
+@HAVE_SPDYLAY_TRUE@am_test_request_response_with_callback_OBJECTS = test_request_response_with_callback.$(OBJEXT) \
+@HAVE_SPDYLAY_TRUE@ $(am__objects_1)
+test_request_response_with_callback_OBJECTS = \
+ $(am_test_request_response_with_callback_OBJECTS)
+@HAVE_SPDYLAY_TRUE@test_request_response_with_callback_DEPENDENCIES = \
+@HAVE_SPDYLAY_TRUE@ $(am__DEPENDENCIES_1)
+am__test_session_timeout_SOURCES_DIST = test_session_timeout.c \
+ common.h common.c
+@HAVE_SPDYLAY_TRUE@am_test_session_timeout_OBJECTS = \
+@HAVE_SPDYLAY_TRUE@ test_session_timeout.$(OBJEXT) \
+@HAVE_SPDYLAY_TRUE@ $(am__objects_1)
+test_session_timeout_OBJECTS = $(am_test_session_timeout_OBJECTS)
+@HAVE_SPDYLAY_TRUE@test_session_timeout_DEPENDENCIES = \
+@HAVE_SPDYLAY_TRUE@ $(am__DEPENDENCIES_1)
+am_test_struct_namevalue_OBJECTS = test_struct_namevalue.$(OBJEXT) \
+ $(am__objects_1)
+test_struct_namevalue_OBJECTS = $(am_test_struct_namevalue_OBJECTS)
+test_struct_namevalue_DEPENDENCIES = $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(test_daemon_start_stop_SOURCES) \
+ $(test_daemon_start_stop_many_SOURCES) $(test_misc_SOURCES) \
+ $(test_new_connection_SOURCES) $(test_notls_SOURCES) \
+ $(test_proxies_SOURCES) $(test_request_response_SOURCES) \
+ $(test_request_response_with_callback_SOURCES) \
+ $(test_session_timeout_SOURCES) \
+ $(test_struct_namevalue_SOURCES)
+DIST_SOURCES = $(test_daemon_start_stop_SOURCES) \
+ $(test_daemon_start_stop_many_SOURCES) \
+ $(am__test_misc_SOURCES_DIST) \
+ $(am__test_new_connection_SOURCES_DIST) \
+ $(am__test_notls_SOURCES_DIST) \
+ $(am__test_proxies_SOURCES_DIST) \
+ $(am__test_request_response_SOURCES_DIST) \
+ $(am__test_request_response_with_callback_SOURCES_DIST) \
+ $(am__test_session_timeout_SOURCES_DIST) \
+ $(test_struct_namevalue_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ check recheck distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red='[0;31m'; \
+ grn='[0;32m'; \
+ lgn='[1;32m'; \
+ blu='[1;34m'; \
+ mgn='[0;35m'; \
+ brg='[1m'; \
+ std='[m'; \
+ fi; \
+}
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+SUBDIRS = .
+AM_CFLAGS = -DDATA_DIR=\"$(top_srcdir)/src/datadir/\" $(am__append_1)
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/applicationlayer \
+ $(LIBCURL_CPPFLAGS)
+
+@HAVE_W32_FALSE@PERF_GET_CONCURRENT = perf_get_concurrent
+TESTS = $(check_PROGRAMS)
+SPDY_SOURCES = \
+ common.h common.c
+
+SPDY_LDADD = \
+ $(top_builddir)/src/microspdy/libmicrospdy.la \
+ -lz
+
+test_daemon_start_stop_SOURCES = \
+ test_daemon_start_stop.c \
+ $(SPDY_SOURCES)
+
+test_daemon_start_stop_LDADD = $(SPDY_LDADD)
+test_daemon_start_stop_many_SOURCES = \
+ test_daemon_start_stop_many.c \
+ $(SPDY_SOURCES)
+
+test_daemon_start_stop_many_LDADD = $(SPDY_LDADD)
+test_struct_namevalue_SOURCES = \
+ test_struct_namevalue.c \
+ $(SPDY_SOURCES)
+
+test_struct_namevalue_LDADD = $(SPDY_LDADD)
+@HAVE_SPDYLAY_TRUE@test_new_connection_SOURCES = \
+@HAVE_SPDYLAY_TRUE@ test_new_connection.c \
+@HAVE_SPDYLAY_TRUE@ $(SPDY_SOURCES)
+
+@HAVE_SPDYLAY_TRUE@test_new_connection_LDADD = $(SPDY_LDADD) \
+@HAVE_SPDYLAY_TRUE@ -lspdylay
+
+@HAVE_SPDYLAY_TRUE@test_request_response_SOURCES = \
+@HAVE_SPDYLAY_TRUE@ test_request_response.c \
+@HAVE_SPDYLAY_TRUE@ $(SPDY_SOURCES)
+
+@HAVE_SPDYLAY_TRUE@test_request_response_LDADD = $(SPDY_LDADD) \
+@HAVE_SPDYLAY_TRUE@ -lspdylay
+
+@HAVE_SPDYLAY_TRUE@test_notls_SOURCES = \
+@HAVE_SPDYLAY_TRUE@ test_notls.c \
+@HAVE_SPDYLAY_TRUE@ $(SPDY_SOURCES)
+
+@HAVE_SPDYLAY_TRUE@test_notls_LDADD = $(SPDY_LDADD) \
+@HAVE_SPDYLAY_TRUE@ -lspdylay
+
+@HAVE_SPDYLAY_TRUE@test_request_response_with_callback_SOURCES = \
+@HAVE_SPDYLAY_TRUE@ test_request_response_with_callback.c \
+@HAVE_SPDYLAY_TRUE@ $(SPDY_SOURCES)
+
+@HAVE_SPDYLAY_TRUE@test_request_response_with_callback_LDADD = $(SPDY_LDADD)
+
+#test_requests_with_assets_SOURCES = \
+# test_requests_with_assets.c \
+# $(SPDY_SOURCES)
+#test_requests_with_assets_LDADD = $(SPDY_LDADD)
+@HAVE_SPDYLAY_TRUE@test_misc_SOURCES = \
+@HAVE_SPDYLAY_TRUE@ test_misc.c \
+@HAVE_SPDYLAY_TRUE@ $(SPDY_SOURCES)
+
+@HAVE_SPDYLAY_TRUE@test_misc_LDADD = $(SPDY_LDADD)
+@HAVE_SPDYLAY_TRUE@test_session_timeout_SOURCES = \
+@HAVE_SPDYLAY_TRUE@ test_session_timeout.c \
+@HAVE_SPDYLAY_TRUE@ $(SPDY_SOURCES)
+
+@HAVE_SPDYLAY_TRUE@test_session_timeout_LDADD = $(SPDY_LDADD)
+@HAVE_SPDYLAY_TRUE@test_proxies_SOURCES = \
+@HAVE_SPDYLAY_TRUE@ test_proxies.c \
+@HAVE_SPDYLAY_TRUE@ $(SPDY_SOURCES)
+
+@HAVE_SPDYLAY_TRUE@test_proxies_LDADD = $(SPDY_LDADD)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/testspdy/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/testspdy/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-checkPROGRAMS:
+ @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+test_daemon_start_stop$(EXEEXT): $(test_daemon_start_stop_OBJECTS) $(test_daemon_start_stop_DEPENDENCIES) $(EXTRA_test_daemon_start_stop_DEPENDENCIES)
+ @rm -f test_daemon_start_stop$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_daemon_start_stop_OBJECTS) $(test_daemon_start_stop_LDADD) $(LIBS)
+
+test_daemon_start_stop_many$(EXEEXT): $(test_daemon_start_stop_many_OBJECTS) $(test_daemon_start_stop_many_DEPENDENCIES) $(EXTRA_test_daemon_start_stop_many_DEPENDENCIES)
+ @rm -f test_daemon_start_stop_many$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_daemon_start_stop_many_OBJECTS) $(test_daemon_start_stop_many_LDADD) $(LIBS)
+
+test_misc$(EXEEXT): $(test_misc_OBJECTS) $(test_misc_DEPENDENCIES) $(EXTRA_test_misc_DEPENDENCIES)
+ @rm -f test_misc$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_misc_OBJECTS) $(test_misc_LDADD) $(LIBS)
+
+test_new_connection$(EXEEXT): $(test_new_connection_OBJECTS) $(test_new_connection_DEPENDENCIES) $(EXTRA_test_new_connection_DEPENDENCIES)
+ @rm -f test_new_connection$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_new_connection_OBJECTS) $(test_new_connection_LDADD) $(LIBS)
+
+test_notls$(EXEEXT): $(test_notls_OBJECTS) $(test_notls_DEPENDENCIES) $(EXTRA_test_notls_DEPENDENCIES)
+ @rm -f test_notls$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_notls_OBJECTS) $(test_notls_LDADD) $(LIBS)
+
+test_proxies$(EXEEXT): $(test_proxies_OBJECTS) $(test_proxies_DEPENDENCIES) $(EXTRA_test_proxies_DEPENDENCIES)
+ @rm -f test_proxies$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_proxies_OBJECTS) $(test_proxies_LDADD) $(LIBS)
+
+test_request_response$(EXEEXT): $(test_request_response_OBJECTS) $(test_request_response_DEPENDENCIES) $(EXTRA_test_request_response_DEPENDENCIES)
+ @rm -f test_request_response$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_request_response_OBJECTS) $(test_request_response_LDADD) $(LIBS)
+
+test_request_response_with_callback$(EXEEXT): $(test_request_response_with_callback_OBJECTS) $(test_request_response_with_callback_DEPENDENCIES) $(EXTRA_test_request_response_with_callback_DEPENDENCIES)
+ @rm -f test_request_response_with_callback$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_request_response_with_callback_OBJECTS) $(test_request_response_with_callback_LDADD) $(LIBS)
+
+test_session_timeout$(EXEEXT): $(test_session_timeout_OBJECTS) $(test_session_timeout_DEPENDENCIES) $(EXTRA_test_session_timeout_DEPENDENCIES)
+ @rm -f test_session_timeout$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_session_timeout_OBJECTS) $(test_session_timeout_LDADD) $(LIBS)
+
+test_struct_namevalue$(EXEEXT): $(test_struct_namevalue_OBJECTS) $(test_struct_namevalue_DEPENDENCIES) $(EXTRA_test_struct_namevalue_DEPENDENCIES)
+ @rm -f test_struct_namevalue$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_struct_namevalue_OBJECTS) $(test_struct_namevalue_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_daemon_start_stop.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_daemon_start_stop_many.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_misc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_new_connection.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_notls.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_proxies.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_request_response.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_request_response_with_callback.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_session_timeout.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_struct_namevalue.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ else \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all $(check_PROGRAMS)
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+test_daemon_start_stop.log: test_daemon_start_stop$(EXEEXT)
+ @p='test_daemon_start_stop$(EXEEXT)'; \
+ b='test_daemon_start_stop'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_daemon_start_stop_many.log: test_daemon_start_stop_many$(EXEEXT)
+ @p='test_daemon_start_stop_many$(EXEEXT)'; \
+ b='test_daemon_start_stop_many'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_struct_namevalue.log: test_struct_namevalue$(EXEEXT)
+ @p='test_struct_namevalue$(EXEEXT)'; \
+ b='test_struct_namevalue'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_new_connection.log: test_new_connection$(EXEEXT)
+ @p='test_new_connection$(EXEEXT)'; \
+ b='test_new_connection'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_request_response.log: test_request_response$(EXEEXT)
+ @p='test_request_response$(EXEEXT)'; \
+ b='test_request_response'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_notls.log: test_notls$(EXEEXT)
+ @p='test_notls$(EXEEXT)'; \
+ b='test_notls'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_request_response_with_callback.log: test_request_response_with_callback$(EXEEXT)
+ @p='test_request_response_with_callback$(EXEEXT)'; \
+ b='test_request_response_with_callback'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_misc.log: test_misc$(EXEEXT)
+ @p='test_misc$(EXEEXT)'; \
+ b='test_misc'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_session_timeout.log: test_session_timeout$(EXEEXT)
+ @p='test_session_timeout$(EXEEXT)'; \
+ b='test_session_timeout'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_proxies.log: test_proxies$(EXEEXT)
+ @p='test_proxies$(EXEEXT)'; \
+ b='test_proxies'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-TESTS check-am clean clean-checkPROGRAMS clean-generic \
+ clean-libtool cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am recheck tags tags-am \
+ uninstall uninstall-am
+
+@ENABLE_SPDY_TRUE@@HAVE_OPENSSL_TRUE@@HAVE_SPDYLAY_TRUE@ #test_requests_with_assets
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/testspdy/common.c b/src/testspdy/common.c
new file mode 100644
index 0000000..e150209
--- /dev/null
+++ b/src/testspdy/common.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file common.c
+ * @brief Common functions used by the tests.
+ * @author Andrey Uzunov
+ */
+
+
+#include "common.h"
+#include <sys/time.h>
+
+#ifdef __GNUC__
+#define FUNC_CONSTRUCTOR(f) static void __attribute__ ((constructor)) f
+#define FUNC_DESTRUCTOR(f) static void __attribute__ ((destructor)) f
+#else // !__GNUC__
+#define FUNC_CONSTRUCTOR(f) _MHD_EXTERN void f
+#define FUNC_DESTRUCTOR(f) _MHD_EXTERN void f
+#endif // __GNUC__
+
+FUNC_CONSTRUCTOR (constructor)()
+{
+ printf("\nTEST START -------------------------------------------------------\n");
+}
+
+FUNC_DESTRUCTOR (destructor)()
+{
+ printf("------------------------------------------------------- TEST END\n");
+}
+
+uint16_t
+get_port(uint16_t min)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ if(2 > min) min=2;
+ uint16_t port = min + (tv.tv_usec+10) % ((1 << 16) - min);
+
+ //port = 12345;
+ printf("Port used: %i\n", port);
+
+ return port;
+}
diff --git a/src/testspdy/common.h b/src/testspdy/common.h
new file mode 100644
index 0000000..70ee261
--- /dev/null
+++ b/src/testspdy/common.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file common.h
+ * @brief Common functions used by the tests.
+ * @author Andrey Uzunov
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#define FAIL_TEST(msg) do{\
+ printf("%i:%s\n", __LINE__, msg);\
+ fflush(stdout);\
+ exit(-10);\
+ }\
+ while(0)
+
+uint16_t
+get_port(uint16_t min);
diff --git a/src/testspdy/test_daemon_start_stop.c b/src/testspdy/test_daemon_start_stop.c
new file mode 100644
index 0000000..d3aec6a
--- /dev/null
+++ b/src/testspdy/test_daemon_start_stop.c
@@ -0,0 +1,49 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file daemon_start_stop.c
+ * @brief starts and stops a SPDY daemon
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include "common.h"
+
+int
+main()
+{
+ SPDY_init();
+
+ struct SPDY_Daemon *daemon = SPDY_start_daemon(get_port(16123),
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ NULL,NULL,NULL,NULL,NULL,SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ SPDY_stop_daemon(daemon);
+
+ SPDY_deinit();
+
+ return 0;
+}
diff --git a/src/testspdy/test_daemon_start_stop_many.c b/src/testspdy/test_daemon_start_stop_many.c
new file mode 100644
index 0000000..b8788de
--- /dev/null
+++ b/src/testspdy/test_daemon_start_stop_many.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file daemon_start_stop_many.c
+ * @brief starts and stops several SPDY daemons, reusing port numbers
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include "common.h"
+
+int
+main()
+{
+ int i;
+ int j;
+ int num_daemons = 3;
+ int num_tries = 5;
+ int port = get_port(15123);
+ struct SPDY_Daemon *daemon[num_daemons];
+
+ SPDY_init();
+
+ for(i=0; i<num_tries; ++i)
+ {
+ for(j=0;j<num_daemons;++j)
+ {
+ daemon[j] = SPDY_start_daemon(port + j,
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ NULL,NULL,NULL,NULL,NULL,SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon[j]){
+ printf("no daemon\n");
+ return 1;
+ }
+ }
+
+
+ for(j=0;j<num_daemons;++j)
+ {
+ SPDY_stop_daemon(daemon[j]);
+ }
+ }
+
+ SPDY_deinit();
+
+ return 0;
+}
diff --git a/src/testspdy/test_misc.c b/src/testspdy/test_misc.c
new file mode 100644
index 0000000..39b672b
--- /dev/null
+++ b/src/testspdy/test_misc.c
@@ -0,0 +1,289 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file misc.c
+ * @brief tests a lot of small calls and callbacks. TODO mention what
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include "stdio.h"
+#include <sys/wait.h>
+#include "common.h"
+
+int port;
+
+#define HTML "<html><head>\
+<link href=\"main.css\" rel=\"stylesheet\" type=\"text/css\" />\
+</head><body>This is libmicrospdy</body></html>"
+
+#define CSS "body{font-size:15px}"
+
+#define SESSION_CLS "1234567890"
+
+#define REQUEST_CLS "1234567890REQ"
+
+pid_t parent;
+pid_t child;
+
+struct SPDY_Session *session1;
+struct SPDY_Session *session2;
+
+void
+killchild()
+{
+ kill(child, SIGKILL);
+ exit(1);
+}
+
+void
+killparent()
+{
+ kill(parent, SIGKILL);
+ _exit(1);
+}
+
+
+void
+create_child()
+{
+ parent = getpid();
+
+ child = fork();
+ if (-1 == child)
+ {
+ fprintf(stderr, "can't fork, error %d\n", errno);
+ exit(EXIT_FAILURE);
+ }
+
+ if (child == 0)
+ {
+ int devnull;
+ char *uri;
+ fflush(stdout);
+ devnull = open("/dev/null", O_WRONLY);
+ if (-1 == devnull)
+ abort ();
+ if (1 != devnull)
+ {
+ dup2(devnull, 1);
+ close(devnull);
+ }
+ asprintf(&uri,"https://127.0.0.1:%i/",port);
+ execlp("spdycat", "spdycat","-anv",uri,NULL );
+ printf("execlp failed\n");
+ killparent();
+ }
+}
+
+void
+response_done_callback(void *cls,
+ struct SPDY_Response * response,
+ struct SPDY_Request * request,
+ enum SPDY_RESPONSE_RESULT status,
+ bool streamopened)
+{
+ (void)status;
+ (void)streamopened;
+
+ if(strcmp(cls,"/main.css"))
+ {
+ session1 = SPDY_get_session_for_request(request);
+ if(NULL == session1)
+ {
+ printf("SPDY_get_session_for_request failed\n");
+ killchild();
+ }
+
+ char *session_cls = strdup(SESSION_CLS);
+ SPDY_set_cls_to_session(session1,session_cls);
+ }
+ else
+ {
+ session2 = SPDY_get_session_for_request(request);
+ if(session1 != session2)
+ {
+ printf("SPDY_get_session_for_request failed the second time\n");
+ killchild();
+ }
+ printf("SPDY_get_session_for_request tested...\n");
+
+ void *session_cls = SPDY_get_cls_from_session(session2);
+ if(NULL == session_cls || strcmp(session_cls, SESSION_CLS))
+ {
+ printf("SPDY_get_cls_from_session failed\n");
+ killchild();
+ }
+ printf("SPDY_set_cls_to_session tested...\n");
+ printf("SPDY_get_cls_from_session tested...\n");
+
+ void *request_cls = SPDY_get_cls_from_request(request);
+ if(NULL == request_cls || strcmp(request_cls, REQUEST_CLS))
+ {
+ printf("SPDY_get_cls_from_request failed\n");
+ killchild();
+ }
+ printf("SPDY_set_cls_to_request tested...\n");
+ printf("SPDY_get_cls_from_request tested...\n");
+ }
+
+ SPDY_destroy_request(request);
+ SPDY_destroy_response(response);
+ free(cls);
+}
+
+void
+standard_request_handler(void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers,
+ bool more)
+{
+ (void)cls;
+ (void)request;
+ (void)priority;
+ (void)host;
+ (void)scheme;
+ (void)headers;
+ (void)method;
+ (void)version;
+ (void)more;
+
+ struct SPDY_Response *response=NULL;
+ char *cls_path = strdup(path);
+
+ if(strcmp(path,"/main.css")==0)
+ {
+ char *request_cls = strdup(REQUEST_CLS);
+ SPDY_set_cls_to_request(request,request_cls);
+ response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,CSS,strlen(CSS));
+ }
+ else
+ {
+ response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,HTML,strlen(HTML));
+ }
+
+ if(NULL==response){
+ fprintf(stdout,"no response obj\n");
+ killchild();
+ }
+
+ if(SPDY_queue_response(request,response,true,false,&response_done_callback,cls_path)!=SPDY_YES)
+ {
+ fprintf(stdout,"queue\n");
+ killchild();
+ }
+}
+
+int
+parentproc()
+{
+ int childstatus;
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+ struct SPDY_Daemon *daemon;
+
+ daemon = SPDY_start_daemon(port,
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ NULL,
+ NULL,
+ &standard_request_handler,
+ NULL,
+ NULL,
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
+ 1800,
+ SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ create_child();
+
+ do
+ {
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(ret) {
+ case -1:
+ printf("select error: %i\n", errno);
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon);
+
+ break;
+ }
+ }
+ while(waitpid(child,&childstatus,WNOHANG) != child);
+
+ SPDY_stop_daemon(daemon);
+
+ return WEXITSTATUS(childstatus);
+}
+
+
+int
+main()
+{
+ port = get_port(13123);
+ SPDY_init();
+
+ int ret = parentproc();
+
+ SPDY_deinit();
+
+ return ret;
+}
diff --git a/src/testspdy/test_new_connection.c b/src/testspdy/test_new_connection.c
new file mode 100644
index 0000000..b848572
--- /dev/null
+++ b/src/testspdy/test_new_connection.c
@@ -0,0 +1,1009 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file request_response.c
+ * @brief tests new connection callback. spdycli.c
+ * code is reused here
+ * @author Andrey Uzunov
+ * @author Tatsuhiro Tsujikawa
+ */
+
+//TODO child exits with ret val 1 sometimes
+
+#include "platform.h"
+#include "microspdy.h"
+#include <sys/wait.h>
+#include "common.h"
+
+#define RESPONSE_BODY "<html><body><b>Hi, this is libmicrospdy!</b></body></html>"
+
+#define CLS "anything"
+
+int port;
+int loop = 1;
+
+pid_t parent;
+pid_t child;
+
+int
+spdylay_printf(const char *format, ...)
+{
+ (void)format;
+
+ return 0;
+}
+
+int
+spdylay_fprintf(FILE *stream, const char *format, ...)
+{
+ (void)stream;
+ (void)format;
+
+ return 0;
+}
+
+void
+killchild(int pid, char *message)
+{
+ printf("%s\n",message);
+ kill(pid, SIGKILL);
+ exit(1);
+}
+
+void
+killparent(int pid, char *message)
+{
+ printf("%s\n",message);
+ kill(pid, SIGKILL);
+ _exit(2);
+}
+
+
+/*****
+ * start of code needed to utilize spdylay
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <spdylay/spdylay.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+enum {
+ IO_NONE,
+ WANT_READ,
+ WANT_WRITE
+};
+
+struct Connection {
+ SSL *ssl;
+ spdylay_session *session;
+ /* WANT_READ if SSL connection needs more input; or WANT_WRITE if it
+ needs more output; or IO_NONE. This is necessary because SSL/TLS
+ re-negotiation is possible at any time. Spdylay API offers
+ similar functions like spdylay_session_want_read() and
+ spdylay_session_want_write() but they do not take into account
+ SSL connection. */
+ int want_io;
+};
+
+struct Request {
+ char *host;
+ uint16_t port;
+ /* In this program, path contains query component as well. */
+ char *path;
+ /* This is the concatenation of host and port with ":" in
+ between. */
+ char *hostport;
+ /* Stream ID for this request. */
+ int32_t stream_id;
+ /* The gzip stream inflater for the compressed response. */
+ spdylay_gzip *inflater;
+};
+
+struct URI {
+ const char *host;
+ size_t hostlen;
+ uint16_t port;
+ /* In this program, path contains query component as well. */
+ const char *path;
+ size_t pathlen;
+ const char *hostport;
+ size_t hostportlen;
+};
+
+/*
+ * Returns copy of string |s| with the length |len|. The returned
+ * string is NULL-terminated.
+ */
+static char* strcopy(const char *s, size_t len)
+{
+ char *dst;
+ dst = malloc(len+1);
+ if (NULL == dst)
+ abort ();
+ memcpy(dst, s, len);
+ dst[len] = '\0';
+ return dst;
+}
+
+/*
+ * Prints error message |msg| and exit.
+ */
+static void die(const char *msg)
+{
+ fprintf(stderr, "FATAL: %s\n", msg);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and message |msg|
+ * and exit.
+ */
+static void dief(const char *func, const char *msg)
+{
+ fprintf(stderr, "FATAL: %s: %s\n", func, msg);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and error code
+ * |error_code| and exit.
+ */
+static void diec(const char *func, int error_code)
+{
+ fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code,
+ spdylay_strerror(error_code));
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Check response is content-encoding: gzip. We need this because SPDY
+ * client is required to support gzip.
+ */
+static void check_gzip(struct Request *req, char **nv)
+{
+ int gzip = 0;
+ size_t i;
+ for(i = 0; nv[i]; i += 2) {
+ if(strcmp("content-encoding", nv[i]) == 0) {
+ gzip = strcmp("gzip", nv[i+1]) == 0;
+ break;
+ }
+ }
+ if(gzip) {
+ int rv;
+ if(req->inflater) {
+ return;
+ }
+ rv = spdylay_gzip_inflate_new(&req->inflater);
+ if(rv != 0) {
+ die("Can't allocate inflate stream.");
+ }
+ }
+}
+
+/*
+ * The implementation of spdylay_send_callback type. Here we write
+ * |data| with size |length| to the network and return the number of
+ * bytes actually written. See the documentation of
+ * spdylay_send_callback for the details.
+ */
+static ssize_t send_callback(spdylay_session *session,
+ const uint8_t *data, size_t length, int flags,
+ void *user_data)
+{
+ (void)session;
+ (void)flags;
+
+ struct Connection *connection;
+ ssize_t rv;
+ connection = (struct Connection*)user_data;
+ connection->want_io = IO_NONE;
+ ERR_clear_error();
+ rv = SSL_write(connection->ssl, data, length);
+ if(rv < 0) {
+ int err = SSL_get_error(connection->ssl, rv);
+ if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ connection->want_io = (err == SSL_ERROR_WANT_READ ?
+ WANT_READ : WANT_WRITE);
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ } else {
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return rv;
+}
+
+/*
+ * The implementation of spdylay_recv_callback type. Here we read data
+ * from the network and write them in |buf|. The capacity of |buf| is
+ * |length| bytes. Returns the number of bytes stored in |buf|. See
+ * the documentation of spdylay_recv_callback for the details.
+ */
+static ssize_t recv_callback(spdylay_session *session,
+ uint8_t *buf, size_t length, int flags,
+ void *user_data)
+{
+ (void)session;
+ (void)flags;
+
+ struct Connection *connection;
+ ssize_t rv;
+ connection = (struct Connection*)user_data;
+ connection->want_io = IO_NONE;
+ ERR_clear_error();
+ rv = SSL_read(connection->ssl, buf, length);
+ if(rv < 0) {
+ int err = SSL_get_error(connection->ssl, rv);
+ if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ connection->want_io = (err == SSL_ERROR_WANT_READ ?
+ WANT_READ : WANT_WRITE);
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ } else {
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ } else if(rv == 0) {
+ rv = SPDYLAY_ERR_EOF;
+ }
+ return rv;
+}
+
+/*
+ * The implementation of spdylay_before_ctrl_send_callback type. We
+ * use this function to get stream ID of the request. This is because
+ * stream ID is not known when we submit the request
+ * (spdylay_submit_request).
+ */
+static void before_ctrl_send_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame,
+ void *user_data)
+{
+ (void)user_data;
+
+ if(type == SPDYLAY_SYN_STREAM) {
+ struct Request *req;
+ int stream_id = frame->syn_stream.stream_id;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req && req->stream_id == -1) {
+ req->stream_id = stream_id;
+ spdylay_printf("[INFO] Stream ID = %d\n", stream_id);
+ }
+ }
+}
+
+static void on_ctrl_send_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame, void *user_data)
+{
+ (void)user_data;
+
+ char **nv;
+ const char *name = NULL;
+ int32_t stream_id;
+ size_t i;
+ switch(type) {
+ case SPDYLAY_SYN_STREAM:
+ nv = frame->syn_stream.nv;
+ name = "SYN_STREAM";
+ stream_id = frame->syn_stream.stream_id;
+ break;
+ default:
+ break;
+ }
+ if(name && spdylay_session_get_stream_user_data(session, stream_id)) {
+ spdylay_printf("[INFO] C ----------------------------> S (%s)\n", name);
+ for(i = 0; nv[i]; i += 2) {
+ spdylay_printf(" %s: %s\n", nv[i], nv[i+1]);
+ }
+ }
+}
+
+static void on_ctrl_recv_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame, void *user_data)
+{
+ (void)user_data;
+
+ struct Request *req;
+ char **nv;
+ const char *name = NULL;
+ int32_t stream_id;
+ size_t i;
+ switch(type) {
+ case SPDYLAY_SYN_REPLY:
+ nv = frame->syn_reply.nv;
+ name = "SYN_REPLY";
+ stream_id = frame->syn_reply.stream_id;
+ break;
+ case SPDYLAY_HEADERS:
+ nv = frame->headers.nv;
+ name = "HEADERS";
+ stream_id = frame->headers.stream_id;
+ break;
+ default:
+ break;
+ }
+ if(!name) {
+ return;
+ }
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ check_gzip(req, nv);
+ spdylay_printf("[INFO] C <---------------------------- S (%s)\n", name);
+ for(i = 0; nv[i]; i += 2) {
+ spdylay_printf(" %s: %s\n", nv[i], nv[i+1]);
+ }
+ }
+}
+
+/*
+ * The implementation of spdylay_on_stream_close_callback type. We use
+ * this function to know the response is fully received. Since we just
+ * fetch 1 resource in this program, after reception of the response,
+ * we submit GOAWAY and close the session.
+ */
+static void on_stream_close_callback(spdylay_session *session,
+ int32_t stream_id,
+ spdylay_status_code status_code,
+ void *user_data)
+{
+ (void)user_data;
+ (void)status_code;
+ struct Request *req;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ int rv;
+ rv = spdylay_submit_goaway(session, SPDYLAY_GOAWAY_OK);
+ if(rv != 0) {
+ diec("spdylay_submit_goaway", rv);
+ }
+ }
+}
+
+#define MAX_OUTLEN 4096
+
+/*
+ * The implementation of spdylay_on_data_chunk_recv_callback type. We
+ * use this function to print the received response body.
+ */
+static void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *data, size_t len,
+ void *user_data)
+{
+ (void)user_data;
+ (void)flags;
+
+ struct Request *req;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ spdylay_printf("[INFO] C <---------------------------- S (DATA)\n");
+ spdylay_printf(" %lu bytes\n", (unsigned long int)len);
+ if(req->inflater) {
+ while(len > 0) {
+ uint8_t out[MAX_OUTLEN];
+ size_t outlen = MAX_OUTLEN;
+ size_t tlen = len;
+ int rv;
+ rv = spdylay_gzip_inflate(req->inflater, out, &outlen, data, &tlen);
+ if(rv == -1) {
+ spdylay_submit_rst_stream(session, stream_id, SPDYLAY_INTERNAL_ERROR);
+ break;
+ }
+ fwrite(out, 1, outlen, stdout);
+ data += tlen;
+ len -= tlen;
+ }
+ } else {
+ /* TODO add support gzip */
+ fwrite(data, 1, len, stdout);
+
+ //check if the data is correct
+ if(strcmp(RESPONSE_BODY, (char *)data) != 0)
+ killparent(parent, "\nreceived data is not the same");
+ }
+ spdylay_printf("\n");
+ }
+}
+
+/*
+ * Setup callback functions. Spdylay API offers many callback
+ * functions, but most of them are optional. The send_callback is
+ * always required. Since we use spdylay_session_recv(), the
+ * recv_callback is also required.
+ */
+static void setup_spdylay_callbacks(spdylay_session_callbacks *callbacks)
+{
+ memset(callbacks, 0, sizeof(spdylay_session_callbacks));
+ callbacks->send_callback = send_callback;
+ callbacks->recv_callback = recv_callback;
+ callbacks->before_ctrl_send_callback = before_ctrl_send_callback;
+ callbacks->on_ctrl_send_callback = on_ctrl_send_callback;
+ callbacks->on_ctrl_recv_callback = on_ctrl_recv_callback;
+ callbacks->on_stream_close_callback = on_stream_close_callback;
+ callbacks->on_data_chunk_recv_callback = on_data_chunk_recv_callback;
+}
+
+/*
+ * Callback function for SSL/TLS NPN. Since this program only supports
+ * SPDY protocol, if server does not offer SPDY protocol the Spdylay
+ * library supports, we terminate program.
+ */
+static int select_next_proto_cb(SSL* ssl,
+ unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ void *arg)
+{
+ (void)ssl;
+
+ int rv;
+ uint16_t *spdy_proto_version;
+ /* spdylay_select_next_protocol() selects SPDY protocol version the
+ Spdylay library supports. */
+ rv = spdylay_select_next_protocol(out, outlen, in, inlen);
+ if(rv <= 0) {
+ die("Server did not advertise spdy/2 or spdy/3 protocol.");
+ }
+ spdy_proto_version = (uint16_t*)arg;
+ *spdy_proto_version = rv;
+ return SSL_TLSEXT_ERR_OK;
+}
+
+/*
+ * Setup SSL context. We pass |spdy_proto_version| to get negotiated
+ * SPDY protocol version in NPN callback.
+ */
+static void init_ssl_ctx(SSL_CTX *ssl_ctx, uint16_t *spdy_proto_version)
+{
+ /* Disable SSLv2 and enable all workarounds for buggy servers */
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+ /* Set NPN callback */
+ SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb,
+ spdy_proto_version);
+}
+
+static void ssl_handshake(SSL *ssl, int fd)
+{
+ int rv;
+ if(SSL_set_fd(ssl, fd) == 0) {
+ dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL));
+ }
+ ERR_clear_error();
+ rv = SSL_connect(ssl);
+ if(rv <= 0) {
+ dief("SSL_connect", ERR_error_string(ERR_get_error(), NULL));
+ }
+}
+
+/*
+ * Connects to the host |host| and port |port|. This function returns
+ * the file descriptor of the client socket.
+ */
+static int connect_to(const char *host, uint16_t port)
+{
+ struct addrinfo hints;
+ int fd = -1;
+ int rv;
+ char service[NI_MAXSERV];
+ struct addrinfo *res, *rp;
+ snprintf(service, sizeof(service), "%u", port);
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ rv = getaddrinfo(host, service, &hints, &res);
+ if(rv != 0) {
+ dief("getaddrinfo", gai_strerror(rv));
+ }
+ for(rp = res; rp; rp = rp->ai_next) {
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if(fd == -1) {
+ continue;
+ }
+ while((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 &&
+ errno == EINTR);
+ if(rv == 0) {
+ break;
+ }
+ close(fd);
+ fd = -1;
+ }
+ freeaddrinfo(res);
+ return fd;
+}
+
+static void make_non_block(int fd)
+{
+ int flags, rv;
+ while((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR);
+ if(flags == -1) {
+ dief("fcntl", strerror(errno));
+ }
+ while((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR);
+ if(rv == -1) {
+ dief("fcntl", strerror(errno));
+ }
+}
+
+/*
+ * Setting TCP_NODELAY is not mandatory for the SPDY protocol.
+ */
+static void set_tcp_nodelay(int fd)
+{
+ int val = 1;
+ int rv;
+ rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
+ if(rv == -1) {
+ dief("setsockopt", strerror(errno));
+ }
+}
+
+/*
+ * Update |pollfd| based on the state of |connection|.
+ */
+static void ctl_poll(struct pollfd *pollfd, struct Connection *connection)
+{
+ pollfd->events = 0;
+ if(spdylay_session_want_read(connection->session) ||
+ connection->want_io == WANT_READ) {
+ pollfd->events |= POLLIN;
+ }
+ if(spdylay_session_want_write(connection->session) ||
+ connection->want_io == WANT_WRITE) {
+ pollfd->events |= POLLOUT;
+ }
+}
+
+/*
+ * Submits the request |req| to the connection |connection|. This
+ * function does not send packets; just append the request to the
+ * internal queue in |connection->session|.
+ */
+static void submit_request(struct Connection *connection, struct Request *req)
+{
+ int pri = 0;
+ int rv;
+ const char *nv[15];
+ /* We always use SPDY/3 style header even if the negotiated protocol
+ version is SPDY/2. The library translates the header name as
+ necessary. Make sure that the last item is NULL! */
+ nv[0] = ":method"; nv[1] = "GET";
+ nv[2] = ":path"; nv[3] = req->path;
+ nv[4] = ":version"; nv[5] = "HTTP/1.1";
+ nv[6] = ":scheme"; nv[7] = "https";
+ nv[8] = ":host"; nv[9] = req->hostport;
+ nv[10] = "accept"; nv[11] = "*/*";
+ nv[12] = "user-agent"; nv[13] = "spdylay/"SPDYLAY_VERSION;
+ nv[14] = NULL;
+ rv = spdylay_submit_request(connection->session, pri, nv, NULL, req);
+ if(rv != 0) {
+ diec("spdylay_submit_request", rv);
+ }
+}
+
+/*
+ * Performs the network I/O.
+ */
+static void exec_io(struct Connection *connection)
+{
+ int rv;
+ rv = spdylay_session_recv(connection->session);
+ if(rv != 0) {
+ diec("spdylay_session_recv", rv);
+ }
+ rv = spdylay_session_send(connection->session);
+ if(rv != 0) {
+ diec("spdylay_session_send", rv);
+ }
+}
+
+static void request_init(struct Request *req, const struct URI *uri)
+{
+ req->host = strcopy(uri->host, uri->hostlen);
+ req->port = uri->port;
+ req->path = strcopy(uri->path, uri->pathlen);
+ req->hostport = strcopy(uri->hostport, uri->hostportlen);
+ req->stream_id = -1;
+ req->inflater = NULL;
+}
+
+static void request_free(struct Request *req)
+{
+ free(req->host);
+ free(req->path);
+ free(req->hostport);
+ spdylay_gzip_inflate_del(req->inflater);
+}
+
+/*
+ * Fetches the resource denoted by |uri|.
+ */
+static void fetch_uri(const struct URI *uri)
+{
+ spdylay_session_callbacks callbacks;
+ int fd;
+ SSL_CTX *ssl_ctx;
+ SSL *ssl;
+ struct Request req;
+ struct Connection connection;
+ int rv;
+ nfds_t npollfds = 1;
+ struct pollfd pollfds[1];
+ uint16_t spdy_proto_version;
+
+ request_init(&req, uri);
+
+ setup_spdylay_callbacks(&callbacks);
+
+ /* Establish connection and setup SSL */
+ fd = connect_to(req.host, req.port);
+ if (-1 == fd)
+ abort ();
+ ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if(ssl_ctx == NULL) {
+ dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL));
+ }
+ init_ssl_ctx(ssl_ctx, &spdy_proto_version);
+ ssl = SSL_new(ssl_ctx);
+ if(ssl == NULL) {
+ dief("SSL_new", ERR_error_string(ERR_get_error(), NULL));
+ }
+ /* To simplify the program, we perform SSL/TLS handshake in blocking
+ I/O. */
+ ssl_handshake(ssl, fd);
+
+ connection.ssl = ssl;
+ connection.want_io = IO_NONE;
+
+ /* Here make file descriptor non-block */
+ make_non_block(fd);
+ set_tcp_nodelay(fd);
+
+ spdylay_printf("[INFO] SPDY protocol version = %d\n", spdy_proto_version);
+ rv = spdylay_session_client_new(&connection.session, spdy_proto_version,
+ &callbacks, &connection);
+ if(rv != 0) {
+ diec("spdylay_session_client_new", rv);
+ }
+
+ /* Submit the HTTP request to the outbound queue. */
+ submit_request(&connection, &req);
+
+ pollfds[0].fd = fd;
+ ctl_poll(pollfds, &connection);
+
+ /* Event loop */
+ while(spdylay_session_want_read(connection.session) ||
+ spdylay_session_want_write(connection.session)) {
+ int nfds = poll(pollfds, npollfds, -1);
+ if(nfds == -1) {
+ dief("poll", strerror(errno));
+ }
+ if(pollfds[0].revents & (POLLIN | POLLOUT)) {
+ exec_io(&connection);
+ }
+ if((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) {
+ die("Connection error");
+ }
+ ctl_poll(pollfds, &connection);
+ }
+
+ /* Resource cleanup */
+ spdylay_session_del(connection.session);
+ SSL_shutdown(ssl);
+ SSL_free(ssl);
+ SSL_CTX_free(ssl_ctx);
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ request_free(&req);
+}
+
+static int parse_uri(struct URI *res, const char *uri)
+{
+ /* We only interested in https */
+ size_t len, i, offset;
+ memset(res, 0, sizeof(struct URI));
+ len = strlen(uri);
+ if(len < 9 || memcmp("https://", uri, 8) != 0) {
+ return -1;
+ }
+ offset = 8;
+ res->host = res->hostport = &uri[offset];
+ res->hostlen = 0;
+ if(uri[offset] == '[') {
+ /* IPv6 literal address */
+ ++offset;
+ ++res->host;
+ for(i = offset; i < len; ++i) {
+ if(uri[i] == ']') {
+ res->hostlen = i-offset;
+ offset = i+1;
+ break;
+ }
+ }
+ } else {
+ const char delims[] = ":/?#";
+ for(i = offset; i < len; ++i) {
+ if(strchr(delims, uri[i]) != NULL) {
+ break;
+ }
+ }
+ res->hostlen = i-offset;
+ offset = i;
+ }
+ if(res->hostlen == 0) {
+ return -1;
+ }
+ /* Assuming https */
+ res->port = 443;
+ if(offset < len) {
+ if(uri[offset] == ':') {
+ /* port */
+ const char delims[] = "/?#";
+ int port = 0;
+ ++offset;
+ for(i = offset; i < len; ++i) {
+ if(strchr(delims, uri[i]) != NULL) {
+ break;
+ }
+ if('0' <= uri[i] && uri[i] <= '9') {
+ port *= 10;
+ port += uri[i]-'0';
+ if(port > 65535) {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ }
+ if(port == 0) {
+ return -1;
+ }
+ offset = i;
+ res->port = port;
+ }
+ }
+ res->hostportlen = uri+offset-res->host;
+ for(i = offset; i < len; ++i) {
+ if(uri[i] == '#') {
+ break;
+ }
+ }
+ if(i-offset == 0) {
+ res->path = "/";
+ res->pathlen = 1;
+ } else {
+ res->path = &uri[offset];
+ res->pathlen = i-offset;
+ }
+ return 0;
+}
+
+
+/*****
+ * end of code needed to utilize spdylay
+ */
+
+
+/*****
+ * start of code needed to utilize microspdy
+ */
+
+void
+new_session_callback (void *cls,
+ struct SPDY_Session * session)
+{
+ char ipstr[1024];
+
+ struct sockaddr *addr;
+ socklen_t addr_len = SPDY_get_remote_addr(session, &addr);
+
+ if(!addr_len)
+ {
+ printf("SPDY_get_remote_addr");
+ abort();
+ }
+
+ if(strcmp(CLS,cls)!=0)
+ {
+ killchild(child,"wrong cls");
+ }
+
+ if(AF_INET == addr->sa_family)
+ {
+ struct sockaddr_in * addr4 = (struct sockaddr_in *) addr;
+ if(NULL == inet_ntop(AF_INET, &(addr4->sin_addr), ipstr, sizeof(ipstr)))
+ {
+ killchild(child,"inet_ntop");
+ }
+ printf("New connection from: %s:%i\n", ipstr, ntohs(addr4->sin_port));
+
+ loop = 0;
+ }
+#if HAVE_INET6
+ else if(AF_INET6 == addr->sa_family)
+ {
+ struct sockaddr_in6 * addr6 = (struct sockaddr_in6 *) addr;
+ if(NULL == inet_ntop(AF_INET6, &(addr6->sin6_addr), ipstr, sizeof(ipstr)))
+ {
+ killchild(child,"inet_ntop");
+ }
+ printf("New connection from: %s:%i\n", ipstr, ntohs(addr6->sin6_port));
+
+ loop = 0;
+ }
+#endif
+ else
+ {
+ killchild(child,"wrong family");
+ }
+}
+
+/*****
+ * end of code needed to utilize microspdy
+ */
+
+//child process
+void
+childproc(int parent)
+{
+ struct URI uri;
+ struct sigaction act;
+ int rv;
+ char *uristr;
+
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, 0);
+
+ asprintf(&uristr, "https://127.0.0.1:%i/",port);
+
+ SSL_load_error_strings();
+ SSL_library_init();
+
+ rv = parse_uri(&uri, uristr);
+ if(rv != 0) {
+ killparent(parent,"parse_uri failed");
+ }
+ fetch_uri(&uri);
+}
+
+//parent proc
+int
+parentproc(int child)
+{
+ int childstatus = 0;
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+ struct SPDY_Daemon *daemon;
+
+ SPDY_init();
+
+ daemon = SPDY_start_daemon(port,
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ &new_session_callback,NULL,NULL,NULL,CLS,SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ do
+ {
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(ret) {
+ case -1:
+ printf("select error: %i\n", errno);
+ killchild(child, "select error");
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon);
+
+ break;
+ }
+ }
+ while(loop && waitpid(child,&childstatus,WNOHANG) != child);
+
+ SPDY_stop_daemon(daemon);
+
+ SPDY_deinit();
+
+ if(loop)
+ return WEXITSTATUS(childstatus);
+ if(waitpid(child,&childstatus,WNOHANG) == child)
+ return WEXITSTATUS(childstatus);
+
+ kill(child,SIGKILL);
+
+ waitpid(child,&childstatus,0);
+
+ return 0;
+}
+
+int main()
+{
+ port = get_port(14123);
+ parent = getpid();
+
+ child = fork();
+ if (child == -1)
+ {
+ fprintf(stderr, "can't fork, error %d\n", errno);
+ exit(EXIT_FAILURE);
+ }
+
+ if (child == 0)
+ {
+ childproc(parent);
+ _exit(0);
+ }
+ else
+ {
+ int ret = parentproc(child);
+ exit(ret);
+ }
+ return 1;
+}
diff --git a/src/testspdy/test_notls.c b/src/testspdy/test_notls.c
new file mode 100644
index 0000000..3f44d96
--- /dev/null
+++ b/src/testspdy/test_notls.c
@@ -0,0 +1,973 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file request_response.c
+ * @brief tests receiving request and sending response. spdycli.c (spdylay)
+ * code is reused here
+ * @author Andrey Uzunov
+ * @author Tatsuhiro Tsujikawa
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include <sys/wait.h>
+#include "common.h"
+
+#define RESPONSE_BODY "<html><body><b>Hi, this is libmicrospdy!</b></body></html>"
+
+#define CLS "anything"
+
+pid_t parent;
+pid_t child;
+char *rcvbuf;
+int rcvbuf_c = 0;
+
+int session_closed_called = 0;
+
+void
+killchild(int pid, char *message)
+{
+ printf("%s\n",message);
+ kill(pid, SIGKILL);
+ exit(1);
+}
+
+void
+killparent(int pid, char *message)
+{
+ printf("%s\n",message);
+ kill(pid, SIGKILL);
+ _exit(1);
+}
+
+
+/*****
+ * start of code needed to utilize spdylay
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <spdylay/spdylay.h>
+
+enum {
+ IO_NONE,
+ WANT_READ,
+ WANT_WRITE
+};
+
+struct Connection {
+ spdylay_session *session;
+ /* WANT_READ if SSL connection needs more input; or WANT_WRITE if it
+ needs more output; or IO_NONE. This is necessary because SSL/TLS
+ re-negotiation is possible at any time. Spdylay API offers
+ similar functions like spdylay_session_want_read() and
+ spdylay_session_want_write() but they do not take into account
+ SSL connection. */
+ int want_io;
+ int fd;
+};
+
+struct Request {
+ char *host;
+ uint16_t port;
+ /* In this program, path contains query component as well. */
+ char *path;
+ /* This is the concatenation of host and port with ":" in
+ between. */
+ char *hostport;
+ /* Stream ID for this request. */
+ int32_t stream_id;
+ /* The gzip stream inflater for the compressed response. */
+ spdylay_gzip *inflater;
+};
+
+struct URI {
+ const char *host;
+ size_t hostlen;
+ uint16_t port;
+ /* In this program, path contains query component as well. */
+ const char *path;
+ size_t pathlen;
+ const char *hostport;
+ size_t hostportlen;
+};
+
+/*
+ * Returns copy of string |s| with the length |len|. The returned
+ * string is NULL-terminated.
+ */
+static char* strcopy(const char *s, size_t len)
+{
+ char *dst;
+ dst = malloc(len+1);
+ if (NULL == dst)
+ abort ();
+ memcpy(dst, s, len);
+ dst[len] = '\0';
+ return dst;
+}
+
+/*
+ * Prints error message |msg| and exit.
+ */
+static void die(const char *msg)
+{
+ fprintf(stderr, "FATAL: %s\n", msg);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and message |msg|
+ * and exit.
+ */
+static void dief(const char *func, const char *msg)
+{
+ fprintf(stderr, "FATAL: %s: %s\n", func, msg);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and error code
+ * |error_code| and exit.
+ */
+static void diec(const char *func, int error_code)
+{
+ fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code,
+ spdylay_strerror(error_code));
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Check response is content-encoding: gzip. We need this because SPDY
+ * client is required to support gzip.
+ */
+static void check_gzip(struct Request *req, char **nv)
+{
+ int gzip = 0;
+ size_t i;
+ for(i = 0; nv[i]; i += 2) {
+ if(strcmp("content-encoding", nv[i]) == 0) {
+ gzip = strcmp("gzip", nv[i+1]) == 0;
+ break;
+ }
+ }
+ if(gzip) {
+ int rv;
+ if(req->inflater) {
+ return;
+ }
+ rv = spdylay_gzip_inflate_new(&req->inflater);
+ if(rv != 0) {
+ die("Can't allocate inflate stream.");
+ }
+ }
+}
+
+/*
+ * The implementation of spdylay_send_callback type. Here we write
+ * |data| with size |length| to the network and return the number of
+ * bytes actually written. See the documentation of
+ * spdylay_send_callback for the details.
+ */
+static ssize_t send_callback(spdylay_session *session,
+ const uint8_t *data, size_t length, int flags,
+ void *user_data)
+{
+ (void)session;
+ (void)flags;
+
+ struct Connection *connection;
+ ssize_t rv;
+ connection = (struct Connection*)user_data;
+ connection->want_io = IO_NONE;
+
+ rv = write(connection->fd,
+ data,
+ length);
+
+ if (rv < 0)
+ {
+ switch(errno)
+ {
+ case EAGAIN:
+ #if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+ #endif
+ connection->want_io = WANT_WRITE;
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ break;
+
+ default:
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return rv;
+}
+
+/*
+ * The implementation of spdylay_recv_callback type. Here we read data
+ * from the network and write them in |buf|. The capacity of |buf| is
+ * |length| bytes. Returns the number of bytes stored in |buf|. See
+ * the documentation of spdylay_recv_callback for the details.
+ */
+static ssize_t recv_callback(spdylay_session *session,
+ uint8_t *buf, size_t length, int flags,
+ void *user_data)
+{
+ (void)session;
+ (void)flags;
+
+ struct Connection *connection;
+ ssize_t rv;
+ connection = (struct Connection*)user_data;
+ connection->want_io = IO_NONE;
+
+ rv = read(connection->fd,
+ buf,
+ length);
+
+ if (rv < 0)
+ {
+ switch(errno)
+ {
+ case EAGAIN:
+ #if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+ #endif
+ connection->want_io = WANT_READ;
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ break;
+
+ default:
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ }
+ else if(rv == 0)
+ rv = SPDYLAY_ERR_EOF;
+ return rv;
+}
+
+/*
+ * The implementation of spdylay_before_ctrl_send_callback type. We
+ * use this function to get stream ID of the request. This is because
+ * stream ID is not known when we submit the request
+ * (spdylay_submit_request).
+ */
+static void before_ctrl_send_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame,
+ void *user_data)
+{
+ (void)user_data;
+
+ if(type == SPDYLAY_SYN_STREAM) {
+ struct Request *req;
+ int stream_id = frame->syn_stream.stream_id;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req && req->stream_id == -1) {
+ req->stream_id = stream_id;
+ printf("[INFO] Stream ID = %d\n", stream_id);
+ }
+ }
+}
+
+static void on_ctrl_send_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame, void *user_data)
+{
+ (void)user_data;
+
+ char **nv;
+ const char *name = NULL;
+ int32_t stream_id;
+ size_t i;
+ switch(type) {
+ case SPDYLAY_SYN_STREAM:
+ nv = frame->syn_stream.nv;
+ name = "SYN_STREAM";
+ stream_id = frame->syn_stream.stream_id;
+ break;
+ default:
+ break;
+ }
+ if(name && spdylay_session_get_stream_user_data(session, stream_id)) {
+ printf("[INFO] C ----------------------------> S (%s)\n", name);
+ for(i = 0; nv[i]; i += 2) {
+ printf(" %s: %s\n", nv[i], nv[i+1]);
+ }
+ }
+}
+
+static void on_ctrl_recv_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame, void *user_data)
+{
+ (void)user_data;
+
+ struct Request *req;
+ char **nv;
+ const char *name = NULL;
+ int32_t stream_id;
+ size_t i;
+ switch(type) {
+ case SPDYLAY_SYN_REPLY:
+ nv = frame->syn_reply.nv;
+ name = "SYN_REPLY";
+ stream_id = frame->syn_reply.stream_id;
+ break;
+ case SPDYLAY_HEADERS:
+ nv = frame->headers.nv;
+ name = "HEADERS";
+ stream_id = frame->headers.stream_id;
+ break;
+ default:
+ break;
+ }
+ if(!name) {
+ return;
+ }
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ check_gzip(req, nv);
+ printf("[INFO] C <---------------------------- S (%s)\n", name);
+ for(i = 0; nv[i]; i += 2) {
+ printf(" %s: %s\n", nv[i], nv[i+1]);
+ }
+ }
+}
+
+/*
+ * The implementation of spdylay_on_stream_close_callback type. We use
+ * this function to know the response is fully received. Since we just
+ * fetch 1 resource in this program, after reception of the response,
+ * we submit GOAWAY and close the session.
+ */
+static void on_stream_close_callback(spdylay_session *session,
+ int32_t stream_id,
+ spdylay_status_code status_code,
+ void *user_data)
+{
+ (void)status_code;
+ (void)user_data;
+
+ struct Request *req;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ int rv;
+ rv = spdylay_submit_goaway(session, SPDYLAY_GOAWAY_OK);
+ if(rv != 0) {
+ diec("spdylay_submit_goaway", rv);
+ }
+ }
+}
+
+#define MAX_OUTLEN 4096
+
+/*
+ * The implementation of spdylay_on_data_chunk_recv_callback type. We
+ * use this function to print the received response body.
+ */
+static void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *data, size_t len,
+ void *user_data)
+{
+ (void)flags;
+ (void)user_data;
+
+ struct Request *req;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ printf("[INFO] C <---------------------------- S (DATA)\n");
+ printf(" %lu bytes\n", (unsigned long int)len);
+ if(req->inflater) {
+ while(len > 0) {
+ uint8_t out[MAX_OUTLEN];
+ size_t outlen = MAX_OUTLEN;
+ size_t tlen = len;
+ int rv;
+ rv = spdylay_gzip_inflate(req->inflater, out, &outlen, data, &tlen);
+ if(rv == -1) {
+ spdylay_submit_rst_stream(session, stream_id, SPDYLAY_INTERNAL_ERROR);
+ break;
+ }
+ fwrite(out, 1, outlen, stdout);
+ data += tlen;
+ len -= tlen;
+ }
+ } else {
+ /* TODO add support gzip */
+ fwrite(data, 1, len, stdout);
+
+ //check if the data is correct
+ //if(strcmp(RESPONSE_BODY, data) != 0)
+ //killparent(parent, "\nreceived data is not the same");
+ if(len + rcvbuf_c > strlen(RESPONSE_BODY))
+ killparent(parent, "\nreceived data is not the same");
+
+ strcpy(rcvbuf + rcvbuf_c,(char*)data);
+ rcvbuf_c+=len;
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * Setup callback functions. Spdylay API offers many callback
+ * functions, but most of them are optional. The send_callback is
+ * always required. Since we use spdylay_session_recv(), the
+ * recv_callback is also required.
+ */
+static void setup_spdylay_callbacks(spdylay_session_callbacks *callbacks)
+{
+ memset(callbacks, 0, sizeof(spdylay_session_callbacks));
+ callbacks->send_callback = send_callback;
+ callbacks->recv_callback = recv_callback;
+ callbacks->before_ctrl_send_callback = before_ctrl_send_callback;
+ callbacks->on_ctrl_send_callback = on_ctrl_send_callback;
+ callbacks->on_ctrl_recv_callback = on_ctrl_recv_callback;
+ callbacks->on_stream_close_callback = on_stream_close_callback;
+ callbacks->on_data_chunk_recv_callback = on_data_chunk_recv_callback;
+}
+
+
+/*
+ * Connects to the host |host| and port |port|. This function returns
+ * the file descriptor of the client socket.
+ */
+static int connect_to(const char *host, uint16_t port)
+{
+ struct addrinfo hints;
+ int fd = -1;
+ int rv;
+ char service[NI_MAXSERV];
+ struct addrinfo *res, *rp;
+ snprintf(service, sizeof(service), "%u", port);
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ rv = getaddrinfo(host, service, &hints, &res);
+ if(rv != 0) {
+ dief("getaddrinfo", gai_strerror(rv));
+ }
+ for(rp = res; rp; rp = rp->ai_next) {
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if(fd == -1) {
+ continue;
+ }
+ while((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 &&
+ errno == EINTR);
+ if(rv == 0) {
+ break;
+ }
+ close(fd);
+ fd = -1;
+ dief("connect", strerror(errno));
+ }
+ freeaddrinfo(res);
+ return fd;
+}
+
+static void make_non_block(int fd)
+{
+ int flags, rv;
+ while((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR);
+ if(flags == -1) {
+ dief("fcntl1", strerror(errno));
+ }
+ while((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR);
+ if(rv == -1) {
+ dief("fcntl2", strerror(errno));
+ }
+}
+
+/*
+ * Setting TCP_NODELAY is not mandatory for the SPDY protocol.
+ */
+static void set_tcp_nodelay(int fd)
+{
+ int val = 1;
+ int rv;
+ rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
+ if(rv == -1) {
+ dief("setsockopt", strerror(errno));
+ }
+}
+
+/*
+ * Update |pollfd| based on the state of |connection|.
+ */
+static void ctl_poll(struct pollfd *pollfd, struct Connection *connection)
+{
+ pollfd->events = 0;
+ if(spdylay_session_want_read(connection->session) ||
+ connection->want_io == WANT_READ) {
+ pollfd->events |= POLLIN;
+ }
+ if(spdylay_session_want_write(connection->session) ||
+ connection->want_io == WANT_WRITE) {
+ pollfd->events |= POLLOUT;
+ }
+}
+
+/*
+ * Submits the request |req| to the connection |connection|. This
+ * function does not send packets; just append the request to the
+ * internal queue in |connection->session|.
+ */
+static void submit_request(struct Connection *connection, struct Request *req)
+{
+ int pri = 0;
+ int rv;
+ const char *nv[15];
+ /* We always use SPDY/3 style header even if the negotiated protocol
+ version is SPDY/2. The library translates the header name as
+ necessary. Make sure that the last item is NULL! */
+ nv[0] = ":method"; nv[1] = "GET";
+ nv[2] = ":path"; nv[3] = req->path;
+ nv[4] = ":version"; nv[5] = "HTTP/1.1";
+ nv[6] = ":scheme"; nv[7] = "https";
+ nv[8] = ":host"; nv[9] = req->hostport;
+ nv[10] = "accept"; nv[11] = "*/*";
+ nv[12] = "user-agent"; nv[13] = "spdylay/"SPDYLAY_VERSION;
+ nv[14] = NULL;
+ rv = spdylay_submit_request(connection->session, pri, nv, NULL, req);
+ if(rv != 0) {
+ diec("spdylay_submit_request", rv);
+ }
+}
+
+/*
+ * Performs the network I/O.
+ */
+static void exec_io(struct Connection *connection)
+{
+ int rv;
+ rv = spdylay_session_recv(connection->session);
+ if(rv != 0) {
+ diec("spdylay_session_recv", rv);
+ }
+ rv = spdylay_session_send(connection->session);
+ if(rv != 0) {
+ diec("spdylay_session_send", rv);
+ }
+}
+
+static void request_init(struct Request *req, const struct URI *uri)
+{
+ req->host = strcopy(uri->host, uri->hostlen);
+ req->port = uri->port;
+ req->path = strcopy(uri->path, uri->pathlen);
+ req->hostport = strcopy(uri->hostport, uri->hostportlen);
+ req->stream_id = -1;
+ req->inflater = NULL;
+}
+
+static void request_free(struct Request *req)
+{
+ free(req->host);
+ free(req->path);
+ free(req->hostport);
+ spdylay_gzip_inflate_del(req->inflater);
+}
+
+/*
+ * Fetches the resource denoted by |uri|.
+ */
+static void fetch_uri(const struct URI *uri)
+{
+ spdylay_session_callbacks callbacks;
+ int fd;
+ struct Request req;
+ struct Connection connection;
+ int rv;
+ nfds_t npollfds = 1;
+ struct pollfd pollfds[1];
+ uint16_t spdy_proto_version = 3;
+
+ request_init(&req, uri);
+
+ setup_spdylay_callbacks(&callbacks);
+
+ /* Establish connection and setup SSL */
+ fd = connect_to(req.host, req.port);
+ if (-1 == fd)
+ abort ();
+
+ connection.fd = fd;
+ connection.want_io = IO_NONE;
+
+ /* Here make file descriptor non-block */
+ make_non_block(fd);
+ set_tcp_nodelay(fd);
+
+ printf("[INFO] SPDY protocol version = %d\n", spdy_proto_version);
+ rv = spdylay_session_client_new(&connection.session, spdy_proto_version,
+ &callbacks, &connection);
+ if(rv != 0) {
+ diec("spdylay_session_client_new", rv);
+ }
+
+ /* Submit the HTTP request to the outbound queue. */
+ submit_request(&connection, &req);
+
+ pollfds[0].fd = fd;
+ ctl_poll(pollfds, &connection);
+
+ /* Event loop */
+ while(spdylay_session_want_read(connection.session) ||
+ spdylay_session_want_write(connection.session)) {
+ int nfds = poll(pollfds, npollfds, -1);
+ if(nfds == -1) {
+ dief("poll", strerror(errno));
+ }
+ if(pollfds[0].revents & (POLLIN | POLLOUT)) {
+ exec_io(&connection);
+ }
+ if((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) {
+ die("Connection error");
+ }
+ ctl_poll(pollfds, &connection);
+ }
+
+ /* Resource cleanup */
+ spdylay_session_del(connection.session);
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ request_free(&req);
+}
+
+static int parse_uri(struct URI *res, const char *uri)
+{
+ /* We only interested in https */
+ size_t len, i, offset;
+ memset(res, 0, sizeof(struct URI));
+ len = strlen(uri);
+ if(len < 9 || memcmp("https://", uri, 8) != 0) {
+ return -1;
+ }
+ offset = 8;
+ res->host = res->hostport = &uri[offset];
+ res->hostlen = 0;
+ if(uri[offset] == '[') {
+ /* IPv6 literal address */
+ ++offset;
+ ++res->host;
+ for(i = offset; i < len; ++i) {
+ if(uri[i] == ']') {
+ res->hostlen = i-offset;
+ offset = i+1;
+ break;
+ }
+ }
+ } else {
+ const char delims[] = ":/?#";
+ for(i = offset; i < len; ++i) {
+ if(strchr(delims, uri[i]) != NULL) {
+ break;
+ }
+ }
+ res->hostlen = i-offset;
+ offset = i;
+ }
+ if(res->hostlen == 0) {
+ return -1;
+ }
+ /* Assuming https */
+ res->port = 443;
+ if(offset < len) {
+ if(uri[offset] == ':') {
+ /* port */
+ const char delims[] = "/?#";
+ int port = 0;
+ ++offset;
+ for(i = offset; i < len; ++i) {
+ if(strchr(delims, uri[i]) != NULL) {
+ break;
+ }
+ if('0' <= uri[i] && uri[i] <= '9') {
+ port *= 10;
+ port += uri[i]-'0';
+ if(port > 65535) {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ }
+ if(port == 0) {
+ return -1;
+ }
+ offset = i;
+ res->port = port;
+ }
+ }
+ res->hostportlen = uri+offset-res->host;
+ for(i = offset; i < len; ++i) {
+ if(uri[i] == '#') {
+ break;
+ }
+ }
+ if(i-offset == 0) {
+ res->path = "/";
+ res->pathlen = 1;
+ } else {
+ res->path = &uri[offset];
+ res->pathlen = i-offset;
+ }
+ return 0;
+}
+
+
+/*****
+ * end of code needed to utilize spdylay
+ */
+
+
+/*****
+ * start of code needed to utilize microspdy
+ */
+
+
+void
+standard_request_handler(void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers,
+ bool more)
+{
+ (void)cls;
+ (void)request;
+ (void)priority;
+ (void)host;
+ (void)scheme;
+ (void)headers;
+ (void)method;
+ (void)version;
+ (void)more;
+
+ struct SPDY_Response *response=NULL;
+
+ if(strcmp(CLS,cls)!=0)
+ {
+ killchild(child,"wrong cls");
+ }
+
+ response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,RESPONSE_BODY,strlen(RESPONSE_BODY));
+
+ if(NULL==response){
+ fprintf(stdout,"no response obj\n");
+ exit(3);
+ }
+
+ if(SPDY_queue_response(request,response,true,false,NULL,(void*)strdup(path))!=SPDY_YES)
+ {
+ fprintf(stdout,"queue\n");
+ exit(4);
+ }
+}
+
+void
+session_closed_handler (void *cls,
+ struct SPDY_Session * session,
+ int by_client)
+{
+ printf("session_closed_handler called\n");
+
+ if(strcmp(CLS,cls)!=0)
+ {
+ killchild(child,"wrong cls");
+ }
+
+ if(SPDY_YES != by_client)
+ {
+ //killchild(child,"wrong by_client");
+ printf("session closed by server\n");
+ }
+ else
+ {
+ printf("session closed by client\n");
+ }
+
+ if(NULL == session)
+ {
+ killchild(child,"session is NULL");
+ }
+
+ session_closed_called = 1;
+}
+
+
+/*****
+ * end of code needed to utilize microspdy
+ */
+
+//child process
+void
+childproc(int port)
+{
+ struct URI uri;
+ struct sigaction act;
+ int rv;
+ char *uristr;
+
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, 0);
+
+ usleep(10000);
+ asprintf(&uristr, "https://127.0.0.1:%i/",port);
+ if(NULL == (rcvbuf = malloc(strlen(RESPONSE_BODY)+1)))
+ killparent(parent,"no memory");
+
+ rv = parse_uri(&uri, uristr);
+ if(rv != 0) {
+ killparent(parent,"parse_uri failed");
+ }
+ fetch_uri(&uri);
+
+ if(strcmp(rcvbuf, RESPONSE_BODY))
+ killparent(parent,"received data is different");
+}
+
+//parent proc
+int
+parentproc( int port)
+{
+ int childstatus;
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+ struct SPDY_Daemon *daemon;
+
+ SPDY_init();
+
+ daemon = SPDY_start_daemon(port,
+ NULL,
+ NULL,
+ NULL,&session_closed_handler,&standard_request_handler,NULL,CLS,
+ SPDY_DAEMON_OPTION_IO_SUBSYSTEM, SPDY_IO_SUBSYSTEM_RAW,
+ SPDY_DAEMON_OPTION_FLAGS, SPDY_DAEMON_FLAG_NO_DELAY,
+ SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ do
+ {
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(ret) {
+ case -1:
+ printf("select error: %i\n", errno);
+ killchild(child, "select error");
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon);
+
+ break;
+ }
+ }
+ while(waitpid(child,&childstatus,WNOHANG) != child);
+
+ //give chance to the client to close socket and handle this in run
+ usleep(100000);
+ SPDY_run(daemon);
+
+ SPDY_stop_daemon(daemon);
+
+ SPDY_deinit();
+
+ return WEXITSTATUS(childstatus);
+}
+
+int main()
+{
+ int port = get_port(12123);
+ parent = getpid();
+
+ child = fork();
+ if (child == -1)
+ {
+ fprintf(stderr, "can't fork, error %d\n", errno);
+ exit(EXIT_FAILURE);
+ }
+
+ if (child == 0)
+ {
+ childproc(port);
+ _exit(0);
+ }
+ else
+ {
+ int ret = parentproc(port);
+ if(1 == session_closed_called && 0 == ret)
+ exit(0);
+ else
+ exit(ret ? ret : 21);
+ }
+ return 1;
+}
diff --git a/src/testspdy/test_proxies.c b/src/testspdy/test_proxies.c
new file mode 100644
index 0000000..44a0c1b
--- /dev/null
+++ b/src/testspdy/test_proxies.c
@@ -0,0 +1,255 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file test_proxies.c
+ * @brief test curl > mhd2spdylay > microspdy2http > mhd
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include "common.h"
+#include <sys/wait.h>
+#include <stdio.h> /* printf, stderr, fprintf */
+#include <sys/types.h> /* pid_t */
+#include <unistd.h> /* _exit, fork */
+#include <stdlib.h> /* exit */
+#include <errno.h> /* errno */
+#include <sys/wait.h> /* pid_t */
+#include "common.h"
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+#define EXPECTED_BODY "<html><head><title>libmicrohttpd demo</title></head><body>libmicrohttpd demo</body></html>"
+
+
+pid_t parent;
+ pid_t child_mhd;
+ pid_t child_spdy2http;
+ pid_t child_mhd2spdy;
+ pid_t child_curl;
+
+ uint16_t mhd_port;
+ uint16_t spdy2http_port;
+ uint16_t mhd2spdy_port;
+
+void
+killproc(int pid, const char *message)
+{
+ printf("%s\nkilling %i\n",message,pid);
+ kill(pid, SIGKILL);
+}
+
+
+void killchildren()
+{
+ if(0 != child_mhd)
+ killproc(child_mhd,"kill mhd\n");
+ if(0 != child_spdy2http)
+ killproc(child_spdy2http,"kill spdy2http\n");
+ if(0 != child_mhd2spdy)
+ killproc(child_mhd2spdy,"kill mhd2spdy\n");
+ if(0 != child_curl)
+ killproc(child_curl,"kill curl\n");
+}
+
+pid_t au_fork()
+{
+ pid_t child = fork();
+ if (child == -1)
+ {
+ killchildren();
+
+ killproc(parent,"fork failed\n");
+ }
+
+ return child;
+}
+
+
+int main()
+{
+ //pid_t child;
+ int childstatus;
+ pid_t wpid;
+
+ parent = getpid();
+ mhd_port = get_port(4000);
+ spdy2http_port = get_port(4100);
+ mhd2spdy_port = get_port(4200);
+
+ child_mhd = au_fork();
+ if (child_mhd == 0)
+ {
+ //run MHD
+ pid_t devnull;
+ char *port_s;
+
+ close(1);
+ devnull = open("/dev/null", O_WRONLY);
+ if (-1 == devnull)
+ abort();
+ if (1 != devnull)
+ {
+ dup2(devnull, 1);
+ close(devnull);
+ }
+ asprintf(&port_s, "%i", mhd_port);
+ execlp ("../examples/minimal_example", "minimal_example", port_s, NULL);
+ fprintf(stderr, "executing mhd failed\nFor this test 'make' must be run before 'make check'!\n");
+ //killchildren();
+ _exit(1);
+ }
+
+
+ child_spdy2http = au_fork();
+ if (child_spdy2http == 0)
+ {
+ //run spdy2http
+ pid_t devnull;
+ char *port_s;
+ //char *url;
+
+ close(1);
+ devnull = open("/dev/null", O_WRONLY);
+ if (-1 == devnull)
+ abort();
+ if (1 != devnull)
+ {
+ dup2(devnull, 1);
+ close(devnull);
+ }
+ //asprintf(&url, "127.0.0.1:%i", mhd_port);
+ asprintf(&port_s, "%i", spdy2http_port);
+ sleep(1);
+ execlp ("../spdy2http/microspdy2http", "microspdy2http", "-v4rtT", "10", "-p", port_s, NULL);
+ fprintf(stderr, "executing microspdy2http failed\n");
+ //killchildren();
+ _exit(1);
+ }
+
+ child_mhd2spdy = au_fork();
+ if (child_mhd2spdy == 0)
+ {
+ //run MHD2sdpy
+ pid_t devnull;
+ char *port_s;
+ char *url;
+
+ close(1);
+ devnull = open("/dev/null", O_WRONLY);
+ if (-1 == devnull)
+ abort();
+ if (1 != devnull)
+ {
+ dup2(devnull, 1);
+ close(devnull);
+ }
+ asprintf(&url, "http://127.0.0.1:%i", spdy2http_port);
+ asprintf(&port_s, "%i", mhd2spdy_port);
+ sleep(2);
+ execlp ("../examples/mhd2spdy", "mhd2spdy", "-vosb", url, "-p", port_s, NULL);
+ fprintf(stderr, "executing mhd2spdy failed\n");
+ //killchildren();
+ _exit(1);
+ }
+
+ child_curl = au_fork();
+ if (child_curl == 0)
+ {
+ //run curl
+ FILE *p;
+ pid_t devnull;
+ char *cmd;
+ unsigned int i;
+ int retc;
+ char buf[strlen(EXPECTED_BODY) + 1];
+
+ close(1);
+ devnull = open("/dev/null", O_WRONLY);
+ if (-1 == devnull)
+ abort ();
+ if (1 != devnull)
+ {
+ dup2(devnull, 1);
+ close(devnull);
+ }
+
+ asprintf (&cmd, "curl --proxy http://127.0.0.1:%i http://127.0.0.1:%i/", mhd2spdy_port, mhd_port);
+ sleep(3);
+ p = popen(cmd, "r");
+ if (p != NULL)
+ {
+ for (i = 0; i < strlen(EXPECTED_BODY) && !feof(p); i++)
+ {
+ retc = fgetc (p);
+ if (EOF == retc)
+ abort (); /* what did feof(p) do there!? */
+ buf[i] = (char) retc;
+ }
+
+ pclose(p);
+ buf[i] = 0;
+ _exit(strcmp(EXPECTED_BODY, buf));
+ }
+ fprintf(stderr, "executing curl failed\n");
+ //killchildren();
+ _exit(1);
+ }
+
+ do
+ {
+ wpid = waitpid(child_mhd,&childstatus,WNOHANG);
+ if(wpid == child_mhd)
+ {
+ fprintf(stderr, "mhd died unexpectedly\n");
+ killchildren();
+ return 1;
+ }
+
+ wpid = waitpid(child_spdy2http,&childstatus,WNOHANG);
+ if(wpid == child_spdy2http)
+ {
+ fprintf(stderr, "spdy2http died unexpectedly\n");
+ killchildren();
+ return 1;
+ }
+
+ wpid = waitpid(child_mhd2spdy,&childstatus,WNOHANG);
+ if(wpid == child_mhd2spdy)
+ {
+ fprintf(stderr, "mhd2spdy died unexpectedly\n");
+ killchildren();
+ return 1;
+ }
+
+ if(waitpid(child_curl,&childstatus,WNOHANG) == child_curl)
+ {
+ killchildren();
+ return WEXITSTATUS(childstatus);
+ }
+ sleep(1);
+ }
+ while(true);
+}
diff --git a/src/testspdy/test_request_response.c b/src/testspdy/test_request_response.c
new file mode 100644
index 0000000..093da03
--- /dev/null
+++ b/src/testspdy/test_request_response.c
@@ -0,0 +1,1029 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file request_response.c
+ * @brief tests receiving request and sending response. spdycli.c (spdylay)
+ * code is reused here
+ * @author Andrey Uzunov
+ * @author Tatsuhiro Tsujikawa
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include <sys/wait.h>
+#include "common.h"
+
+#define RESPONSE_BODY "<html><body><b>Hi, this is libmicrospdy!</b></body></html>"
+
+#define CLS "anything"
+
+pid_t parent;
+pid_t child;
+char *rcvbuf;
+int rcvbuf_c = 0;
+
+int session_closed_called = 0;
+
+void
+killchild(int pid, char *message)
+{
+ printf("%s\n",message);
+ kill(pid, SIGKILL);
+ exit(1);
+}
+
+void
+killparent(int pid, char *message)
+{
+ printf("%s\n",message);
+ kill(pid, SIGKILL);
+ _exit(1);
+}
+
+
+/*****
+ * start of code needed to utilize spdylay
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <spdylay/spdylay.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+enum {
+ IO_NONE,
+ WANT_READ,
+ WANT_WRITE
+};
+
+struct Connection {
+ SSL *ssl;
+ spdylay_session *session;
+ /* WANT_READ if SSL connection needs more input; or WANT_WRITE if it
+ needs more output; or IO_NONE. This is necessary because SSL/TLS
+ re-negotiation is possible at any time. Spdylay API offers
+ similar functions like spdylay_session_want_read() and
+ spdylay_session_want_write() but they do not take into account
+ SSL connection. */
+ int want_io;
+};
+
+struct Request {
+ char *host;
+ uint16_t port;
+ /* In this program, path contains query component as well. */
+ char *path;
+ /* This is the concatenation of host and port with ":" in
+ between. */
+ char *hostport;
+ /* Stream ID for this request. */
+ int32_t stream_id;
+ /* The gzip stream inflater for the compressed response. */
+ spdylay_gzip *inflater;
+};
+
+struct URI {
+ const char *host;
+ size_t hostlen;
+ uint16_t port;
+ /* In this program, path contains query component as well. */
+ const char *path;
+ size_t pathlen;
+ const char *hostport;
+ size_t hostportlen;
+};
+
+/*
+ * Returns copy of string |s| with the length |len|. The returned
+ * string is NULL-terminated.
+ */
+static char* strcopy(const char *s, size_t len)
+{
+ char *dst;
+ dst = malloc(len+1);
+ if (NULL == dst)
+ abort ();
+ memcpy(dst, s, len);
+ dst[len] = '\0';
+ return dst;
+}
+
+/*
+ * Prints error message |msg| and exit.
+ */
+static void die(const char *msg)
+{
+ fprintf(stderr, "FATAL: %s\n", msg);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and message |msg|
+ * and exit.
+ */
+static void dief(const char *func, const char *msg)
+{
+ fprintf(stderr, "FATAL: %s: %s\n", func, msg);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and error code
+ * |error_code| and exit.
+ */
+static void diec(const char *func, int error_code)
+{
+ fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code,
+ spdylay_strerror(error_code));
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Check response is content-encoding: gzip. We need this because SPDY
+ * client is required to support gzip.
+ */
+static void check_gzip(struct Request *req, char **nv)
+{
+ int gzip = 0;
+ size_t i;
+ for(i = 0; nv[i]; i += 2) {
+ if(strcmp("content-encoding", nv[i]) == 0) {
+ gzip = strcmp("gzip", nv[i+1]) == 0;
+ break;
+ }
+ }
+ if(gzip) {
+ int rv;
+ if(req->inflater) {
+ return;
+ }
+ rv = spdylay_gzip_inflate_new(&req->inflater);
+ if(rv != 0) {
+ die("Can't allocate inflate stream.");
+ }
+ }
+}
+
+/*
+ * The implementation of spdylay_send_callback type. Here we write
+ * |data| with size |length| to the network and return the number of
+ * bytes actually written. See the documentation of
+ * spdylay_send_callback for the details.
+ */
+static ssize_t send_callback(spdylay_session *session,
+ const uint8_t *data, size_t length, int flags,
+ void *user_data)
+{
+ (void)session;
+ (void)flags;
+
+ struct Connection *connection;
+ ssize_t rv;
+ connection = (struct Connection*)user_data;
+ connection->want_io = IO_NONE;
+ ERR_clear_error();
+ rv = SSL_write(connection->ssl, data, length);
+ if(rv < 0) {
+ int err = SSL_get_error(connection->ssl, rv);
+ if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ connection->want_io = (err == SSL_ERROR_WANT_READ ?
+ WANT_READ : WANT_WRITE);
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ } else {
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return rv;
+}
+
+/*
+ * The implementation of spdylay_recv_callback type. Here we read data
+ * from the network and write them in |buf|. The capacity of |buf| is
+ * |length| bytes. Returns the number of bytes stored in |buf|. See
+ * the documentation of spdylay_recv_callback for the details.
+ */
+static ssize_t recv_callback(spdylay_session *session,
+ uint8_t *buf, size_t length, int flags,
+ void *user_data)
+{
+ (void)session;
+ (void)flags;
+
+ struct Connection *connection;
+ ssize_t rv;
+ connection = (struct Connection*)user_data;
+ connection->want_io = IO_NONE;
+ ERR_clear_error();
+ rv = SSL_read(connection->ssl, buf, length);
+ if(rv < 0) {
+ int err = SSL_get_error(connection->ssl, rv);
+ if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ connection->want_io = (err == SSL_ERROR_WANT_READ ?
+ WANT_READ : WANT_WRITE);
+ rv = SPDYLAY_ERR_WOULDBLOCK;
+ } else {
+ rv = SPDYLAY_ERR_CALLBACK_FAILURE;
+ }
+ } else if(rv == 0) {
+ rv = SPDYLAY_ERR_EOF;
+ }
+ return rv;
+}
+
+/*
+ * The implementation of spdylay_before_ctrl_send_callback type. We
+ * use this function to get stream ID of the request. This is because
+ * stream ID is not known when we submit the request
+ * (spdylay_submit_request).
+ */
+static void before_ctrl_send_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame,
+ void *user_data)
+{
+ (void)user_data;
+
+ if(type == SPDYLAY_SYN_STREAM) {
+ struct Request *req;
+ int stream_id = frame->syn_stream.stream_id;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req && req->stream_id == -1) {
+ req->stream_id = stream_id;
+ printf("[INFO] Stream ID = %d\n", stream_id);
+ }
+ }
+}
+
+static void on_ctrl_send_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame, void *user_data)
+{
+ (void)user_data;
+
+ char **nv;
+ const char *name = NULL;
+ int32_t stream_id;
+ size_t i;
+ switch(type) {
+ case SPDYLAY_SYN_STREAM:
+ nv = frame->syn_stream.nv;
+ name = "SYN_STREAM";
+ stream_id = frame->syn_stream.stream_id;
+ break;
+ default:
+ break;
+ }
+ if(name && spdylay_session_get_stream_user_data(session, stream_id)) {
+ printf("[INFO] C ----------------------------> S (%s)\n", name);
+ for(i = 0; nv[i]; i += 2) {
+ printf(" %s: %s\n", nv[i], nv[i+1]);
+ }
+ }
+}
+
+static void on_ctrl_recv_callback(spdylay_session *session,
+ spdylay_frame_type type,
+ spdylay_frame *frame, void *user_data)
+{
+ (void)user_data;
+
+ struct Request *req;
+ char **nv;
+ const char *name = NULL;
+ int32_t stream_id;
+ size_t i;
+ switch(type) {
+ case SPDYLAY_SYN_REPLY:
+ nv = frame->syn_reply.nv;
+ name = "SYN_REPLY";
+ stream_id = frame->syn_reply.stream_id;
+ break;
+ case SPDYLAY_HEADERS:
+ nv = frame->headers.nv;
+ name = "HEADERS";
+ stream_id = frame->headers.stream_id;
+ break;
+ default:
+ break;
+ }
+ if(!name) {
+ return;
+ }
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ check_gzip(req, nv);
+ printf("[INFO] C <---------------------------- S (%s)\n", name);
+ for(i = 0; nv[i]; i += 2) {
+ printf(" %s: %s\n", nv[i], nv[i+1]);
+ }
+ }
+}
+
+/*
+ * The implementation of spdylay_on_stream_close_callback type. We use
+ * this function to know the response is fully received. Since we just
+ * fetch 1 resource in this program, after reception of the response,
+ * we submit GOAWAY and close the session.
+ */
+static void on_stream_close_callback(spdylay_session *session,
+ int32_t stream_id,
+ spdylay_status_code status_code,
+ void *user_data)
+{
+ (void)user_data;
+ (void)status_code;
+
+ struct Request *req;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ int rv;
+ rv = spdylay_submit_goaway(session, SPDYLAY_GOAWAY_OK);
+ if(rv != 0) {
+ diec("spdylay_submit_goaway", rv);
+ }
+ }
+}
+
+#define MAX_OUTLEN 4096
+
+/*
+ * The implementation of spdylay_on_data_chunk_recv_callback type. We
+ * use this function to print the received response body.
+ */
+static void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *data, size_t len,
+ void *user_data)
+{
+ (void)user_data;
+ (void)flags;
+
+ struct Request *req;
+ req = spdylay_session_get_stream_user_data(session, stream_id);
+ if(req) {
+ printf("[INFO] C <---------------------------- S (DATA)\n");
+ printf(" %lu bytes\n", (unsigned long int)len);
+ if(req->inflater) {
+ while(len > 0) {
+ uint8_t out[MAX_OUTLEN];
+ size_t outlen = MAX_OUTLEN;
+ size_t tlen = len;
+ int rv;
+ rv = spdylay_gzip_inflate(req->inflater, out, &outlen, data, &tlen);
+ if(rv == -1) {
+ spdylay_submit_rst_stream(session, stream_id, SPDYLAY_INTERNAL_ERROR);
+ break;
+ }
+ fwrite(out, 1, outlen, stdout);
+ data += tlen;
+ len -= tlen;
+ }
+ } else {
+ /* TODO add support gzip */
+ fwrite(data, 1, len, stdout);
+
+ //check if the data is correct
+ //if(strcmp(RESPONSE_BODY, data) != 0)
+ //killparent(parent, "\nreceived data is not the same");
+ if(len + rcvbuf_c > strlen(RESPONSE_BODY))
+ killparent(parent, "\nreceived data is not the same");
+
+ strcpy(rcvbuf + rcvbuf_c,(char*)data);
+ rcvbuf_c+=len;
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * Setup callback functions. Spdylay API offers many callback
+ * functions, but most of them are optional. The send_callback is
+ * always required. Since we use spdylay_session_recv(), the
+ * recv_callback is also required.
+ */
+static void setup_spdylay_callbacks(spdylay_session_callbacks *callbacks)
+{
+ memset(callbacks, 0, sizeof(spdylay_session_callbacks));
+ callbacks->send_callback = send_callback;
+ callbacks->recv_callback = recv_callback;
+ callbacks->before_ctrl_send_callback = before_ctrl_send_callback;
+ callbacks->on_ctrl_send_callback = on_ctrl_send_callback;
+ callbacks->on_ctrl_recv_callback = on_ctrl_recv_callback;
+ callbacks->on_stream_close_callback = on_stream_close_callback;
+ callbacks->on_data_chunk_recv_callback = on_data_chunk_recv_callback;
+}
+
+/*
+ * Callback function for SSL/TLS NPN. Since this program only supports
+ * SPDY protocol, if server does not offer SPDY protocol the Spdylay
+ * library supports, we terminate program.
+ */
+static int select_next_proto_cb(SSL* ssl,
+ unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ void *arg)
+{
+ (void)ssl;
+
+ int rv;
+ uint16_t *spdy_proto_version;
+ /* spdylay_select_next_protocol() selects SPDY protocol version the
+ Spdylay library supports. */
+ rv = spdylay_select_next_protocol(out, outlen, in, inlen);
+ if(rv <= 0) {
+ die("Server did not advertise spdy/2 or spdy/3 protocol.");
+ }
+ spdy_proto_version = (uint16_t*)arg;
+ *spdy_proto_version = rv;
+ return SSL_TLSEXT_ERR_OK;
+}
+
+/*
+ * Setup SSL context. We pass |spdy_proto_version| to get negotiated
+ * SPDY protocol version in NPN callback.
+ */
+static void init_ssl_ctx(SSL_CTX *ssl_ctx, uint16_t *spdy_proto_version)
+{
+ /* Disable SSLv2 and enable all workarounds for buggy servers */
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+ /* Set NPN callback */
+ SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb,
+ spdy_proto_version);
+}
+
+static void ssl_handshake(SSL *ssl, int fd)
+{
+ int rv;
+ if(SSL_set_fd(ssl, fd) == 0) {
+ dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL));
+ }
+ ERR_clear_error();
+ rv = SSL_connect(ssl);
+ if(rv <= 0) {
+ dief("SSL_connect", ERR_error_string(ERR_get_error(), NULL));
+ }
+}
+
+/*
+ * Connects to the host |host| and port |port|. This function returns
+ * the file descriptor of the client socket.
+ */
+static int connect_to(const char *host, uint16_t port)
+{
+ struct addrinfo hints;
+ int fd = -1;
+ int rv;
+ char service[NI_MAXSERV];
+ struct addrinfo *res, *rp;
+ snprintf(service, sizeof(service), "%u", port);
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ rv = getaddrinfo(host, service, &hints, &res);
+ if(rv != 0) {
+ dief("getaddrinfo", gai_strerror(rv));
+ }
+ for(rp = res; rp; rp = rp->ai_next) {
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if(fd == -1) {
+ continue;
+ }
+ while((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 &&
+ errno == EINTR);
+ if(rv == 0) {
+ break;
+ }
+ close(fd);
+ fd = -1;
+ }
+ freeaddrinfo(res);
+ return fd;
+}
+
+static void make_non_block(int fd)
+{
+ int flags, rv;
+ while((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR);
+ if(flags == -1) {
+ dief("fcntl", strerror(errno));
+ }
+ while((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR);
+ if(rv == -1) {
+ dief("fcntl", strerror(errno));
+ }
+}
+
+/*
+ * Setting TCP_NODELAY is not mandatory for the SPDY protocol.
+ */
+static void set_tcp_nodelay(int fd)
+{
+ int val = 1;
+ int rv;
+ rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
+ if(rv == -1) {
+ dief("setsockopt", strerror(errno));
+ }
+}
+
+/*
+ * Update |pollfd| based on the state of |connection|.
+ */
+static void ctl_poll(struct pollfd *pollfd, struct Connection *connection)
+{
+ pollfd->events = 0;
+ if(spdylay_session_want_read(connection->session) ||
+ connection->want_io == WANT_READ) {
+ pollfd->events |= POLLIN;
+ }
+ if(spdylay_session_want_write(connection->session) ||
+ connection->want_io == WANT_WRITE) {
+ pollfd->events |= POLLOUT;
+ }
+}
+
+/*
+ * Submits the request |req| to the connection |connection|. This
+ * function does not send packets; just append the request to the
+ * internal queue in |connection->session|.
+ */
+static void submit_request(struct Connection *connection, struct Request *req)
+{
+ int pri = 0;
+ int rv;
+ const char *nv[15];
+ /* We always use SPDY/3 style header even if the negotiated protocol
+ version is SPDY/2. The library translates the header name as
+ necessary. Make sure that the last item is NULL! */
+ nv[0] = ":method"; nv[1] = "GET";
+ nv[2] = ":path"; nv[3] = req->path;
+ nv[4] = ":version"; nv[5] = "HTTP/1.1";
+ nv[6] = ":scheme"; nv[7] = "https";
+ nv[8] = ":host"; nv[9] = req->hostport;
+ nv[10] = "accept"; nv[11] = "*/*";
+ nv[12] = "user-agent"; nv[13] = "spdylay/"SPDYLAY_VERSION;
+ nv[14] = NULL;
+ rv = spdylay_submit_request(connection->session, pri, nv, NULL, req);
+ if(rv != 0) {
+ diec("spdylay_submit_request", rv);
+ }
+}
+
+/*
+ * Performs the network I/O.
+ */
+static void exec_io(struct Connection *connection)
+{
+ int rv;
+ rv = spdylay_session_recv(connection->session);
+ if(rv != 0) {
+ diec("spdylay_session_recv", rv);
+ }
+ rv = spdylay_session_send(connection->session);
+ if(rv != 0) {
+ diec("spdylay_session_send", rv);
+ }
+}
+
+static void request_init(struct Request *req, const struct URI *uri)
+{
+ req->host = strcopy(uri->host, uri->hostlen);
+ req->port = uri->port;
+ req->path = strcopy(uri->path, uri->pathlen);
+ req->hostport = strcopy(uri->hostport, uri->hostportlen);
+ req->stream_id = -1;
+ req->inflater = NULL;
+}
+
+static void request_free(struct Request *req)
+{
+ free(req->host);
+ free(req->path);
+ free(req->hostport);
+ spdylay_gzip_inflate_del(req->inflater);
+}
+
+/*
+ * Fetches the resource denoted by |uri|.
+ */
+static void fetch_uri(const struct URI *uri)
+{
+ spdylay_session_callbacks callbacks;
+ int fd;
+ SSL_CTX *ssl_ctx;
+ SSL *ssl;
+ struct Request req;
+ struct Connection connection;
+ int rv;
+ nfds_t npollfds = 1;
+ struct pollfd pollfds[1];
+ uint16_t spdy_proto_version;
+
+ request_init(&req, uri);
+
+ setup_spdylay_callbacks(&callbacks);
+
+ /* Establish connection and setup SSL */
+ fd = connect_to(req.host, req.port);
+ if (-1 == fd)
+ abort ();
+ ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if(ssl_ctx == NULL) {
+ dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL));
+ }
+ init_ssl_ctx(ssl_ctx, &spdy_proto_version);
+ ssl = SSL_new(ssl_ctx);
+ if(ssl == NULL) {
+ dief("SSL_new", ERR_error_string(ERR_get_error(), NULL));
+ }
+ /* To simplify the program, we perform SSL/TLS handshake in blocking
+ I/O. */
+ ssl_handshake(ssl, fd);
+
+ connection.ssl = ssl;
+ connection.want_io = IO_NONE;
+
+ /* Here make file descriptor non-block */
+ make_non_block(fd);
+ set_tcp_nodelay(fd);
+
+ printf("[INFO] SPDY protocol version = %d\n", spdy_proto_version);
+ rv = spdylay_session_client_new(&connection.session, spdy_proto_version,
+ &callbacks, &connection);
+ if(rv != 0) {
+ diec("spdylay_session_client_new", rv);
+ }
+
+ /* Submit the HTTP request to the outbound queue. */
+ submit_request(&connection, &req);
+
+ pollfds[0].fd = fd;
+ ctl_poll(pollfds, &connection);
+
+ /* Event loop */
+ while(spdylay_session_want_read(connection.session) ||
+ spdylay_session_want_write(connection.session)) {
+ int nfds = poll(pollfds, npollfds, -1);
+ if(nfds == -1) {
+ dief("poll", strerror(errno));
+ }
+ if(pollfds[0].revents & (POLLIN | POLLOUT)) {
+ exec_io(&connection);
+ }
+ if((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) {
+ die("Connection error");
+ }
+ ctl_poll(pollfds, &connection);
+ }
+
+ /* Resource cleanup */
+ spdylay_session_del(connection.session);
+ SSL_shutdown(ssl);
+ SSL_free(ssl);
+ SSL_CTX_free(ssl_ctx);
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ request_free(&req);
+}
+
+static int parse_uri(struct URI *res, const char *uri)
+{
+ /* We only interested in https */
+ size_t len, i, offset;
+ memset(res, 0, sizeof(struct URI));
+ len = strlen(uri);
+ if(len < 9 || memcmp("https://", uri, 8) != 0) {
+ return -1;
+ }
+ offset = 8;
+ res->host = res->hostport = &uri[offset];
+ res->hostlen = 0;
+ if(uri[offset] == '[') {
+ /* IPv6 literal address */
+ ++offset;
+ ++res->host;
+ for(i = offset; i < len; ++i) {
+ if(uri[i] == ']') {
+ res->hostlen = i-offset;
+ offset = i+1;
+ break;
+ }
+ }
+ } else {
+ const char delims[] = ":/?#";
+ for(i = offset; i < len; ++i) {
+ if(strchr(delims, uri[i]) != NULL) {
+ break;
+ }
+ }
+ res->hostlen = i-offset;
+ offset = i;
+ }
+ if(res->hostlen == 0) {
+ return -1;
+ }
+ /* Assuming https */
+ res->port = 443;
+ if(offset < len) {
+ if(uri[offset] == ':') {
+ /* port */
+ const char delims[] = "/?#";
+ int port = 0;
+ ++offset;
+ for(i = offset; i < len; ++i) {
+ if(strchr(delims, uri[i]) != NULL) {
+ break;
+ }
+ if('0' <= uri[i] && uri[i] <= '9') {
+ port *= 10;
+ port += uri[i]-'0';
+ if(port > 65535) {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ }
+ if(port == 0) {
+ return -1;
+ }
+ offset = i;
+ res->port = port;
+ }
+ }
+ res->hostportlen = uri+offset-res->host;
+ for(i = offset; i < len; ++i) {
+ if(uri[i] == '#') {
+ break;
+ }
+ }
+ if(i-offset == 0) {
+ res->path = "/";
+ res->pathlen = 1;
+ } else {
+ res->path = &uri[offset];
+ res->pathlen = i-offset;
+ }
+ return 0;
+}
+
+
+/*****
+ * end of code needed to utilize spdylay
+ */
+
+
+/*****
+ * start of code needed to utilize microspdy
+ */
+
+
+void
+standard_request_handler(void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers,
+ bool more)
+{
+ (void)cls;
+ (void)request;
+ (void)priority;
+ (void)host;
+ (void)scheme;
+ (void)headers;
+ (void)method;
+ (void)version;
+
+ struct SPDY_Response *response=NULL;
+
+ if(strcmp(CLS,cls)!=0)
+ {
+ killchild(child,"wrong cls");
+ }
+
+ if(false != more){
+ fprintf(stdout,"more has wrong value\n");
+ exit(5);
+ }
+
+ response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,RESPONSE_BODY,strlen(RESPONSE_BODY));
+
+ if(NULL==response){
+ fprintf(stdout,"no response obj\n");
+ exit(3);
+ }
+
+ if(SPDY_queue_response(request,response,true,false,NULL,(void*)strdup(path))!=SPDY_YES)
+ {
+ fprintf(stdout,"queue\n");
+ exit(4);
+ }
+}
+
+void
+session_closed_handler (void *cls,
+ struct SPDY_Session * session,
+ int by_client)
+{
+ printf("session_closed_handler called\n");
+
+ if(strcmp(CLS,cls)!=0)
+ {
+ killchild(child,"wrong cls");
+ }
+
+ if(SPDY_YES != by_client)
+ {
+ //killchild(child,"wrong by_client");
+ printf("session closed by server\n");
+ }
+ else
+ {
+ printf("session closed by client\n");
+ }
+
+ if(NULL == session)
+ {
+ killchild(child,"session is NULL");
+ }
+
+ session_closed_called = 1;
+}
+
+
+/*****
+ * end of code needed to utilize microspdy
+ */
+
+//child process
+void
+childproc(int port)
+{
+ struct URI uri;
+ struct sigaction act;
+ int rv;
+ char *uristr;
+
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, 0);
+
+ asprintf(&uristr, "https://127.0.0.1:%i/",port);
+ if(NULL == (rcvbuf = malloc(strlen(RESPONSE_BODY)+1)))
+ killparent(parent,"no memory");
+
+ SSL_load_error_strings();
+ SSL_library_init();
+
+ rv = parse_uri(&uri, uristr);
+ if(rv != 0) {
+ killparent(parent,"parse_uri failed");
+ }
+ fetch_uri(&uri);
+
+ if(strcmp(rcvbuf, RESPONSE_BODY))
+ killparent(parent,"received data is different");
+}
+
+//parent proc
+int
+parentproc( int port)
+{
+ int childstatus;
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+ struct SPDY_Daemon *daemon;
+
+ SPDY_init();
+
+ daemon = SPDY_start_daemon(port,
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ NULL,&session_closed_handler,&standard_request_handler,NULL,CLS,SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ do
+ {
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(ret) {
+ case -1:
+ printf("select error: %i\n", errno);
+ killchild(child, "select error");
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon);
+
+ break;
+ }
+ }
+ while(waitpid(child,&childstatus,WNOHANG) != child);
+
+ //give chance to the client to close socket and handle this in run
+ usleep(100000);
+ SPDY_run(daemon);
+
+ SPDY_stop_daemon(daemon);
+
+ SPDY_deinit();
+
+ return WEXITSTATUS(childstatus);
+}
+
+int main()
+{
+ int port = get_port(12123);
+ parent = getpid();
+
+ child = fork();
+ if (child == -1)
+ {
+ fprintf(stderr, "can't fork, error %d\n", errno);
+ exit(EXIT_FAILURE);
+ }
+
+ if (child == 0)
+ {
+ childproc(port);
+ _exit(0);
+ }
+ else
+ {
+ int ret = parentproc(port);
+ if(1 == session_closed_called && 0 == ret)
+ exit(0);
+ else
+ exit(ret ? ret : 21);
+ }
+ return 1;
+}
diff --git a/src/testspdy/test_request_response_with_callback.c b/src/testspdy/test_request_response_with_callback.c
new file mode 100644
index 0000000..95fc263
--- /dev/null
+++ b/src/testspdy/test_request_response_with_callback.c
@@ -0,0 +1,320 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file request_response_with_callback.c
+ * @brief tests responses with callbacks
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include "stdio.h"
+#include <sys/wait.h>
+#include <ctype.h>
+#include "common.h"
+#include <sys/time.h>
+#include <sys/stat.h>
+
+int port;
+
+pid_t parent;
+pid_t child;
+
+int run = 1;
+int chunk_size=1;
+
+void
+killchild()
+{
+ kill(child, SIGKILL);
+ exit(1);
+}
+
+void
+killparent()
+{
+ kill(parent, SIGKILL);
+ _exit(1);
+}
+
+ssize_t
+response_callback (void *cls,
+ void *buffer,
+ size_t max,
+ bool *more)
+{
+ FILE *fd =(FILE*)cls;
+
+ size_t n;
+ if(chunk_size % 2)
+ n = chunk_size;
+ else
+ n = max - chunk_size;
+
+ if(n < 1) n = 1;
+ else if (n > max) n=max;
+ chunk_size++;
+
+ int ret = fread(buffer,1,n,fd);
+ *more = feof(fd) == 0;
+
+ //printf("more is %i\n",*more);
+
+ if(!(*more))
+ fclose(fd);
+
+ return ret;
+}
+
+
+void
+response_done_callback(void *cls,
+ struct SPDY_Response * response,
+ struct SPDY_Request * request,
+ enum SPDY_RESPONSE_RESULT status,
+ bool streamopened)
+{
+ (void)status;
+ (void)streamopened;
+
+ printf("answer for %s was sent\n", (char*)cls);
+
+ SPDY_destroy_request(request);
+ SPDY_destroy_response(response);
+ free(cls);
+
+ run = 0;
+}
+
+void
+standard_request_handler(void *cls,
+ struct SPDY_Request * request,
+ uint8_t priority,
+ const char *method,
+ const char *path,
+ const char *version,
+ const char *host,
+ const char *scheme,
+ struct SPDY_NameValue * headers,
+ bool more)
+{
+ (void)cls;
+ (void)request;
+ (void)priority;
+ (void)host;
+ (void)scheme;
+ (void)headers;
+ (void)method;
+ (void)version;
+ (void)more;
+
+ struct SPDY_Response *response=NULL;
+ struct SPDY_NameValue *resp_headers;
+
+ printf("received request for '%s %s %s'\n", method, path, version);
+
+ FILE *fd = fopen(DATA_DIR "spdy-draft.txt","r");
+
+ if(NULL == (resp_headers = SPDY_name_value_create()))
+ {
+ fprintf(stdout,"SPDY_name_value_create failed\n");
+ killchild();
+ }
+ if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_TYPE,"text/plain"))
+ {
+ fprintf(stdout,"SPDY_name_value_add failed\n");
+ killchild();
+ }
+
+ response = SPDY_build_response_with_callback(200,NULL,
+ SPDY_HTTP_VERSION_1_1,resp_headers,&response_callback,fd,SPDY_MAX_SUPPORTED_FRAME_SIZE);
+ SPDY_name_value_destroy(resp_headers);
+
+ if(NULL==response){
+ fprintf(stdout,"no response obj\n");
+ killchild();
+ }
+
+ void *clspath = strdup(path);
+
+ if(SPDY_queue_response(request,response,true,false,&response_done_callback,clspath)!=SPDY_YES)
+ {
+ fprintf(stdout,"queue\n");
+ killchild();
+ }
+}
+
+int
+parentproc()
+{
+ int childstatus;
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+ struct SPDY_Daemon *daemon;
+
+ SPDY_init();
+
+ daemon = SPDY_start_daemon(port,
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ NULL,
+ NULL,
+ &standard_request_handler,
+ NULL,
+ NULL,
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
+ 1800,
+ SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ do
+ {
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+ if(SPDY_NO == ret || timeoutlong > 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(ret) {
+ case -1:
+ printf("select error: %i\n", errno);
+ break;
+ case 0:
+
+ break;
+ default:
+ SPDY_run(daemon);
+
+ break;
+ }
+ }
+ while(waitpid(child,&childstatus,WNOHANG) != child);
+
+ SPDY_stop_daemon(daemon);
+
+ SPDY_deinit();
+
+ return WEXITSTATUS(childstatus);
+}
+
+#define MD5_LEN 32
+
+int
+md5(char *cmd, char *md5_sum)
+{
+ FILE *p = popen(cmd, "r");
+ if (p == NULL) return 0;
+
+ int i, ch;
+ for (i = 0; i < MD5_LEN && isxdigit(ch = fgetc(p)); i++) {
+ *md5_sum++ = ch;
+ }
+
+ *md5_sum = '\0';
+ pclose(p);
+ return i == MD5_LEN;
+}
+
+int
+childproc()
+{
+ char *cmd1;
+ char *cmd2;
+ char md5_sum1[33];
+ char md5_sum2[33];
+ int ret;
+ struct timeval tv1;
+ struct timeval tv2;
+ struct stat st;
+ //int secs;
+ uint64_t usecs;
+
+ asprintf(&cmd1, "spdycat https://127.0.0.1:%i/ | md5sum",port);
+ asprintf(&cmd2, "md5sum " DATA_DIR "spdy-draft.txt");
+
+ gettimeofday(&tv1, NULL);
+ md5(cmd1,md5_sum1);
+ gettimeofday(&tv2, NULL);
+ md5(cmd2,md5_sum2);
+
+ printf("downloaded file md5: %s\n", md5_sum1);
+ printf("original file md5: %s\n", md5_sum2);
+ ret = strcmp(md5_sum1, md5_sum2);
+
+ if(0 == ret && 0 == stat(DATA_DIR "spdy-draft.txt", &st))
+ {
+ usecs = (uint64_t)1000000 * (uint64_t)(tv2.tv_sec - tv1.tv_sec) + tv2.tv_usec - tv1.tv_usec;
+ printf("%lld bytes read in %llu usecs\n", (long long)st.st_size, (long long unsigned )usecs);
+ }
+
+ return ret;
+}
+
+
+int
+main()
+{
+ port = get_port(11123);
+ parent = getpid();
+
+ child = fork();
+ if (-1 == child)
+ {
+ fprintf(stderr, "can't fork, error %d\n", errno);
+ exit(EXIT_FAILURE);
+ }
+
+ if (child == 0)
+ {
+ int ret = childproc();
+ _exit(ret);
+ }
+ else
+ {
+ int ret = parentproc();
+ exit(ret);
+ }
+ return 1;
+}
diff --git a/src/testspdy/test_session_timeout.c b/src/testspdy/test_session_timeout.c
new file mode 100644
index 0000000..ec1eced
--- /dev/null
+++ b/src/testspdy/test_session_timeout.c
@@ -0,0 +1,338 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2013 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file session_timeout.c
+ * @brief tests closing sessions after set timeout. Openssl is used for
+ * client
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include "stdio.h"
+#include <sys/wait.h>
+#include <ctype.h>
+#include "common.h"
+#include <sys/time.h>
+#include <sys/stat.h>
+#include "../microspdy/internal.h"
+
+#define TIMEOUT 2
+#define SELECT_MS_TIMEOUT 20
+
+int port;
+
+pid_t parent;
+pid_t child;
+
+int run = 1;
+int chunk_size=1;
+int new_session;
+int closed_session;
+int do_sleep;
+
+
+
+static unsigned long long
+monotonic_time (void)
+{
+#ifdef HAVE_CLOCK_GETTIME
+#ifdef CLOCK_MONOTONIC
+ struct timespec ts;
+ if (0 == clock_gettime (CLOCK_MONOTONIC, &ts))
+ return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+#endif
+#endif
+ return time (NULL) * 1000;
+}
+
+
+static void
+killchild(char *msg)
+{
+ printf("%s\n",msg);
+ kill(child, SIGKILL);
+ exit(1);
+}
+
+
+static void
+killparent(char *msg)
+{
+ printf("%s\n",msg);
+ kill(parent, SIGKILL);
+ _exit(1);
+}
+
+
+static void
+new_session_cb (void *cls,
+ struct SPDY_Session * session)
+{
+ (void)cls;
+ (void)session;
+
+ if(!new_session)do_sleep = 1;
+ new_session = 1;
+ printf("new session\n");
+}
+
+
+static void
+closed_session_cb (void *cls,
+ struct SPDY_Session * session,
+ int by_client)
+{
+ (void)cls;
+ (void)session;
+
+ printf("closed_session_cb called\n");
+
+ if(SPDY_YES == by_client)
+ {
+ killchild("closed by the client");
+ }
+ if(closed_session)
+ {
+ killchild("closed_session_cb called twice");
+ }
+
+ closed_session = 1;
+}
+
+
+static int
+parentproc()
+{
+ int childstatus;
+ unsigned long long timeoutlong=0;
+ struct timeval timeout;
+ int ret;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ int maxfd = -1;
+ struct SPDY_Daemon *daemon;
+ unsigned long long beginning = 0;
+ unsigned long long now;
+
+ SPDY_init();
+
+ daemon = SPDY_start_daemon(port,
+ DATA_DIR "cert-and-key.pem",
+ DATA_DIR "cert-and-key.pem",
+ &new_session_cb,
+ &closed_session_cb,
+ NULL,
+ NULL,
+ NULL,
+ SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
+ TIMEOUT,
+ SPDY_DAEMON_OPTION_END);
+
+ if(NULL==daemon){
+ printf("no daemon\n");
+ return 1;
+ }
+
+ do
+ {
+ do_sleep=0;
+ FD_ZERO(&read_fd_set);
+ FD_ZERO(&write_fd_set);
+ FD_ZERO(&except_fd_set);
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+
+ if(new_session && !closed_session)
+ {
+ if(SPDY_NO == ret)
+ {
+ killchild("SPDY_get_timeout returned wrong SPDY_NO");
+ }
+ /*if(timeoutlong)
+ {
+ killchild("SPDY_get_timeout returned wrong timeout");
+ }*/
+ now = monotonic_time ();
+ if(now - beginning > TIMEOUT*1000 + SELECT_MS_TIMEOUT)
+ {
+ printf("Started at: %llums\n",beginning);
+ printf("Now is: %llums\n",now);
+ printf("Timeout is: %i\n",TIMEOUT);
+ printf("Select Timeout is: %ims\n",SELECT_MS_TIMEOUT);
+ printf("SPDY_get_timeout gave: %llums\n",timeoutlong);
+ killchild("Timeout passed but session was not closed");
+ }
+ if(timeoutlong > beginning + TIMEOUT *1000)
+ {
+ printf("Started at: %llums\n",beginning);
+ printf("Now is: %llums\n",now);
+ printf("Timeout is: %i\n",TIMEOUT);
+ printf("Select Timeout is: %ims\n",SELECT_MS_TIMEOUT);
+ printf("SPDY_get_timeout gave: %llums\n",timeoutlong);
+ killchild("SPDY_get_timeout returned wrong timeout");
+ }
+ }
+ else
+ {
+ if(SPDY_YES == ret)
+ {
+ killchild("SPDY_get_timeout returned wrong SPDY_YES");
+ }
+ }
+
+ if(SPDY_NO == ret || timeoutlong >= 1000)
+ {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ }
+ else
+ {
+ timeout.tv_sec = timeoutlong / 1000;
+ timeout.tv_usec = (timeoutlong % 1000) * 1000;
+ }
+
+ //ignore values
+ timeout.tv_sec = 0;
+ timeout.tv_usec = SELECT_MS_TIMEOUT * 1000;
+
+ maxfd = SPDY_get_fdset (daemon,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set);
+
+ ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
+
+ switch(ret) {
+ case -1:
+ printf("select error: %i\n", errno);
+ break;
+ case 0:
+ /*if(new_session)
+ {
+ killchild("select returned wrong number");
+ }*/
+ break;
+ default:
+ SPDY_run(daemon);
+ if(0 == beginning)
+ {
+ beginning = monotonic_time ();
+ }
+ /*if(do_sleep)
+ {
+ sleep(TIMEOUT);
+ do_sleep = 0;
+ }*/
+ break;
+ }
+ }
+ while(waitpid(child,&childstatus,WNOHANG) != child);
+
+ if(!new_session || !closed_session)
+ {
+ killchild("child is dead, callback wasn't called");
+ }
+
+ ret = SPDY_get_timeout(daemon, &timeoutlong);
+
+ if(SPDY_YES == ret)
+ {
+ killchild("SPDY_get_timeout returned wrong SPDY_YES after child died");
+ }
+
+ SPDY_stop_daemon(daemon);
+
+ SPDY_deinit();
+
+ return 0;
+}
+
+
+static int
+childproc()
+{
+ pid_t devnull;
+ int out;
+
+ out=dup(1);
+ if (-1 == out)
+ abort();
+ //close(0);
+ close(1);
+ close(2);
+ /*devnull = open("/dev/null", O_RDONLY);
+ if (0 != devnull)
+ {
+ dup2(devnull, 0);
+ close(devnull);
+ }*/
+ devnull = open("/dev/null", O_WRONLY);
+ if (-1 == devnull)
+ abort ();
+ if (1 != devnull)
+ {
+ dup2(devnull, 1);
+ close(devnull);
+ }
+ devnull = open("/dev/null", O_WRONLY);
+ if (-1 == devnull)
+ abort ();
+ if (2 != devnull)
+ {
+ dup2(devnull, 2);
+ close(devnull);
+ }
+ char *uri;
+ asprintf (&uri, "127.0.0.1:%i", port);
+ execlp ("openssl", "openssl", "s_client", "-connect", uri, NULL);
+ close(1);
+ dup2(out,1);
+ close(out);
+ killparent ("executing openssl failed");
+ return 1;
+}
+
+
+int
+main()
+{
+ port = get_port(11123);
+ parent = getpid();
+
+ child = fork();
+ if (-1 == child)
+ {
+ fprintf(stderr, "can't fork, error %d\n", errno);
+ exit(EXIT_FAILURE);
+ }
+
+ if (child == 0)
+ {
+ int ret = childproc();
+ _exit(ret);
+ }
+ else
+ {
+ int ret = parentproc();
+ exit(ret);
+ }
+ return 1;
+}
diff --git a/src/testspdy/test_struct_namevalue.c b/src/testspdy/test_struct_namevalue.c
new file mode 100644
index 0000000..54e7934
--- /dev/null
+++ b/src/testspdy/test_struct_namevalue.c
@@ -0,0 +1,346 @@
+/*
+ This file is part of libmicrospdy
+ Copyright Copyright (C) 2012 Andrey Uzunov
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file struct_namevalue.c
+ * @brief tests all the API functions for handling struct SPDY_NameValue
+ * @author Andrey Uzunov
+ */
+
+#include "platform.h"
+#include "microspdy.h"
+#include "common.h"
+#include "../microspdy/structures.h"
+#include "../microspdy/alstructures.h"
+
+char *pairs[] = {"one","1","two","2","three","3","four","4","five","5"};
+char *pairs_with_dups[] = {"one","1","two","2","one","11","two","22","three","3","two","222","two","2222","four","","five","5"};//82
+char *pairs_with_empty[] = {"name","","name","value"};
+char *pairs_different[] = {"30","thirty","40","fouthy"};
+int size;
+int size2;
+int brake_at = 3;
+bool flag;
+
+
+int
+iterate_cb (void *cls, const char *name, const char * const * value, int num_values)
+{
+ int *c = (int*)cls;
+
+ if(*c < 0 || *c > size)
+ exit(11);
+
+ if(strcmp(name,pairs[*c]) != 0)
+ {
+ FAIL_TEST("name is wrong\n");
+ }
+
+ if(1 != num_values)
+ {
+ FAIL_TEST("num_values is wrong\n");
+ }
+
+ if(strcmp(value[0],pairs[(*c)+1]) != 0)
+ {
+ FAIL_TEST("value is wrong\n");
+ }
+
+ (*c)+=2;
+
+ return SPDY_YES;
+}
+
+int
+iterate_brake_cb (void *cls, const char *name, const char * const *value, int num_values)
+{
+ (void)name;
+ (void)value;
+ (void)num_values;
+
+ int *c = (int*)cls;
+
+ if(*c < 0 || *c >= brake_at)
+ {
+ FAIL_TEST("iteration was not interrupted\n");
+ }
+
+ (*c)++;
+
+ if(*c == brake_at) return SPDY_NO;
+
+ return SPDY_YES;
+}
+
+int
+main()
+{
+ SPDY_init();
+
+ const char *const*value;
+ const char *const*value2;
+ int i;
+ int j;
+ int cls = 0;
+ int ret;
+ int ret2;
+ void *ob1;
+ void *ob2;
+ void *ob3;
+ void *stream;
+ char data[] = "anything";
+ struct SPDY_NameValue *container;
+ struct SPDY_NameValue *container2;
+ struct SPDY_NameValue *container3;
+ struct SPDY_NameValue *container_arr[2];
+
+ size = sizeof(pairs)/sizeof(pairs[0]);
+
+ if(NULL == (container = SPDY_name_value_create ()))
+ {
+ FAIL_TEST("SPDY_name_value_create failed\n");
+ }
+
+ if(NULL != SPDY_name_value_lookup (container, "anything", &ret))
+ {
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ }
+
+ if(SPDY_name_value_iterate (container, NULL, NULL) != 0)
+ {
+ FAIL_TEST("SPDY_name_value_iterate failed\n");
+ }
+
+ for(i=0;i<size; i+=2)
+ {
+ if(SPDY_YES != SPDY_name_value_add(container,pairs[i],pairs[i+1]))
+ {
+ FAIL_TEST("SPDY_name_value_add failed\n");
+ }
+
+ if(SPDY_name_value_iterate (container, NULL, NULL) != ((i / 2) + 1))
+ {
+ FAIL_TEST("SPDY_name_value_iterate failed\n");
+ }
+ }
+
+ if(NULL != SPDY_name_value_lookup (container, "anything", &ret))
+ {
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ }
+
+ for(i=size - 2; i >= 0; i-=2)
+ {
+ value = SPDY_name_value_lookup(container,pairs[i], &ret);
+ if(NULL == value || 1 !=ret || strcmp(value[0], pairs[i+1]) != 0)
+ {
+ printf("%p; %i; %i\n", value, ret,
+ (NULL == value) ? -1 : strcmp(value[0], pairs[i+1]));
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ }
+ }
+
+ SPDY_name_value_iterate (container, &iterate_cb, &cls);
+
+ cls = 0;
+ if(SPDY_name_value_iterate (container, &iterate_brake_cb, &cls) != brake_at)
+ {
+ FAIL_TEST("SPDY_name_value_iterate with brake failed\n");
+ }
+
+ SPDY_name_value_destroy(container);
+
+ //check everything with NULL values
+ for(i=0; i<7; ++i)
+ {
+ printf("%i ",i);
+ ob1 = (i & 4) ? data : NULL;
+ ob2 = (i & 2) ? data : NULL;
+ ob3 = (i & 1) ? data : NULL;
+ if(SPDY_INPUT_ERROR != SPDY_name_value_add(ob1,ob2,ob3))
+ {
+ FAIL_TEST("SPDY_name_value_add with NULLs failed\n");
+ }
+ }
+ printf("\n");
+ fflush(stdout);
+
+ if(SPDY_INPUT_ERROR != SPDY_name_value_iterate(NULL,NULL,NULL))
+ {
+ FAIL_TEST("SPDY_name_value_iterate with NULLs failed\n");
+ }
+
+ for(i=0; i<7; ++i)
+ {
+ printf("%i ",i);
+ ob1 = (i & 4) ? data : NULL;
+ ob2 = (i & 2) ? data : NULL;
+ ob3 = (i & 1) ? data : NULL;
+ if(NULL != SPDY_name_value_lookup(ob1,ob2,ob3))
+ {
+ FAIL_TEST("SPDY_name_value_lookup with NULLs failed\n");
+ }
+ }
+ printf("\n");
+ SPDY_name_value_destroy(NULL);
+
+ if(NULL == (container = SPDY_name_value_create ()))
+ {
+ FAIL_TEST("SPDY_name_value_create failed\n");
+ }
+
+ size = sizeof(pairs_with_dups)/sizeof(pairs_with_dups[0]);
+
+ for(i=0;i<size; i+=2)
+ {
+ if(SPDY_YES != SPDY_name_value_add(container,pairs_with_dups[i],pairs_with_dups[i+1]))
+ {
+ FAIL_TEST("SPDY_name_value_add failed\n");
+ }
+ }
+ if(SPDY_name_value_iterate (container, NULL, NULL) != atoi(pairs_with_dups[size - 1]))
+ {
+ FAIL_TEST("SPDY_name_value_iterate failed\n");
+ }
+ for(i=size - 2; i >= 0; i-=2)
+ {
+ value = SPDY_name_value_lookup(container,pairs_with_dups[i], &ret);
+ if(NULL == value)
+ {
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ }
+ flag = false;
+ for(j=0; j<ret; ++j)
+ if(0 == strcmp(pairs_with_dups[i + 1], value[j]))
+ {
+ if(flag)
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ flag=true;
+ }
+
+ if(!flag)
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ }
+ if(SPDY_NO != SPDY_name_value_add(container,pairs_with_dups[0],pairs_with_dups[1]))
+ FAIL_TEST("SPDY_name_value_add failed\n");
+
+ SPDY_name_value_destroy(container);
+
+ if(NULL == (container = SPDY_name_value_create ()))
+ {
+ FAIL_TEST("SPDY_name_value_create failed\n");
+ }
+
+ size = sizeof(pairs_with_empty)/sizeof(pairs_with_empty[0]);
+
+ for(i=0;i<size; i+=2)
+ {
+ if(SPDY_YES != SPDY_name_value_add(container,pairs_with_empty[i],pairs_with_empty[i+1]))
+ {
+ FAIL_TEST("SPDY_name_value_add failed\n");
+ }
+ value = SPDY_name_value_lookup(container,pairs_with_empty[i], &ret);
+ if(NULL == value || 1 != ret)
+ {
+ printf("%p; %i\n", value, ret);
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ }
+ }
+
+ ret = SPDY_name_value_iterate(container, NULL, NULL);
+ if(SPDY_INPUT_ERROR != SPDY_name_value_add(container, "capitalLeter","anything")
+ || SPDY_name_value_iterate(container, NULL, NULL) != ret)
+ {
+ FAIL_TEST("SPDY_name_value_add failed\n");
+ }
+
+ SPDY_name_value_destroy(container);
+
+ if(NULL == (container = SPDY_name_value_create ()))
+ {
+ FAIL_TEST("SPDY_name_value_create failed\n");
+ }
+
+ size = sizeof(pairs_with_dups)/sizeof(pairs_with_dups[0]);
+
+ for(i=0;i<size; i+=2)
+ {
+ if(SPDY_YES != SPDY_name_value_add(container,pairs_with_dups[i],pairs_with_dups[i+1]))
+ {
+ FAIL_TEST("SPDY_name_value_add failed\n");
+ }
+ }
+
+ if(NULL == (container2 = SPDY_name_value_create ()))
+ {
+ FAIL_TEST("SPDY_name_value_create failed\n");
+ }
+
+ size2 = sizeof(pairs_different)/sizeof(pairs_different[0]);
+
+ for(i=0;i<size2; i+=2)
+ {
+ if(SPDY_YES != SPDY_name_value_add(container2,pairs_different[i],pairs_different[i+1]))
+ {
+ FAIL_TEST("SPDY_name_value_add failed\n");
+ }
+ }
+
+ container_arr[0] = container;
+ container_arr[1] = container2;
+ if(0 > (ret = SPDYF_name_value_to_stream(container_arr, 2, &stream)) || NULL == stream)
+ FAIL_TEST("SPDYF_name_value_to_stream failed\n");
+ ret = SPDYF_name_value_from_stream(stream, ret, &container3);
+ if(SPDY_YES != ret)
+ FAIL_TEST("SPDYF_name_value_from_stream failed\n");
+
+ if(SPDY_name_value_iterate(container3, NULL, NULL)
+ != (SPDY_name_value_iterate(container, NULL, NULL) + SPDY_name_value_iterate(container2, NULL, NULL)))
+ FAIL_TEST("SPDYF_name_value_from_stream failed\n");
+
+ for(i=size - 2; i >= 0; i-=2)
+ {
+ value = SPDY_name_value_lookup(container,pairs_with_dups[i], &ret);
+ if(NULL == value)
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ value2 = SPDY_name_value_lookup(container3,pairs_with_dups[i], &ret2);
+ if(NULL == value2)
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+
+ for(j=0; j<ret; ++j)
+ if(0 != strcmp(value2[j], value[j]))
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ }
+ for(i=size2 - 2; i >= 0; i-=2)
+ {
+ value = SPDY_name_value_lookup(container2,pairs_different[i], &ret);
+ if(NULL == value)
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ value2 = SPDY_name_value_lookup(container3,pairs_different[i], &ret2);
+ if(NULL == value2)
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+
+ for(j=0; j<ret; ++j)
+ if(0 != strcmp(value2[j], value[j]))
+ FAIL_TEST("SPDY_name_value_lookup failed\n");
+ }
+
+ SPDY_deinit();
+
+ return 0;
+}
diff --git a/src/testzzuf/Makefile.am b/src/testzzuf/Makefile.am
new file mode 100644
index 0000000..329fe04
--- /dev/null
+++ b/src/testzzuf/Makefile.am
@@ -0,0 +1,109 @@
+# This Makefile.am is in the public domain
+SUBDIRS = .
+
+if USE_COVERAGE
+ AM_CFLAGS = -fprofile-arcs -ftest-coverage
+endif
+
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/include \
+ $(LIBCURL_CPPFLAGS)
+
+EXTRA_DIST = README socat.c
+
+check_PROGRAMS = \
+ test_get \
+ test_get_chunked \
+ test_post \
+ test_post_form \
+ test_put \
+ test_put_chunked \
+ test_put_large \
+ test_get11 \
+ test_post11 \
+ test_post_form11 \
+ test_put11 \
+ test_put_large11 \
+ test_long_header
+
+TESTS = $(check_PROGRAMS)
+
+test_get_SOURCES = \
+ test_get.c
+test_get_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get_chunked_SOURCES = \
+ test_get_chunked.c
+test_get_chunked_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post_SOURCES = \
+ test_post.c
+test_post_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post_form_SOURCES = \
+ test_post_form.c
+test_post_form_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_SOURCES = \
+ test_put.c
+test_put_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_chunked_SOURCES = \
+ test_put_chunked.c
+test_put_chunked_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_large_SOURCES = \
+ test_put_large.c
+test_put_large_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+
+
+test_get11_SOURCES = \
+ test_get.c
+test_get11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post11_SOURCES = \
+ test_post.c
+test_post11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post_form11_SOURCES = \
+ test_post_form.c
+test_post_form11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put11_SOURCES = \
+ test_put.c
+test_put11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_large11_SOURCES = \
+ test_put_large.c
+test_put_large11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_long_header_SOURCES = \
+ test_long_header.c
+test_long_header_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
diff --git a/src/testzzuf/Makefile.in b/src/testzzuf/Makefile.in
new file mode 100644
index 0000000..da555b8
--- /dev/null
+++ b/src/testzzuf/Makefile.in
@@ -0,0 +1,1420 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+check_PROGRAMS = test_get$(EXEEXT) test_get_chunked$(EXEEXT) \
+ test_post$(EXEEXT) test_post_form$(EXEEXT) test_put$(EXEEXT) \
+ test_put_chunked$(EXEEXT) test_put_large$(EXEEXT) \
+ test_get11$(EXEEXT) test_post11$(EXEEXT) \
+ test_post_form11$(EXEEXT) test_put11$(EXEEXT) \
+ test_put_large11$(EXEEXT) test_long_header$(EXEEXT)
+subdir = src/testzzuf
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp $(top_srcdir)/test-driver README
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4/ax_append_flag.m4 \
+ $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_count_cpus.m4 \
+ $(top_srcdir)/m4/ax_have_epoll.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_require_defined.m4 \
+ $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/MHD_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am_test_get_OBJECTS = test_get.$(OBJEXT)
+test_get_OBJECTS = $(am_test_get_OBJECTS)
+test_get_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_test_get11_OBJECTS = test_get.$(OBJEXT)
+test_get11_OBJECTS = $(am_test_get11_OBJECTS)
+test_get11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_get_chunked_OBJECTS = test_get_chunked.$(OBJEXT)
+test_get_chunked_OBJECTS = $(am_test_get_chunked_OBJECTS)
+test_get_chunked_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_long_header_OBJECTS = test_long_header.$(OBJEXT)
+test_long_header_OBJECTS = $(am_test_long_header_OBJECTS)
+test_long_header_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_post_OBJECTS = test_post.$(OBJEXT)
+test_post_OBJECTS = $(am_test_post_OBJECTS)
+test_post_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_post11_OBJECTS = test_post.$(OBJEXT)
+test_post11_OBJECTS = $(am_test_post11_OBJECTS)
+test_post11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_post_form_OBJECTS = test_post_form.$(OBJEXT)
+test_post_form_OBJECTS = $(am_test_post_form_OBJECTS)
+test_post_form_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_post_form11_OBJECTS = test_post_form.$(OBJEXT)
+test_post_form11_OBJECTS = $(am_test_post_form11_OBJECTS)
+test_post_form11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_put_OBJECTS = test_put.$(OBJEXT)
+test_put_OBJECTS = $(am_test_put_OBJECTS)
+test_put_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_put11_OBJECTS = test_put.$(OBJEXT)
+test_put11_OBJECTS = $(am_test_put11_OBJECTS)
+test_put11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_put_chunked_OBJECTS = test_put_chunked.$(OBJEXT)
+test_put_chunked_OBJECTS = $(am_test_put_chunked_OBJECTS)
+test_put_chunked_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_put_large_OBJECTS = test_put_large.$(OBJEXT)
+test_put_large_OBJECTS = $(am_test_put_large_OBJECTS)
+test_put_large_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+am_test_put_large11_OBJECTS = test_put_large.$(OBJEXT)
+test_put_large11_OBJECTS = $(am_test_put_large11_OBJECTS)
+test_put_large11_DEPENDENCIES = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(test_get_SOURCES) $(test_get11_SOURCES) \
+ $(test_get_chunked_SOURCES) $(test_long_header_SOURCES) \
+ $(test_post_SOURCES) $(test_post11_SOURCES) \
+ $(test_post_form_SOURCES) $(test_post_form11_SOURCES) \
+ $(test_put_SOURCES) $(test_put11_SOURCES) \
+ $(test_put_chunked_SOURCES) $(test_put_large_SOURCES) \
+ $(test_put_large11_SOURCES)
+DIST_SOURCES = $(test_get_SOURCES) $(test_get11_SOURCES) \
+ $(test_get_chunked_SOURCES) $(test_long_header_SOURCES) \
+ $(test_post_SOURCES) $(test_post11_SOURCES) \
+ $(test_post_form_SOURCES) $(test_post_form11_SOURCES) \
+ $(test_put_SOURCES) $(test_put11_SOURCES) \
+ $(test_put_chunked_SOURCES) $(test_put_large_SOURCES) \
+ $(test_put_large11_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ check recheck distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red='[0;31m'; \
+ grn='[0;32m'; \
+ lgn='[1;32m'; \
+ blu='[1;34m'; \
+ mgn='[0;35m'; \
+ brg='[1m'; \
+ std='[m'; \
+ fi; \
+}
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPU_COUNT = @CPU_COUNT@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_CPPFLAGS = @GNUTLS_CPPFLAGS@
+GNUTLS_LDFLAGS = @GNUTLS_LDFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+HAVE_CURL_BINARY = @HAVE_CURL_BINARY@
+HAVE_MAKEINFO_BINARY = @HAVE_MAKEINFO_BINARY@
+HIDDEN_VISIBILITY_CFLAGS = @HIDDEN_VISIBILITY_CFLAGS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSPDY_VERSION_AGE = @LIBSPDY_VERSION_AGE@
+LIBSPDY_VERSION_CURRENT = @LIBSPDY_VERSION_CURRENT@
+LIBSPDY_VERSION_REVISION = @LIBSPDY_VERSION_REVISION@
+LIBTOOL = @LIBTOOL@
+LIB_VERSION_AGE = @LIB_VERSION_AGE@
+LIB_VERSION_CURRENT = @LIB_VERSION_CURRENT@
+LIB_VERSION_REVISION = @LIB_VERSION_REVISION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MHD_LIBDEPS = @MHD_LIBDEPS@
+MHD_LIBDEPS_PKGCFG = @MHD_LIBDEPS_PKGCFG@
+MHD_LIB_CFLAGS = @MHD_LIB_CFLAGS@
+MHD_LIB_CPPFLAGS = @MHD_LIB_CPPFLAGS@
+MHD_LIB_LDFLAGS = @MHD_LIB_LDFLAGS@
+MHD_REQ_PRIVATE = @MHD_REQ_PRIVATE@
+MKDIR_P = @MKDIR_P@
+MS_LIB_TOOL = @MS_LIB_TOOL@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_INCLUDES = @OPENSSL_INCLUDES@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_SUBMINOR = @PACKAGE_VERSION_SUBMINOR@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPDY_LIBDEPS = @SPDY_LIBDEPS@
+SPDY_LIB_CFLAGS = @SPDY_LIB_CFLAGS@
+SPDY_LIB_CPPFLAGS = @SPDY_LIB_CPPFLAGS@
+SPDY_LIB_LDFLAGS = @SPDY_LIB_LDFLAGS@
+STRIP = @STRIP@
+VERSION = @VERSION@
+_libcurl_config = @_libcurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+have_socat = @have_socat@
+have_zzuf = @have_zzuf@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_cv_objdir = @lt_cv_objdir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# This Makefile.am is in the public domain
+SUBDIRS = .
+@USE_COVERAGE_TRUE@AM_CFLAGS = -fprofile-arcs -ftest-coverage
+AM_CPPFLAGS = -I$(top_srcdir)/src/include \
+ $(LIBCURL_CPPFLAGS)
+
+EXTRA_DIST = README socat.c
+TESTS = $(check_PROGRAMS)
+test_get_SOURCES = \
+ test_get.c
+
+test_get_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get_chunked_SOURCES = \
+ test_get_chunked.c
+
+test_get_chunked_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post_SOURCES = \
+ test_post.c
+
+test_post_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post_form_SOURCES = \
+ test_post_form.c
+
+test_post_form_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_SOURCES = \
+ test_put.c
+
+test_put_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_chunked_SOURCES = \
+ test_put_chunked.c
+
+test_put_chunked_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_large_SOURCES = \
+ test_put_large.c
+
+test_put_large_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_get11_SOURCES = \
+ test_get.c
+
+test_get11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post11_SOURCES = \
+ test_post.c
+
+test_post11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_post_form11_SOURCES = \
+ test_post_form.c
+
+test_post_form11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put11_SOURCES = \
+ test_put.c
+
+test_put11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_put_large11_SOURCES = \
+ test_put_large.c
+
+test_put_large11_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+test_long_header_SOURCES = \
+ test_long_header.c
+
+test_long_header_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/testzzuf/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/testzzuf/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-checkPROGRAMS:
+ @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+test_get$(EXEEXT): $(test_get_OBJECTS) $(test_get_DEPENDENCIES) $(EXTRA_test_get_DEPENDENCIES)
+ @rm -f test_get$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get_OBJECTS) $(test_get_LDADD) $(LIBS)
+
+test_get11$(EXEEXT): $(test_get11_OBJECTS) $(test_get11_DEPENDENCIES) $(EXTRA_test_get11_DEPENDENCIES)
+ @rm -f test_get11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get11_OBJECTS) $(test_get11_LDADD) $(LIBS)
+
+test_get_chunked$(EXEEXT): $(test_get_chunked_OBJECTS) $(test_get_chunked_DEPENDENCIES) $(EXTRA_test_get_chunked_DEPENDENCIES)
+ @rm -f test_get_chunked$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_get_chunked_OBJECTS) $(test_get_chunked_LDADD) $(LIBS)
+
+test_long_header$(EXEEXT): $(test_long_header_OBJECTS) $(test_long_header_DEPENDENCIES) $(EXTRA_test_long_header_DEPENDENCIES)
+ @rm -f test_long_header$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_long_header_OBJECTS) $(test_long_header_LDADD) $(LIBS)
+
+test_post$(EXEEXT): $(test_post_OBJECTS) $(test_post_DEPENDENCIES) $(EXTRA_test_post_DEPENDENCIES)
+ @rm -f test_post$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_post_OBJECTS) $(test_post_LDADD) $(LIBS)
+
+test_post11$(EXEEXT): $(test_post11_OBJECTS) $(test_post11_DEPENDENCIES) $(EXTRA_test_post11_DEPENDENCIES)
+ @rm -f test_post11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_post11_OBJECTS) $(test_post11_LDADD) $(LIBS)
+
+test_post_form$(EXEEXT): $(test_post_form_OBJECTS) $(test_post_form_DEPENDENCIES) $(EXTRA_test_post_form_DEPENDENCIES)
+ @rm -f test_post_form$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_post_form_OBJECTS) $(test_post_form_LDADD) $(LIBS)
+
+test_post_form11$(EXEEXT): $(test_post_form11_OBJECTS) $(test_post_form11_DEPENDENCIES) $(EXTRA_test_post_form11_DEPENDENCIES)
+ @rm -f test_post_form11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_post_form11_OBJECTS) $(test_post_form11_LDADD) $(LIBS)
+
+test_put$(EXEEXT): $(test_put_OBJECTS) $(test_put_DEPENDENCIES) $(EXTRA_test_put_DEPENDENCIES)
+ @rm -f test_put$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_put_OBJECTS) $(test_put_LDADD) $(LIBS)
+
+test_put11$(EXEEXT): $(test_put11_OBJECTS) $(test_put11_DEPENDENCIES) $(EXTRA_test_put11_DEPENDENCIES)
+ @rm -f test_put11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_put11_OBJECTS) $(test_put11_LDADD) $(LIBS)
+
+test_put_chunked$(EXEEXT): $(test_put_chunked_OBJECTS) $(test_put_chunked_DEPENDENCIES) $(EXTRA_test_put_chunked_DEPENDENCIES)
+ @rm -f test_put_chunked$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_put_chunked_OBJECTS) $(test_put_chunked_LDADD) $(LIBS)
+
+test_put_large$(EXEEXT): $(test_put_large_OBJECTS) $(test_put_large_DEPENDENCIES) $(EXTRA_test_put_large_DEPENDENCIES)
+ @rm -f test_put_large$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_put_large_OBJECTS) $(test_put_large_LDADD) $(LIBS)
+
+test_put_large11$(EXEEXT): $(test_put_large11_OBJECTS) $(test_put_large11_DEPENDENCIES) $(EXTRA_test_put_large11_DEPENDENCIES)
+ @rm -f test_put_large11$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_put_large11_OBJECTS) $(test_put_large11_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_get.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_get_chunked.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_long_header.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_post.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_post_form.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_put.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_put_chunked.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_put_large.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ else \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all $(check_PROGRAMS)
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+test_get.log: test_get$(EXEEXT)
+ @p='test_get$(EXEEXT)'; \
+ b='test_get'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_get_chunked.log: test_get_chunked$(EXEEXT)
+ @p='test_get_chunked$(EXEEXT)'; \
+ b='test_get_chunked'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_post.log: test_post$(EXEEXT)
+ @p='test_post$(EXEEXT)'; \
+ b='test_post'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_post_form.log: test_post_form$(EXEEXT)
+ @p='test_post_form$(EXEEXT)'; \
+ b='test_post_form'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_put.log: test_put$(EXEEXT)
+ @p='test_put$(EXEEXT)'; \
+ b='test_put'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_put_chunked.log: test_put_chunked$(EXEEXT)
+ @p='test_put_chunked$(EXEEXT)'; \
+ b='test_put_chunked'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_put_large.log: test_put_large$(EXEEXT)
+ @p='test_put_large$(EXEEXT)'; \
+ b='test_put_large'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_get11.log: test_get11$(EXEEXT)
+ @p='test_get11$(EXEEXT)'; \
+ b='test_get11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_post11.log: test_post11$(EXEEXT)
+ @p='test_post11$(EXEEXT)'; \
+ b='test_post11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_post_form11.log: test_post_form11$(EXEEXT)
+ @p='test_post_form11$(EXEEXT)'; \
+ b='test_post_form11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_put11.log: test_put11$(EXEEXT)
+ @p='test_put11$(EXEEXT)'; \
+ b='test_put11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_put_large11.log: test_put_large11$(EXEEXT)
+ @p='test_put_large11$(EXEEXT)'; \
+ b='test_put_large11'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test_long_header.log: test_long_header$(EXEEXT)
+ @p='test_long_header$(EXEEXT)'; \
+ b='test_long_header'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-TESTS check-am clean clean-checkPROGRAMS clean-generic \
+ clean-libtool cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am recheck tags tags-am \
+ uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/testzzuf/README b/src/testzzuf/README
new file mode 100644
index 0000000..5ee1569
--- /dev/null
+++ b/src/testzzuf/README
@@ -0,0 +1,13 @@
+Testcases in this directory require zzuf and socat.
+
+zzuf is used to randomly mess with the TCP connection between the CURL
+clients and the MHD server. The goal is to expose problems in MHD's
+error handling (by introducing random syntax errors). socat is
+used to listen on port 11081 and forward the randomzied stream to
+port 11080 where MHD is waiting.
+
+As a result, the testcases in this directory do NOT check that
+whatever CURL returns is what was expected -- random modifications to
+the TCP stream can have random effects ;-). Testcases "fail" if the
+code crashes or hangs indefinitely.
+
diff --git a/src/testzzuf/socat.c b/src/testzzuf/socat.c
new file mode 100644
index 0000000..8743dc9
--- /dev/null
+++ b/src/testzzuf/socat.c
@@ -0,0 +1,114 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file socat.c
+ * @brief Code to fork-exec zzuf and start the socat process
+ * @author Christian Grothoff
+ */
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif /* !WIN32_LEAN_AND_MEAN */
+#include <windows.h>
+#endif
+
+
+/**
+ * A larger loop count will run more random tests --
+ * which would be good, except that it may take too
+ * long for most user's patience. So this small
+ * value is the default.
+ */
+#define LOOP_COUNT 10
+
+#define CURL_TIMEOUT 50L
+
+static pid_t zzuf_pid;
+
+static void
+zzuf_socat_start ()
+{
+ int status;
+ char *const args[] = {
+ "zzuf",
+ "--ratio=0.0:0.75",
+ "-n",
+ "-A",
+ "--",
+ "socat",
+ "-lf",
+ "/dev/null",
+ "TCP4-LISTEN:11081,reuseaddr,fork",
+ "TCP4:127.0.0.1:11080",
+ NULL,
+ };
+ zzuf_pid = fork ();
+ if (zzuf_pid == -1)
+ {
+ fprintf (stderr, "fork failed: %s\n", strerror (errno));
+ exit (1);
+ }
+ if (zzuf_pid != 0)
+ {
+ sleep (1); /* allow zzuf and socat to start */
+ status = 0;
+ if (0 < waitpid (zzuf_pid, &status, WNOHANG))
+ {
+ if (WIFEXITED (status))
+ fprintf (stderr,
+ "zzuf died with status code %d!\n",
+ WEXITSTATUS (status));
+ if (WIFSIGNALED (status))
+ fprintf (stderr,
+ "zzuf died from signal %d!\n", WTERMSIG (status));
+ exit (1);
+ }
+ return;
+ }
+ setpgrp ();
+ execvp ("zzuf", args);
+ fprintf (stderr, "execution of `zzuf' failed: %s\n", strerror (errno));
+ zzuf_pid = 0; /* fork failed */
+ exit (1);
+}
+
+
+static void
+zzuf_socat_stop ()
+{
+ int status;
+ if (zzuf_pid != 0)
+ {
+ if (0 != killpg (zzuf_pid, SIGINT))
+ fprintf (stderr, "Failed to killpg: %s\n", strerror (errno));
+ kill (zzuf_pid, SIGINT);
+ waitpid (zzuf_pid, &status, 0);
+ sleep (1); /* allow socat to also die in peace */
+ }
+}
+
+/* end of socat.c */
diff --git a/src/testzzuf/test_get.c b/src/testzzuf/test_get.c
new file mode 100644
index 0000000..47cbe12
--- /dev/null
+++ b/src/testzzuf/test_get.c
@@ -0,0 +1,314 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_get.c
+ * @brief Testcase for libmicrohttpd GET operations
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#include "socat.c"
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int ptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&ptr != *unused)
+ {
+ *unused = &ptr;
+ return MHD_YES;
+ }
+ *unused = NULL;
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ if (ret == MHD_NO)
+ abort ();
+ return ret;
+}
+
+
+static int
+testInternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ int running;
+ time_t start;
+ struct timeval tv;
+ int i;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_NO_FLAG /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (c != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ curl_multi_info_read (multi, &running);
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ MHD_run (d);
+ }
+ if (c != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ }
+ }
+ fprintf (stderr, "\n");
+ curl_multi_cleanup (multi);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalGet ();
+ errorCount += testMultithreadedGet ();
+ errorCount += testExternalGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testzzuf/test_get_chunked.c b/src/testzzuf/test_get_chunked.c
new file mode 100644
index 0000000..faa2632
--- /dev/null
+++ b/src/testzzuf/test_get_chunked.c
@@ -0,0 +1,330 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_get_chunked.c
+ * @brief Testcase for libmicrohttpd GET operations with chunked content encoding
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#include "socat.c"
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+/**
+ * MHD content reader callback that returns
+ * data in chunks.
+ */
+static ssize_t
+crc (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ struct MHD_Response **responseptr = cls;
+
+ if (pos == 128 * 10)
+ {
+ MHD_add_response_header (*responseptr, "Footer", "working");
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+ if (max < 128)
+ abort (); /* should not happen in this testcase... */
+ memset (buf, 'A' + (pos / 128), 128);
+ return 128;
+}
+
+/**
+ * Dummy function that does nothing.
+ */
+static void
+crcf (void *ptr)
+{
+ free (ptr);
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size, void **ptr)
+{
+ static int aptr;
+ const char *me = cls;
+ struct MHD_Response *response;
+ struct MHD_Response **responseptr;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ if (&aptr != *ptr)
+ {
+ /* do never respond on first call */
+ *ptr = &aptr;
+ return MHD_YES;
+ }
+ responseptr = malloc (sizeof (struct MHD_Response *));
+ response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ 1024,
+ &crc, responseptr, &crcf);
+ *responseptr = response;
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+static int
+testInternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+static int
+testExternalGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ int running;
+ time_t start;
+ struct timeval tv;
+ int i;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_NO_FLAG /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (c != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ curl_multi_info_read (multi, &running);
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ MHD_run (d);
+ }
+ if (c != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ }
+ }
+ fprintf (stderr, "\n");
+ curl_multi_cleanup (multi);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalGet ();
+ errorCount += testMultithreadedGet ();
+ errorCount += testExternalGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testzzuf/test_long_header.c b/src/testzzuf/test_long_header.c
new file mode 100644
index 0000000..2004138
--- /dev/null
+++ b/src/testzzuf/test_long_header.c
@@ -0,0 +1,234 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_long_header.c
+ * @brief Testcase for libmicrohttpd handling of very long headers
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#include "socat.c"
+
+/**
+ * We will set the memory available per connection to
+ * half of this value, so the actual value does not have
+ * to be big at all...
+ */
+#define VERY_LONG (1024*10)
+
+static int oneone;
+
+static int
+apc_all (void *cls, const struct sockaddr *addr, socklen_t addrlen)
+{
+ return MHD_YES;
+}
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ const char *me = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp (me, method))
+ return MHD_NO; /* unexpected method */
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testLongUrlGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ char *url;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 11080,
+ &apc_all,
+ NULL,
+ &ahc_echo,
+ "GET",
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT,
+ (size_t) (VERY_LONG / 2), MHD_OPTION_END);
+
+ if (d == NULL)
+ return 1;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+ c = curl_easy_init ();
+ url = malloc (VERY_LONG);
+ memset (url, 'a', VERY_LONG);
+ url[VERY_LONG - 1] = '\0';
+ memcpy (url, "http://localhost:11081/",
+ strlen ("http://localhost:11081/"));
+ curl_easy_setopt (c, CURLOPT_URL, url);
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+
+ MHD_stop_daemon (d);
+ free (url);
+ return 0;
+}
+
+
+static int
+testLongHeaderGet ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ char *url;
+ struct curl_slist *header = NULL;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 11080,
+ &apc_all,
+ NULL,
+ &ahc_echo,
+ "GET",
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT,
+ (size_t) (VERY_LONG / 2), MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ url = malloc (VERY_LONG);
+ memset (url, 'a', VERY_LONG);
+ url[VERY_LONG - 1] = '\0';
+ url[VERY_LONG / 2] = ':';
+ url[VERY_LONG / 2 + 1] = ' ';
+ header = curl_slist_append (header, url);
+
+ curl_easy_setopt (c, CURLOPT_HTTPHEADER, header);
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_slist_free_all (header);
+ header = NULL;
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+
+ MHD_stop_daemon (d);
+ free (url);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testLongUrlGet ();
+ errorCount += testLongHeaderGet ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testzzuf/test_post.c b/src/testzzuf/test_post.c
new file mode 100644
index 0000000..632609f
--- /dev/null
+++ b/src/testzzuf/test_post.c
@@ -0,0 +1,394 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_post.c
+ * @brief Testcase for libmicrohttpd POST operations using URL-encoding
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+
+#include "socat.c"
+
+#define POST_DATA "name=daniel&project=curl"
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+
+static void
+completed_cb (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct MHD_PostProcessor *pp = *con_cls;
+
+ if (NULL != pp)
+ MHD_destroy_post_processor (pp);
+ *con_cls = NULL;
+}
+
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+
+/**
+ * Note that this post_iterator is not perfect
+ * in that it fails to support incremental processing.
+ * (to be fixed in the future)
+ */
+static int
+post_iterator (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *value, uint64_t off, size_t size)
+{
+ int *eok = cls;
+
+ if ((0 == strcmp (key, "name")) &&
+ (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size)))
+ (*eok) |= 1;
+ if ((0 == strcmp (key, "project")) &&
+ (size == strlen ("curl")) && (0 == strncmp (value, "curl", size)))
+ (*eok) |= 2;
+ return MHD_YES;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int eok;
+ struct MHD_Response *response;
+ struct MHD_PostProcessor *pp;
+ int ret;
+
+ if (0 != strcmp ("POST", method))
+ {
+ return MHD_NO; /* unexpected method */
+ }
+ pp = *unused;
+ if (pp == NULL)
+ {
+ eok = 0;
+ pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok);
+ *unused = pp;
+ }
+ MHD_post_process (pp, upload_data, *upload_data_size);
+ if ((eok == 3) && (0 == *upload_data_size))
+ {
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ MHD_destroy_post_processor (pp);
+ *unused = NULL;
+ return ret;
+ }
+ *upload_data_size = 0;
+ return MHD_YES;
+}
+
+
+static int
+testInternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+
+ return 0;
+}
+
+static int
+testMultithreadedPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+static int
+testExternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ int running;
+ time_t start;
+ struct timeval tv;
+ int i;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_NO_FLAG /* | MHD_USE_DEBUG */ ,
+ 1082, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
+ curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
+ curl_easy_setopt (c, CURLOPT_POST, 1L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (c != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ curl_multi_info_read (multi, &running);
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ MHD_run (d);
+ }
+ if (c != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ }
+
+ }
+ fprintf (stderr, "\n");
+ curl_multi_cleanup (multi);
+ zzuf_socat_stop ();
+
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalPost ();
+ errorCount += testMultithreadedPost ();
+ errorCount += testExternalPost ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testzzuf/test_post_form.c b/src/testzzuf/test_post_form.c
new file mode 100644
index 0000000..3e2cdff
--- /dev/null
+++ b/src/testzzuf/test_post_form.c
@@ -0,0 +1,410 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_post_form.c
+ * @brief Testcase for libmicrohttpd POST operations using multipart/postform data
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+
+#include "socat.c"
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+
+static void
+completed_cb (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct MHD_PostProcessor *pp = *con_cls;
+
+ if (NULL != pp)
+ MHD_destroy_post_processor (pp);
+ *con_cls = NULL;
+}
+
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+/**
+ * Note that this post_iterator is not perfect
+ * in that it fails to support incremental processing.
+ * (to be fixed in the future)
+ */
+static int
+post_iterator (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *filename,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *value, uint64_t off, size_t size)
+{
+ int *eok = cls;
+
+ if (key == NULL)
+ return MHD_YES;
+#if 0
+ fprintf (stderr, "PI sees %s-%.*s\n", key, size, value);
+#endif
+ if ((0 == strcmp (key, "name")) &&
+ (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size)))
+ (*eok) |= 1;
+ if ((0 == strcmp (key, "project")) &&
+ (size == strlen ("curl")) && (0 == strncmp (value, "curl", size)))
+ (*eok) |= 2;
+ return MHD_YES;
+}
+
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ static int eok;
+ struct MHD_Response *response;
+ struct MHD_PostProcessor *pp;
+ int ret;
+
+ if (0 != strcmp ("POST", method))
+ {
+ return MHD_NO; /* unexpected method */
+ }
+ pp = *unused;
+ if (pp == NULL)
+ {
+ eok = 0;
+ pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok);
+ if (pp == NULL)
+ return MHD_NO;
+ *unused = pp;
+ }
+ MHD_post_process (pp, upload_data, *upload_data_size);
+ if ((eok == 3) && (0 == *upload_data_size))
+ {
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ MHD_destroy_post_processor (pp);
+ *unused = NULL;
+ return ret;
+ }
+ *upload_data_size = 0;
+ return MHD_YES;
+}
+
+static struct curl_httppost *
+make_form ()
+{
+ struct curl_httppost *post = NULL;
+ struct curl_httppost *last = NULL;
+
+ curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
+ CURLFORM_COPYCONTENTS, "daniel", CURLFORM_END);
+ curl_formadd (&post, &last, CURLFORM_COPYNAME, "project",
+ CURLFORM_COPYCONTENTS, "curl", CURLFORM_END);
+ return post;
+}
+
+
+static int
+testInternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int i;
+ struct curl_httppost *pd;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ pd = make_form ();
+ curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+static int
+testMultithreadedPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ int i;
+ struct curl_httppost *pd;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION /* | MHD_USE_DEBUG */ ,
+ 11080, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ pd = make_form ();
+ curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+static int
+testExternalPost ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ int running;
+ time_t start;
+ struct timeval tv;
+ struct curl_httppost *pd;
+ int i;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_NO_FLAG /* | MHD_USE_DEBUG */ ,
+ 1082, NULL, NULL, &ahc_echo, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:1082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ pd = make_form ();
+ curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_formfree (pd);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (c != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ curl_formfree (pd);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ curl_formfree (pd);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ curl_multi_info_read (multi, &running);
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ MHD_run (d);
+ }
+ if (c != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ }
+ curl_formfree (pd);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalPost ();
+ errorCount += testMultithreadedPost ();
+ errorCount += testExternalPost ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testzzuf/test_put.c b/src/testzzuf/test_put.c
new file mode 100644
index 0000000..c6240c4
--- /dev/null
+++ b/src/testzzuf/test_put.c
@@ -0,0 +1,361 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_put.c
+ * @brief Testcase for libmicrohttpd PUT operations
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+
+#include "socat.c"
+
+static int oneone;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
+{
+ unsigned int *pos = ptr;
+ unsigned int wrt;
+
+ wrt = size * nmemb;
+ if (wrt > 8 - (*pos))
+ wrt = 8 - (*pos);
+ memcpy (stream, &("Hello123"[*pos]), wrt);
+ (*pos) += wrt;
+ return wrt;
+}
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ int *done = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp ("PUT", method))
+ return MHD_NO; /* unexpected method */
+ if ((*done) == 0)
+ {
+ if (*upload_data_size != 8)
+ return MHD_YES; /* not yet ready */
+ if (0 == memcmp (upload_data, "Hello123", 8))
+ {
+ *upload_data_size = 0;
+ }
+ else
+ {
+ printf ("Invalid upload data `%8s'!\n", upload_data);
+ return MHD_NO;
+ }
+ *done = 1;
+ return MHD_YES;
+ }
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testInternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 11080,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION /* | MHD_USE_DEBUG */ ,
+ 11080,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+static int
+testExternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ int running;
+ time_t start;
+ struct timeval tv;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ int i;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_NO_FLAG /* | MHD_USE_DEBUG */ ,
+ 11080,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (c != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ curl_multi_info_read (multi, &running);
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ MHD_run (d);
+ }
+ if (c != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ }
+ }
+ fprintf (stderr, "\n");
+ curl_multi_cleanup (multi);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalPut ();
+ errorCount += testMultithreadedPut ();
+ errorCount += testExternalPut ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testzzuf/test_put_chunked.c b/src/testzzuf/test_put_chunked.c
new file mode 100644
index 0000000..c023c5e
--- /dev/null
+++ b/src/testzzuf/test_put_chunked.c
@@ -0,0 +1,371 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file daemontest_put_chunked.c
+ * @brief Testcase for libmicrohttpd PUT operations with chunked encoding
+ * for the upload data
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#include "socat.c"
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
+{
+ unsigned int *pos = ptr;
+ unsigned int wrt;
+
+ wrt = size * nmemb;
+ if (wrt > 8 - (*pos))
+ wrt = 8 - (*pos);
+ if (wrt > 4)
+ wrt = 4; /* only send half at first => force multiple chunks! */
+ memcpy (stream, &("Hello123"[*pos]), wrt);
+ (*pos) += wrt;
+ return wrt;
+}
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ int *done = cls;
+ struct MHD_Response *response;
+ int ret;
+ int have;
+
+ if (0 != strcmp ("PUT", method))
+ return MHD_NO; /* unexpected method */
+ if ((*done) < 8)
+ {
+ have = *upload_data_size;
+ if (have + *done > 8)
+ {
+ return MHD_NO;
+ }
+ if (0 == memcmp (upload_data, &"Hello123"[*done], have))
+ {
+ *done += have;
+ *upload_data_size = 0;
+ }
+ else
+ {
+ return MHD_NO;
+ }
+#if 0
+ fprintf (stderr, "Not ready for response: %u/%u\n", *done, 8);
+#endif
+ return MHD_YES;
+ }
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testInternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ 11080,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11080/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ /*
+ // by not giving the file size, we force chunking!
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ */
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ CURLcode errornum;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
+ 11081,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ /*
+ // by not giving the file size, we force chunking!
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ */
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ if (CURLE_OK != (errornum = curl_easy_perform (c)))
+ {
+ fprintf (stderr,
+ "curl_easy_perform failed: `%s'\n",
+ curl_easy_strerror (errornum));
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ return 32;
+ }
+ curl_easy_cleanup (c);
+ MHD_stop_daemon (d);
+ if (cbc.pos != strlen ("/hello_world"))
+ return 64;
+ if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
+ return 128;
+
+ return 0;
+}
+
+
+static int
+testExternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ char buf[2048];
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ int running;
+ time_t start;
+ struct timeval tv;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ int i;
+
+ multi = NULL;
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_DEBUG,
+ 11082,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11082/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ /*
+ // by not giving the file size, we force chunking!
+ curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
+ */
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (c != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ curl_multi_info_read (multi, &running);
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ MHD_run (d);
+ }
+ if (c != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ }
+ }
+ fprintf (stderr, "\n");
+ curl_multi_cleanup (multi);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ errorCount += testInternalPut ();
+ errorCount += testMultithreadedPut ();
+ errorCount += testExternalPut ();
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}
diff --git a/src/testzzuf/test_put_large.c b/src/testzzuf/test_put_large.c
new file mode 100644
index 0000000..1fdc92a
--- /dev/null
+++ b/src/testzzuf/test_put_large.c
@@ -0,0 +1,382 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2007, 2008 Christian Grothoff
+
+ libmicrohttpd 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, or (at your
+ option) any later version.
+
+ libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file test_put_large.c
+ * @brief Testcase for libmicrohttpd PUT operations
+ * @author Christian Grothoff
+ */
+
+#include "MHD_config.h"
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#include "socat.c"
+
+static int oneone;
+
+/**
+ * Do not make this much larger since we will hit the
+ * MHD default buffer limit and the test code is not
+ * written for incremental upload processing...
+ */
+#define PUT_SIZE (256 * 1024)
+
+static char *put_buffer;
+
+struct CBC
+{
+ char *buf;
+ size_t pos;
+ size_t size;
+};
+
+static size_t
+putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
+{
+ unsigned int *pos = ptr;
+ unsigned int wrt;
+
+ wrt = size * nmemb;
+ if (wrt > PUT_SIZE - (*pos))
+ wrt = PUT_SIZE - (*pos);
+ memcpy (stream, &put_buffer[*pos], wrt);
+ (*pos) += wrt;
+ return wrt;
+}
+
+static size_t
+copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ struct CBC *cbc = ctx;
+
+ if (cbc->pos + size * nmemb > cbc->size)
+ return 0; /* overflow */
+ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
+ cbc->pos += size * nmemb;
+ return size * nmemb;
+}
+
+static int
+ahc_echo (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **unused)
+{
+ int *done = cls;
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 != strcmp ("PUT", method))
+ return MHD_NO; /* unexpected method */
+ if ((*done) == 0)
+ {
+ if (*upload_data_size != PUT_SIZE)
+ {
+#if 0
+ fprintf (stderr,
+ "Waiting for more data (%u/%u)...\n",
+ *upload_data_size, PUT_SIZE);
+#endif
+ return MHD_YES; /* not yet ready */
+ }
+ if (0 == memcmp (upload_data, put_buffer, PUT_SIZE))
+ {
+ *upload_data_size = 0;
+ }
+ else
+ {
+ return MHD_NO;
+ }
+ *done = 1;
+ return MHD_YES;
+ }
+ response = MHD_create_response_from_buffer (strlen (url),
+ (void *) url,
+ MHD_RESPMEM_MUST_COPY);
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static int
+testInternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ char buf[2048];
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
+ 11080,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 1;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+static int
+testMultithreadedPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ struct CBC cbc;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ char buf[2048];
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION /* | MHD_USE_DEBUG */ ,
+ 11080,
+ NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
+ if (d == NULL)
+ return 16;
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_perform (c);
+ curl_easy_cleanup (c);
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+static int
+testExternalPut ()
+{
+ struct MHD_Daemon *d;
+ CURL *c;
+ struct CBC cbc;
+ CURLM *multi;
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ int running;
+ time_t start;
+ struct timeval tv;
+ unsigned int pos = 0;
+ int done_flag = 0;
+ char buf[2048];
+ int i;
+
+ cbc.buf = buf;
+ cbc.size = 2048;
+ cbc.pos = 0;
+ multi = NULL;
+ d = MHD_start_daemon (MHD_NO_FLAG /* | MHD_USE_DEBUG */,
+ 11080,
+ NULL, NULL, &ahc_echo, &done_flag,
+ MHD_OPTION_CONNECTION_MEMORY_LIMIT,
+ (size_t) (PUT_SIZE * 4), MHD_OPTION_END);
+ if (d == NULL)
+ return 256;
+ multi = curl_multi_init ();
+ if (multi == NULL)
+ {
+ MHD_stop_daemon (d);
+ return 512;
+ }
+ zzuf_socat_start ();
+ for (i = 0; i < LOOP_COUNT; i++)
+ {
+ fprintf (stderr, ".");
+
+ c = curl_easy_init ();
+ curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
+ curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
+ curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
+ curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
+ curl_easy_setopt (c, CURLOPT_READDATA, &pos);
+ curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE);
+ curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
+ curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
+ if (oneone)
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
+ // NOTE: use of CONNECTTIMEOUT without also
+ // setting NOSIGNAL results in really weird
+ // crashes on my system!
+ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
+
+
+
+ mret = curl_multi_add_handle (multi, c);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 1024;
+ }
+ start = time (NULL);
+ while ((time (NULL) - start < 5) && (c != NULL))
+ {
+ max = 0;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ curl_multi_perform (multi, &running);
+ mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
+ if (mret != CURLM_OK)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 2048;
+ }
+ if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_multi_cleanup (multi);
+ curl_easy_cleanup (c);
+ zzuf_socat_stop ();
+ MHD_stop_daemon (d);
+ return 4096;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+ select (max + 1, &rs, &ws, &es, &tv);
+ curl_multi_perform (multi, &running);
+ if (running == 0)
+ {
+ curl_multi_info_read (multi, &running);
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ c = NULL;
+ }
+ MHD_run (d);
+ }
+ if (c != NULL)
+ {
+ curl_multi_remove_handle (multi, c);
+ curl_easy_cleanup (c);
+ }
+ }
+ fprintf (stderr, "\n");
+ zzuf_socat_stop ();
+ curl_multi_cleanup (multi);
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+int
+main (int argc, char *const *argv)
+{
+ unsigned int errorCount = 0;
+
+ oneone = (NULL != strrchr (argv[0], (int) '/')) ?
+ (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ return 2;
+ put_buffer = malloc (PUT_SIZE);
+ memset (put_buffer, 1, PUT_SIZE);
+ errorCount += testInternalPut ();
+ errorCount += testMultithreadedPut ();
+ errorCount += testExternalPut ();
+ free (put_buffer);
+ if (errorCount != 0)
+ fprintf (stderr, "Error (code: %u)\n", errorCount);
+ curl_global_cleanup ();
+ return errorCount != 0; /* 0 == pass */
+}