diff options
author | joe <joe@61a7d7f5-40b7-0310-9c16-bb0ea8cb1845> | 2004-10-02 18:47:02 +0000 |
---|---|---|
committer | joe <joe@61a7d7f5-40b7-0310-9c16-bb0ea8cb1845> | 2004-10-02 18:47:02 +0000 |
commit | 0294ff3d3282d1b1c5497f00ea25e5e55e6f4338 (patch) | |
tree | 978af6f81c7b7715597871b1e89a9ad083907f1a | |
download | neon-0294ff3d3282d1b1c5497f00ea25e5e55e6f4338.tar.gz |
Import neon 0.24.0 to begin 0.24.x branch.
git-svn-id: http://svn.webdav.org/repos/projects/neon/branches/0.24.x@243 61a7d7f5-40b7-0310-9c16-bb0ea8cb1845
157 files changed, 39934 insertions, 0 deletions
diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..0522756 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,22 @@ +config.h.in +config.h +configure +config.status +config.log +conftest.c +Makefile +aclocal.m4 +*.cache +libtool +neon-config +reconf* +confdefs.h +ltmain.sh +ltconfig +config.sub +config.guess +*.out +*.log +.version +config.hw +neon.pc diff --git a/.package b/.package new file mode 100644 index 0000000..493f404 --- /dev/null +++ b/.package @@ -0,0 +1 @@ +announce-list=neon@webdav.org diff --git a/.release.sh b/.release.sh new file mode 100755 index 0000000..2a540ae --- /dev/null +++ b/.release.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +set -ex + +major=`echo $1 | awk -F. '{print $1;}'` +minor=`echo $1 | awk -F. '{print $2;}'` +rel=`echo $1 | awk -F. '{print $3;}'` +version=$1 + +for f in config.hw; do +in=$f.in +out=$f +sed -e "s/@VERSION@/$version/g" \ + -e "s/@MAJOR@/$major/g" \ + -e "s/@MINOR@/$minor/g" \ + -e "s/@RELEASE@/$release/g" < $in > $out +done + +echo $1 > .version + +# for the documentation: +date +"%e %B %Y" | tr -d '\n' > doc/date.xml +echo -n $1 > doc/version.xml + +# Try to create a valid Makefile +tmp=`mktemp /tmp/neon-XXXXXX` +sed -e "s/@SET_MAKE@//g" -e "s|@SHELL@|/bin/sh|g" < Makefile.in > $tmp +make -f $tmp docs +rm -f $tmp @@ -0,0 +1,5 @@ +neon is Copyright (C) 1999-2003 Joe Orton <joe@manyfish.co.uk> +Portions are: +Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> +Copyright (C) 1999-2000 Peter Boos <pedib@colorfullife.com> +Copyright (C) 1991, 1995, 1996, 1997 Free Software Foundation, Inc. @@ -0,0 +1,37 @@ + +Known problems/bugs in neon -*- text -*- +--------------------------- + +* 2818 requires that a on rejection of the SSL server cert, a "bad certificate" + message should be sent - this is not being done currently (and can probably + only be done with OpenSSL by actually doing cert verification in the verify + callback) + +* ne_lock_discover does not handle multiple (shared) locks on + a single resource. + +* SSL session caching issues; only cache for clean shutdowns, and + only cache on shutdown, since the SSL_SESSION may change during + an ne_session. + +* test failures in `socket' on some non-Linux platforms: + +19. write_reset........... FAIL (write got 0 not reset) +20. read_reset............ FAIL (read got -3 not reset) + +* server auth should not run on a CONNECT request; once a connection + has been CONNECT tunneled, subsequent requests through the tunnel + should not give proxy auth. + +* what is passed as 'path' to req create hook: auth needs Request-URI; + how does that interact with proxies? also they will be passed NULL + for a CONNECT request, or "*" possibly as well. + +* expect100 support is broken. + +* It would be nice to fail with a friendly error message if a client +cert is requested by the srever but one is not provided. Currently, +returning -1 from the provide_client_cert function would allow that +(as it forces the SSL handshake to fail), but that would prevent +opportunistic use of client certificates, of the "SSLVerifyClient +optional" variety. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..27a656c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,666 @@ +Fri Jun 20 17:51:05 2003 Joe Orton <joe@manyfish.co.uk> + + * configure.in, neon-config.in: Don't pass user-supplied CPPFLAGS + through to neon-config, it is no longer necessary. + +Sun Apr 6 19:51:31 2003 Joe Orton <joe@manyfish.co.uk> + + * doc/manual.xml: Include clicert reference docs. + + * doc/ref/clicert.xml: New file. + +Sun Apr 6 19:24:47 2003 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (XMLTO): New variable. + (docs-man, docs-pdf, docs-ps, docs-html): Use $(XMLTO) variable. + +Wed Mar 26 20:09:12 2003 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (DIST_HEADERS): Add ne_tls.h + + * neon.mak: Build ne_openssl.obj for SSL build, ne_stubssl.obj for + non-SSL build. + +Sun Mar 9 10:38:36 2003 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Set ALLOW_INSTALL=yes early to allow later + overrides. + + * Makefile.in (install-yes): Remove dependence on subdirs. + (install-lib): Depend on subdirs; don't install neon.pc. + (install-config): Install neon.pc here. + +Sat Mar 1 21:50:17 2003 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (cover): New target. + +Sat Mar 1 20:39:32 2003 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Add --enable-memleak argument; if enabled, have + config.h include memleak.h, add top_srcdir/src to include path, + and substitute ALLOW_INSTALL as 'memleak'. Otherwise, substitute + ALLOW_INSTALL as 'yes'. + + * Makefile.in (install): Use install-@ALLOW_INSTALL@. + (install-yes): Renamed from install. + (install-memleak): Prevent installation with non-standard ABI. + +Fri Dec 27 15:15:54 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.pc.in: New file. + + * configure.in: Generate neon.pc. + + * Makefile.in (install-lib): Install neon.pc + +Tue Nov 19 11:24:40 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Use NEON_TEST before NEON_XML_PARSER to prevent + problems if NEON_XML_PARSER adds anything to CPPFLAGS which breaks + gcc -Werror (e.g. -I/usr/local/include). + +Sat Sep 21 12:29:56 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-config.in: Add support for `--la-file' argument, to print + location of libtool .la file. + +Sat Sep 14 12:46:21 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Add AC_COPYRIGHT, use AC_MSG_NOTICE for + post-configure message, update for modern use of AC_CONFIG_FILES + and AC_OUTPUT. + +Thu Aug 29 23:49:40 2002 Joe Orton <joe@manyfish.co.uk> + + Further Win32 updates from Blair Zajac: + + * config.hw.in: Define HAVE_ERRNO_H; fix non-SSL build. + + * neon.mak: Fix to build DAV sources if EXPAT_FLAGS is set but not + EXPAT_SRC. + +Sun Aug 25 23:37:59 2002 Joe Orton <joe@manyfish.co.uk> + + Win32 updates from Blair Zajac <blair@orcaware.com>: + + * neon.mak: Fix expansion of $(LIB32_OBJS) variable that was + causing nmake failures. Allow non-DAV build if EXPAT_SRC is not + specified. + + * config.hw.in: Remove hard-coded defines. + + * INSTALL.win32: Overhaul. + +Sat Aug 10 10:42:09 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Substitue OPENSSL into Makefiles as an absolute + path. + +Fri Aug 9 20:45:01 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Update to use modern three-argument AC_INIT and + AC_CONFIG_SRCDIR. Pick up version string from .version; thanks to + Greg Stein for they `esyscmd' trick. Move NEON_WITH_LIBS earlier. + Use AC_HELP_STRING for --disable-webdav. + +Fri Aug 9 20:41:24 2002 Joe Orton <joe@manyfish.co.uk> + + * config.hw.in, neon.mak, INSTALL.win32: Win32 build update from + Gerald Richter: add optional NEON_NODAV and OPENSSL_STATIC flags, + update locations of includes and libraries. + +Sun Jun 30 11:08:56 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.mak: Enable WIN32_LEAN_AND_MEAN etc, to prevent X509_NAME + definition conflicting with OpenSSL headers in recent versions of + Platform SDK (Branko Èibej). + +Thu Jun 13 20:35:43 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Call NEON_WITH_LIBS for --with-libs option. + +Tue Jun 4 13:27:48 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.mak: Always build ne_compress.obj; patch from Branko Èibej. + +Tue Jun 4 09:38:24 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (man1dir, man3dir): Use $(mandir). (Rodney Dawes) + +Mon Jun 3 20:47:14 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (install-html, install-man): Fix for VPATH builds. + +Sat May 25 15:01:18 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Only enable SSL tests if an `openssl' binary is + found in $PATH. + +Thu May 23 20:31:01 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Require a release version of autoconf. + +Sat May 18 14:43:15 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (docs-man): Use customisation layer. + +Sat Apr 13 22:34:00 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in, neon-config.in: Pass LDFLAGS and CPPFLAGS setting + given to configure through to neon-config --libs/--cflags output. + +Mon Feb 25 20:53:28 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.mak (OPENSSL_FLAGS): Define NEON_SSL not ENABLE_SSL + (Branko). + +Mon Feb 25 20:46:04 2002 Joe Orton <joe@manyfish.co.uk> + + * config.hw.in: Add NE_FMT_OFF_T (Dan Berlin, Branko Èibej). + +Sun Feb 10 20:35:55 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (docs-html): Use XSL customisation layer for the + HTML output. + +Wed Feb 6 00:42:32 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Require autoconf 2.52d for AC_C_BIGENDIAN which + supports cross-compiling. + +Sat Jan 26 11:19:39 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Define TESTS and HELPERS appropriately for whether + DAV, SSL, zlib are supported. + +Sat Jan 26 11:03:38 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.mak: Add OpenSSL, zlib support (Branko Èibej). + +Sat Jan 26 00:15:29 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.dsp, neon.dsw: Removed per advice from Branko. + +Thu Jan 24 21:02:02 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (check): Run check target in test subdir. + (subdirs): Remove redundant subshell. + (distclean): Clean harder. + +Thu Jan 24 20:46:28 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.mak: Remove sslcerts.c from build. + +Sun Jan 20 12:51:27 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Create test/common for vpath build. + +Tue Jan 8 21:35:12 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.mak: Rewritten by Branko Èibej <brane@xbc.nu>. + + * config.hw.in: Update from Branko Èibej. + +Thu Jan 3 08:48:00 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (neon-config): Add target. + +Thu Jan 3 08:47:06 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-config.in (libs): Don't print -L/usr/lib or -L/lib. + +Thu Jan 3 08:43:08 2002 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Make neon-config executable in the build tree. + +Mon Dec 17 22:54:00 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (install-lib, install-headers, install-config): + Support DESTDIR; patch from Pawel Golaszewski <ues@ds.pg.gda.pl>. + +Sat Oct 27 12:23:57 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-config.in: Add "--support FEATURE" option; the script exits + with success if given FEATURE is supported. + +Sat Oct 6 13:11:57 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Require autoconf 2.50. Use AC_HELP_STRING. + +Sun Sep 30 23:44:56 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in, neon-config.in: Clean up handling of cflags/libs + exporting. + +Sat Sep 29 12:45:25 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Add Makefile re-generation target, have clean + recurse into test too, have check depend on subdirs, distribute + ne_compress.h. + +Tue Sep 25 07:34:32 2001 Mo DeJong <supermo@bayarea.net> + + * configure.in: Move check for signal.h into + LIBNEON_SOURCE_CHECKS. + +Mon Sep 24 20:28:26 2001 Joe Orton <joe@manyfish.co.uk> + + * autogen.sh: Bomb out on errors (set -e). + +Mon Sep 24 20:20:08 2001 Joe Orton <joe@manyfish.co.uk> + + * autogen.sh: Clean up, use libtoolize. + + * ltmain.sh: Removed from CVS. + +Mon Sep 24 20:17:18 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Use three-argument AC_DEFINE for _GNU_SOURCE (Mo + DeJong). + +Tue Sep 11 23:20:23 2001 Joe Orton <joe@manyfish.co.uk> + + * config.hw.in: Define USE_DAV_LOCKS. + +Mon Aug 13 21:07:28 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.mak: Add support for SSL (Peter Boos). + +Sat Jun 30 12:22:17 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Add NEONLIBS to LIBS, so that the depedencies are + added to the link line. This means they are picked up as + references in the .so, and also listed in the libtool .la file. + +Tue Jun 12 13:02:58 2001 Joe Orton <joe@manyfish.co.uk> + + * config.hw.in: Renamed from config.hw, and have version + substituted in when tarball is rolled. Adjust for XML parser + changes. (Gerald Richter) + +Sun Jun 10 16:41:46 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Check for pipe() for tests, use NEON_DEBUG. + +Fri Jun 8 22:57:24 2001 Joe Orton <joe@manyfish.co.uk> + + * config.hw: Fixes from Gerald Richter <richter@ecos.de>. + + * neon.mak, INSTALL.win32: New files from Gerald Richter. + +Thu May 31 00:00:32 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Disable shared library build by default. Use + NE_DEBUGGING rather than DEBUGGING. Check for 'usleep' for tests. + +Sun Apr 29 16:41:17 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Produce test/Makefile. + +Wed Mar 14 22:51:28 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (DIST_HEADERS): Add http_auth.h + +Wed Mar 14 22:45:51 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Build bundled directory regardless of + --disable-webdav. + +Sun Feb 25 17:00:40 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Rename NEON_IS_BUNDLED to NEON_BUILD_BUNDLED. + +Sun Feb 25 16:53:28 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Set NEON_IS_BUNDLED to "yes". + +Sat Feb 24 00:09:57 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Add --disable-webdav flag to disable WebDAV + support. Set NEON_NEED_XML_PARSER=yes if DAV is not disabled. + +Sun Jan 28 23:10:39 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Remove neon_config.h from DIST_HEADERS. + +Sun Jan 28 10:41:40 2001 Joe Orton <joe@manyfish.co.uk> + + * configure.in: Don't produce neon_config.h. + +Tue Jan 23 23:16:25 2001 Joe Orton <joe@light.plus.com> + + * configure.in: Moved version defns into NEON_VERSIONS macros. + Produce src/neon_config.h. + +Tue Jan 16 20:16:47 2001 Joe Orton <joe@light.plus.com> + + * configure.in: Bumped version to 0.10.1. + +Mon Jan 15 22:59:47 2001 Joe Orton <joe@light.plus.com> + + * configure.in: Bumped version to 0.10.0. + +Sun Jan 14 23:55:47 2001 Joe Orton <joe@light.plus.com> + + * Makefile.in (distclean): Don't remove neon_config.h. (install): + Depend on subdirs. + +Wed Jan 10 22:46:53 2001 Joe Orton <joe@light.plus.com> + + * configure.in: Use NEON_LINK_FLAGS to pass through -version-info + and interface version flags. Add -I${top_builddir} to CFLAGS. + Remove NEONOBJS declaration, let NEON_LIBTOOL_BUILD do it. + +Wed Dec 20 00:11:56 2000 Joe Orton <joe@light.plus.com> + + * configure.in: Bumped version to 0.9.1, interface version to + 9:1:0. + +Tue Dec 19 22:15:45 2000 Joe Orton <joe@light.plus.com> + + * Makefile.in: Build using recursive make into src directory. Add + VPATH support. (Mo DeJong <mdejong@cygnus.com>) + +Tue Dec 19 22:13:40 2000 Joe Orton <joe@light.plus.com> + + * configure.in: Build using new src/Makefile.in. + +Sun Dec 17 19:53:36 2000 Joe Orton <joe@light.plus.com> + + * config.sub, config.guess, ltconfig, ltmain.sh: Update to libtool + 1.3.5 versions. + +Sun Dec 17 18:43:00 2000 Joe Orton <joe@light.plus.com> + + * Makefile.in: Remove all traces of example programs. Fix + uritest. + +Thu Dec 14 21:47:48 2000 Joe Orton <joe@light.plus.com> + + * configure.in: Bumped version to 0.8.0, interface version to + 8:0:0. + +Thu Dec 14 20:57:49 2000 Joe Orton <joe@light.plus.com> + + * configure.in: Removed configuration of examples. + +Wed Dec 13 20:14:53 2000 Joe Orton <joe@light.plus.com> + + * config.hw: Added for Windows. + +Sun Nov 26 09:52:29 2000 Joe Orton <joe@light.plus.com> + + * example/: Removed directory (now in separate neon-examples + package). + +Tue Sep 12 10:33:50 2000 Joe Orton <joe@light.plus.com> + + * Makefile.in: Remove sitecopy bits which somehow got left in + there. + +Tue Sep 12 00:41:49 2000 Joe Orton <joe@light.plus.com> + + * configure.in: Bumped version to 0.7.3, interface version to + 7:1:1. + +Tue Sep 12 00:39:49 2000 Joe Orton <joe@light.plus.com> + + * Makefile.in (distclean): New target. + +Thu Sep 7 00:14:15 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped version to 0.7.2, interface version to + 7:0:1. + +Thu Sep 7 00:10:54 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in: Added 'all' target (patch by Greg Stein + <gstein@lyra.org>). + +Sun Sep 3 10:32:09 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in (SHELL): Added definition. Changed xalloc.h to + ne_alloc.h in DIST_HEADERS. Thanks to Eric Mumpower + <nocturne@arepa.com>. + +Tue Aug 15 21:53:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped version to 0.7.1, interface version to + 6:1:1. + +Mon Aug 14 09:28:47 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped version to 0.7.0, interface version to + 6:0:0. + +Sun Aug 13 15:59:58 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped version to 0.6.1, interface version to + 5:1:1. + +Sat Aug 12 17:10:09 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped version to 0.6.0, interface version to + 5:0:1. + +Sat Aug 12 17:08:54 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Add --with-extra-includes and --with-extra-libs + configure parameters + +Sat Aug 12 17:07:22 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (redirect_notify): New function. (main): Support + automatic redirects. + +Sat Aug 12 16:53:50 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in: Build http_redirect.lo. + +Sat Aug 12 14:43:28 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (pretty_progress_bar): Use 'off_t' not size_t + arguments. + +Sat Aug 12 02:11:05 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped interface version to 4:1:0, version to + 0.5.1. + +Fri Aug 11 17:18:19 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped interface version to 4:0:0, version to + 0.5.0. + +Fri Jul 28 13:35:06 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped interface version to 3:2:1, version to + 0.4.2. + +Fri Jul 28 11:26:18 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped interface version to 3:1:1, version to + 0.4.1. + +Fri Jul 28 11:25:05 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c: Include nsocket.h not socket.h. + +Fri Jul 28 10:31:50 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped interface version to 3:0:1. Bumped version + to 0.4.0. + +Thu Jul 27 22:01:11 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in: Install nsocket.h not socket.h. + +Thu Jul 27 21:59:45 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Tell neon-config that header files are in + $(includedir)/neon. Use NEON_WARNINGS macro for compiler + warnings. + +Thu Jul 20 19:20:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in: Install headers into $(includedir)/neon rather + libneon. Add dav_locks.h, xalloc.h, neon_md5.h, neon_i18n.h to + list of headers installed. + +Mon Jul 17 09:11:46 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped version to 0.3.1, interface version to + 2:0:1. + +Sun Jul 16 18:47:47 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped version to 0.3.0. + +Sun Jul 16 17:17:51 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in (nbrowse, nserver, debug-nserver, debug-nbrowse): + Added targets. + +Sun Jul 16 17:15:08 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (main): Updated for new SSL interface. + +Sun Jul 16 16:51:16 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Added --enable-gnome-examples switch. + +Sun Jun 18 12:56:00 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nserver.c: New file. + +Sun Jun 18 12:54:43 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nbrowse.glade: Added Glade project file. + +Sun Jun 18 12:51:56 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nbrowse/main.c, example/nbrowse/interface.c, + example/nbrowse/callbacks.c, example/nbrowse/support.c: Added + files from Glade. + +Thu May 25 01:04:11 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Bumped NEON_INTERFACE_VERSION to 1:0:0. With + --enable-warnings, only include -Wstrict-prototypes if we're not + building with SSL support. + +Thu May 25 01:01:01 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in: Pass --quiet to libtool to make it a bit less + chatty. (again, debug-nget): New target. + +Tue May 23 17:14:43 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (main): Use http_session_create/destroy. Quit if + http_set_secure fails. + +Tue May 23 15:35:16 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (parse_args, conn_notify): New functions. + (main): Call neon_debug_init, call sock_init. Register + conn_notify. If scheme is "https", use a secure connection. + +Tue May 23 14:14:05 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (parse_args): New function. (main): + +Sun May 21 23:54:48 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-config.in: Use @NEON_LIBDIR@ not -L@libdir@ in --libs + output. + +Sun May 21 23:53:41 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Call NEON_SSL macro, add LDFLAGS to NEON_LIBDIR to + pick up OpenSSL library location. + +Sat May 20 21:47:54 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c: Include neon_config.h for NEON_VERSION + declaration. + +Sat May 20 21:47:29 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Added --enable-warnings parameter. + +Sat May 20 21:46:54 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in (libneon.la): Fixed passing interface version. + +Sun May 14 00:40:38 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in (NEON_VERSION): Bumped to 0.2.0. + +Sun May 14 00:28:33 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in (install-config): New goal. + +Sun May 14 00:26:00 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Added --enable-debugging argument. Added + NEON_LIBDIR, NEON_INCLUDEDIR, NEON_LIBS, NEON_CFLAGS for + neon-config. Produce neon-config from neon-config.in. + + * neon-config.in: New file, modified from libxml. + +Sat May 13 23:16:46 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Builed static and shared libraries by default. + +Sat May 13 16:32:15 2000 Joe Orton <joe@orton.demon.co.uk> + + * config.sub, config.guess, ltmain.sh, ltconfig: Updated from + libtool-1.3.4. + +Sat May 13 13:45:56 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c: Don't include neon.h. + +Sat May 13 13:44:22 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in: Use libtool. (install-examples): New goal. + + * configure.in: Define NEON_VERSION. + +Thu May 11 14:14:00 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (main): Set user-agent. + +Thu May 11 14:10:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * ltconfig, ltmain.sh, config.sub, config.guess: Added libtool + support files. + + * configure.in: Call AC_PROG_LIBTOOL and AC_DISABLE_STATIC. + + * .cvsignore: Added libtool. + +Wed May 10 19:17:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in: Print configuration message, check for ranlib. + +Wed May 10 19:16:43 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (main): Allow output to stdout. + +Wed May 10 19:15:56 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.in: Added shared, install* targets + +Wed May 10 17:47:30 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c (pretty_progress_bar): New function, from + cadaver. + +Wed May 10 14:42:45 2000 Joe Orton <joe@orton.demon.co.uk> + + * example/nget.c: New file. + +Wed May 10 14:41:39 2000 Joe Orton <joe@orton.demon.co.uk> + + * configure.in, Makefile.in, .cvsignore, install-sh: New files. diff --git a/INSTALL.win32 b/INSTALL.win32 new file mode 100644 index 0000000..9fc5bdd --- /dev/null +++ b/INSTALL.win32 @@ -0,0 +1,130 @@ +Building neon on Windows uses a single Nmake neon.mak file. By +placing various parameters on nmake's command line, you can specify +exactly the features and behavior of the Neon libraries. The +parameters are additive, so to add more features, add the command line +options specified in the particular section below. + +All the builds described below should work with Microsoft VC++ 5 and +6. + +Build neon +__________ + +This is the most basic version of the Neon library you can build. It +does not require any third party libraries, but you do not get the +full capabilities of Neon. + +Compile Neon with no parameters + + nmake /f neon.mak + +After compiling the library, the directory contains libneon.lib, +against which you can link your program. + + +Build neon with WebDAV support +______________________________ + +To compile Neon with WebDAV support, Neon must compile and link +against a third-party XML parser, either expat, expat-lite, libxml or +libxml2. This Windows neon.mak file is designed to compile and link +against the pre-built Expat Windows libraries version 1.95.X or newer. +This library is available for download from + + http://sourceforge.net/projects/expat/ + +Download the latest expat_win32bin package named + + expat_win32bin_X_YY_Z.exe + +and install it on your system. It wants to install itself into +Q:\some\dir\Expat-X.Y.ZZ. Choose your installation location for expat +and then compile Neon with + + nmake /f neon.mak EXPAT_SRC=\path\to\Expat-X.YY.Z + +NOTE: When you run your program make sure the LIBEXPAT.DLL from expat +is accessible, i.e. is in your PATH. + +This should work with Microsoft VC++ 5 and 6. + + +Build neon with dynamically linked SSL support +______________________________________________ + +To build neon on Windows with SSL support you need OpenSSL already +installed on your system (I used OpenSSL 0.9.6g). It can be +downloaded from + + http://www.openssl.org/source/openssl-0.9.6g.tar.gz + +After compiling OpenSSL, now simply point make to the OpenSSL sources: + + nmake /f neon.mak OPENSSL_SRC=\path\to\openssl + +NOTE: The include files for OpenSSL reside in inc32/ directory +("../openssl-0.9.6g/inc32"). + +NOTE: Make sure that your program is linked against libeay32.lib and +ssleay32.lib (normally in "../openssl-0.9.6g/out32dll") and that +libeay32.dll and ssleay32.dll is accessible, i.e. is in your PATH. + + +Build neon with statically linked OpenSSL support +_________________________________________________ + +If you want to statically link against OpenSSL, then add the +OPENSSL_STATIC parameter. + + nmake /f neon.mak OPENSSL_SRC=\path\to\openssl OPENSSL_STATIC=yes + + +Build neon with statically linked Zlib support +______________________________________________ + +If you want to build Neon with the capability to decompress compressed +content, then you need to compile against the Zlib library. + +Currently, the Neon's neon.mak file expects to compile and link a self +compiled version of Zlib. You need Zlib 1.1.4 or greater. Zlib 1.1.3 +and older has a serious security issue. + +Here's how to compile Zlib. + + 1) Get one of the Zlib source file packages in Zip format from + http://www.gzip.org/zlib/ + 2) Unzip it. + 3) Get the package + http://www.gzip.org/zlib/contrib/zlib113-win32.zip + 4) Unzip it and copy the Makefile from this package to the Zlib + 1.1.4 or greater package. + 5) Run nmake in the Zlib 1.1.4 or greater directory. + +Now add the ZLIB_SRC parameter to Neon's neon.mak pointing to your +newly compiled zlib. + + nmake /f neon.mak ZLIB_SRC=\path\to\zlib + + +Build neon with dynamically linked Zlib support +_______________________________________________ + +To build Neon with dynamically linked Zlib support, use the +instructions for the statically linked Zlib support above and add the +ZLIB_DLL parameter + + nmake /f neon.mak ZLIB_SRC=\path\to\zlib ZLIB_DLL=yes + + +Build neon with debugging support +_________________________________ + +Set the DEBUG_BUILD parameter + + nmake /f neon.mak DEBUG_BUILD=yes + +It does not matter what value DEBUG_BUILD is set to, as long as it is +not set to "". + +After compiling the library, the directory contains libneonD.lib, +against which you can link your program. diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..1d07c7f --- /dev/null +++ b/Makefile.in @@ -0,0 +1,158 @@ +# Copyright (C) 2001-2002 Joe Orton <joe@manyfish.co.uk> +# Copyright (C) 1994, 1995-8, 1999, 2000 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. + +# libtool bits mostly stolen from libxml and libtool/demo + +SHELL = @SHELL@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +mandir = @mandir@ +man1dir = $(mandir)/man1 +man3dir = $(mandir)/man3 +datadir = $(prefix)/share +docdir = $(datadir)/doc/neon-@NEON_VERSION@ +includedir = @includedir@ +neonincludes = $(includedir)/neon +pkgconfigdir = $(libdir)/pkgconfig + +top_srcdir = @top_srcdir@ +top_builddir = . +srcdir = @srcdir@ +VPATH = @srcdir@ + +@SET_MAKE@ + +LDFLAGS = -L. @LDFLAGS@ +LIBS = @LIBS@ +CC = @CC@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL = @INSTALL@ +transform = @program_transform_name@ + +LIBTOOL = @LIBTOOL@ + +XMLTO = xmlto --skip-validation + +# The headers to distribute - making up the public interface of neon +DIST_HEADERS = ne_request.h ne_session.h ne_utils.h ne_uri.h ne_socket.h \ + ne_basic.h ne_207.h ne_props.h ne_xml.h ne_dates.h ne_string.h \ + ne_cookies.h ne_defs.h ne_locks.h ne_alloc.h ne_md5.h ne_i18n.h \ + ne_redirect.h ne_auth.h ne_compress.h ne_acl.h ne_ssl.h + +all: subdirs + +check: subdirs + cd test && $(MAKE) check + +# Useful for doing coverage analysis; use e.g.: +# make TESTS=string-tests MODULE=ne_string cover +cover: subdirs + rm -f src/$(MODULE).da + cd test && $(MAKE) check + cd src; gcov -cb $(MODULE) + +subdirs: + cd src && $(MAKE) + @echo + @echo " Compilation complete. Run '$(MAKE) install' (as root?) to install neon." + @echo + +# Uses Tim Waugh's excellent "xmlto" utility, see +# http://cyberelk.net/tim/xmlto/. (The docs target is executed at release +# time before generating a .tar.gz) +docs: docs-man docs-html + +docs-man: + rm -rf doc/man; mkdir doc/man + $(XMLTO) -o `pwd`/doc/man man doc/manual.xml + +docs-pdf: + $(XMLTO) -o `pwd`/doc pdf doc/manual.xml + +docs-ps: + $(XMLTO) -o `pwd`/doc ps doc/manual.xml + +docs-html: + test -d doc/html && rm -rf doc/html || true + mkdir doc/html + $(XMLTO) -o `pwd`/doc/html -x doc/html.xsl html doc/manual.xml + +# Validate the manual source +docs-valid: + xmllint --noout --valid doc/manual.xml + +clean: + cd src && $(MAKE) clean + cd test && $(MAKE) clean + +distclean: clean + rm -rf Makefile config.h config.status src/Makefile libtool config.log config.cache neon-config autom4te.cache test/Makefile + +again: clean + +Makefile: Makefile.in + @./config.status Makefile + +neon-config: neon-config.in + @./config.status neon-config + +install-docs: install-man install-html + +install-html: + $(INSTALL) -d $(DESTDIR)$(docdir)/html + for d in $(srcdir)/doc/html/*.html; do \ + $(INSTALL_DATA) $$d $(DESTDIR)$(docdir)/html; \ + done + +install-man: + $(INSTALL) -d $(DESTDIR)$(man3dir) + $(INSTALL) -d $(DESTDIR)$(man1dir) + for m in $(srcdir)/doc/man/*.3; do \ + $(INSTALL_DATA) $$m $(DESTDIR)$(man3dir); done + for m in $(srcdir)/doc/man/*.1; do \ + $(INSTALL_DATA) $$m $(DESTDIR)$(man1dir); done + +install: install-@ALLOW_INSTALL@ + +install-memleak: + @echo "ERROR: The neon internal memory leak checking code is for testing" + @echo "ERROR: purposes only; this copy of neon must not be installed." + @false + +install-yes: install-lib install-headers install-config install-docs + +# libtool does all the necessary magic here +install-lib: subdirs + $(INSTALL) -d $(DESTDIR)$(libdir) + $(LIBTOOL) --mode=install $(INSTALL) src/libneon.la \ + $(DESTDIR)$(libdir)/libneon.la + +install-headers: + $(INSTALL) -d $(DESTDIR)$(neonincludes) + @for h in $(DIST_HEADERS); do \ + echo Installing $$h into $(DESTDIR)$(neonincludes); \ + $(INSTALL_DATA) $(srcdir)/src/$$h $(DESTDIR)$(neonincludes)/$$h \ + || exit 1; \ + done + +install-config: neon-config neon.pc + $(INSTALL) -d $(DESTDIR)$(bindir) + @echo Installing neon-config into $(DESTDIR)$(bindir) + @$(INSTALL_SCRIPT) neon-config \ + $(DESTDIR)$(bindir)/`echo neon-config|sed '$(transform)'` + $(INSTALL) -d $(DESTDIR)$(pkgconfigdir) + $(INSTALL_DATA) neon.pc $(DESTDIR)$(pkgconfigdir)/neon.pc + @@ -0,0 +1,873 @@ +Changes in release 0.24.0: +* Major changes to XML interface: + - have the start-element callback either accept, decline, abort, + or return a state integer. + - remove 'struct ne_xml_elm'; callbacks are passed {nspace, name} + strings along with a state integer. + - dropped "collect", "strip-leading-whitespace" modes + - push responsibility for accumulating cdata onto caller; drop 'cdata' + argument from end-element callback. + - don't abort if no handler accepts a particular element, just ignore + that branch of the tree. + - dropped support for libxml 1.x and expat < 1.95.0. + - guarantee that start_element callback is not passed attrs=NULL + - add ne_xml_doc_encoding() to retrieve encoding of parsed XML document. +* Major changes to SSL interface: + - rewrite of interfaces for handling server and client certificates; + ne_ssl.h: many new functions available. + - only PKCS#12-encoded client certs are supported. + - changes to most names of SSL-related functions operating on an + ne_session, e.g. ne_ssl_load_cert->ne_ssl_trust_cert. + - client cert provider callback is passed the set of acceptable CA + names sent by the server + - the entire chain of certs presented by server is now accessible +* Remove unused ne_register_progress() from socket layer. +* Changes to resolver interface: ne_addr_first and _next return const; + ne_addr_print renamed to ne_iaddr_print; ne_iaddr_make and ne_iaddr_free + have been added. +* ne_request_create() now duplicates the method string passed in. +* ne_redirect_location() will now return NULL in some cases. +* Split socket creation to ne_sock_create() from ne_sock_connect: + - should report connect() error messages properly on Win32. +* Fix several memory leaks in error handling paths. +* Add a pkg-config file, neon.pc.in. + +Changes in release 0.23.9: +* Fix inability to connect on AIX 4.3. +* neon-config exports includes needed for OpenSSL given by pkg-config. +* ne_redirect_location will return NULL if redirect hooks have not + been registered for the session (Ralf Mattes <rm@fabula.de>). + +Changes in release 0.23.8: +* SECURITY: Prevent control characters from being included in the + reason_phrase field filled in by ne_parse_statusline(), and in + the session error string. +* Disable getaddrinfo() support on HP-UX; fix resolver for HP-UX 11.11. +* Fix digest auth response verification for >9 responses in session + (bug manifests as "Server was not authenticated correctly" error). +* On Linux, skip slow lookup for IPv6 addresses when IPv6 support is + not loaded in kernel (thanks to Daniel Stenberg for this technique). +* Update to autoconf 2.57 and libtool 1.4.3. + +Changes in release 0.23.7: +* Fix for handling EINTR during write() call (Sergey N Ushakov). +* When available, use pkg-config to determine compiler flags needed to + use OpenSSL headers and libraries. + +Changes in release 0.23.6: +* Fixes for error handling in socket layer on Win32 from Johan Lindh + and Sergey N Ushakov <ushakov@int.com.ru>: + - meaningful error messages rather than "No error" + - handle persistent connection timeouts properly +* Fix to use RFC2617-style digest auth when possible (had reverted to + only using RFC2068-style in 0.16.1). +* Fix NULL pointer dereference on certain ill-formed PROPFIND responses. +* Allow ne_sock_init to re-initialize after ne_sock_finish has been called + (Sergey N Ushakov). + +Changes in release 0.23.5: +* Fix rejection of SSL server certificates which had commonName as + the least specific attribute in the subject name. +* Fix to dereference entities (e.g. "&") in attribute values with libxml. +* Fix ne_socket.c build on HP-UX 10.20 (thanks to Branko Èibej) +* Remove misguided insistence on "secure" versions of zlib/OpenSSL; + no checks for zlib version are now performed, only OpenSSL 0.9.6 is + required. --with-force-ssl, --with-force-zlib option removed. +* Add --with-egd[=PATH] option, conditionally enable EGD support; either + using EGD socket at PATH, or fall back on system defaults. $EGDSOCKET + and $HOME/.entropy are no longer used. +* Add support for `--la-file' argument to neon-config, which prints the + full path of the installed libneon.la file. + +Changes in release 0.23.4: +* Ignore an unclean SSL shutdown on persistent connection timeout + (fixing spurious "Secure connection truncated" errors). +* Fix a segfault on second and subsequent requests using a given + session, when the first fails with NE_LOOKUP. +* Fix configure for gcc installations which produce warnings by default + (such as gcc on hppa2.0n-hp-hpux11.00 using native as) + +Changes in release 0.23.3: +* Further build fixes for Win32 (Blair Zajac). +* Another fix for use of SSL against Tomcat 3.2. + +Changes in release 0.23.2: +* Build fix for Win32 (Blair Zajac). + +Changes in release 0.23.1: +* Identify as correct version, not 0.22. + +Changes in release 0.23.0: +* Improved address resolver (ne_addr_*) replacing ne_name_lookup(): + - use getaddrinfo() if found; include support for IPv6 (based on work + by Noriaki Takamiya <takamiya@po.ntts.co.jp>) +* For a hostname with multiple addresses, each address is tried in turn + until a connection is made. +* Support for seeding OpenSSL's PRNG via $EGDSOCKET or $HOME/.entropy, + to enable SSL on platforms which lack a /dev/random device. +* RFC2818 compliance for certificate identity checks in SSL: + - use `dNSname' values in subjectAltName extension if present + - hostname comparison fixed to not be case-sensitive +* Fix interop with buggy SSL implementation in Tomcat 3.2. +* Added NE_DBG_SSL debug channel. +* ne_strerror changed to return the passed-in buffer. +* Added ne_strnzcpy macro to ne_string.h. +* Win32 build fixes, improvements, and documentation updates, from + Blair Zajac <blair@orcaware.com>. +* Fix ne_sock_init so SIGPIPE signals are ignored even if SSL library + initialization fails (e.g. platforms without /dev/random). +* Added reference documentation: + - ne_sock_init, ne_addr_*. + +Changes in release 0.22.0: +* Remove the const qualifier from the reason_phrase field in ne_status. + - ne_parse_statusline() now strdup's the reason_phrase +* Remove the status_line argument from ne_207_end_propstat and _end_response +* Change ne_session_create, ne_session_proxy, ne_sock_connect, and the + 'port' field of the ne_uri structure to use an unsigned int for port numbers +* ne_uri_defaultport returns unsigned and '0' on an unknown port (not -1). +* Changes to hooks interface: + - pass an ne_request pointer to per-request hooks + - replace "accessor" hooks with ne_{get,set}_{request,session}_private +* Authentication changes: + - the hooks changes fix a segfault if auth is enabled for an SSL session + through a proxy server + - fix ne_forget_auth segfault if either proxy or server auth are not used +* Improvements to persistent connection retry logic and error handling + in request code; fixing some cases where some errors where incorrectly + treated as a persistent connection timeout + - a TCP RST at the appropriate time is now treated as a persistent + connection timeout. + - handle persistent connection timeouts on SSL connections +* Changes to SSL support: + - improved error handling + - OpenSSL 0.9.6f or later is required for security fixes and functional + correctness; 0.9.6 or later required for functional correctness + - use --with-force-ssl to override OpenSSL version check + - fix for proxy CONNECT tunnelling with some proxies (e.g. Traffic-Server) + - fix potential segfault if client cert. provider callback is used + - fix to use supplied password callback for PEM-encoded client certificates + (Daniel Berlin <dberlin@dberlin.org>) +* strerror_r is used if available for thread-safe error handling. +* Remove ne_read_file(). +* ne_version_match replaces ne_version_minimum (semantics changed slightly). +* XML request bodies use a content-type of "application/xml" now; + applications can use NE_XML_MEDIA_TYPE from ne_xml.h +* Fix decompress code on big-endian or 64-bit platforms. +* Fix to build on Darwin 6 (aka Mac OS X 10.2) (Wilfredo Sánchez, + <wsanchez@mit.edu>) +* Win32 changes: + - remove conflict between OpenSSL's X509_NAME and recent versions of + the Platform SDK (Branko Èibej) + - fix inverted debug/non-debug build logic (Branko Èibej) + - add NODAV and OPENSSL_STATIC flags to neon.mak (Gerald Richter) + +Changes in release 0.21.3: +* Fix segfault if using proxy server with SSL session and server + certificate verification fails. +* Fix leak of proxy hostname once per session (if a proxy is used). +* Add --with-libs configure argument; e.g. --with-libs=/usr/local picks + up any support libraries in /usr/local/{lib,include} + +Changes in release 0.21.2: +* Fix 'make install' for VPATH builds. +* Use $(mandir) for installing man pages (Rodney Dawes). +* Follow some simple (yet illegal) relativeURI redirects. +* Always build ne_compress.obj in Win32 build (Branko Èibej). +* Fix decompression logic bug (Justin Erenkrantz <jerenkrantz@apache.org>) + (could give a decompress failure for particular responses) +* Fix ne_proppatch() to submit lock tokens for available locks. +* More optimisation of ne_sock_readline. + +Changes in release 0.21.1: +* Don't include default SSL port in Host request header, which can + help interoperability with misbehaving servers (thanks to Rodney Dawes + <dobey@ximian.com>). +* Don't give a "truncated response" error from ne_decompress_destroy if + the acceptance function returns non-zero. +* Fix for Win32 build (Sander Striker <striker@apache.org>). +* Fix for cookie name/value being free()d (thanks to Dan Mullen). +* Optimisation of ne_sock_readline. + +Changes in release 0.21.0: +* Socket layer implements read buffering; efficiency and performance + improvement. Based on work by Jeff Johnson <jbj@redhat.com> +* Cleanup of socket interface: + - renamed everything, s/sock_/ne_sock_/, s/SOCK_/NE_SOCK_/ + - removed unused and inappropriate interfaces. + - renaming done by Olof Oberg <mill@pedgr571.sn.umu.se> + - see src/ChangeLog for the gory details. +* Fix typoed 'ne_destroy_fn' typedef (Olof Oberg). +* Support OpenSSL/ENGINE branch. +* Bogus ne_utf8_encode/decode functions removed. +* ne_base64() moved to ne_string.[ch]. +* ne_token drops 'quotes' parameter; ne_qtoken added. +* ne_buffer_create_sized renamed to ne_buffer_ncreate. +* ne_xml_get_attr takes extra arguments and can resolve namespaces. +* ne_accept_response function type takes const ne_status pointer. +* Drop support for automatically following redirects: + - ne_redirect_register just takes a session pointer + - ne_redirect_location returns an ne_uri pointer +* configure changes: --with-ssl and --with-socks no longer take a directory + argument. To use SOCKS or SSL libraries/headers in non-system locations, + use ./configure CPPFLAGS=-I/... LDFLAGS=-L/... +* Reference documentation included for most of ne_alloc.h and ne_string.h, + and parts of ne_session.h and ne_request.h. + - see installed man pages, HTML documentation. + +Changes in release 0.20.0: +* Major changes to DAV lock handling interface (ne_locks.h): + - struct ne_lock uses a full URI structure to identify locked resource + - ne_lock() requires that owner/token fields are malloc-allocated (or NULL) + on entry + - introduce a "lock store" type, ne_lock_store, to replace the lock session; + accessor functions all renamed to ne_lockstore_*. + - ne_lock_iterate replaced with a first/next "cursor"-style interface + - If: headers use an absoluteURI (RFC2518 compliance fix). + - fix for handling shared locks on DAV servers which return many active locks + in the LOCK response (thanks to Keith Wannamaker) +* Moved URI/path manipulation functions under ne_* namespace (ne_uri.h): + - path handling functions renamed to ne_path_* + - URI structure handling to ne_uri_*; struct uri becomes ne_uri. + - ne_uri_parse doesn't take a 'defaults' parameter any more + - if URI port is unspecified, ne_uri_parse sets port to 0 not -1. + - added ne_uri_unparse and ne_uri_defaultport functions. +* New 'ne_fill_server_uri' function to initialize a URI structure with + the server details for a given session (useful with locks interface). +* ne_decompress_{reader,destroy} are defined as passthrough-functions + if zlib support is not enabled. +* API change: ne_ssl_provide_fn returns void not int. +* Added NE_SSL_FAILMASK for verify failure sanity check. +* Removed return codes NE_SERVERAUTH and and NE_AUTHPROXY; correct + documentation, NE_PROXYAUTH is given for proxy auth failure. +* Require zlib >= 1.1.4 to avoid possible vulnerability in earlier versions. + See http://www.gzip.org/zlib/advisory-2002-03-11.txt for more details. + (version check can be skipped by passing --with-force-zlib to configure) +* New 'ne_ssl_readable_dname' function to create a human-readable string + from an X509 distinguished name. +* Fix support for newer versions of libxml2 (thanks to Jon Trowbridge + <trow@gnu.org>). +* Fix corruption of reason_phrase in status object returned by + ne_propset_status. +* More lenient handling of whitespace in response headers. +* ne_content_type_handler will give a charset of "ISO-8859-1" if no charset + parameter is specified for a text/* media type (as per RFC2616). +* Miscellaneous cleanups and fixes (Jeff Johnson <jbj@redhat.com>). + +Changes in release 0.19.4: +* Support bundled build of expat 1.95.x (Branko Èibej). + +Changes in release 0.19.3: +* For platforms lacking snprintf or vsnprintf in libc, require trio. +* Add NE_FMT_OFF_T to fix Win32 build (Dan Berlin, Branko Èibej). +* Fix SSL support in Win32 build (Branko Èibej). + +Changes in release 0.19.2: +* Fix non-SSL build broken in 0.19.1. +* Working SOCKSv5 support (thanks to Torsten Kalix <torsten.kalix@bredex.de>) + +Changes in release 0.19.1: +* Add missing stubs for ne_ssl_* functions for non-SSL build. +* Fix some error messages in new SSL code. + +Changes in release 0.19.0: +* Major API change: ne_session_create now takes (scheme, hostname, port) + arguments: a session is clarified to be "a group of requests to a + certain server". + - removal of ne_session_server, ne_set_secure, and ne_set_proxy_decider + - ne_session_proxy returns void. + - DNS lookups are delayed until request dispatch time. +* Significant improvements to TLS/SSL support: + - SSL is enabled if scheme passed to ne_session_create is "https" + - new interfaces to load CA certs and to load SSL library's bundled CA certs + - add server cert verification callback. An SSL connection to a server + with an unknown CA will now fail unless a verification callback is used. + - enable SSL session caching (performance improvement) + - support for wildcard server certs where commonName is "*.example.com". + - thanks to Tommi Komulainen for the contribution of code from mutt's + IMAP/SSL implementation under the LGPL, from which bits of this were derived. +* Improved SSL client certificate support: + - far simpler interface, all done at ne_session.h level. + - supports PKCS#12 and PEM-encoded certificates. + - optional callback for only providing client when demanded by server. +* Support for TLS upgrade is removed, since it isn't useful. +* If NEON_SSL is defined, API extensions are available to: + - allow access to the SSL_CTX * to adjust session SSL options + - retrieve the server certificate (X509 *) +* Decompress fixes: + - fix potential segfault in ne_decompress_destroy + - check the CRC of the deflated output (and fail if it doesn't match) + - fail appropriately on truncated responses, and trailing bytes in response. +* Added ne_set_read_timeout to use configurable timeout on socket reads. +* Malformed response headers will be ignored rather than failing the request. +* ne_set_error takes printf-style vararg. +* Fixes for ne_get_range and improve error handling. +* Functions which append to an ne_buffer do not return a success value, + but they do use ne_realloc/ne_malloc under the hood now, so an OOM callback + will be used (with the usual caveats). +* XML interface does not strip leading whitespace from cdata by default, + the NE_XML_STRIPWS flag is available to restore this feature if required. +* Upgraded to libtool 1.4.2: + - should fix --enable-shared on Mac OS X 10.1 +* Test suite now contains over one hundred tests. + +Changes in release 0.18.5: +* Removed old neon.dsp, neon.dsw. +* Update Win32 build to add OpenSSL and zlib support (Branko Èibej). +* Fix ne_compress.c to compile on Win32 (Branko Èibej). + +Changes in release 0.18.4: +* Fixes for Content-Type parsing using ne_content_type_handler (Greg Stein) + - also now parses the charset parameter from header value. +* Removed ne_concat() function, which didn't work and wasn't used. + +Changes in release 0.18.3: +* Fix parsing lock timeout from server (Arun Garg). +* Send Timeout headers in LOCK and refresh LOCK requests (Arun Garg). +* Updated neon.mak and config.hw.in for Win32 build (patch from + Branko Èibej <brane@xbc.nu>). +* Define XML_BYTE_ORDER for bundled expat build in support macro + NEON_XML_PARSER(). + +Changes in release 0.18.2: +* Fix --with-neon=PATH in support macros. +* Support DESTDIR in Makefile install targets (patch by + Pawel Golaszewski <blues@blysk.ds.pg.gda.pl>). +* Portability fixes: + - fix configure check for time_t on some platforms (e.g Solaris 2.6). + - remove expect100_works bitfield in ne_session structure (thanks to + Yan Periard <yperiard@ems.net>). + +Changes in release 0.18.1: +* Minor fix for authentication: "attempt" counter was not reset correctly + after authentication failed, so subsequent requests would not authenticate + correctly either. +* API change: ne_session_destroy returns void (there was no error case). +* Portability fixes (non-GCC compilers, 64-bit platforms, UnixWare 7) +* Optimisations in string manipulation routines. +* config.hw is included in the release tarball again. +* Improvements in the autoconf support macros: + - check for neon-config in PATH if --with-neon is not given + - stop if --with-neon is used, and the check for external neon fails + - added NEON_WITHOUT_ACL to prevent build of ne_acl.o + +Changes in release 0.18.0: +* API change: authentication callback is passed fixed-size username/password + buffers, and an 'attempt' counter. Authentication is retried *forever* + until either it succeeds, or the callback returns non-zero. +* API clarifications: + - ne_propname may have a NULL nspace field, indicating the property has no + namespace. This holds for properties returned by the propfind interfaces. + - added NE_ELM_PROPS_UNUSED as the lowest element number which should + be used with handlers added to the XML parser returned by + ne_propfind_get_parser. +* Fixes and cleanups of lock discovery interface. +* Fix for short write handling in ne_get() (thanks to rado <dzusto@yahoo.com>). +* Fix for XML namespace prefix handling where a prefix could be mapped to an + incorrect URI (e.g. in PROPFINDs against mod_dav with >10 namespaces used) +* Add '--support <feature>' option to neon-config; the script exits with + success if given feature is supported. Known features are ssl, dav, zlib. +* Support for SSL, DAV, zlib is exported by neon.m4 as shell variable + NEON_SUPPORTS_{SSL,DAV,ZLIB}={yes,no} for bundled and external builds. +* `neon-config --cflags` won't include -I/usr/include for SSL build. +* Fix to call progress callbacks while sending request bodies again. +* Test changes: + - portability fixes, auth interface and progress tests. + +Changes in release 0.17.2: +* Accept Status-Lines with no reason phrase (Jeremy Elson). +* Fix handling of persistent connection timeout, and better error + handling if sending a request fails. +* Fix crashes in locking code. +* Return parse error on XML namespace prefix declaration with + an empty value. Thanks to Julian Reschke. +* Allow passing property names with NULL namespace to ne_proppatch. +* Fix for cross-compilation (Mo DeJong). +* Moved ne_propname definition from ne_207.h to ne_props.h. +* Test changes: + - updated for Status-Line parsing changes (Jeremy Elson) + - better persistent connection tests + - fixed for --disable-webdav build + +Changes in release 0.17.1: +* Add support for ACL method (Arun Garg <arung@pspl.co.in>), + see ne_acl.h. +* Fixes and clean up of libraries exported via `neon-config --libs' +* Fix timezone handling when parsing dates (on some platforms). +* Upgrade to autoconf 2.52 and libtool 1.4 (thanks to Mo DeJong). +* Cleanup/simplification of request dispatching: + - better handling of error cases, including fix for a possible + infinite loop when the server closes the connection prematurely. +* Add '--without-zlib' configure option. +* Test changes: + - prettify output; imitate Perl test suite output. + - add tests for interim 1xx responses, persistent connections, more + unbounded operations. + +Changes in release 0.17.0: +* Add support for decoding gzip Content-Encoding: see ne_compress.h. + - built if zlib is found; `neon-config --cflags' will define NEON_ZLIB if so. +* Rewrite hooks interface to register individual callbacks. + - inspired by the Apache 2.0/APR hooks interface +* Register cookies hooks using ne_cookie_register(). +* Clean up configure scripts to enable use of autoconf 2.5x (Mo DeJong). +* Use new endianess configure macro to allow cross-compiling (Mo DeJong). +* Fix invalid C code in sock_init() in Win32 build (Mo DeJong). +* Fix use of signal() on Win32 (Mo DeJong). +* Workaround libxml 1.x string handling not being UTF-8. +* Test changes: + - add tests for decompression interface. + +Changes in release 0.16.1: +* Also handle write errors in ne_get_range. +* Dump request body blocks in debugging mode. +* Fix ne_shave() causing memory corruption when the result should + have been the empty string. +* Refactor auth header parsing code; more efficient now. + - fixes digest auth RFC2617-style broken in 0.16.0 + +Changes in release 0.16.0: +* API change: ne_copy takes a depth parameter (thanks to Arun Garg, Medha Atre) +* API change: validate callback to ne_xml also takes a userdata arg. +* Added 'ne_lock_refresh' for performing lock refresh (Arun Garg). +* Add SSL support to Win32 build (Peter Boos <PediB@colorfullife.com>) + (see INSTALL.win32 for details). Compile with USE_DAV_LOCKS also. +* Remove Server header parser for 100-continue support in ne_options. + (and remove broken_expect100 from ne_server_capabilities). +* Set SIGPIPE disposition to "ignored" in sock_init(). +* On platforms with setvbuf(), turn off buffering for the debug log + stream. +* Ignore repeated calls to sock_init(). +* Fixes to error handling in ne_get_range. +* Minor improvements to memory handling in auth code. +* Fix for start_propstat callback being called with NULL response + argument when given invalid XML, causing a segfault in propfind code. +* Test changes: + - add regression test for the propfind segfault. + - handle segfaults better (reap the child, flush the debug log). + +Changes in release 0.15.3: +* Fix --with-expat=DIR build. + +Changes in release 0.15.2: +* Fix Win32 for XML parser changes (Gerald Richter). +* Substitute versions into config.hw at distribution time. +* Add date parser for ISO8601-formatted dates as defined by RFC2518, e.g. + the creationdate property (Taisuke Yamada <tai@iij.ad.jp>). +* Fix Y2K bug in RFC1036 date parsing algorithm. +* Test changes: + - add tests for date parsing functions. + +Changes in release 0.15.1: +* Win32 update from Gerald Richter <richter@ecos.de> + - new files neon.mak, INSTALL.win32 +* Fix for ne_socket.h includes (Mo DeJong). +* More improvements for XML parser selection logic: + - if parser is required, be sure to fail configure if none is found. + - added --with-included-expat for bundled expat logic. +* Rename --enable-debugging to --enable-debug (Mo DeJong). + - added NEON_DEBUG macro to exported autoconf macros. +* Call progress callbacks for request bodies. +* Test changes: + - check that reading response headers is a bounded operation. + - use a pipe between child and parent to avoid race condition and + tedious sleep(). + +Changes in release 0.15.0: +* Major API renaming to use ne_/NE_ namespace: + - http_ to ne_, HTTP_ to NE_, dav_ to ne_, DAV_ to NE_, neon_ to ne_ + - hip_xml_ to ne_xml_, HIP_ELM_ to NE_ELM_, HIP_XML_ -> NE_XML_ + - sbuffer_ to ne_buffer_ + - DEBUG() to NE_DEBUG(), DEBUG_ to NE_DBG_ +* Type renames: + - http_req to ne_request + - sbuffer to 'ne_buffer *' +* Note, 'ne_buffer' is not an implicit pointer type, you must + specify the '*' now, e.g. 'ne_buffer *buf = ne_buffer_create();'. +* ne_buffer is no longer opaque. + - ne_buffer_data() removed: use buf->data instead. + - ne_buffer_size() is a macro. +* Header renames and additions: + - http_request.h -> ne_request.h + - Session code split into ne_session.h + - hip_xml.h -> ne_xml.h, nsocket.h -> ne_socket.h, http_utils.h -> ne_utils.h + - neon_md5.h -> ne_md5.h, dav_207.h -> ne_207.h + - http_basic.h and dav_basic.h merged into ne_basic.h +* New functions: + - ne_token and ne_shave, to obsolete split_string, shave_string. +* Removed: ne_get_request_headers(). +* autoconf changes: + - disable building shared neon library by default. + - option --enable-libxml is replaced by --with-libxml1 and + --with-libxml2 to force use of a particular parser. +* Fix auth code to only take MD5 digests of response body blocks when + necessary (thanks to Kai Sommerfeld). +* Fix alignment bug in MD5 code which could cause SIGBUS on Sparc + architectures (Kai Sommerfeld). +* Rewrite of request body handling: + - ne_set_request_body_fd replaces _stream, using an int fd rather than + a FILE *. + - added ne_set_request_body_provider to give a callback which is called + to provide request body blocks. + - removal of 'use_body' hook in favour of 'ne_pull_request_body' function + to allow hooks to manually read the request body. + - ne_{put,get,post,put_if_unmodified} all take an integer fd rather than a + FILE * stream. +* Test changes: + - added framework for testing "over the wire" (fork a server process) + - added tests for response message length handling, chunked responses, + header folding, sending request bodies. + - start at listing RFC2616 requirements and whether they are met + or not in test/STATUS. + - test for MD5 alignment bug on Sparc (thanks to Kai Sommerfeld). + +Changes in release 0.14.0: +* Add C++ inclusion safety to http_auth.h (Kai Sommerfeld). +* Define ssize_t on Win32. (Kai Sommerfeld). +* Add C++ inclusion safety to dav_locks.h and ne_alloc.h (thanks to + Gregor Bornemann <Gregor.Bornemann@germany.sun.com>). +* Significant API change to properties code, to allow use of allprop + and complex properties: + - dav_propfind_set_complex and _set_flat are removed. + - add parameter to dav_propfind_named to take the list of property names + to be fetched. + - new function dav_propfind_set_private to set private callback. + - all properties not handled by caller are stored as flat properties. +* Untested: add basic SOCKSv5 support: configure --with-socks. + - please report success/failure to neon@webdav.org +* Win32/MSVC build files from Magnus Sirwiö <sirwio@hotmail.com>. +* Fix for expat detection from Shane Mayer <shanemayer42@yahoo.com>. +* Namespace-protect md5 code and more. + - md5_* -> ne_md5_* + - ascii_to_md5 -> ne_ascii_to_md5 (and moved to neon_md5.h) +* Parse authinfo segment in URIs (Johan Lindh <johan@link-Data.com>). + - added 'authinfo' field to struct uri. +* New API: hip_xml_get_attr to retrieve attributes. +* Store language for properties, access with dav_propset_lang. + - only if property is defined on the property element itself. +* Started a simple test suite (test/*). + - includes some simple HTTP server tests. +* Remove "Content-Length: 0" header for request with no body, fixing + interop with Squid 2.3-STABLE1 (thanks to Kai Sommerfeld). +* http_parse_statusline skips leading whitespace. (Johan Lindh). +* Partial fix for timezone/date parsing problems. + +Changes in release 0.13.0: +* Fix ne_strndup allocating one byte less than it should (Kai Sommerfeld) + - if you use uri_parse, this bug may have caused subtle memory corruption + in your application. +* Revert API changes in 0.12: property values are not UTF-8 encoded/decoded + internally. (thanks to Greg Stein) +* Add another optional argument to NEON_BUNDLED macros, actions to + be run if bundled build is *not* selected. +* API change: added argument to http_add_hooks to register cleanup function + for the cookie. +* Removed dav_lock_unregister in favour of automatic cleanup when session + is destroyed. +* Fixed leaks in redirect code (Kai Sommerfeld). +* Fixed crashes in hip_xml_destroy (Kai Sommerfeld). +* Redirects to a different hostname/port/scheme are never followed: the request + will fail with HTTP_REDIRECT instead. Redirect notification callback is + only called for *followed* redirects. + New API: http_redirect_location() for retrieving location of last redirect. +* Authentication is now implemented as a hook, independently of http_request.c: + - API change: removed 'hostname' argument from auth callbacks. + - API change: you must now include http_auth.h from your application. + - Also fixes case of using server and proxy authentication simultaneously +* Added 'http_forget_auth' to clear authentication session. +* New API: http_session_hook_private for retrieving private per-session cookie + for hooks. +* API change: http_set_request_body_stream has a return error value. +* API change: http_set_request_body_buffer now takes the buffer length too. +* New API: caller-pulls interface for reading response body: + http_begin_request, http_end_request, http_read_response_block. + An alternative to using the (much simpler) http_request_dispatch. +* Make --disable-webdav build work. +* New API: dav_propnames for retrieving property names. +* New API: dav_propfind_get_request to access request object of handler. +* API change: progress and connection status callbacks implemented at + http_request.h level. Socket-level status callbacks removed, progress + callbacks made per-socket. +* Supports new expat (Sam TH <sam@uchicago.edu>) +* Supports libxml2 (in preference to libxml1). +* API change: added namespace protection to base64 and dates functions: + all have ne_ prefix now. +* Fixed ranged GETs where a specific range is requested (Johan Lindh + <johan@linkdata.se>). +* Limit number of response header fields to 100. +* Allow requests for the '*' URI even if a proxy server is in use. +* libxml: Get useful error messages for parse errors. + +Changes in release 0.12.0: +* Portability fixes to http_request.c and http_auth.c. + - fixes digest auth on big-endian architectures. +* Fix warnings from stray tokens after #endif's in uri.h and string_utils.h. +* Add C++ inclusion safety to http_redirect.h (Kai Sommerfeld + <kai.sommerfeld@germany.sun.com>). +* Make redirects to a different host work (Kai Sommerfeld). +* Fix reading response bodies when non-chunked and no Content-Length + (Kai Sommerfeld). +* API change: 'http_add_hooks takes a 'const' request object. +* Fixed memory leaks in session hooks (thanks to Kai Sommerfeld). +* Fix passing NULL props argument to dav_simple_propfind, to support + allprop requests. +**** MAJOR INTERFACE CHANGE **** + - URIs passed to http_request_create() are NOT escaped by neon. You + MUST do this yourself to remain HTTP compliant, using e.g. + uri_abspath_escape. (Kai Sommerfeld) +* Added --disable-webdav flag to configure, to disable DAV support in + the library. This allows building neon without an XML parser. +* Corresponding NEON_WITHOUT_WEBDAV macro for use in bundled builds. +* Fix Makefile dependancies. +* A bundled neon directory builds or doesn't build automatically + (i.e. you recurse into it unconditionally). +* API clarification: + - dav_propset_status may return NULL if the server does not return + a response for the given property (issue is open for debate). +* API change up for debate: + - Property values to dav_proppatch are UTF-8 encoded internally. + - Property values in dav_propfind_* are UTF-8 decoded internally. +* API additions: ne_realloc, ne_utf8_encode. + +Changes in release 0.11.0: +* Added SSL client certificate support with 'sock_set_client_cert'. + - Supports certs in PEM-encoded files. + - Specify a callback for prompting the user for the password with + sock_set_key_prompt. +* Added 'ne_oom_callback', to register a callback which is used if + malloc() returns NULL. (Mike Rosellini <m@icopyright.com>) +* Register appropriate callback with libxml to handle <![CDATA blocks + as normal character data (fixes PROPFINDs against sharemation.com). +* Added 'NEON_REQUIRE' macro to declare that you require a neon library + of a given minimum version, e.g. NEON_REQUIRE(0,10) means "I need + neon 0.11 or later". The _BUNDLED macros will fall back on the bundled + copy if an external library is found which is not of a new enough version. +* Added neon_version_minimum() function call for run-time version detection. +* neon_config.h has been removed. +* Use closesocket() to close sockets on Win32 (Markus Fleck <fleck@isoc.de>). + +Changes in release 0.10.1: +* Default expect-100 to OFF. + +Changes in release 0.10.0: +* hip_xml API changes: + - The search for a handler for a new child element begins at the + handler of the parent element, and carries on up the stack. + (previously, it always started from the base of the stack) + - Documentation written: doc/parsing-xml.txt +* Remove memory leaks and tidy debugging output in new properties code. +* API changes to DAV locking interface: + - New function: dav_lock_copy to copy a lock object. + - Re-ordered arguments to callback of dav_lock_discover, and made the + lock object passed back const. + - Fix leaks and crashes due to vague interface definitions. +* API change to dav_propfind_set_complex: use a callback to return the + 'private' structure. +* NEON_NORMAL_BUILD and NEON_LIBTOOL_BUILD macros defined for setting + up neon's Makefile in a bundled build: see macros/neon.m4. +* NEON_VPATH_BUNDLED macro added which takes separate srcdir and + builddir arguments for supporting VPATH builds (thanks to Peter Moulder + <pjm@bofh.asn.au>). +* Added optional final argument to NEON_(VPATH_)BUNDLED, which gives + a set of actions to be run if the bundled build is chosen. +* NEON_SSL checks for OpenSSL in /usr too. +* API change: when using http_session_decide_proxy, it MUST be called + before using http_session_server to prevent the DNS lookup on the origin + server being optimised out. The real scheme in use is passed to the + callback now. +* New function, dav_207_ignore_unknown, to ignore any unknown XML fragments + in the 207 response. Used by properties layer. + +Changes in release 0.9.2: +* Fix using both dav_propfind_set_complex and dav_propfind_set_flat with the + same propfind_handler. + +Changes in release 0.9.1: +* dav_propfind interface + - Guarantee that the 'private' structure will be initialized to zero on + creation. + - Make it the *callers* responsibility to free() the private structure. +* Fix a few arguments/variables which mirrored globally declared symbols. + +Changes in release 0.9.0: +* Removed old dav_propfind_* interface, replaced with a better, more + powerful, and easier to use interface: + - 'dav_simple_propfind' interface for just fetching "flat" (byte-string) + properties. + - 'dav_propfind_*' interface for fetching flat and/or "complex" (structured + XML) properties. + - Lets you retrieve the 'status' information, to see what happened if + fetching the property failed (e.g 404 Not Found). +* Fixes to doc/using-neon.txt (thanks to Greg Stein). +* Allow building when srcdir != builddir (Mo DeJong <mdejong@cygnus.com>) + +Changes in release 0.8.1: +* Fix segfault in PROPFIND code. + +Changes in release 0.8.0: +* Fix for using COPY/MOVE over SSL (thanks to David Sloat). +* Fix for using a proxy server and SSL. +* Added 'http_get_scheme' API call. +* Added 'http_redirect.h' to list of installed headers (thanks to everyone ;). +* Changes for building on Windows (Peter Boos <PediB@colorfullife.com>) +* Fixes for building on BeOS (Sam TH <sam@uchicago.edu> and David Reid + <dreid@jetnet.co.uk>). +* Add buffering to socket code for pre-BONE BeOS systems (David Reid). +* Interface changes for hip_xml: + - Renamed hip_xml_add_(mixed_)handler to hip_xml_push_(mixed_)handler + - Documentation updates. + - Added HIP_ELM_UNUSED for lowest element id which should be used. +*** MAJOR INTERFACE CHANGE *** + - Removed 'http_status *' pointer from http_request_dispatch. + - Added http_get_status(req) to retrieve the response-status information + instead. You don't have to declare an http_status object yourself now. +* Similarly, added DAV_ELM_207_UNUSED for lowest element id which should + be used by users of dav_207_* code (incl. use of dav_propfind_* + code). +* New NEON_* autoconf macro interface: + - Use NEON_BUNDLED if sources are bundled, otherwise NEON_LIBRARY. + - The NEON_XML_PARSER macro is NOT called automatically. You must + call this yourself if using NEON_BUNDLED; see doc/using-neon.txt + for details. +* Fix use of 'socket' in nsocket.h function prototypes (Greg Stein). +* Remove extra backslash at line 69 of src/Makefile.incl (Dirk Bergstrom). +* Examples directory is now a separate package. + +Changes in release 0.7.7: +* Another fix for linking against a libtool-built expat (Greg Stein). + +Changes in release 0.7.6: +* Better check for closed SSL connection after doing SSL_peek. (thanks + to Jeff Costlow <j.costlow@f5.com>). +* Attempt at correct sock_block() implementation for SSL. +* sock_peek() will return SOCK_CLOSED correctly. + +Changes in release 0.7.5: +* Fixed workaround for linking against a libtool-built expat (Greg Stein). + +Changes in release 0.7.4: +* Fix for fd leak on connect failure (David Sloat <d.sloat@f5.com>). +* Fix for Digest auth against IIS5 (David Sloat). +* Workaround for linking against a libtool-built libexpat.la (Greg Stein). + +Changes in release 0.7.3: +* Check for -lsocket and -linet in configure. +* Workaround for SSL problems. + +Changes in release 0.7.2: +* Define SHELL in Makefile (thanks to Eric Mumpower <nocturne@arepa.com>). +* Added 'all' target to Makefile (Greg Stein <gstein@lyra.org>) +* Added '--with-expat' argument to configure (Greg Stein) +* Added 'dav_propfind_destroy' function. + +Changes in release 0.7.1: +* Don't register response body/header authentication callbacks if no + credentials-supplying callback has been registered (speed optimisation). + +Changes in release 0.7.0: +* Deprecated use of 'NULL' to http_add_response_header_handler. + New interface, http_add_response_header_catcher, to register + a callback which is passed ALL response headers regardless of name. +* Speed optimisation (~10%?): storing response-header handlers in a + hash table for faster look. +* New SBUFFER_CAST() macro for getting to the 'char *' of an sbuffer + as fast as possible. + +Changes in release 0.6.1: +* Fix for retrying request if connection is closed by server. +* Make redirect hook work for >1 request per session. + +Changes in release 0.6.0: +* New interface to allow following HTTP redirects (301/302 responses). + A callback must be given to get user confirmation if the request method + is not GET, HEAD, or PROPFIND. +* New interface to determine whether the proxy server should be used + for a given request: http_session_decide_proxy. +* Fix nget build again. Support automatic redirects in 'nget'. +* Add --with-extra-includes and --with-extra-libs configure parameters + to point configure at + +Changes in release 0.5.1: +* Prevent segfault if USE_DAV_LOCKS is defined, and a locking session + is not registered (thanks to David Sloat). + +Changes in release 0.5.0: +* Rename xmalloc, xstrdup etc to ne_malloc, ne_strdup etc. +* Some speed optimisation in response-header reading. +* Use 'off_t' rather than 'size_t' in sock_progress callback, + sock_readfile_blocked, and sock_transfer. + +Changes in release 0.4.2: +* Fix for sending request bodies after getting 100-continue response. + +Changes in release 0.4.1: +* Fix nget build. + +Changes in release 0.4.0: +* Install library headers into .../include/neon not .../include/libneon +* Install all necessary library headers. +* Compile support for WebDAV locking throughout the library +* Rename md5.h to neon_md5.h (avoids conflict with md5.h in OpenSSL) +* Rename socket.h to nsocket.h (avoids possible conflict with C library) +* Update licensing notice on macros/neon*.m4: note that these files are + NOT under the LGPL, and can be used in other packages regardless of + the license the package uses. +* Update NEON_LIBRARY m4 function to allow optional specification of + names of bundled neon/expat source directories. +* Increase socket read timeout to 60 seconds. +* Added an POST method: from Sander Alberink <sander.alberink@cmg.nl>. +* Added 'http_get_request_headers' to return the sbuffer containing + all request headers. +* Allow passing NULL as name to http_add_response_header_handler: + the handler callback is passed the entire header string, of ALL + response headers. + +Changes in release 0.3.1: +* Compile fix for dav_locks.c (thanks to Paul D'Anna) + +Changes in release 0.3.0: +* Rewrite of socket handling layer. All sock_* functions changed. +* Added basic SSL support: --with-ssl (requires OpenSSL). + NOTE: Certificates are NOT presented for verification. +* 'nget' accepts URL's using the 'https' scheme. +* New example program, 'nserver', to display the Server: string, + e.g. 'nserver https://www.eu.c2.net/' +* Fixed request re-send when persistent connection times out. +* "Hooks" support: allow external hooks into the HTTP request/ + response dispatch loop. +* New printf-style interface for adding request headers. +* Make symbols used in header files C++-safe (Tom Bednarz). +* WebDAV locking support: lock discovery, LOCK (exclusive/shared) + UNLOCK. "If:" headers are sent as appropriate. Simple interface + for implementors of new methods to indicate which locks are + required for the method. +* Primitive HTTP cookies support. +* Primitive hack at a GNOME-based GUI example program "nbrowse". + Enable build with --enable-gnome-examples. It crashes, and + not much else. Requires GNOME and POSIX threads. Example usage: + 'nbrowse dav.ics.uci.edu /msdav/' + Many thanks to Lee Mallabone for Gtk help, and showing how to + use Gtk and threads. + +Changes in release 0.2.0: +* Use libtool: new configure options to select whether to build + shared and/or static libraries. Should build shared libraries + portably now. +* Complete rewrite of the hip_xml interface to use opaque pointers. + New functions: hip_xml_create, hip_xml_destroy: create parser. + hip_xml_{set,get}_error: Access to error string. + hip_xml_add_handler: Register callbacks for a set of elements. + hip_xml_valid: Returns whether the parse was valid or not. + Removed functions: hip_xml_init, hip_xml_finish. +* Removed functions made reduntant by above changes in dav_207. +* Don't include config.h in header files +* Fix PROPFIND allprop request body (Michael Sobolev) +* Added C++ safety macros around header files. +* Added neon-config script for getting correct CFLAGS and LIBS + values for using libneon in applications. + +Changes in release 0.1.1: +* Fix for short writes in GET + +Changes in release 0.1.0: +* Initial release. @@ -0,0 +1,45 @@ + +neon is an HTTP and WebDAV client library, with a C language API. +Bindings for other languages may also be available, see the web site +for more details. + +Mailing list: neon@webdav.org || Web site: http://www.webdav.org/neon/ + +WARNING: THE NEON API IS NOT YET STABLE. + +Current features: + + - High-level interface to HTTP and WebDAV methods. + - Low-level interface to HTTP request handling, to allow implementing + new methods easily. + - Persistent connection support (HTTP/1.1 and HTTP/1.0 aware) + - Basic and digest authentication (RFC2617) (including auth-int, md5-sess) + - Proxy support (including basic/digest authentication) + - SSL/TLS support using OpenSSL (including client certificate support) + - Generic WebDAV 207 XML response handling mechanism + - XML parsing using expat or libxml (1.x or 2.x) parser + - Easy generation of error messages from 207 error responses + - Basic HTTP/1.1 methods: GET, PUT, HEAD, OPTIONS, conditional PUT + - WebDAV resource manipulation: MOVE, COPY, DELETE, MKCOL. + - WebDAV metadata support: set and remove properties (PROPPATCH), query + any set of properties (PROPFIND). + - WebDAV locking support + - Autoconf macros supplied for easily embedding neon directly inside + an application source tree. + +Provides lower-level interfaces to directly implement new HTTP +methods, and higher-level interfaces so that you don't have to worry +about the lower-level stuff. + +neon is licensed under the GNU Library GPL; see src/COPYING.LIB for +full details. The manual is licensed under the terms of the GNU FDL; +see doc/fdl.sgml or the generated documentation. The autoconf macros +in the "macros" directory are under a more liberal license, see each +file for details. The test suite is licensed under the GNU GPL; see +test/COPYING for full details. + +neon is Copyright (C) 1999-2003 Joe Orton +Portions are: +Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> +Copyright (C) 1999-2000, Peter Boos <pedib@colorfullife.com> +Copyright (C) 1991, 1995, 1996, 1997 Free Software Foundation, Inc. @@ -0,0 +1,45 @@ +In alphabetical order: + +Arun Garg <arung@pspl.co.in> +Blair Zajac <blair@orcaware.com> +Branko Èibej <brane@xbc.nu> +Daniel Berlin <dberlin@dberlin.org> +David Sloat <d.sloat@f5.com> +David Reid <dreid@jetnet.co.uk> +Dirk Bergstrom <dirk@juniper.net> +Gerald Richter <richter@ecos.de> +Greg Stein <gstein@lyra.org> +Gregor Bornemann <Gregor.Bornemann@germany.sun.com> +Jeff Johnson <jbj@redhat.com> +Jeremy Elson <jelson@circlemud.org> +Johan Lindh <johan@linkdata.se> +Justin Erenkrantz <jerenkrantz@apache.org> +Kai Sommerfeld <kai.sommerfeld@germany.sun.com> +Keith Wannamaker <keith@wannamaker.org> +Lee Mallabone <lee0@callnetuk.com> +Magnus Sirwiö <sirwio@hotmail.com> +Michael Sobolev <mss@despair.spb.ru> +Mike Rosellini <m@icopyright.com> +Mo DeJong <mdejong@cygnus.com> +Noriaki Takamiya <takamiya@po.ntts.co.jp> +Olof Oberg <mill@pedgr571.sn.umu.se> +Pawel Golaszewski <blues@ds.pg.gda.pl> +Peter Boos <PediB@colorfullife.com> +Peter Moulder <pjm@bofh.asn.au> +rado <dzusto@yahoo.com> +Rodney Dawes <dobey@ximian.com> +Sam TH <sam@uchicago.edu> +Sander Alberink <sander.alberink@cmg.nl> +Sander Striker <striker@apache.org> +Shane Mayer <shanemayer42@yahoo.com> +Taisuke Yamada <tai@iij.ad.jp> +Tom Bednarz <tombednarz@hotmail.com> +Torsten Kalix <torsten.kalix@bredex.de> +Wilfredo Sánchez <wsanchez@mit.edu> + +Originators of stolen code: + +Tommi Komulainen <Tommi.Komulainen@iki.fi> +Daniel Veillard <Daniel.Veillard@w3.org> +Eric S Raymond <esr@snark.thyrsus.com> +Ulrich Drepper <drepper@gnu.org> @@ -0,0 +1,121 @@ + +To Do List for neon -*- text -*- +=================== + +Please submit feature requests to <mailto:neon@webdav.org> + +For one-point-oh +---------------- + +23. Mechanism for aborting a request mid-response; e.g., when a GET + fails due to out of disk space, abort the download. + +31. Make it threadsafe: + socket.c: getservbyname -> getservbyname_r. + +38. Replace all use of split_string/pair_string with ne_token. + +40. XML body acceptance callback should check Content-Type. Should + also pass encoding to expat if one is given (how about libxml?). + Recent mod_dav's return XML bodies in 424 responses which need + displaying properly. + +44. Finer-grained connection status feedback, i.e., "Sent Request", + "Got response status-line"... "Reading response body" + +58. 2616 is quite strict about when to retry non-idempotent requests + and when not to. neon may not be compliant here. + +61. Make everything namespace-safe: + remove split_string/pair_string. + +62. Select which auth mechanisms are allowed, e.g. JUST SAY NO to + basic might very well be useful to some apps. + +63. Unconditionally turn off Nagle algorithm. + +Longer term +----------- + + +1. Support for HTTP-extended authoring methods ala WebRFM etc; using + New-URI header etc. Also support the BROWSE and INDEX methods. The + protocol is documented at: + http://www.ics.uci.edu/pub/ietf/webdav/ns_dav.html + DON'T do this inside ne_basic.c, do it separately in + ne_author.c or something. + +2. Add proper domain support to authentication code. (requires full + URI parsing support). Need to tell the auth layer the server + details. + +4. Better cnonce generation for authentication: use /dev/{u}random or + whatever like mod_auth_digest. + +6. PUT with ranges... ne_put_range + +9. DeltaV support (http://www.webdav.org/deltav/). See also the + subversion project (http://subversion.tigris.org/) who might build + a versioning system over DAV. + +10. ACL support (http://www.webdav.org/acl/) + +11. DASL support (http://www.webdav.org/dasl/). Xythos have server + support for this (www.sharemation.com). The UI is probably the + hardest problem here. + => Jim Whitehead's UCI postgrad team is working on this and + has written a DASL implementation. + +14. Improved request-header manipulation... some kind of indexed table + (a la Apache, libghttp, so we're sure we don't add the same header + to the request twice. Better control over adding Cache-Control + headers would be good too. + +17. Should we really be i18n'izing the low-level error messages in + ne_request.c, ne_207.c ? It seems nice and clever to, so the user + REALLY know what is going wrong with the server (probably), but it + is maybe a bit frightening. + +20. Add decent and proper URI parser + handling. Or stop pretending we + are doing "URI" parsing, and just handle HTTP URL's. + +21. Storing multiple authentication "sessions" within an actual + auth_session, so I can log into e.g. /foo/ and /bar/ (which are + not in the same authentication domain) and switch between them + without having to re-enter passwords all the time. + +28. Support response caching? + +35. Allow i18n'ization if building a shared library, iff gettext + support is on the system (and hence add -lintl or whatever to + NEON_LIBS). If system has no gettext support, then it's probably + impossible to support i18n in the library (although *applications* + can support it by bundling gettext themselves). Take a look at how + other libraries handle this. + +46. Asynchronous request-dispatching? Makes integration into GUI loop + easy... any other reasons? Must leave existing request_dispatch + interface intact. + +47. Indexed table-based response-header access? Might simplify things + like response body acceptance callbacks (i.e., can get access to + Content-Type header for XML). + +48. Possibly, store the time of last interaction over the TCP socket, + call it 't'. If the next request is made after t+20, presume the + persistent connection is dead, so re-connect automatically. If we + don't do this, then we have two wasted write() calls making the + request, then failing, then re-connecting. It's really only worth + doing this if this actually saves any packets on the wire, which + it probably doesn't. strace / tcpdump might help here. + +50. opendir/readdir/closedir-esque interface for PROPFIND depth 1, + a la EZDAV. (cadaver has it already) + +53. "ne_session" concept is hazy. Abstract out a "connection" concept + too, and allow >1 connection per-session in multi-threaded use, + somehow. + +57. Add function to map of status-code values to i18n-ized reason + phrase. + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..cc95493 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,24 @@ +#!/bin/sh +rm -f ltconfig ltmain.sh config.cache aclocal.m4 +# remove the autoconf cache +rm -rf autom4te*.cache +# create a .version file for configure.in +if test ! -f .version; then + # Building from CVS rather than in a release + echo 0.0.0-dev > .version + # for the documentation: + date +"%e %B %Y" | tr -d '\n' > doc/date.xml + echo -n 0.0.0-dev > doc/version.xml +fi +set -e +echo -n "aclocal... " +${ACLOCAL:-aclocal} -I macros +echo -n "autoheader... " +${AUTOHEADER:-autoheader} +echo -n "libtoolize... " +${LIBTOOLIZE:-libtoolize} --copy --force >/dev/null +echo -n "autoconf... " +${AUTOCONF:-autoconf} -Wall +echo okay. +# remove the autoconf cache +rm -rf autom4te*.cache diff --git a/config.hw.in b/config.hw.in new file mode 100644 index 0000000..1f09719 --- /dev/null +++ b/config.hw.in @@ -0,0 +1,59 @@ +/* + Win32 config.h + Copyright (C) 1999-2000, Peter Boos <pedib@colorfullife.com> + Copyright (C) 2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ +#if defined(_WIN32) && !defined(WIN32) +#define WIN32 +#endif + +#ifdef WIN32 + +#define NEON_VERSION "@VERSION@" +#define NEON_VERSION_MAJOR (@MAJOR@) +#define NEON_VERSION_MINOR (@MINOR@) + +#define HAVE_ERRNO_H +#define HAVE_LIMITS_H +#define HAVE_STDLIB_H +#define HAVE_STRING_H + +#define HAVE_MEMCPY + +#ifndef NEON_NODAV +#define USE_DAV_LOCKS +#endif + +#define NE_FMT_SIZE_T "u" +#define NE_FMT_SSIZE_T "d" +#define NE_FMT_OFF_T "ld" + +/* Win32 uses a underscore, so we use a macro to eliminate that. */ +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define strcasecmp strcmpi +#define strncasecmp strnicmp +#define ssize_t int +#define inline __inline +#define off_t _off_t + +#include <io.h> +#define read _read + +#endif diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..c7a2f3d --- /dev/null +++ b/configure.in @@ -0,0 +1,152 @@ +dnl Require autoconf 2.53 for an AC_C_BIGENDIAN which supports +dnl cross-compiling. +AC_PREREQ(2.53) + +dnl Extract the version (sans LF) from .version, created at release-time. +m4_define(ne_version, [esyscmd([echo -n `cat .version`])]) + +AC_INIT(neon, ne_version, [neon@webdav.org]) + +AC_COPYRIGHT([Copyright (c) 2000, 2001, 2002 Joe Orton +This configure script may be copied, distributed and modified under the +terms of the GNU Library General Public license; see COPYING for more details]) + +AC_CONFIG_HEADER(config.h) +AC_CONFIG_SRCDIR(src/ne_request.c) + +NEON_WITH_LIBS + +# Pass through initial LDFLAGS verbatim to neon-config, so that extra +# libraries which are detected (e.g. OpenSSL) can still be found when +# building using the --libs output of neon-config. +user_LDFLAGS=$LDFLAGS +AC_SUBST(user_LDFLAGS) + +# By default, allow 'make install' to work. +ALLOW_INSTALL=yes +AC_SUBST(ALLOW_INSTALL) + +# Always defined +AC_DEFINE([_GNU_SOURCE], 1, [Unconditionally define _GNU_SOURCE]) +# Defined when neon is built as library +AC_DEFINE(NEON_IS_LIBRARY, 1, [Define when building neon as a library]) + +AC_PROG_INSTALL + +AC_DISABLE_SHARED +AC_PROG_LIBTOOL + +AC_EXEEXT + +top_builddir=`pwd` +AC_SUBST(top_builddir) + +AC_ARG_ENABLE(webdav, +AC_HELP_STRING([--disable-webdav], [disable WebDAV support])) + +if test "$enable_webdav" = "no"; then + NEON_WITHOUT_WEBDAV +else + # Yes, we do need an XML parser. The _BUNDLED macros handle + # this normally. + NEON_NEED_XML_PARSER=yes +fi + +# The bundled macros also set this, which makes sure we recurse +# into the 'src' directory. +NEON_BUILD_BUNDLED=yes +AC_SUBST(NEON_BUILD_BUNDLED) + +# Define NEON_VERSION* and make the appropriate substitutions. +NEON_VERSIONS + +# Pass the interface version on to libtool when linking libneon.la +NEON_LINK_FLAGS="-version-info ${NEON_INTERFACE_VERSION}" + +LIBNEON_SOURCE_CHECKS + +# Use the libtool-type build. +NEON_LIBTOOL_BUILD +# Checks to compile test suite +NEON_TEST +# Find an XML parser +NEON_XML_PARSER +# Extra checks for debugging, compiler warnings +NEON_DEBUG +# Leave till last to prevent CFLAGS affecting checks. +NEON_WARNINGS + +CFLAGS="$CFLAGS -I\${top_builddir}" + +dnl Substitute NEON_VERSION for neon-config too. +AC_SUBST(NEON_VERSION) + +AC_ARG_ENABLE(memleak, +AC_HELP_STRING([--enable-memleak], [for test builds only: enable memory leak checking])) + +dnl Have autoheader include the following template in config.h.in: +AH_VERBATIM([NEON_MEMLEAK], +[/* Enable memory leak detection. */ +#ifdef NEON_MEMLEAK +# include "memleak.h" +#endif]) + +if test "$enable_memleak" = "yes"; then + CPPFLAGS="$CPPFLAGS -DNEON_MEMLEAK -I\$(top_srcdir)/src" + # disable 'make install' + ALLOW_INSTALL=memleak +fi + +# Enable tests for optional features +TESTS="\$(BASIC_TESTS)" +HELPERS="" +if test "$NEON_SUPPORTS_SSL" = "yes"; then + # Only enable SSL tests if an openssl binary is found (needed to make + # certs etc). + AC_PATH_PROG(OPENSSL, openssl, notfound) + if test "$OPENSSL" != "notfound"; then + TESTS="$TESTS \$(SSL_TESTS)" + HELPERS="$HELPERS \$(SSL_HELPERS)" + else + AC_MSG_WARN([no openssl binary in \$PATH: SSL tests disabled]) + fi +fi +if test "$NEON_SUPPORTS_DAV" = "yes"; then + TESTS="$TESTS \$(DAV_TESTS)" +fi +if test "$NEON_SUPPORTS_ZLIB" = "yes"; then + TESTS="$TESTS \$(ZLIB_TESTS)" + HELPERS="$HELPERS \$(ZLIB_HELPERS)" +fi +AC_SUBST(HELPERS) +AC_SUBST(TESTS) + +AC_CONFIG_FILES([neon-config], [chmod +x neon-config]) +AC_CONFIG_FILES([Makefile src/Makefile test/Makefile neon.pc]) + +AC_OUTPUT + +# for VPATH builds: +test -d test/common || mkdir test/common + +AC_MSG_NOTICE([Configured to build AC_PACKAGE_STRING: + + Install prefix: ${prefix} + Compiler: ${CC} + XML Parser: ${neon_xml_parser_message} + SSL library: ${neon_ssl_message} + zlib support: ${neon_zlib_message} + Build libraries: Shared=${enable_shared}, Static=${enable_static} + +Now run 'make' to compile the neon library. +]) + +case $ALLOW_INSTALL in +memleak) + AC_MSG_NOTICE([Configured with development-only flags: + +WARNING: This copy of neon has been configured with memory leak checking +WARNING: enabled, which should only be used in a development copy of neon. +WARNING: This neon library should not be installed for use by applications. +]);; +esac diff --git a/doc/.cvsignore b/doc/.cvsignore new file mode 100644 index 0000000..30791a1 --- /dev/null +++ b/doc/.cvsignore @@ -0,0 +1,13 @@ +*.? +*.html +*.pdf +*.ps +*.tex +*.sgml +*.junk +html +man +man3 +man1 +version.xml +date.xml diff --git a/doc/TODO b/doc/TODO new file mode 100644 index 0000000..febed30 --- /dev/null +++ b/doc/TODO @@ -0,0 +1,156 @@ +/* List of interfaces needing reference documentation. -*- c -*- */ + +/* ne_session.h */ + +### DONE: basics +ne_session *ne_session_create(const char *scheme, const char *hostname, int port); +void ne_session_destroy(ne_session *sess); +void ne_close_connection(ne_session *sess); +void ne_session_proxy(ne_session *sess, const char *hostname, int port); + +### DONE: error handling +void ne_set_error(ne_session *sess, const char *format, ...); +const char *ne_get_error(ne_session *sess); + +### DONE: options +void ne_set_useragent(ne_session *sess, const char *product); +void ne_set_expect100(ne_session *sess, int use_expect100); +void ne_set_persist(ne_session *sess, int persist); +void ne_set_read_timeout(ne_session *sess, int timeout); + +### TODO: progress + status callbcacks +void ne_set_progress(ne_session *sess, ne_progress progress, void *userdata); + +### TODO: status callback +typedef enum ne_conn_status; +typedef void (*ne_notify_status)(void *userdata, ne_conn_status status, const char *info); +void ne_set_status(ne_session *sess, ne_notify_status status, void *userdata); + +### DONE: SSL verification + +typedef struct ne_ssl_dname; +char *ne_ssl_readable_dname(const ne_ssl_dname *dn); +typedef struct ne_ssl_certificate; +#define NE_SSL_* +typedef int (*ne_ssl_verify_fn)(void *userdata, int failures, + const ne_ssl_certificate *cert); +void ne_ssl_set_verify(ne_session *sess, ne_ssl_verify_fn fn, void *userdata); + +### DONE: SSL server certs +int ne_ssl_load_ca(ne_session *sess, const char *file); +int ne_ssl_load_default_ca(ne_session *sess); + +### TODO: SSL client certs +typedef int (*ne_ssl_keypw_fn)(void *userdata, char *pwbuf, size_t len); +void ne_ssl_keypw_prompt(ne_session *sess, ne_ssl_keypw_fn fn, void *ud); +int ne_ssl_load_pkcs12(ne_session *sess, const char *fn); +int ne_ssl_load_pem(ne_session *sess, const char *certfn, const char *keyfn); +typedef void (*ne_ssl_provide_fn)(void *userdata, ne_session *sess, + const ne_ssl_dname *server); +void ne_ssl_provide_ccert(ne_session *sess, ne_ssl_provide_fn fn, void *userdata); + +#ifdef NEON_SSL +SSL_CTX *ne_ssl_get_context(ne_session *sess); +X509 *ne_ssl_server_cert(ne_session *req); +#endif + +### TODO: utility functions +int ne_version_pre_http11(ne_session *sess); +const char *ne_get_server_hostport(ne_session *sess); +const char *ne_get_scheme(ne_session *sess); +void ne_fill_server_uri(ne_session *sess, ne_uri *uri); + +/* end of ne_session.h *****************************************/ + +/* ne_request.h */ + +### DONE: request basics +ne_request *ne_request_create(ne_session *sess, const char *method, const char *path); +int ne_request_dispatch(ne_request *req); +void ne_request_destroy(ne_request *req); + +### DONE: request status +const ne_status *ne_get_status(ne_request *req); + +### TODO: request bodies +void ne_set_request_body_buffer(ne_request *req, const char *buf, size_t count); +int ne_set_request_body_fd(ne_request *req, int fd, size_t count); + +typedef ssize_t (*ne_provide_body)(void *userdata, + char *buffer, size_t buflen); +void ne_set_request_body_provider(ne_request *req, size_t size, + ne_provide_body provider, void *userdata); + +### TODO: response bodies +typedef int (*ne_accept_response)(void *userdata, ne_request *req, ne_status *st); +int ne_accept_2xx(void *userdata, ne_request *req, ne_status *st); +int ne_accept_always(void *userdata, ne_request *req, ne_status *st); +void ne_add_response_body_reader(ne_request *req, ne_accept_response accpt, + ne_block_reader reader, void *userdata); + +### TODO: response headers +typedef void (*ne_header_handler)(void *userdata, const char *value); +void ne_add_response_header_handler(ne_request *req, const char *name, + ne_header_handler hdl, void *userdata); +void ne_add_response_header_catcher(ne_request *req, + ne_header_handler hdl, void *userdata); + +void ne_duplicate_header(void *userdata, const char *value); +void ne_handle_numeric_header(void *userdata, const char *value); + +### DONE: request headers +void ne_add_request_header(ne_request *req, const char *name, const char *value); +void ne_print_request_header(ne_request *req, const char *name, const char *format, ...); + +### TODO: misc +ne_session *ne_get_session(ne_request *req); + +### TODO: caller-pulls request interface +int ne_begin_request(ne_request *req); +int ne_end_request(ne_request *req); +ssize_t ne_read_response_block(ne_request *req, char *buffer, size_t buflen); + +### TODO: hooks +typedef void (*ne_free_hooks)(void *cookie); +typedef void (*ne_create_request_fn)(void *userdata, ne_request *req, + const char *method, const char *path); +void ne_hook_create_request(ne_session *sess, ne_create_request_fn fn, void *userdata); + +typedef void (*ne_pre_send_fn)(void *userdata, ne_buffer *header); +void ne_hook_pre_send(ne_session *sess, ne_pre_send_fn fn, void *userdata); + +typedef int (*ne_post_send_fn)(void *userdata, const ne_status *status); +void ne_hook_post_send(ne_session *sess, ne_post_send_fn fn, void *userdata); + +typedef void (*ne_destroy_fn)(void *userdata); +void ne_hook_destroy_request(ne_session *sess, ne_destroy_fn fn, void *userdata); + +void ne_hook_destroy_session(ne_session *sess, ne_destroy_fn fn, void *userdata); + +typedef void *(*ne_accessor_fn)(void *userdata); +void ne_hook_session_accessor(ne_session *sess, const char *id, ne_accessor_fn, void *userdata); +void ne_hook_request_accessor(ne_request *req, const char *id, ne_accessor_fn, void *userdata); + +void *ne_null_accessor(void *userdata); + +void *ne_session_hook_private(ne_session *sess, const char *id); + +void *ne_request_hook_private(ne_request *req, const char *id); + +/* ne_207.h */ +/* ne_acl.h */ +/* DONE: ne_alloc.h */ +/* DONE: ne_auth.h */ +/* ne_basic.h */ +/* ne_compress.h */ +/* ne_cookies.h */ +/* ne_dates.h */ +/* ne_locks.h */ +/* ne_props.h */ +/* ne_redirect.h */ +/* ne_socket.h */ +/* MOSTLY DONE: ne_string.h */ +/* ne_uri.h */ +/* ne_utils.h */ +/* ne_xml.h */ + diff --git a/doc/fdl.sgml b/doc/fdl.sgml new file mode 100644 index 0000000..37470db --- /dev/null +++ b/doc/fdl.sgml @@ -0,0 +1,466 @@ +<appendix id="gfdl"> +<title>GNU Free Documentation License</title> +<!-- - GNU Project - Free Software Foundation (FSF) --> +<!-- LINK REV="made" HREF="mailto:webmasters@gnu.org" --> + + + <!-- sect1> + <title>GNU Free Documentation License</title --> + + <para>Version 1.1, March 2000</para> + + <blockquote> + <para>Copyright (C) 2000 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed.</para> + </blockquote> + + <sect1 label="0"> + <title>PREAMBLE</title> + + <para>The purpose of this License is to make a manual, textbook, + or other written document "free" in the sense of freedom: to + assure everyone the effective freedom to copy and redistribute it, + with or without modifying it, either commercially or + noncommercially. Secondarily, this License preserves for the + author and publisher a way to get credit for their work, while not + being considered responsible for modifications made by + others.</para> + + <para>This License is a kind of "copyleft", which means that + derivative works of the document must themselves be free in the + same sense. It complements the GNU General Public License, which + is a copyleft license designed for free software.</para> + + <para>We have designed this License in order to use it for manuals + for free software, because free software needs free documentation: + a free program should come with manuals providing the same + freedoms that the software does. But this License is not limited + to software manuals; it can be used for any textual work, + regardless of subject matter or whether it is published as a + printed book. We recommend this License principally for works + whose purpose is instruction or reference.</para> + </sect1> + + <sect1 label="1"> + <title>APPLICABILITY AND DEFINITIONS</title> + + <para>This License applies to any manual or other work that + contains a notice placed by the copyright holder saying it can be + distributed under the terms of this License. The "Document", + below, refers to any such manual or work. Any member of the + public is a licensee, and is addressed as "you".</para> + + <para>A "Modified Version" of the Document means any work + containing the Document or a portion of it, either copied + verbatim, or with modifications and/or translated into another + language.</para> + + <para>A "Secondary Section" is a named appendix or a front-matter + section of the Document that deals exclusively with the + relationship of the publishers or authors of the Document to the + Document's overall subject (or to related matters) and contains + nothing that could fall directly within that overall subject. + (For example, if the Document is in part a textbook of + mathematics, a Secondary Section may not explain any mathematics.) + The relationship could be a matter of historical connection with + the subject or with related matters, or of legal, commercial, + philosophical, ethical or political position regarding + them.</para> + + <para>The "Invariant Sections" are certain Secondary Sections + whose titles are designated, as being those of Invariant Sections, + in the notice that says that the Document is released under this + License.</para> + + <para>The "Cover Texts" are certain short passages of text that + are listed, as Front-Cover Texts or Back-Cover Texts, in the + notice that says that the Document is released under this + License.</para> + + <para>A "Transparent" copy of the Document means a + machine-readable copy, represented in a format whose specification + is available to the general public, whose contents can be viewed + and edited directly and straightforwardly with generic text + editors or (for images composed of pixels) generic paint programs + or (for drawings) some widely available drawing editor, and that + is suitable for input to text formatters or for automatic + translation to a variety of formats suitable for input to text + formatters. A copy made in an otherwise Transparent file format + whose markup has been designed to thwart or discourage subsequent + modification by readers is not Transparent. A copy that is not + "Transparent" is called "Opaque".</para> + + <para>Examples of suitable formats for Transparent copies include + plain ASCII without markup, Texinfo input format, LaTeX input + format, SGML or XML using a publicly available DTD, and + standard-conforming simple HTML designed for human modification. + Opaque formats include PostScript, PDF, proprietary formats that + can be read and edited only by proprietary word processors, SGML + or XML for which the DTD and/or processing tools are not generally + available, and the machine-generated HTML produced by some word + processors for output purposes only.</para> + + <para>The "Title Page" means, for a printed book, the title page + itself, plus such following pages as are needed to hold, legibly, + the material this License requires to appear in the title page. + For works in formats which do not have any title page as such, + "Title Page" means the text near the most prominent appearance of + the work's title, preceding the beginning of the body of the + text.</para> + </sect1> + + <sect1 label="2"> + <title>VERBATIM COPYING</title> + + <para>You may copy and distribute the Document in any medium, + either commercially or noncommercially, provided that this + License, the copyright notices, and the license notice saying this + License applies to the Document are reproduced in all copies, and + that you add no other conditions whatsoever to those of this + License. You may not use technical measures to obstruct or + control the reading or further copying of the copies you make or + distribute. However, you may accept compensation in exchange for + copies. If you distribute a large enough number of copies you + must also follow the conditions in section 3.</para> + + <para>You may also lend copies, under the same conditions stated + above, and you may publicly display copies.</para> + </sect1> + + <sect1 label="3"> + <title>COPYING IN QUANTITY</title> + + <para>If you publish printed copies of the Document numbering more + than 100, and the Document's license notice requires Cover Texts, + you must enclose the copies in covers that carry, clearly and + legibly, all these Cover Texts: Front-Cover Texts on the front + cover, and Back-Cover Texts on the back cover. Both covers must + also clearly and legibly identify you as the publisher of these + copies. The front cover must present the full title with all + words of the title equally prominent and visible. You may add + other material on the covers in addition. Copying with changes + limited to the covers, as long as they preserve the title of the + Document and satisfy these conditions, can be treated as verbatim + copying in other respects.</para> + + <para>If the required texts for either cover are too voluminous to + fit legibly, you should put the first ones listed (as many as fit + reasonably) on the actual cover, and continue the rest onto + adjacent pages.</para> + + <para>If you publish or distribute Opaque copies of the Document + numbering more than 100, you must either include a + machine-readable Transparent copy along with each Opaque copy, or + state in or with each Opaque copy a publicly-accessible + computer-network location containing a complete Transparent copy + of the Document, free of added material, which the general + network-using public has access to download anonymously at no + charge using public-standard network protocols. If you use the + latter option, you must take reasonably prudent steps, when you + begin distribution of Opaque copies in quantity, to ensure that + this Transparent copy will remain thus accessible at the stated + location until at least one year after the last time you + distribute an Opaque copy (directly or through your agents or + retailers) of that edition to the public.</para> + + <para>It is requested, but not required, that you contact the + authors of the Document well before redistributing any large + number of copies, to give them a chance to provide you with an + updated version of the Document.</para> + </sect1> + + <sect1 label="4"> + <title>MODIFICATIONS</title> + + <para>You may copy and distribute a Modified Version of the + Document under the conditions of sections 2 and 3 above, provided + that you release the Modified Version under precisely this + License, with the Modified Version filling the role of the + Document, thus licensing distribution and modification of the + Modified Version to whoever possesses a copy of it. In addition, + you must do these things in the Modified Version:</para> + + <orderedlist numeration="upperalpha"> + <listitem><para>Use in the Title Page + (and on the covers, if any) a title distinct from that of the + Document, and from those of previous versions (which should, if + there were any, be listed in the History section of the + Document). You may use the same title as a previous version if + the original publisher of that version gives permission.</para> + </listitem> + + <listitem><para>List on the Title Page, + as authors, one or more persons or entities responsible for + authorship of the modifications in the Modified Version, + together with at least five of the principal authors of the + Document (all of its principal authors, if it has less than + five).</para> + </listitem> + + <listitem><para>State on the Title page + the name of the publisher of the Modified Version, as the + publisher.</para> + </listitem> + + <listitem><para>Preserve all the + copyright notices of the Document.</para> + </listitem> + + <listitem><para>Add an appropriate + copyright notice for your modifications adjacent to the other + copyright notices.</para> + </listitem> + + <listitem><para>Include, immediately + after the copyright notices, a license notice giving the public + permission to use the Modified Version under the terms of this + License, in the form shown in the Addendum below.</para> + </listitem> + + <listitem><para>Preserve in that license + notice the full lists of Invariant Sections and required Cover + Texts given in the Document's license notice.</para> + </listitem> + + <listitem><para>Include an unaltered + copy of this License.</para> + </listitem> + + <listitem><para>Preserve the section + entitled "History", and its title, and add to it an item stating + at least the title, year, new authors, and publisher of the + Modified Version as given on the Title Page. If there is no + section entitled "History" in the Document, create one stating + the title, year, authors, and publisher of the Document as given + on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence.</para> + </listitem> + + <listitem><para>Preserve the network + location, if any, given in the Document for public access to a + Transparent copy of the Document, and likewise the network + locations given in the Document for previous versions it was + based on. These may be placed in the "History" section. You + may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission.</para> + </listitem> + + <listitem><para>In any section entitled + "Acknowledgements" or "Dedications", preserve the section's + title, and preserve in the section all the substance and tone of + each of the contributor acknowledgements and/or dedications + given therein.</para> + </listitem> + + <listitem><para>Preserve all the + Invariant Sections of the Document, unaltered in their text and + in their titles. Section numbers or the equivalent are not + considered part of the section titles.</para> + </listitem> + + <listitem><para>Delete any section + entitled "Endorsements". Such a section may not be included in + the Modified Version.</para> + </listitem> + + <listitem><para>Do not retitle any + existing section as "Endorsements" or to conflict in title with + any Invariant Section.</para> + </listitem> + </orderedlist> + + <para>If the Modified Version includes new front-matter sections + or appendices that qualify as Secondary Sections and contain no + material copied from the Document, you may at your option + designate some or all of these sections as invariant. To do this, + add their titles to the list of Invariant Sections in the Modified + Version's license notice. These titles must be distinct from any + other section titles.</para> + + <para>You may add a section entitled "Endorsements", provided it + contains nothing but endorsements of your Modified Version by + various parties--for example, statements of peer review or that + the text has been approved by an organization as the authoritative + definition of a standard.</para> + + <para>You may add a passage of up to five words as a Front-Cover + Text, and a passage of up to 25 words as a Back-Cover Text, to the + end of the list of Cover Texts in the Modified Version. Only one + passage of Front-Cover Text and one of Back-Cover Text may be + added by (or through arrangements made by) any one entity. If the + Document already includes a cover text for the same cover, + previously added by you or by arrangement made by the same entity + you are acting on behalf of, you may not add another; but you may + replace the old one, on explicit permission from the previous + publisher that added the old one.</para> + + <para>The author(s) and publisher(s) of the Document do not by + this License give permission to use their names for publicity for + or to assert or imply endorsement of any Modified Version.</para> + </sect1> + + <sect1 label="5"> + <title>COMBINING DOCUMENTS</title> + + <para>You may combine the Document with other documents released + under this License, under the terms defined in section 4 above for + modified versions, provided that you include in the combination + all of the Invariant Sections of all of the original documents, + unmodified, and list them all as Invariant Sections of your + combined work in its license notice.</para> + + <para>The combined work need only contain one copy of this + License, and multiple identical Invariant Sections may be replaced + with a single copy. If there are multiple Invariant Sections with + the same name but different contents, make the title of each such + section unique by adding at the end of it, in parentheses, the + name of the original author or publisher of that section if known, + or else a unique number. Make the same adjustment to the section + titles in the list of Invariant Sections in the license notice of + the combined work.</para> + + <para>In the combination, you must combine any sections entitled + "History" in the various original documents, forming one section + entitled "History"; likewise combine any sections entitled + "Acknowledgements", and any sections entitled "Dedications". You + must delete all sections entitled "Endorsements."</para> + </sect1> + + <sect1 label="6"> + <title>COLLECTIONS OF DOCUMENTS</title> + + <para>You may make a collection consisting of the Document and + other documents released under this License, and replace the + individual copies of this License in the various documents with a + single copy that is included in the collection, provided that you + follow the rules of this License for verbatim copying of each of + the documents in all other respects.</para> + + <para>You may extract a single document from such a collection, + and distribute it individually under this License, provided you + insert a copy of this License into the extracted document, and + follow this License in all other respects regarding verbatim + copying of that document.</para> + </sect1> + + <sect1 label="7"> + <title>AGGREGATION WITH INDEPENDENT WORKS</title> + + <para>A compilation of the Document or its derivatives with other + separate and independent documents or works, in or on a volume of + a storage or distribution medium, does not as a whole count as a + Modified Version of the Document, provided no compilation + copyright is claimed for the compilation. Such a compilation is + called an "aggregate", and this License does not apply to the + other self-contained works thus compiled with the Document, on + account of their being thus compiled, if they are not themselves + derivative works of the Document.</para> + + <para>If the Cover Text requirement of section 3 is applicable to + these copies of the Document, then if the Document is less than + one quarter of the entire aggregate, the Document's Cover Texts + may be placed on covers that surround only the Document within the + aggregate. Otherwise they must appear on covers around the whole + aggregate.</para> + </sect1> + + <sect1 label="8"> + <title>TRANSLATION</title> + + <para>Translation is considered a kind of modification, so you may + distribute translations of the Document under the terms of section + 4. Replacing Invariant Sections with translations requires + special permission from their copyright holders, but you may + include translations of some or all Invariant Sections in addition + to the original versions of these Invariant Sections. You may + include a translation of this License provided that you also + include the original English version of this License. In case of + a disagreement between the translation and the original English + version of this License, the original English version will + prevail.</para> + </sect1> + + <sect1 label="9"> + <title>TERMINATION</title> + + <para>You may not copy, modify, sublicense, or distribute the + Document except as expressly provided for under this License. Any + other attempt to copy, modify, sublicense or distribute the + Document is void, and will automatically terminate your rights + under this License. However, parties who have received copies, or + rights, from you under this License will not have their licenses + terminated so long as such parties remain in full + compliance.</para> + </sect1> + + <sect1 label="10"> + <title>FUTURE REVISIONS OF THIS LICENSE</title> + + <para>The Free Software Foundation may publish new, revised + versions of the GNU Free Documentation License from time to time. + Such new versions will be similar in spirit to the present + version, but may differ in detail to address new problems or + concerns. See <ulink + url="http://www.gnu.org/copyleft/">http://www.gnu.org/copyleft/</ulink>.</para> + + <para>Each version of the License is given a distinguishing + version number. If the Document specifies that a particular + numbered version of this License "or any later version" applies to + it, you have the option of following the terms and conditions + either of that specified version or of any later version that has + been published (not as a draft) by the Free Software Foundation. + If the Document does not specify a version number of this License, + you may choose any version ever published (not as a draft) by the + Free Software Foundation.</para> + </sect1> + + <sect1 label=""> + <title>How to use this License for your documents</title> + + <para>To use this License in a document you have written, include + a copy of the License in the document and put the following + copyright and license notices just after the title page:</para> + +<blockquote><para> + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.1 + or any later version published by the Free Software Foundation; + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + A copy of the license is included in the section entitled "GNU + Free Documentation License". +</para></blockquote> + + <para>If you have no Invariant Sections, write "with no Invariant + Sections" instead of saying which ones are invariant. If you have + no Front-Cover Texts, write "no Front-Cover Texts" instead of + "Front-Cover Texts being LIST"; likewise for Back-Cover + Texts.</para> + + <para>If your document contains nontrivial examples of program + code, we recommend releasing these examples in parallel under your + choice of free software license, such as the GNU General Public + License, to permit their use in free software.</para> + </sect1> + +</appendix> +<!-- Keep this comment at the end of the file +Local variables: +mode: sgml +sgml-omittag:nil +sgml-shorttag:t +sgml-minimize-attributes:nil +sgml-always-quote-attributes:t +sgml-indent-step:2 +sgml-parent-document: ("referenz.sgml" "appendix") +sgml-exposed-tags:nil +sgml-local-ecat-files:nil +sgml-local-catalogs: CATALOG +sgml-validate-command: "nsgmls -s referenz.sgml" +ispell-skip-sgml: t +End: +--> diff --git a/doc/feat.xml b/doc/feat.xml new file mode 100644 index 0000000..547ab9f --- /dev/null +++ b/doc/feat.xml @@ -0,0 +1,78 @@ + <sect1 id="features"> + <title>Feature list</title> + + <para>The major features of the neon library are as follows:</para> + + <itemizedlist> + + <listitem><para>A high-level interface to common HTTP and WebDAV +methods. This allows you to easily dispatch a GET or a MKCOL request +against a resource with a single function call.</para></listitem> + + <listitem><para>A low-level interface for HTTP request +handling; allowing you to implement requests using arbitrary methods +and request headers, capture arbitrary response headers, and so +on.</para></listitem> + + <listitem><para>Persistent connection support; neon groups a +set of requests to a server into a "session"; requests within that +session can use a persistent (also known as "keep-alive") +connection.</para></listitem> + + <listitem><para>Modern HTTP authentication support: a complete +implementation of the new authentication standard, RFC2617, +supporting the Digest (MD5) and Basic schemes, including integrity +checking. Credentials are supplied by an application-defined +callback.</para></listitem> + + <listitem><para>Proxy server support; a session can be set to +use a proxy server. Authentication is supported for the Proxy as well +as the origin server.</para></listitem> + + <listitem><para>Complete SSL support; a simple interface for +enabling SSL, hiding the complexity of using an SSL library directly. +Client certificate support, callback-based server certificate +verification, along with functions to load trusted CA +certificates.</para></listitem> + +<!-- + <listitem><para>Compression support.</para></listitem> +--> + + <listitem><para>Generic XML parsing interface for handling XML +response bodies using SAX-like callbacks. Both the expat and libxml +XML parser libraries are supported.</para></listitem> + + <listitem><para>WebDAV metadata support; set and remove +properties, query properties (PROPFIND); simple interface for +retrieving "flat" byte-string properties, more advanced support for +parsing "complex" XML structured properties.</para></listitem> + +<!-- + <listitem><para>WebDAV locking support</para></listitem> +--> + + <listitem><para>Build environment support: the neon source +tree is designed so that it can be embedded in your application's +build tree; autoconf macros are supplied for integration. To get +started quickly a <xref linkend="refconfig"/> script is included, +to easily determine how to compile and link against an installed copy +of neon</para></listitem> + + <listitem><para>Complete test suite: the neon test suite +comprises half as many lines of source code as the library itself, +including many tests for protocol compliance in network behaviour, and +that the library implementation meets the guarantees made by the +API.</para> </listitem> + +<!-- + + <listitem><para>Thorough documentation: neon documentation is +provided in HTML and man page formats (from a single DocBook XML +source)</para></listitem> + +--> + + </itemizedlist> + + </sect1> diff --git a/doc/html.xsl b/doc/html.xsl new file mode 100644 index 0000000..a96867d --- /dev/null +++ b/doc/html.xsl @@ -0,0 +1,70 @@ +<?xml version='1.0'?> + +<!-- This file wraps around the DocBook HTML XSL stylesheet to customise + - some parameters; add the CSS stylesheet, etc. + --> + +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0' + xmlns="http://www.w3.org/TR/xhtml1/transitional" + exclude-result-prefixes="#default"> + +<xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/chunk.xsl"/> + +<xsl:variable name="html.stylesheet">../manual.css</xsl:variable> + +<!-- for sections with id attributes, use the id in the filename. + This produces meaningful (and persistent) URLs for the sections. --> +<xsl:variable name="use.id.as.filename" select="1"/> + +<!-- enable the shaded verbatim (programlisting etc) blocks. --> +<!-- <xsl:variable name="shade.verbatim" select="1"/> --> + +<!-- add class="programlisting" attribute on the <pre>s from + <programlisting>s so that the CSS can style them nicely. --> +<xsl:attribute-set name="shade.verbatim.style"> + <xsl:attribute name="class">programlisting</xsl:attribute> +</xsl:attribute-set> + +<!-- use sane ANSI C function prototypes --> +<xsl:variable name="funcsynopsis.style" select="'ansi'"/> + +<!-- split each sect1 into a separate chunk --> +<xsl:variable name="chunk.first.sections" select="1"/> + +<!-- don't generate table of contents within each chapter chunk. --> +<xsl:variable name="generate.chapter.toc" select="0"/> + +<xsl:variable name="generate.appendix.toc" select="0"/> + +<!-- don't include manual page numbers in refentry cross-references, they + look weird --> +<xsl:variable name="refentry.xref.manvolnum" select="0"/> + +<!-- do generate variablelist's as tables --> +<xsl:variable name="variablelist.as.table" select="1"/> + +<!-- and css'ize the tables so they can look pretty --> +<xsl:variable name="table.borders.with.css" select="1"/> + +<!-- disable ugly tabular output --> +<xsl:variable name="funcsynopsis.tabular.threshold" select="99999"/> + +<!-- change some presentation choices --> +<xsl:template match="type"> + <xsl:call-template name="inline.italicseq"/> +</xsl:template> + +<xsl:template match="parameter"> + <xsl:call-template name="inline.monoseq"/> +</xsl:template> + +<!-- enclose the whole funcprototype in <code> --> +<xsl:template match="funcprototype" mode="ansi-nontabular"> + <div class="funcprototype"><code> + <xsl:apply-templates mode="ansi-nontabular"/> + </code></div> +</xsl:template> + +</xsl:stylesheet> + diff --git a/doc/manual.css b/doc/manual.css new file mode 100644 index 0000000..621feb5 --- /dev/null +++ b/doc/manual.css @@ -0,0 +1,42 @@ + +p, pre.funcsynopsisinfo { margin-left: 0.4em; margin-right: 0.4em; } + +span.term { margin-left: 0.6em; margin-bottom: 0.0em } + +div.legalnotice { font-size: 80%; margin-left: 2em; } + +a:visited { color: darkgreen; } + +div.navheader { border-top: thin solid #bbf2bb; } +div.navfooter { border-bottom: thin solid #bbf2bb; } + +div.funcprototype { margin-top: 0.2em; margin-left: 0.4em; margin-bottom: 0.2em; } + +pre.programlisting, pre.screen { + background-color: #dddddd; + margin-left: 0.6em; margin-right: 1em; + padding: 0.3em; width: 50em; +} + +div.funcsynopsis, div.cmdsynopsis { + background-color: #dddddd; + margin-left: 0.4em; margin-right: 0.4em; + padding: 0.1em; +} + +div.warning { + border: thin solid #777777; +} + +h1.title { border-bottom: thick solid #bbf2bb; padding-bottom: 0.1em; } + +div.toc { + border-left: thick solid #bbf2bb; padding-left: 0.5em; + } + +h2, h3 { padding-left: 0.2em; padding-top: -0.1em; } + +h2 { background-color: #bbf2bb; font-size: 110%; + padding-bottom: 0.3em; padding-top: 0.2em; spacing-top: 0.1em; } + +h3 { border-bottom: thin solid #bbf2bb; } diff --git a/doc/manual.xml b/doc/manual.xml new file mode 100644 index 0000000..893f007 --- /dev/null +++ b/doc/manual.xml @@ -0,0 +1,167 @@ +<?xml version='1.0'?> <!-- -*- text -*- --> + +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [ + +<!ENTITY % isoent SYSTEM + "http://www.oasis-open.org/docbook/xml/4.2/ent/iso-num.ent"> +<!ENTITY % isopub SYSTEM + "http://www.oasis-open.org/docbook/xml/4.2/ent/iso-pub.ent"> + +%isoent; +%isopub; + +<!ENTITY fdl SYSTEM "fdl.sgml"> + +<!-- date/version stamp files created as release tarball is rolled --> +<!ENTITY date SYSTEM "date.xml"> +<!ENTITY version SYSTEM "version.xml"> + +<!ENTITY neon "neon"> + +<!-- a useful entity for writing reference examples --> +<!ENTITY egsess "ne_session *sess = ne_session_create(...);"> + +<!ENTITY null "<literal>NULL</literal>"> + +<!ENTITY nul "<literal>NUL</literal>"> + +<!-- xml.xml entities: --> +<!ENTITY startelm "<emphasis>start-element</emphasis>"> +<!ENTITY cdata "<emphasis>character-data</emphasis>"> +<!ENTITY endelm "<emphasis>end-element</emphasis>"> + +<!ENTITY section.xml SYSTEM "xml.xml"> +<!ENTITY section.features SYSTEM "feat.xml"> +<!ENTITY section.using SYSTEM "using.xml"> + +<!ENTITY refneon SYSTEM "ref/neon.xml"> +<!ENTITY refconfig SYSTEM "ref/config.xml"> + +<!ENTITY refsess SYSTEM "ref/sess.xml"> +<!ENTITY referr SYSTEM "ref/err.xml"> +<!ENTITY refopts SYSTEM "ref/opts.xml"> +<!ENTITY refsslvfy SYSTEM "ref/sslvfy.xml"> +<!ENTITY refsslcert SYSTEM "ref/sslcert.xml"> +<!ENTITY refssldname SYSTEM "ref/ssldname.xml"> +<!ENTITY refsslca SYSTEM "ref/sslca.xml"> +<!ENTITY refreq SYSTEM "ref/req.xml"> +<!ENTITY refreqhdr SYSTEM "ref/reqhdr.xml"> +<!ENTITY refstatus SYSTEM "ref/status.xml"> +<!ENTITY refgetst SYSTEM "ref/getst.xml"> +<!ENTITY refreqbody SYSTEM "ref/reqbody.xml"> +<!ENTITY refauth SYSTEM "ref/auth.xml"> +<!ENTITY refalloc SYSTEM "ref/alloc.xml"> +<!ENTITY refbuf SYSTEM "ref/buf.xml"> +<!ENTITY refbufcr SYSTEM "ref/bufcr.xml"> +<!ENTITY refbufapp SYSTEM "ref/bufapp.xml"> +<!ENTITY refbufdest SYSTEM "ref/bufdest.xml"> +<!ENTITY refbufutil SYSTEM "ref/bufutil.xml"> +<!ENTITY reftok SYSTEM "ref/tok.xml"> +<!ENTITY refshave SYSTEM "ref/shave.xml"> +<!ENTITY refvers SYSTEM "ref/vers.xml"> +<!ENTITY refinit SYSTEM "ref/init.xml"> +<!ENTITY refresolve SYSTEM "ref/resolve.xml"> +<!ENTITY refiaddr SYSTEM "ref/iaddr.xml"> +<!ENTITY refclicert SYSTEM "ref/clicert.xml"> + +]> + +<book> + <bookinfo> + <title>neon HTTP/WebDAV client library</title> + <author> + <firstname>Joe</firstname><surname>Orton</surname> + <affiliation> + <address><email>neon@webdav.org</email></address> + </affiliation> + </author> + <copyright><year>2001-2003</year><holder>Joe Orton</holder></copyright> + + <legalnotice> + <para>Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.1 + or any later version published by the Free Software Foundation; + with no Invariant Sections, with no Front-Cover Texts, + and with no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License".</para> + </legalnotice> + + </bookinfo> + + <chapter id="intro"> + <title>Introduction</title> + + <para>This chapter provides an introduction to neon, giving an +overview of the range of features offered, and some general guidelines +for using the neon API.</para> + + <para>neon aims to provide a modern, flexible, and simple API +in the C programming language for implementing HTTP and WebDAV +support. The WebDAV functionality is entirely separate from the basic +HTTP functionality; neon can be used simply as an HTTP client library, +ignoring the WebDAV support if desired.</para> + + §ion.features; + + §ion.using; + + </chapter> + + <chapter id="api"> + <title>The neon API for the C language</title> + + §ion.xml; + + </chapter> + + <reference id="ref"> + + <!-- these are used in the man page header/footers --> + <referenceinfo> + <title>neon API reference</title> + <date>&date;</date> + <productname>neon &version;</productname> + </referenceinfo> + + <title>neon API reference</title> + + &refneon; <!-- neon --> + &refconfig; <!-- neon-config --> + + &refreqhdr; <!-- ne_add_request_header --> + &refresolve; <!-- ne_addr_resolve --> + &refbuf; <!-- ne_buffer --> + &refbufapp; <!-- ne_buffer_append --> + &refbufutil; <!-- ne_buffer_clear --> + &refbufcr; <!-- ne_buffer_create --> + &refbufdest; <!-- ne_buffer_destroy --> + &referr; <!-- ne_get_error --> + &refgetst; <!-- ne_get_status --> + &refiaddr; <!-- ne_iaddr_make --> + &refalloc; <!-- ne_malloc --> + &refreq; <!-- ne_request_create --> + &refsess; <!-- ne_session_create --> + &refopts; <!-- ne_set_useragent --> + &refreqbody; <!-- ne_set_request_body_buffer --> + &refauth; <!-- ne_set_server_auth --> + &refshave; <!-- ne_shave --> + &refinit; <!-- ne_sock_init --> + &refsslcert; <!-- ne_ssl_certificate --> + &refssldname; <!-- ne_ssl_dname --> + &refsslca; <!-- ne_ssl_load_ca --> + &refsslvfy; <!-- ne_ssl_set_verify --> + &refclicert; <!-- ne_ssl_client_cert --> + &refstatus; <!-- ne_status --> + &reftok; <!-- ne_token --> + &refvers; <!-- ne_version_match --> + + <!-- REFEND --> + <!-- ******************************************************************* --> + + </reference> + +&fdl; + +</book> diff --git a/doc/parsing-xml.txt b/doc/parsing-xml.txt new file mode 100644 index 0000000..dc3e467 --- /dev/null +++ b/doc/parsing-xml.txt @@ -0,0 +1,170 @@ + +Requirements for XML parsing in neon +------------------------------------ + +Before describing the interface given in neon for parsing XML, here +are the requirements which it must satisfy: + + 1. to support using either libxml or expat as the underlying parser + 2. to allow "independent" sections to handle parsing one XML + document + 3. to map element namespaces/names to an integer for easier + comparison. + +A description of requirement (2) is useful since it is the "hard" +requirement, and adds most of the complexity of interface: WebDAV +PROPFIND responses are made up of a large boilerplate XML + + <multistatus><response><propstat><prop>...</prop></propstat> etc. + +neon should handle the parsing of these standard elements, and expose +the meaning of the response using a convenient interface. But, within +the <prop> elements, there may also be fragments of XML: neon can +never know how to parse these, since they are property- and hence +application-specific. The simplest example of this is the +DAV:resourcetype property. + +So there is requirement (2) that two "independent" sections of code +can handle the parsing of one XML document. + +Callback-based XML parsing +-------------------------- + +There are two ways of parsing XML documents commonly used: + + 1. Build an in-memory tree of the document + 2. Use callbacks + +Where practical, using callbacks is more efficient than building a +tree, so this is what neon uses. The standard interface for +callback-based XML parsing is called SAX, so understanding the SAX +interface is useful to understanding the XML parsing interface +provided by neon. + +The SAX interface works by registering callbacks which are called *as +the XML is parsed*. The most important callbacks are for 'start +element' and 'end element'. For instance, if the XML document below is +parsed by a SAX-like interface: + + <hello> + <foobar></foobar> + </hello> + +Say we have registered callbacks "startelm" for 'start element' and +"endelm" for 'end element'. Simplified somewhat, the callbacks will +be called in this order, with these arguments: + + 1. startelm("hello") + 2. startelm("foobar") + 3. endelm("foobar") + 4. endelm("hello") + +See the expat 'xmlparse.h' header for a more complete definition of a +SAX-like interface. + +The hip_xml interface +--------------------- + +The hip_xml interface satisfies requirement (2) by introducing the +"handler" concept. A handler is made up of these things: + + - a set of XML elements + - a callback to validate an element + - a callback which is called when an element is opened + - a callback which is called when an element is closed + - (optionally, a callback which is called for CDATA) + +Registering a handler essentially says: + + "If you encounter any of this set of elements, I want these + callbacks to be called." + +Handlers are kept in a STACK inside hip_xml. The first handler +registered becomes the BASE of the stack, subsequent handlers are +PUSHed on top. + +During XML parsing, the handler which is used for an XML element is +recorded. When a new element is started, the search for a handler for +this element begins at the handler used for the parent element, and +carries on up the stack. For the root element, the search always +starts at the BASE of the stack. + +A user's guide to hip_xml +------------------------- + +The first thing to do when using hip_xml is to know what set of XML +elements you are going to be parsing. This can usually be done by +looking at the DTD provided for the documents you are going to be +parsing. The DTD is also very useful in writing the 'validate' +callback function, since it can tell you what parent/child pairs are +valid, and which aren't. + +In this example, we'll parse XML documents which look like: + +<T:list-of-things xmlns:T="http://things.org/"> + <T:a-thing>foo</T:a-thing> + <T:a-thing>bar</T:a-thing> +</T:list-of-things> + +So, given the set of elements, declare the element id's and the +element array: + +#define ELM_listofthings (HIP_ELM_UNUSED) +#define ELM_a_thing (HIP_ELM_UNUSED + 1) + +const static struct my_elms[] = { + { "http://things.org/", "list-of-things", ELM_listofthings, 0 }, + { "http://things.org/", "a-thing", ELM_a_thing, HIP_XML_CDATA }, + { NULL } +}; + +This declares we know about two elements: list-of-things, and a-thing, +and that the 'a-thing' element contains character data. + +The definition of the validation callback is very simple: + +static int validate(hip_xml_elmid parent, hip_xml_elmid child) +{ + /* Only allow 'list-of-things' as the root element. */ + if (parent == HIP_ELM_root && child == ELM_listofthings || + parent = ELM_listofthings && child == ELM_a_thing) { + return HIP_XML_VALID; + } else { + return HIP_XML_INVALID; + } +} + +For this example, we can ignore the start-element callback, and just +use the end-element callback: + +static int endelm(void *userdata, const struct hip_xml_elm *s, + const char *cdata) +{ + printf("Got a thing: %s\n", cdata); + return 0; +} + +This endelm callback just prints the cdata which was contained in the +"a-thing" element. + +Now, on to parsing. A new parser object is created for parsing each +XML document. Creating a new parser object is as simple as: + + hip_xml_parser *parser; + + parser = hip_xml_create(); + +Next register the handler, passing NULL as the start-element callback, +and also as userdata, which we don't use here. + + hip_xml_push_handler(parser, my_elms, + validate, NULL, endelm, + NULL); + +Finally, call hip_xml_parse, passing the chunks of XML document to the +hip_xml as you get them. The output should be: + + Got a thing: foo + Got a thing: bar + +for the XML document. diff --git a/doc/ref/alloc.xml b/doc/ref/alloc.xml new file mode 100644 index 0000000..cc0ac22 --- /dev/null +++ b/doc/ref/alloc.xml @@ -0,0 +1,88 @@ + <refentry id="refalloc"> + + <refmeta> + <refentrytitle>ne_malloc</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_malloc">ne_malloc</refname> + <refname id="ne_calloc">ne_calloc</refname> + <refname id="ne_realloc">ne_realloc</refname> + <refname id="ne_strdup">ne_strdup</refname> + <refname id="ne_strndup">ne_strndup</refname> + <refname id="ne_oom_callback">ne_oom_callback</refname> + <refpurpose>memory allocation wrappers</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_alloc.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>void *<function>ne_malloc</function></funcdef> + <paramdef>size_t <parameter>size</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void *<function>ne_calloc</function></funcdef> + <paramdef>size_t <parameter>size</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void *<function>ne_realloc</function></funcdef> + <paramdef>void *<parameter>size</parameter></paramdef> + <paramdef>size_t <parameter>len</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>char *<function>ne_strdup</function></funcdef> + <paramdef>const char *<parameter>s</parameter></paramdef> + <paramdef>size_t <parameter>size</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>char *<function>ne_strndup</function></funcdef> + <paramdef>const char *<parameter>s</parameter></paramdef> + <paramdef>size_t <parameter>size</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_oom_callback</function></funcdef> + <paramdef>void (*<parameter>callback</parameter>)(void)</paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The functions <function>ne_malloc</function>, +<function>ne_calloc</function>, <function>ne_realloc</function>, +<function>ne_strdup</function> and <function>ne_strdnup</function> +provide wrappers for the equivalent functions in the standard C +library. The wrappers provide the extra guarantee that if the C +library equivalent returns &null; when no memory is available, an +optional callback will be called, and the library will then call +<function>abort</function>().</para> + + <para><function>ne_oom_callback</function> registers a callback +which will be invoked if an out of memory error is detected.</para> + + </refsect1> + + <refsect1> + <title>Notes</title> + + <para>If the operating system uses optimistic memory +allocation, the C library memory allocation routines will not return +&null;, so it is not possible to gracefully handle memory allocation +failures.</para> + + </refsect1> + + </refentry> diff --git a/doc/ref/auth.xml b/doc/ref/auth.xml new file mode 100644 index 0000000..9b20201 --- /dev/null +++ b/doc/ref/auth.xml @@ -0,0 +1,114 @@ + <refentry id="refauth"> + + <refmeta> + <refentrytitle>ne_set_server_auth</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_set_server_auth">ne_set_server_auth</refname> + <refname id="ne_set_proxy_auth">ne_set_proxy_auth</refname> + <refname id="ne_forget_auth">ne_forget_auth</refname> + <refpurpose>register authentication callbacks</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_auth.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>typedef int (*<function>ne_request_auth</function>)</funcdef> + <paramdef>void *<parameter>userdata</parameter></paramdef> + <paramdef>const char *<parameter>realm</parameter></paramdef> + <paramdef>int <parameter>attempt</parameter></paramdef> + <paramdef>char *<parameter>username</parameter></paramdef> + <paramdef>char *<parameter>password</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_set_server_auth</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>ne_request_auth <parameter>callback</parameter></paramdef> + <paramdef>void *<parameter>userdata</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_set_proxy_auth</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>ne_request_auth <parameter>callback</parameter></paramdef> + <paramdef>void *<parameter>userdata</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_forget_auth</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <type>ne_request_auth</type> function type defines a +callback which is invoked when a server or proxy server requires user +authentication for a particular request. The +<parameter>realm</parameter> string is supplied by the server. <!-- +FIXME --> The <parameter>attempt</parameter> is a counter giving the +number of times the request has been retried with different +authentication credentials. The first time the callback is invoked +for a particular request, <parameter>attempt</parameter> will be zero.</para> + + <para>To retry the request using new authentication +credentials, the callback should return zero, and the +<parameter>username</parameter> and <parameter>password</parameter> +buffers must contain &nul;-terminated strings. The +<literal>NE_ABUFSIZ</literal> constant gives the size of these +buffers.</para> + + <tip> + <para>If you only wish to allow the user one attempt to enter +credentials, use the value of the <parameter>attempt</parameter> +parameter as the return value of the callback.</para> + </tip> + + <para>To abort the request, the callback should return a +non-zero value; in which case the contents of the +<parameter>username</parameter> and <parameter>password</parameter> +buffers are ignored.</para> + + <para>The <function>ne_forget_auth</function> function can be +used to discard the cached authentication credentials.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <programlisting> +/* Function which prompts for a line of user input: */ +extern char *prompt_for(const char *prompt); + +static int +my_auth(void *userdata, const char *realm, int attempts, + char *username, char *password) +{ + strncpy(username, prompt_for("Username: "), NE_ABUFSIZ); + strncpy(password, prompt_for("Password: "), NE_ABUFSIZ); + return attempts; +} + +int main(...) +{ + &egsess; + + ne_set_server_auth(sess, my_auth, NULL); + + /* ... */ +}</programlisting> + </refsect1> + + </refentry> diff --git a/doc/ref/buf.xml b/doc/ref/buf.xml new file mode 100644 index 0000000..68b515c --- /dev/null +++ b/doc/ref/buf.xml @@ -0,0 +1,53 @@ + <refentry id="refbuf"> + + <refmeta> + <refentrytitle>ne_buffer</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_buffer">ne_buffer</refname> + <refpurpose>string buffer handling</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis><funcsynopsisinfo>#include <ne_string.h> + +typedef struct { + char *data; + size_t used; + size_t length; +} <type>ne_buffer</type>;</funcsynopsisinfo></funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <type>ne_buffer</type> type represents an expandable +memory buffer for holding &nul;-terminated strings. The +<structfield>data</structfield> field points to the beginnning of the +string, the length of which is given by the +<structfield>used</structfield> field. The current size of memory +allocated is given by the <structfield>length</structfield> field. It +is not recommended that the fields of a buffer are manipulated +directly. The <structfield>data</structfield> pointer may change when +the buffer is modified.</para> + + <para>A buffer is created using <xref +linkend="ne_buffer_create"/> or <xref +linkend="ne_buffer_create_sized"/>, and destroyed using <xref +linkend="ne_buffer_destroy"/> or <xref linkend="ne_buffer_finish"/>. +The functions <xref linkend="ne_buffer_append"/>, <xref +linkend="ne_buffer_zappend"/> and <xref linkend="ne_buffer_concat"/> are +used to append data to a buffer.</para> + + <para>If the string referenced by the +<structfield>data</structfield> pointer is modified directly (rather +than using one of the functions listed above), +<function>ne_buffer_altered</function> must be called.</para> + + </refsect1> + + </refentry> diff --git a/doc/ref/bufapp.xml b/doc/ref/bufapp.xml new file mode 100644 index 0000000..a75ce81 --- /dev/null +++ b/doc/ref/bufapp.xml @@ -0,0 +1,89 @@ + <refentry id="refbufapp"> + + <refmeta> + <refentrytitle>ne_buffer_append</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_buffer_append">ne_buffer_append</refname> + <refname id="ne_buffer_zappend">ne_buffer_zappend</refname> + <refname id="ne_buffer_concat">ne_buffer_concat</refname> + <refpurpose>append data to a string buffer</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_string.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>void <function>ne_buffer_append</function></funcdef> + <paramdef>ne_buffer *<parameter>buf</parameter></paramdef> + <paramdef>const char *<parameter>string</parameter></paramdef> + <paramdef>size_t <parameter>len</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_buffer_zappend</function></funcdef> + <paramdef>ne_buffer *<parameter>buf</parameter></paramdef> + <paramdef>const char *<parameter>string</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_buffer_concat</function></funcdef> + <paramdef>ne_buffer *<parameter>buf</parameter></paramdef> + <paramdef>const char *<parameter>str</parameter></paramdef> + <paramdef>...</paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <function>ne_buffer_append</function> and +<function>ne_buffer_zappend</function> functions append a string to +the end of a buffer; extending the buffer as necessary. The +<parameter>len</parameter> passed to +<function>ne_buffer_append</function> specifies the length of the +string to append; there must be no &nul; terminator in the first +<parameter>len</parameter> bytes of the string. +<function>ne_buffer_zappend</function> must be passed a +&nul;-terminated string.</para> + + <para>The <function>ne_buffer_concat</function> function takes +a variable-length argument list following <parameter>str</parameter>; +each argument must be a <type>char *</type> pointer to a +&nul;-terminated string. A &null; pointer must be given as the last +argument to mark the end of the list. The strings (including +<parameter>str</parameter>) are appended to the buffer in the order +given. None of the strings passed to +<function>ne_buffer_concat</function> are modified.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>The following code will output "<literal>Hello, world. +And goodbye.</literal>".</para> + + <programlisting>ne_buffer *buf = ne_buffer_create(); +ne_buffer_zappend(buf, "Hello"); +ne_buffer_concat(buf, ", world. ", "And ", "goodbye.", NULL); +puts(buf->data); +ne_buffer_destroy(buf);</programlisting> + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_buffer"/>, <xref linkend="ne_buffer_create"/>, +<xref linkend="ne_buffer_destroy"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/bufcr.xml b/doc/ref/bufcr.xml new file mode 100644 index 0000000..0c572a6 --- /dev/null +++ b/doc/ref/bufcr.xml @@ -0,0 +1,60 @@ + <refentry id="refbufcr"> + + <refmeta> + <refentrytitle>ne_buffer_create</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_buffer_create">ne_buffer_create</refname> + <refname id="ne_buffer_create_sized">ne_buffer_ncreate</refname> + <refpurpose>general purpose of group of functions</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_alloc.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>ne_buffer *<function>ne_buffer_create</function></funcdef> + <void/> + </funcprototype> + + <funcprototype> + <funcdef>ne_buffer *<function>ne_buffer_ncreate</function></funcdef> + <paramdef>size_t <parameter>size</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><function>ne_buffer_create</function> creates a new +buffer object, with an implementation-defined initial size. +<function>ne_buffer_ncreate</function> creates an +<type>ne_buffer</type> where the minimum initial size is given in the +<parameter>size</parameter> parameter. The buffer created will +contain the empty string (<literal>""</literal>).</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para>Both functions return a pointer to a new buffer object, +and never &null;.</para> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_buffer"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/bufdest.xml b/doc/ref/bufdest.xml new file mode 100644 index 0000000..5dc4dfc --- /dev/null +++ b/doc/ref/bufdest.xml @@ -0,0 +1,81 @@ + <refentry id="refbufdest"> + + <refmeta> + <refentrytitle>ne_buffer_destroy</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_buffer_destroy">ne_buffer_destroy</refname> + <refname id="ne_buffer_finish">ne_buffer_finish</refname> + <refpurpose>destroy a buffer object</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_string.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>void <function>ne_buffer_destroy</function></funcdef> + <paramdef>ne_buffer *<parameter>buf</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>char *<function>ne_buffer_finish</function></funcdef> + <paramdef>ne_buffer *<parameter>buf</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><function>ne_buffer_destroy</function> frees all memory +associated with the buffer. <function>ne_buffer_finish</function> +frees the buffer structure, but not the actual string stored in the +buffer, which is returned and must be <function>free</function>()d by +the caller.</para> + + <para>Any use of the buffer object after calling either of these +functions gives undefined behaviour.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para><function>ne_buffer_finish</function> returns the +<function>malloc</function>-allocated string stored in the buffer.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>An example use of <function>ne_buffer_finish</function>; +the <function>duplicate</function> function returns a string made up of +<parameter>n</parameter> copies of <parameter>str</parameter>:</para> + + <programlisting>static char *duplicate(int n, const char *str) +{ + ne_buffer *buf = ne_buffer_create(); + while (n--) { + ne_buffer_zappend(buf, str); + } + return ne_buffer_finish(buf); +}</programlisting> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_buffer"/>, <xref linkend="ne_buffer_create"/>, +<xref linkend="ne_buffer_zappend"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/bufutil.xml b/doc/ref/bufutil.xml new file mode 100644 index 0000000..3f3f3c5 --- /dev/null +++ b/doc/ref/bufutil.xml @@ -0,0 +1,62 @@ + <refentry id="refbufutil"> + + <refmeta> + <refentrytitle>ne_buffer_clear</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_buffer_clear">ne_buffer_clear</refname> + <refname id="ne_buffer_grow">ne_buffer_grow</refname> + <refname id="ne_buffer_altered">ne_buffer_altered</refname> + <refpurpose>general purpose of group of functions</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_string.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>void <function>ne_buffer_clear</function></funcdef> + <paramdef>ne_buffer *<parameter>buf</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_buffer_altered</function></funcdef> + <paramdef>ne_buffer *<parameter>buf</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_buffer_grow</function></funcdef> + <paramdef>ne_buffer *<parameter>buf</parameter></paramdef> + <paramdef>size_t <parameter>size</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <function>ne_buffer_clear</function> function sets +the string stored in <parameter>buf</parameter> to be the empty string +(<literal>""</literal>).</para> + + <para>The <function>ne_buffer_altered</function> function must +be used after the string stored in the buffer +<parameter>buf</parameter> is modified by directly rather than using +<xref linkend="ne_buffer_append"/>, <xref linkend="ne_buffer_zappend"/> +or <xref linkend="ne_buffer_concat"/>.</para> + + <para>The <function>ne_buffer_grow</function> function +ensures that at least <parameter>size</parameter> bytes are allocated +for the string; this can be used if a large amount of data is going to +be appended to the buffer and may result in more efficient memory +allocation.</para> + + </refsect1> + + </refentry> diff --git a/doc/ref/clicert.xml b/doc/ref/clicert.xml new file mode 100644 index 0000000..47a2ce0 --- /dev/null +++ b/doc/ref/clicert.xml @@ -0,0 +1,153 @@ +<refentry id="refclicert"> + + <refmeta> + <refentrytitle>ne_ssl_client_cert</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_ssl_clicert_read">ne_ssl_clicert_read</refname> + <refname id="ne_ssl_clicert_name">ne_ssl_clicert_name</refname> + <refname id="ne_ssl_clicert_encrypted">ne_ssl_clicert_encrypted</refname> + <refname id="ne_ssl_clicert_decrypt">ne_ssl_clicert_decrypt</refname> + <refname id="ne_ssl_clicert_owner">ne_ssl_clicert_owner</refname> + <refname id="ne_ssl_clicert_free">ne_ssl_clicert_free</refname> + <refpurpose>SSL client certificate handling</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_ssl.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>ne_ssl_client_cert *<function>ne_ssl_clicert_read</function></funcdef> + <paramdef>const char *<parameter>filename</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>const char *<function>ne_ssl_clicert_name</function></funcdef> + <paramdef>const ne_ssl_client_cert *<parameter>ccert</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_ssl_clicert_encrypted</function></funcdef> + <paramdef>const ne_ssl_client_cert *<parameter>ccert</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_ssl_clicert_decrypt</function></funcdef> + <paramdef>ne_ssl_client_cert *<parameter>ccert</parameter></paramdef> + <paramdef>const char *<parameter>password</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>const ne_ssl_certificate *<function>ne_ssl_clicert_owner</function></funcdef> + <paramdef>const ne_ssl_client_cert *<parameter>ccert</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_ssl_clicert_free</function></funcdef> + <paramdef>ne_ssl_client_cert *<parameter>ccert</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <function>ne_ssl_clicert_read</function> function reads + a <firstterm>client certificate</firstterm> from a + PKCS#12-formatted file, and returns an + <type>ne_ssl_client_certificate</type> object. If the client + certificate is encrypted, it must be decrypted before it is used. + An <type>ne_ssl_client_certificate</type> object holds a client + certificate and the associated private key, not just a + certificate; the term "<glossterm>client certificate</glossterm>" + will used to refer to this pair.</para> + + <para>A client certificate can be in one of two states: + <emphasis>encrypted</emphasis> or <emphasis>decrypted</emphasis>. + The <function>ne_ssl_clicert_encrypted</function> function will + return non-zero if the client certificate is in the + <emphasis>encrypted</emphasis> state. A client certificate object + returned by <function>ne_ssl_clicert_read</function> may be + initially in either state, depending on whether the file was + encrypted or not.</para> + + <para><function>ne_ssl_clicert_decrypt</function> can be used to + decrypt a client certificate using the appropriate password. This + function must only be called if the object is in the + <emphasis>encrypted</emphasis> state; if decryption fails, the + certificate state does not change, so decryption can be attempted + more than once using different passwords.</para> + + <para>A client certificate can be given a "friendly name" when it + is created; <function>ne_ssl_clicert_owner</function> will return + this name (or &null; if no friendly name was specified). + <function>ne_ssl_clicert_owner</function> can be used when the + client certificate is in either the encrypted or decrypted state, + and will return the same string for the lifetime of the + object.</para> + + <para>The function <function>ne_ssl_clicert_owner</function> + returns the certificate part of the client certificate; it must + only be called if the client certificate is in the + <emphasis>decrypted</emphasis> state.</para> + + <para>When the client certificate is no longer needed, the + <function>ne_ssl_clicert_free</function> function should be used + to destroy the object.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para><function>ne_ssl_clicert_read</function> returns a client + certificate object, or &null; if the file could not be read. + <function>ne_ssl_clicert_encrypted</function> returns zero if the + object is in the decrypted state, or non-zero if it is in the + encrypted state. <function>ne_ssl_clicert_name</function> returns + a &nul;-terminated friendly name string, or &null;. + <function>ne_ssl_clicert_owner</function> returns a certificate + object.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>The following code reads a client certificate and decrypts + it if necessary, then loads it into an HTTP session.</para> + + <programlisting>ne_ssl_client_certificate *ccert; + +ccert = ne_ssl_clicert_read("/path/to/client.p12"); + +if (ccert == NULL) { + /* handle error... */ +} else if (ne_ssl_clicert_encrypted(ccert)) { + char *password = prompt_for_password(); + + if (ne_ssl_clicert_decrypt(ccert, password)) { + /* could not decrypt! handle error... */ + } +} + +ne_ssl_set_clicert(sess, ccert); +</programlisting> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_ssl_certificate"/></para> + </refsect1> + +</refentry> + diff --git a/doc/ref/config.xml b/doc/ref/config.xml new file mode 100644 index 0000000..4823cca --- /dev/null +++ b/doc/ref/config.xml @@ -0,0 +1,124 @@ + <refentry id="refconfig"> + + <refentryinfo><title>neon</title></refentryinfo> + + <refmeta> + <refentrytitle>neon-config</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv> + <refname id="neon-config">neon-config</refname> + + <refpurpose>script providing information about installed copy + of neon library</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <cmdsynopsis> + <command>neon-config</command> + <arg choice="opt"><option>--prefix</option></arg> + <group> + <arg><option>--cflags</option></arg> + <arg><option>--libs</option></arg> + <arg><option>--la-file</option></arg> + <arg><option>--support</option> <replaceable>feature</replaceable></arg> + <arg><option>--help</option></arg> + <arg><option>--version</option></arg> + </group> + </cmdsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <command>neon-config</command> script provides +information about an installed copy of the neon library. The +<option>--cflags</option> and <option>--libs</option> options instruct +how to compile and link an application against the library; the +<option>--version</option> and <option>--support</option> options can +help determine whether the library meets the applications +requirements.</para> + + </refsect1> + + <refsect1> + <title>Options</title> + + <variablelist> + + <varlistentry> + <term><option>--cflags</option></term> + <listitem><simpara>Print the flags which should be passed to +the C compiler when compiling object files, when the object files use +neon header files.</simpara></listitem> + </varlistentry> + + <varlistentry> + <term><option>--libs</option></term> + <listitem><simpara>Print the flags which should be passed to +the linker when linking an application which uses the neon +library</simpara></listitem> + </varlistentry> + + <varlistentry> + <term><option>--la-file</option></term> + <listitem><simpara>Print the location of the libtool library +script, <filename>libneon.la</filename>, which can be used to link against +&neon; by applications using libtool.</simpara></listitem> + </varlistentry> + + <varlistentry> + <term><option>--version</option></term> + <listitem><simpara>Print the version of the library</simpara></listitem> + </varlistentry> + + <varlistentry> + <term><option>--prefix</option> <replaceable>dir</replaceable></term> + <listitem><simpara>If <replaceable>dir</replaceable> is given; relocate output of +<option>--cflags</option> and <option>--libs</option> as if neon was +installed in given prefix directory. Otherwise, print the +installation prefix of the library.</simpara></listitem> + </varlistentry> + + <varlistentry> + <term><option>--support</option> <replaceable>feature</replaceable></term> + <listitem><simpara>The script exits with success if +<replaceable>feature</replaceable> is supported by the +library.</simpara></listitem> + </varlistentry> + + <varlistentry> + <term><option>--help</option></term> + <listitem><simpara>Print help message; includes list of known + features and whether they are supported or not.</simpara></listitem> + </varlistentry> + + </variablelist> + + </refsect1> + + <refsect1 id="example"> + <title>Example</title> + + <para>Below is a Makefile fragment which could be used to +build an application against an installed neon library, when the +<command>neon-config</command> script can be found in +<envar>$PATH</envar>.</para> + + <programlisting>CFLAGS = `neon-config --cflags` +LIBS = `neon-config --libs` +OBJECTS = myapp.o +TARGET = myapp + +$(TARGET): $(OBJECTS) + $(CC) $(LDFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS) + +myapp.o: myapp.c + $(CC) $(CFLAGS) -c myapp.c -o myapp.o</programlisting> + + </refsect1> + + </refentry> diff --git a/doc/ref/err.xml b/doc/ref/err.xml new file mode 100644 index 0000000..50551b5 --- /dev/null +++ b/doc/ref/err.xml @@ -0,0 +1,66 @@ + <refentry id="referr"> + + <refmeta> + <refentrytitle>ne_get_error</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_get_error">ne_get_error</refname> + <refname id="ne_set_error">ne_set_error</refname> + <refpurpose>error handling for HTTP sessions</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_session.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>const char *<function>ne_get_error</function></funcdef> + <paramdef>ne_sesssion *<parameter>session</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_set_error</function></funcdef> + <paramdef>ne_sesssion *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>format</parameter></paramdef> + <paramdef>...</paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The session error string is used to store any +human-readable error information associated with any errors which +occur whilst using the HTTP session.</para> + + <para>The <function>ne_get_error</function> function returns the current +session error string. This string persists only until it is changed by a +subsequent operation on the session.</para> + + <para>The <function>ne_set_error</function> function can be +used to set a new session error string, using a +<function>printf</function>-style format string interface.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + <para>Retrieve the current error string:</para> + <programlisting>&egsess; +... +printf("Error was: %s\n", ne_get_error(sess));</programlisting> + + <para>Set a new error string:</para> + <programlisting>&egsess; +... +ne_set_error(sess, "Response missing header %s", "somestring");</programlisting> + </refsect1> + + </refentry> diff --git a/doc/ref/getst.xml b/doc/ref/getst.xml new file mode 100644 index 0000000..5a419c4 --- /dev/null +++ b/doc/ref/getst.xml @@ -0,0 +1,63 @@ + <refentry id="refgetst"> + + <refmeta> + <refentrytitle>ne_get_status</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_get_status">ne_get_status</refname> + <refpurpose>retrieve HTTP response status for request</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_request.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>const ne_status *<function>ne_get_status</function></funcdef> + <paramdef>const ne_request *<parameter>request</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <function>ne_get_status</function> function returns +a pointer to the HTTP status object giving the result of a request. +The object returned only becomes valid once the request has been +<emphasis>successfully</emphasis> dispatched (the return value of +<function>ne_request_dispatch</function> or +<function>ne_begin_request</function> was zero). The object remains +valid until the associated request object is destroyed.</para> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_status"/>, <xref + linkend="ne_request_create"/></para> + + </refsect1> + + <refsect1> + <title>Example</title> + + <para>Display the response status code of applying the +<literal>HEAD</literal> method to some resource.</para> + + <programlisting>ne_request *req = ne_request_create(sess, "HEAD", "/foo/bar"); +if (ne_request_dispatch(req)) + /* handle errors... */ +else + printf("Response status code was %d\n", ne_get_status(req)->code); +ne_request_destroy(req);</programlisting> + </refsect1> + + </refentry> diff --git a/doc/ref/iaddr.xml b/doc/ref/iaddr.xml new file mode 100644 index 0000000..cf64a13 --- /dev/null +++ b/doc/ref/iaddr.xml @@ -0,0 +1,124 @@ +<refentry id="refiaddr"> + + <refmeta> + <refentrytitle>ne_iaddr_make</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_iaddr_make">ne_iaddr_make</refname> + <refname id="ne_iaddr_cmp">ne_iaddr_cmp</refname> + <refname id="ne_iaddr_print">ne_iaddr_print</refname> + <refname id="ne_iaddr_free">ne_iaddr_free</refname> + <refpurpose>functions to manipulate and compare network addresses</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_socket.h> + +typedef enum { + ne_iaddr_ipv4 = 0, + ne_iaddr_ipv6 +} <type>ne_iaddr_type</type>;</funcsynopsisinfo> + + <funcprototype> + <funcdef>ne_inet_addr *<function>ne_iaddr_make</function></funcdef> + <paramdef>ne_iaddr_type <parameter>type</parameter></paramdef> + <paramdef>const unsigned char *<parameter>raw</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_iaddr_cmp</function></funcdef> + <paramdef>const ne_inet_addr *<parameter>i1</parameter></paramdef> + <paramdef>const ne_inet_addr *<parameter>i2</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>char *<function>ne_iaddr_print</function></funcdef> + <paramdef>const ne_inet_addr *<parameter>ia</parameter></paramdef> + <paramdef>char *<parameter>buffer</parameter></paramdef> + <paramdef>size_t <parameter>bufsiz</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_iaddr_free</function></funcdef> + <paramdef>const ne_inet_addr *<parameter>addr</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><function>ne_iaddr_make</function> creates an + <type>ne_inet_addr</type> object from a raw binary network + address; for instance the four bytes <literal>0x7f 0x00 0x00 + 0x01</literal> represent the IPv4 address + <literal>127.0.0.1</literal>. The object returned is suitable for + passing to <function>ne_sock_connect</function>. A binary IPv4 + address contains four bytes; a binary IPv6 address contains + sixteen bytes; addresses passed must be in network byte + order.</para> + + <para><function>ne_iaddr_cmp</function> can be used to compare two + network addresses; returning zero only if they are identical. The + addresses need not be of the same address type; if the addresses + are not of the same type, the return value is guaranteed to be + non-zero.</para> + + <para><function>ne_iaddr_print</function> can be used to print the + human-readable string representation of a network address into a + buffer, for instance the string + <literal>"127.0.0.1"</literal>.</para> + + <para><function>ne_iaddr_free</function> releases the memory + associated with a network address object.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para><function>ne_iaddr_make</function> returns &null; if the + address type passed is not supported (for instance on a platform + which does not support IPv6).</para> + + + <para><function>ne_iaddr_print</function> returns the + <parameter>buffer</parameter> pointer, and never &null;.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>The following example connects a socket to port 80 at the + address <literal>127.0.0.1</literal>.</para> + + <programlisting>unsigned char addr[] = "\0x7f\0x00\0x00\0x01"; +ne_inet_addr *ia; + +ia = ne_iaddr_make(ne_iaddr_ipv4, addr); +if (ia != NULL) { + ne_socket *sock = ne_sock_connect(ia, 80); + ne_iaddr_free(ia); + /* ... */ +} else { + /* ... */ +}</programlisting> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_addr_resolve"/></para> + </refsect1> + +</refentry> + diff --git a/doc/ref/init.xml b/doc/ref/init.xml new file mode 100644 index 0000000..32dcda5 --- /dev/null +++ b/doc/ref/init.xml @@ -0,0 +1,55 @@ +<refentry id="refsockinit"> + + <refmeta> + <refentrytitle>ne_sock_init</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_sock_init">ne_sock_init</refname> + <refpurpose>perform library initialization</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_socket.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>int <function>ne_sock_init</function></funcdef> + <void/> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>In some platforms and configurations, &neon; may be using + some socket or SSL libraries which require global initialization + before use. To perform this initialization, the + <function>ne_sock_init</function> function must be called once + before any other library functions are used.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para><function>ne_sock_init</function> returns zero on success, + or non-zero on error. If an error occurs, no further use of the + &neon; library should be attempted.</para> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="refneon"/></para> + </refsect1> + +</refentry> + diff --git a/doc/ref/neon.xml b/doc/ref/neon.xml new file mode 100644 index 0000000..9857c63 --- /dev/null +++ b/doc/ref/neon.xml @@ -0,0 +1,145 @@ +<refentry id="refneon"> + + <refmeta> + <refentrytitle>neon</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>neon</refname> + <refpurpose>HTTP and WebDAV client library</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para>neon is an HTTP and WebDAV client library. The major + abstractions exposed are the HTTP <emphasis>session</emphasis>, + created by <xref linkend="ne_session_create"/>; and the HTTP + <emphasis>request</emphasis>, created by <xref + linkend="ne_request_create"/>. HTTP authentication is handled + transparently for server and proxy servers, see <xref + linkend="ne_set_server_auth"/>; complete SSL/TLS support is also + included, see <xref linkend="ne_ssl_set_verify"/>.</para> + + </refsect1> + + <refsect1> + <title>Conventions</title> + + <para>Some conventions are used throughout the neon API, to + provide a consistent and simple interface; these are documented + below.</para> + + <refsect2> + <title>Thread-safeness and global initialization</title> + + <para>&neon; itself is implemented to be thread-safe (avoiding + any use of global state), but in some configurations makes use of + other libraries which require global initialization. The + <xref linkend="ne_sock_init"/> function should be called before + any other use of the &neon; library interface.</para> + </refsect2> + + <refsect2> + <title>Namespaces</title> + + <para>To avoid possible collisions between names used for symbols + and preprocessor macros by an application and the libraries it + uses, it is good practice for each library to reserve a particular + <emphasis>namespace prefix</emphasis>. An application which + ensures it uses no names with these prefixes is then guaranteed to + avoid such collisions.</para> + + <para>The &neon; library reserves the use of the namespace + prefixes <literal>ne_</literal> and <literal>NE_</literal>. The + libraries used by &neon; may also reserve certain namespaces; + collisions between these libraries and a &neon;-based application + will not be detected at compile time, since the underlying library + interfaces are not exposed through the &neon; header files. Such + collisions can only be detected at link time, when the linker + attempts to resolve symbols. The following list documents some of + the namespaces claimed by libraries used by &neon;; this list may + be incomplete.</para> + + <variablelist> + + <varlistentry> + <term>SSL, ssl, TLS, tls, ERR_, BIO_, d2i_, i2d_, ASN1_</term> + <listitem><simpara>Some of the many prefixes used by the OpenSSL + library; little attempt has been made to keep exported symbols + within any particular prefixes for this + library.</simpara></listitem> + </varlistentry> + + <varlistentry> + <term>XML_, Xml[A-Z]</term> <listitem><simpara>Namespaces + used by the expat library.</simpara></listitem> + </varlistentry> + + <varlistentry> + <term>xml[A-Z], html[A-Z], docb[A-Z]</term> + <listitem><simpara>Namespaces used by the libxml2 library; a + relatively small number of symbols are used without these + prefixes.</simpara></listitem> + </varlistentry> + + </variablelist> + + </refsect2> + + <refsect2> + <title>Argument validation</title> + + <para>&neon; does not attempt to validate that arguments passed to + functions conform to the API (for instance, checking that pointer + arguments are not &null;). Any use of the &neon; API which is not + documented to produce a certain behaviour results in + <emphasis>undefined behaviour</emphasis>, by definition.</para> + + </refsect2> + + <refsect2> + <title>URI paths, WebDAV metadata</title> + + <para>The path strings passed to any function must be + <emphasis>URI-encoded</emphasis> by the application; &neon; never + performs any URI encoding or decoding internally. WebDAV property + names and values must be valid UTF-8 encoded Unicode + strings.</para> + + </refsect2> + + <refsect2> + <title>Memory handling</title> + + <para>neon does not attempt to cope gracefully with an + out-of-memory situation; instead, by default, the + <function>abort</function> function is called to immediately + terminate the process. An application may register a custom + function which will be called before <function>abort</function> in + such a situation; see <xref linkend="ne_oom_callback"/>.</para> + + </refsect2> + + <refsect2> + <title>Callbacks and userdata</title> + + <para>Whenever a callback is registered, a + <literal>userdata</literal> pointer is also used to allow the + application to associate a context with the callback. The + userdata is of type <type>void *</type>, allowing any pointer to + be used.</para> + + </refsect2> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="refsess"/>, <xref linkend="ne_oom_callback"/></para> + </refsect1> + +</refentry> + diff --git a/doc/ref/opts.xml b/doc/ref/opts.xml new file mode 100644 index 0000000..fe8f368 --- /dev/null +++ b/doc/ref/opts.xml @@ -0,0 +1,126 @@ + <refentry id="refopts"> + + <refmeta> + <refentrytitle>ne_set_useragent</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_set_useragent">ne_set_useragent</refname> + <refname id="ne_set_persist">ne_set_persist</refname> + <refname id="ne_set_read_timeout">ne_set_read_timeout</refname> + <refname id="ne_set_expect100">ne_set_expect100</refname> + <refpurpose>common settings for HTTP sessions</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_session.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>void <function>ne_set_useragent</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>product</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_set_persist</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>int <parameter>flag</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_set_read_timeout</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>int <parameter>timeout</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_set_expect100</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>int <parameter>flag</parameter></paramdef> + </funcprototype> + +<!-- + <funcprototype> + <funcdef>const char *<function>ne_get_scheme</function></funcdef> + <paramdef>ne_sesssion *<parameter>session</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>const char *<function>ne_get_server_hostport</function></funcdef> + <paramdef>ne_sesssion *<parameter>session</parameter></paramdef> + </funcprototype> +--> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <!-- TODO: intro para? --> + + <para>The <literal>User-Agent</literal> request header is used +to identify the software which generated the request for statistical +or debugging purposes. neon does not send a +<literal>User-Agent</literal> header unless a call is made to the +<function>ne_set_useragent</function>. +<function>ne_set_useragent</function> must be passed a product string +conforming to RFC2616's product token grammar; of the form +<literal>"Product/Version"</literal>.</para> + + <para>By default neon will use a persistent connection +whenever possible. For specific applications, or for debugging +purposes, it is sometimes useful to disable persistent connections. +The <function>ne_set_persist</function> function will disable +persistent connections if passed a <parameter>flag</parameter> +parameter of <literal>0</literal>, and will enable them +otherwise.</para> + + <para>When neon reads from a socket, by default the read +operation will time out after 60 seconds, and the request will fail +giving an <errorcode>NE_TIMEOUT</errorcode> error. To configure this +timeout interval, call <function>ne_set_read_timeout</function> giving +the desired number of seconds as the <parameter>timeout</parameter> +parameter.</para> + + <para>An extension introduced in the HTTP/1.1 specification +was the use of the <literal>Expect: 100-continue</literal> header. +This header allows an HTTP client to be informed of the expected +response status before the request message body is sent: a useful +optimisation for situations where a large message body is to be sent. +The <function>ne_set_expect100</function> function can be used to +enable this feature by passing the <parameter>flag</parameter> +parameter as any non-zero integer.</para> + +<warning><para>Unfortunately, if this header is sent to a server which +is not fully compliant with the HTTP/1.1 specification, a deadlock +occurs resulting in a temporarily "hung" connection. neon will +recover gracefully from this situation, but only after a 15 second +timeout. It is highly recommended that this option is not enabled +unless it is known that the server in use correctly implements +<literal>Expect: 100-continue</literal> support.</para></warning> + + </refsect1> + + <refsect1> + <title>Examples</title> + <para>Set a user-agent string:</para> + <programlisting>&egsess; +ne_set_useragent(sess, "MyApplication/2.1");</programlisting> + + <para>Disable use of persistent connections:</para> + <programlisting>ne_session *sess = ne_session_create(...); +ne_set_persist(sess, 0);</programlisting> + + <para>Set a 30 second read timeout:</para> + <programlisting>&egsess; +ne_set_read_timeout(sess, 30);</programlisting> + + </refsect1> + + </refentry> diff --git a/doc/ref/req.xml b/doc/ref/req.xml new file mode 100644 index 0000000..7c36d5c --- /dev/null +++ b/doc/ref/req.xml @@ -0,0 +1,169 @@ + <refentry id="refreq"> + + <refmeta> + <refentrytitle>ne_request_create</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_request_create">ne_request_create</refname> + <refname id="ne_request_dispatch">ne_request_dispatch</refname> + <refname id="ne_request_destroy">ne_request_destroy</refname> + <refpurpose>low-level HTTP request handling</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_request.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>ne_request *<function>ne_request_create</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>method</parameter></paramdef> + <paramdef>const char *<parameter>path</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_request_dispatch</function></funcdef> + <paramdef>ne_request *<parameter>req</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_request_destroy</function></funcdef> + <paramdef>ne_request *<parameter>req</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>An HTTP request, represented by the +<type>ne_request</type> type, specifies that some operation is to be +performed on some resource. The +<function>ne_request_create</function> function creates a request +object, specifying the operation in the <parameter>method</parameter> +parameter. The location of the resource is determined by the server in +use for the session given by the <parameter>sess</parameter> +parameter, combined with the <parameter>path</parameter> parameter.</para> + +<para>The <parameter>path</parameter> string used must conform to the +<literal>abs_path</literal> definition given in RFC2396, with an +optional "?query" part, and must be URI-escaped by the caller (for +instance, using <function>ne_path_escape</function>. If the string +comes from an untrusted source, failure to perform URI-escaping +results in a security vulnerability.</para> + + <para>To dispatch a request, and process the response, the +<function>ne_request_dispatch</function> function can be used. An +alternative is to use the (more complex, but more flexible) +combination of the <function>ne_begin_request</function>, +<function>ne_end_request</function>, and +<function>ne_read_response_block</function> functions; see +<function>ne_begin_request</function>.</para> + + <para>To add extra headers in the request, the functions <xref +linkend="ne_add_request_header"/> and <xref +linkend="ne_print_request_header"/> can be used. To include a message +body with the request, one of the functions +<function>ne_set_request_body_buffer</function>, <xref +linkend="ne_set_request_body_fd"/>, or +<function>ne_set_request_body_provider</function> can be used.</para> + + <para>The return value of +<function>ne_request_dispatch</function> indicates merely whether the +request was sent and the response read successfully. To discover the +result of the operation, <xref linkend="ne_get_status"/>, along with +any processing of the response headers and message body.</para> + + <para>A request can only be dispatched once: calling +<function>ne_request_dispatch</function> more than once on a single +<type>ne_request</type> object produces undefined behaviour. Once all +processing associated with the request object is complete, use the +<function>ne_request_destroy</function> function to destroy the +resources associated with it. Any subsequent use of the request +object produces undefined behaviour.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para>The <function>ne_request_create</function> function +returns a pointer to a request object (and never &null;).</para> + + <para>The <function>ne_request_dispatch</function> function +returns zero if the request was dispatched successfully, and a +non-zero error code otherwise.</para> + + </refsect1> + +<!-- TODO: abs_path description in a NOTES section --> + + <refsect1> + <title>Errors</title> + + <variablelist> + <varlistentry><term><errorcode>NE_ERROR</errorcode></term> + <listitem> + <simpara>Request failed (see session error string)</simpara> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_LOOKUP</errorcode></term> + <listitem> + <simpara>The DNS lookup for the server (or proxy server) failed.</simpara> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_AUTH</errorcode></term> + <listitem> + <simpara>Authentication failed on the server.</simpara> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_PROXYAUTH</errorcode></term> + <listitem> + <simpara>Authentication failed on the proxy server.</simpara> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_CONNECT</errorcode></term> + <listitem> + <simpara>A connection to the server could not be established.</simpara> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_TIMEOUT</errorcode></term> + <listitem> + <simpara>A timeout occurred while waiting for the server to respond.</simpara> + </listitem> + </varlistentry> + </variablelist> + + </refsect1> + + <refsect1> + <title>Example</title> + + <para>An example of applying a <literal>MKCOL</literal> + operation to the resource at the location + <literal>http://www.example.com/foo/bar/</literal>:</para> + + <programlisting>ne_session *sess = ne_session_create("http", "www.example.com", 80); +ne_request *req = ne_request_create(sess, "MKCOL", "/foo/bar/"); +if (ne_request_dispatch(req)) { + printf("Request failed: %s\n", ne_get_error(sess)); +} +ne_request_destroy(req);</programlisting> + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_get_error"/>, <xref +linkend="ne_set_error"/>, <xref linkend="ne_get_status"/>, <xref +linkend="ne_add_request_header"/>, <xref +linkend="ne_set_request_body_buffer"/>.</para> + + </refsect1> + + </refentry> diff --git a/doc/ref/reqbody.xml b/doc/ref/reqbody.xml new file mode 100644 index 0000000..e2c8eb3 --- /dev/null +++ b/doc/ref/reqbody.xml @@ -0,0 +1,69 @@ + <refentry id="refreqbody"> + + <refmeta> + <refentrytitle>ne_set_request_body_buffer</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_set_request_body_buffer">ne_set_request_body_buffer</refname> + <refname id="ne_set_request_body_fd">ne_set_request_body_fd</refname> + <refpurpose>include a message body with a request</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_request.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>void <function>ne_set_request_body_buffer</function></funcdef> + <paramdef>ne_request *<parameter>req</parameter></paramdef> + <paramdef>const char *<parameter>buf</parameter></paramdef> + <paramdef>size_t <parameter>count</parameter></paramdef> + </funcprototype> + + <!-- this is a better interface for set_request_body_fd: + <funcprototype> + <funcdef>int <function>ne_set_request_body_fd</function></funcdef> + <paramdef>ne_request *<parameter>req</parameter></paramdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>off_t <parameter>begin</parameter></paramdef> + <paramdef>size_t <parameter>count</parameter></paramdef> + </funcprototype> + --> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <function>ne_set_request_body_buffer</function> +function specifies that a message body should be included with the +body, which is stored in the <parameter>count</parameter> bytes buffer +<parameter>buf</parameter>.</para> + +<!-- + <para>The <function>ne_set_request_body_fd</function> function +can be used to include a message body with a request which is read +from a file descriptor. The body is read from the file descriptor +<parameter>fd</parameter>, which must be a associated with a seekable +file (not a pipe, socket, or FIFO). <parameter>count</parameter> +bytes are read, beginning at offset <parameter>begin</parameter> +(passing <parameter>begin</parameter> as zero means the body is read +from the beginning of the file).</para> + +--> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_request_create"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/reqhdr.xml b/doc/ref/reqhdr.xml new file mode 100644 index 0000000..4d811b1 --- /dev/null +++ b/doc/ref/reqhdr.xml @@ -0,0 +1,63 @@ + <refentry id="refreqhdr"> + + <refmeta> + <refentrytitle>ne_add_request_header</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_add_request_header">ne_add_request_header</refname> + <refname id="ne_print_request_header">ne_print_request_header</refname> + <refpurpose>add headers to a request</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_request.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>void <function>ne_add_request_header</function></funcdef> + <paramdef>ne_request *<parameter>request</parameter></paramdef> + <paramdef>const char *<parameter>name</parameter></paramdef> + <paramdef>const char *<parameter>value</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_print_request_header</function></funcdef> + <paramdef>ne_request *<parameter>request</parameter></paramdef> + <paramdef>const char *<parameter>name</parameter></paramdef> + <paramdef>const char *<parameter>format</parameter></paramdef> + <paramdef>...</paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The functions <function>ne_add_request_header</function> +and <function>ne_print_request_header</function> can be used to add +headers to a request, before it is sent.</para> + + <para><function>ne_add_request_header</function> simply adds a +header of given <parameter>name</parameter>, with given +<parameter>value</parameter>.</para> + + <para><function>ne_print_request_header</function> adds a +header of given <parameter>name</parameter>, taking the value from the +<function>printf</function>-like <parameter>format</parameter> string +parameter and subsequent variable-length argument list.</para> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_request_create"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/resolve.xml b/doc/ref/resolve.xml new file mode 100644 index 0000000..067cf61 --- /dev/null +++ b/doc/ref/resolve.xml @@ -0,0 +1,145 @@ +<refentry id="refresolve"> + + <refmeta> + <refentrytitle>ne_addr_resolve</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_addr_resolve">ne_addr_resolve</refname> + <refname id="ne_addr_result">ne_addr_result</refname> + <refname id="ne_addr_first">ne_addr_first</refname> + <refname id="ne_addr_next">ne_addr_next</refname> + <refname id="ne_addr_error">ne_addr_error</refname> + <refname id="ne_addr_destroy">ne_addr_destroy</refname> + <refpurpose>functions to resolve hostnames to addresses</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_socket.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>ne_sock_addr *<function>ne_addr_resolve</function></funcdef> + <paramdef>const char *<parameter>hostname</parameter></paramdef> + <paramdef>int <parameter>flags</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_addr_result</function></funcdef> + <paramdef>const ne_sock_addr *<parameter>addr</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>const ne_inet_addr *<function>ne_addr_first</function></funcdef> + <paramdef>ne_sock_addr *<parameter>addr</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>const ne_inet_addr *<function>ne_addr_next</function></funcdef> + <paramdef>ne_sock_addr *<parameter>addr</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>char *<function>ne_addr_error</function></funcdef> + <paramdef>const ne_sock_addr *<parameter>addr</parameter></paramdef> + <paramdef>char *<parameter>buffer</parameter></paramdef> + <paramdef>size_t <parameter>bufsiz</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_addr_destroy</function></funcdef> + <paramdef>ne_sock_addr *<parameter>addr</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <function>ne_addr_resolve</function> function resolves + the given <parameter>hostname</parameter>, returning an + <type>ne_sock_addr</type> object representing the address (or + addresses) associated with the hostname. The + <parameter>flags</parameter> parameter is currently unused, and + must be passed as 0.</para> + + <para>The <parameter>hostname</parameter> passed to + <function>ne_addr_resolve</function> can be a DNS hostname + (e.g. <literal>"www.example.com"</literal>) or an IPv4 dotted quad + (e.g. <literal>"192.0.34.72"</literal>); or, on systems which + support IPv6, an IPv6 hex address, which may be enclosed in + brackets, e.g. <literal>"[::1]"</literal>.</para> + + <para>To determine whether the hostname was successfully resolved, + the <function>ne_addr_result</function> function is used, which + returns non-zero if an error occurred. If an error did occur, the + <function>ne_addr_error</function> function can be used, which + will copy the error string into a given + <parameter>buffer</parameter> (of size + <parameter>bufsiz</parameter>).</para> + + <para>The functions <function>ne_addr_first</function> and + <function>ne_addr_next</function> are used to retrieve the + Internet addresses associated with an address object which has + been successfully resolved. <function>ne_addr_first</function> + returns the first address; <function>ne_addr_next</function> + returns the next address after the most recent call to + <function>ne_addr_next</function> or + <function>ne_addr_first</function>, or &null; if there are no more + addresses. The <type>ne_inet_addr</type> pointer returned by + these functions can be passed to + <function>ne_sock_connect</function> to connect a socket.</para> + + <para>After the address object has been used, it should be + destroyed using <function>ne_addr_destroy</function>.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para><function>ne_addr_resolve</function> returns a pointer to an + address object, and never &null;. + <function>ne_addr_error</function> returns the + <parameter>buffer</parameter> parameter .</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>The code below prints out the set of addresses associated + with the hostname <literal>www.google.com</literal>.</para> + + <programlisting>ne_sock_addr *addr; +char buf[256]; + +addr = ne_addr_resolve("www.google.com", 0); +if (ne_addr_result(addr)) { + printf("Could not resolve www.google.com: %s\n", + ne_addr_error(addr, buf, sizeof buf)); +} else { + const ne_inet_addr *ia; + printf("www.google.com:"); + for (ia = ne_addr_first(addr); ia != NULL; ia = ne_addr_next(addr)) { + printf(" %s", ne_iaddr_print(ia, buf, sizeof buf)); + } + putchar('\n'); +} +ne_addr_destroy(addr); +</programlisting> + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_iaddr_print"/></para> + </refsect1> + +</refentry> + diff --git a/doc/ref/sess.xml b/doc/ref/sess.xml new file mode 100644 index 0000000..8fcd9cb --- /dev/null +++ b/doc/ref/sess.xml @@ -0,0 +1,123 @@ + <refentry id="refsess"> + + <refmeta> + <refentrytitle>ne_session_create</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_session_create">ne_session_create</refname> + <refname id="ne_close_connection">ne_close_connection</refname> + <refname id="ne_session_proxy">ne_session_proxy</refname> + <refname id="ne_session_destroy">ne_session_destroy</refname> + <refpurpose>set up HTTP sessions</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + <funcsynopsisinfo>#include <ne_session.h></funcsynopsisinfo> + <funcprototype> + <funcdef>ne_session *<function>ne_session_create</function></funcdef> + <paramdef>const char *<parameter>scheme</parameter></paramdef> + <paramdef>const char *<parameter>hostname</parameter></paramdef> + <paramdef>unsigned int <parameter>port</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_session_proxy</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>hostname</parameter></paramdef> + <paramdef>unsigned int <parameter>port</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_close_connection</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_session_destroy</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>An <type>ne_session</type> object represents an HTTP +session - a logical grouping of a sequence of HTTP requests made to a +certain server. Any requests made using the session can use a +persistent connection, share cached authentication credentials and any +other common attributes.</para> + + <para>A new HTTP session is created using +<function>ne_session_create</function>, giving the +<parameter>hostname</parameter> and <parameter>port</parameter> of the +server to use, along with the <parameter>scheme</parameter> used to +contact the server (usually <literal>"http"</literal>). Before the +first use of <function>ne_session_create</function> in a process, +<xref linkend="ne_sock_init"/> must have been called to perform any +global initialization needed by any libraries used by &neon;.</para> + + <para>To enable SSL/TLS for the session, pass the string +<literal>"https"</literal> as the <parameter>scheme</parameter> +parameter, and either register a certificate verification function +(see <xref linkend="ne_ssl_set_verify"/>) or load the appropriate CA +certificate (see <xref linkend="ne_ssl_load_ca"/>, <xref +linkend="ne_ssl_load_default_ca"/>).</para> + + <para>If an HTTP proxy server should be used for the session, +<function>ne_session_proxy</function> must be called giving the +hostname and port on which to contact the proxy.</para> + + <para>If it is known that the session will not be used for a +significant period of time, <function>ne_close_connection</function> +can be called to close the connection, if one remains open. Use of +this function is entirely optional, but it must not be called if there +is a request active using the session.</para> + + <para>Once a session has been completed, +<function>ne_session_destroy</function> must be called to destroy the +resources associated with the session. Any subsequent use of the +session pointer produces undefined behaviour.</para> + + </refsect1> + + <refsect1> + <title>Notes</title> + + <para>The hostname passed to +<function>ne_session_create</function> is resolved when the first +request using the session is dispached; a DNS resolution failure can +only be detected at that time (using the <literal>NE_LOOKUP</literal> +error code); see <xref linkend="ne_request_dispatch"/> for +details.</para> + + </refsect1> + + <refsect1> + <title>Return Values</title> + <para><function>ne_session_create</function> will return + a pointer to a new session object (and never &null;).</para> + </refsect1> + + <refsect1> + <title>Examples</title> + <para>Create and destroy a session:</para> + <programlisting>ne_session *sess; +sess = ne_session_create("http", "host.example.com", 80); +/* ... use sess ... */ +ne_session_destroy(sess); +</programlisting> + </refsect1> + + <refsect1> + <title>See Also</title> + + <para><xref linkend="ne_ssl_set_verify"/>, <xref linkend="ne_ssl_load_ca"/>, <xref linkend="ne_sock_init"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/shave.xml b/doc/ref/shave.xml new file mode 100644 index 0000000..a5b745a --- /dev/null +++ b/doc/ref/shave.xml @@ -0,0 +1,51 @@ + <refentry id="refshave"> + + <refmeta> + <refentrytitle>ne_shave</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>ne_shave</refname> + <refpurpose>trim whitespace from a string</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_string.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>char *<function>ne_shave</function></funcdef> + <paramdef>char *<parameter>str</parameter></paramdef> + <paramdef>const char *<parameter>whitespace</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><function>ne_shave</function> returns a portion of +<parameter>str</parameter> with any leading or trailing characters in +the <parameter>whitespace</parameter> array removed. +<parameter>str</parameter> may be modified.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>The following code segment will output + <literal>"fish"</literal>:</para> + + <programlisting>char s[] = ".!.fish!.!"; +puts(ne_shave(s, ".!"));</programlisting> + + </refsect1> + + </refentry> + diff --git a/doc/ref/sslca.xml b/doc/ref/sslca.xml new file mode 100644 index 0000000..c68739d --- /dev/null +++ b/doc/ref/sslca.xml @@ -0,0 +1,81 @@ + <refentry id="refsslca"> + + <refmeta> + <refentrytitle>ne_ssl_load_ca</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_ssl_load_ca">ne_ssl_load_ca</refname> + <refname id="ne_ssl_load_default_ca">ne_ssl_load_default_ca</refname> + <refpurpose>load SSL Certificate Authorities</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_session.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>int <function>ne_ssl_load_ca</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>filename</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_ssl_load_default_ca</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>To indicate that a given CA certificate is trusted by the user, +the certificate can be loaded using the <function>ne_ssl_load_ca</function> +function. The <parameter>filename</parameter> parameter given must specify +the location of a PEM-encoded CA certificate.</para> + + <para>The SSL library in use by neon may include a default set +of CA certificates; calling the +<function>ne_ssl_load_default_ca</function> function will indicate +that these CAs are trusted by the user.</para> + + <para>If no CA certificates are loaded, or the server presents +a certificate which is invalid in some way, then the certificate must +be manually verified (see <xref linkend="ne_ssl_set_verify"/>), otherwise the +connection will fail.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para>Both <function>ne_ssl_load_ca</function> and +<function>ne_ssl_load_default_ca</function> functions return +<literal>0</literal> on success, or non-zero on failure.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>Load the CA certificate stored in <filename>/path/to/cacert.pem</filename>:</para> + <programlisting>&egsess; + +if (ne_ssl_load_ca(sess, "/path/to/cacert.pem")) { + printf("Could not load CA cert: %s\n", ne_get_error(sess)); +}</programlisting> + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_get_error"/>, <xref + linkend="ne_ssl_set_verify"/></para> </refsect1> + + </refentry> diff --git a/doc/ref/sslcert.xml b/doc/ref/sslcert.xml new file mode 100644 index 0000000..46dbffd --- /dev/null +++ b/doc/ref/sslcert.xml @@ -0,0 +1,66 @@ + <refentry id="refsslcert"> + + <refmeta> + <refentrytitle>ne_ssl_certificate</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_ssl_certificate">ne_ssl_certificate</refname> + <refname id="ne_ssl_dname">ne_ssl_dname</refname> + <refpurpose>structures representing SSL certificates</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis><funcsynopsisinfo>#include <ne_session.h> + +/* A simplified X.509 distinguished name. */ +typedef struct { + const char *country, *state, *locality, *organization; + const char *organizationalUnit; + const char *commonName; +} <type>ne_ssl_dname</type>; + +/* A simplified SSL certificate. */ +typedef struct { + const <type>ne_ssl_dname</type> *subject, *issuer; + const char *from, *until; +} <type>ne_ssl_certificate</type>; + +</funcsynopsisinfo></funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <type>ne_ssl_dname</type> structure is used to +represent a simplified X.509 distinguished name, as used in SSL +certificates; a distinguished name is used to uniquely identify an +entity. Along with the fields giving the geographical and +organizational location of the entity, the +<structfield>commonName</structfield> field will be assigned the DNS +hostname of the entity. The +<function>ne_ssl_readable_dname</function> function can be used to +create a single-line string out of an <type>ne_ssl_dname</type> +structure.</para> + + <para>The <type>ne_ssl_certificate</type> structure is used to +represent a simplified SSL certificate; containing the distinguished +names of the <firstterm>issuer</firstterm> and +<firstterm>subject</firstterm> of the certificate. The issuer is the +entity which has digitally signed the certificate to guarantee its +authenticity; the subject is the owner of the certificate. A +certificate is only valid for a certain period of time: the +<structfield>from</structfield> and <structfield>until</structfield> +contain strings giving the validity period.</para> + + </refsect1> + + <refsect1> + <title>See Also</title> + <para><xref linkend="ne_ssl_dname"/>, <xref linkend="ne_ssl_set_verify"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/ssldname.xml b/doc/ref/ssldname.xml new file mode 100644 index 0000000..accc2cb --- /dev/null +++ b/doc/ref/ssldname.xml @@ -0,0 +1,66 @@ + + <refentry id="refssldname"> + + <refmeta> + <refentrytitle>ne_ssl_dname</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_ssl_readable_dname">ne_ssl_readable_dname</refname> + <refname id="ne_ssl_dname_cmp">ne_ssl_dname_cmp</refname> + <refpurpose>SSL distinguished name handling</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_ssl.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>const char *<function>ne_ssl_readable_dname</function></funcdef> + <paramdef>const ne_ssl_dname *<parameter>dname</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_ssl_dname_cmp</function></funcdef> + <paramdef>const ne_ssl_dname *<parameter>dn1</parameter></paramdef> + <paramdef>const ne_ssl_dname *<parameter>dn2</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <function>ne_ssl_readable_dname</function> function +creates a single-line, human-readable string out of an +<type>ne_ssl_dname</type> object. The returned string is +<function>malloc</function>()-allocated, and must be +<function>free</function>()d by the caller.</para> + + <para>The <function>ne_ssl_dname_cmp</function> function + compares two distinguished names, and returns zero if they are + equal, or non-zero otherwise.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para><function>ne_ssl_readable_dname</function> returns a + <function>malloc</function>-allocated string, and never + NULL.</para> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_ssl_certificate"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/sslvfy.xml b/doc/ref/sslvfy.xml new file mode 100644 index 0000000..98f0054 --- /dev/null +++ b/doc/ref/sslvfy.xml @@ -0,0 +1,140 @@ + <refentry id="refsslvfy"> + + <refmeta> + <refentrytitle>ne_ssl_set_verify</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_ssl_set_verify">ne_ssl_set_verify</refname> + <refpurpose>register an SSL certificate verification callback</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_session.h></funcsynopsisinfo> + + <!-- hard to put data type declarations here --> + + <funcprototype> + <funcdef>typedef int (*<function>ne_ssl_verify_fn</function>)</funcdef> + <paramdef>void *<parameter>userdata</parameter></paramdef> + <paramdef>int <parameter>failures</parameter></paramdef> + <paramdef>const ne_ssl_certificate *<parameter>cert</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_ssl_set_verify</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>ne_ssl_verify_fn <parameter>verify_fn</parameter></paramdef> + <paramdef>void *<parameter>userdata</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>To enable manual SSL certificate verification, a +callback can be registered using +<function>ne_ssl_set_verify</function>. If such a callback is not +registered, when a connection is established to an SSL server which +does not present a certificate signed by a trusted CA (see <xref +linkend="ne_ssl_load_ca"/>), or if the certificate presented is invalid in +some way, the connection will fail.</para> + + <para>When the callback is invoked, the +<parameter>failures</parameter> parameter gives a bitmask indicating +in what way the automatic certificate verification failed. The value +is equal to the bit-wise OR of one or more of the following +constants (and is guaranteed to be non-zero):</para> + + <variablelist> + <varlistentry><term><filename>NE_SSL_NOTYETVALID</filename></term> + <listitem> + <para>The certificate is not yet valid.</para> + </listitem> + </varlistentry> + <varlistentry><term><filename>NE_SSL_EXPIRED</filename></term> + <listitem> + <para>The certificate has expired.</para> + </listitem> + </varlistentry> + <varlistentry><term><filename>NE_SSL_CNMISMATCH</filename></term> + <listitem> + <para>The hostname used for the session does not match +the hostname to which the certificate was issued: this could mean that +the connection has been intercepted.</para> + </listitem> + </varlistentry> + <varlistentry><term><filename>NE_SSL_UNKNOWNCA</filename></term> + <listitem> + <para>The Certificate Authority which signed the certificate +is not trusted.</para> + </listitem> + </varlistentry> + </variablelist> + + <para>The <parameter>cert</parameter> parameter passed to the +callback describes the certificate which was presented by the server, +see <xref linkend="ne_ssl_certificate"/> for more details. The certificate +object given is only valid until the callback returns.</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para>The verification callback must return zero to indicate +that the certificate should be trusted; and non-zero otherwise (in +which case, the connection will fail).</para> + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>Manual certificate verification:</para> + <programlisting> +static int +my_verify(void *userdata, int failures, const ne_ssl_certificate *cert) +{ + /* leak the return values of ne_ssl_readable_dname for simplicity! */ + printf("Issuer: %s\n", ne_ssl_readable_dname(cert->issuer); + printf("Subject: %s\n", ne_ssl_readable_dname(cert->subject); + if (failures & NE_SSL_CNMISMATCH) { + printf("Server certificate was issued to `%s'; " + "connection may have been intercepted!\n", + cert->subject->commonName); + } + if (failures & NE_SSL_EXPIRED) { + printf("Server certificate expired on %s!", cert->until); + } + /* ... check for other failures ... */ + if (prompt_user()) + return 1; /* fail verification */ + else + return 0; /* trust certificate */ +} + +int +main(...) +{ + ne_session *sess = ne_session_create("https", "some.host.name", 443); + ne_ssl_set_verify(sess, my_verify, NULL); + ... +}</programlisting> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_ssl_certificate"/>, <xref linkend="ne_ssl_load_ca"/>, + <xref linkend="ne_ssl_dname"/>, <xref linkend="ne_ssl_readable_dname"/></para> + </refsect1> + + </refentry> diff --git a/doc/ref/status.xml b/doc/ref/status.xml new file mode 100644 index 0000000..56616ea --- /dev/null +++ b/doc/ref/status.xml @@ -0,0 +1,74 @@ + <refentry id="refstatus"> + + <refmeta> + <refentrytitle>ne_status</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_status">ne_status</refname> + <refpurpose>HTTP status structure</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis><funcsynopsisinfo>#include <ne_utils.h> + +typedef struct { + int major_version, minor_version; + int code, klass; + const char *reason_phrase; +} <type>ne_status</type>;</funcsynopsisinfo></funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>An <type>ne_status</type> type represents an HTTP +response status; used in response messages giving a result of request. +The <structfield>major_version</structfield> and +<structfield>minor_version</structfield> fields give the HTTP version +supported by the server issuing the response. The +<structfield>code</structfield> field gives the status code of the +result (lying between 100 and 999 inclusive), and the +<structfield>klass</structfield> field gives the class, which is equal +to the most significant digit of the status.</para> + + <para>There are five classes of HTTP status code defined by + RFC2616:</para> + + <variablelist> + <varlistentry> + <term><literal>1xx</literal></term> + <listitem><para>Informational response.</para></listitem> + </varlistentry> + + <varlistentry> + <term><literal>2xx</literal></term> + <listitem><para>Success: the operation was successful</para></listitem> + </varlistentry> + + <varlistentry> + <term><literal>3xx</literal></term> + <listitem><para>Redirection</para></listitem> + </varlistentry> + + <varlistentry> + <term><literal>4xx</literal></term> <listitem><para>Client + error: the request made was incorrect in some + manner.</para></listitem> + </varlistentry> + + <varlistentry> + <term><literal>5xx</literal></term> + <listitem><para>Server error</para></listitem> + </varlistentry> + </variablelist> + + </refsect1> + + <refsect1> <title>See also</title> <para><xref +linkend="ne_get_status"/>.</para> </refsect1> + + </refentry> diff --git a/doc/ref/tok.xml b/doc/ref/tok.xml new file mode 100644 index 0000000..2e6211f --- /dev/null +++ b/doc/ref/tok.xml @@ -0,0 +1,76 @@ + <refentry id="reftok"> + + <refmeta> + <refentrytitle>ne_token</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>ne_token</refname> + <refname>ne_qtoken</refname> + <refpurpose>string tokenizers</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_string.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>char *<function>ne_token</function></funcdef> + <paramdef>char **<parameter>str</parameter></paramdef> + <paramdef>char <parameter>sep</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>char *<function>ne_qtoken</function></funcdef> + <paramdef>char **<parameter>str</parameter></paramdef> + <paramdef>char <parameter>sep</parameter></paramdef> + <paramdef>const char *<parameter>quotes</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <!-- FIXME: italics on tokenize --> + + <para><function>ne_token</function> and +<function>ne_qtoken</function> tokenize the string at the location +stored in the pointer <parameter>str</parameter>. Each time the +function is called, it returns the next token, and modifies the +<parameter>str</parameter> pointer to point to the remainer of the +string, or &null; if there are no more tokens in the string. A token +is delimited by the separator character <parameter>sep</parameter>; if +<function>ne_qtoken</function> is used any quoted segments of the +string are skipped when searching for a separator. A quoted segment +is enclosed in a pair of one of the characters given in the +<parameter>quotes</parameter> string.</para> + + <para>The string being tokenized is modified each time +the tokenizing function is called; replacing the next separator +character with a &nul; terminator.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>The following function prints out each token in a +comma-separated string <parameter>list</parameter>, which is +modified in-place:</para> + + <programlisting>static void splitter(char *list) +{ + do { + printf("Token: %s\n", ne_token(&list, ',')); + while (list); +}</programlisting> + + </refsect1> + + </refentry> diff --git a/doc/ref/vers.xml b/doc/ref/vers.xml new file mode 100644 index 0000000..2e7a50d --- /dev/null +++ b/doc/ref/vers.xml @@ -0,0 +1,63 @@ +<refentry id="refvers"> + + <refmeta> + <refentrytitle>ne_version_match</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>ne_version_match</refname> + <refname>ne_version_string</refname> + <refpurpose>library versioning</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_utils.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>int <function>ne_version_match</function></funcdef> + <paramdef>int <parameter>major</parameter></paramdef> + <paramdef>int <parameter>minor</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>const char *<function>ne_version_string</function></funcdef> + <void/> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <function>ne_version_match</function> function returns + non-zero if the library version is not of major version + <parameter>major</parameter>, or the minor version is less than + <parameter>minor</parameter>. For &neon; versions 0.x, every + minor version is assumed to be incompatible with every other minor + version.</para> <!-- TODO: remove that for 1.0 --> + + <para>The <function>ne_version_string</function> function returns + a string giving the library version.</para> + + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>To require &neon; 1.x, version 1.2 or later:</para> + + <programlisting>if (ne_version_match(1, 2)) { + printf("Library version out of date: 1.2 required, found %s.", + ne_version_string()); + exit(1); +}</programlisting> + + </refsect1> + +</refentry> diff --git a/doc/refentry.xml b/doc/refentry.xml new file mode 100644 index 0000000..bf724e0 --- /dev/null +++ b/doc/refentry.xml @@ -0,0 +1,56 @@ +<refentry id="refXXXX"> + + <refmeta> + <refentrytitle>ne_foo</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_foo">ne_foo</refname> + <refname id="ne_bar">ne_bar</refname> + <refpurpose>functions which do foo and bar</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_header.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>void <function>ne_set_useragent</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>product</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>XXX</para> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para>XXX</para> + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>XXX</para> + </refsect1> + + <refsect1> + <title>See also</title> + + <para>XXX</para> + </refsect1> + +</refentry> + diff --git a/doc/using-neon.txt b/doc/using-neon.txt new file mode 100644 index 0000000..45ddfc3 --- /dev/null +++ b/doc/using-neon.txt @@ -0,0 +1,166 @@ + +Guide to neon +============= + +Using libneon from applications +------------------------------- + +The neon source package is designed to be easily incorporated into +applications: + +- autoconf macros are distributed in the 'macros' subdirectory of the + neon distribution. Use NEON_LIBRARY from your configure.in to check + for the presence of the neon library installed on the system. The + macro adds an '--with-neon=...' argument to configure, which allows + the user to specify a location for the library (the standard /usr + and /usr/local directories are checked automatically without having + to be specified). + +- The 'src' directory of the neon package can be imported directly + into your application, if you do not wish to add an external + dependency. If you wish to bundle, use the NEON_BUNDLED macro + to configure neon in your application: here, the neon sources are + bundled in a directory called 'libneon': + + NEON_BUNDLED(libneon, ...) + + If your application supports builds where srcdir != builddir, you + should use the NEON_VPATH_BUNDLED macro like this: + + NEON_VPATH_BUNDLED(${srcdir}/libneon, libneon, ...) + + (thanks to Peter Moulder for getting this working properly). + + If you use this macro, a '--with-included-neon' option will + be added to the generated configure script. This allows the user + to force the bundled neon to be used in the application, rather than + any neon library found on the system. If you allow neon to be + configured this way, you must also configure an XML parser. Use + the NEON_XML_PARSER macro to do this. + +- The final argument to the _BUNDLED macros is a set of actions which + are executed if the bundled build *is* chosen (rather than an + external neon which might have been found on the user's system). + In here, use either the NEON_LIBTOOL_BUILD or NEON_NORMAL_BUILD + macro to set up the neon Makefile appropriately: including adding + the neon source directory to the recursive make. + +- A full fragment might be: + + NEON_BUNDLED(libneon, [ + NEON_NORMAL_BUILD + NEON_XML_PARSER + SUBDIRS="libneon $SUBDIRS" + ]) + + This means the bundled neon source directory (called 'libneon') is + used if no neon is found on the system, and the standard XML parser + search is used. + +The neon API +============ + +neon offers two levels of API for use in applications: + +- Low-level HTTP request/response handling +- High-level method invocation + +The low-level interface allows for easily designing new method +handlers, taking care of things like persistent connections, +authentication, and proxy servers. The high-level interface allows +you to easily integrate existing HTTP (and WebDAV) methods into your +application. This document details both interfaces. + +N.B.: Documentation is always WRONG. The definitive API reference is in +src/*.c, with src/*.h is hopefully fairly similar. + +PLEASE NOTE: the neon API is NOT STABLE, and is not considered to be +stable or backwards-compatible until version 1.0 is reached. If +you are not happy with this constraint, then please either: + +1. pick a version of neon and never upgrade +2. don't use neon (yet). +3. contribute sufficient development resources to neon that all + bugs are fixed yesterday, features added tomorrow, and 1.0 is + reached on by the end of the week. + +An Important Note +----------------- + +Most neon functions which allocate memory with malloc() will call +abort() if malloc() returns NULL. This is a design decision, the +rationale being: + +- it makes the interfaces cleaner. + +- if malloc() DOES return NULL there is not much you can do about it. + +- Apparently, malloc() won't return NULL on systems which over-commit + memory (e.g. Linux), so it doesn't make any real difference anyway. + +The author is open to persuasion on this: mail neon@webdav.org. + +Namespaces +---------- + +neon reserves these namespaces: + ne_* + uri_* + sock_* + +Eventually all symbols globally defined by the library should fall +within a reserved namespace. The author is considering moving +all symbols into the ne_* namespace. + +Note that the XML parser used will also reserve a namespace: +expat takes XML_*, libxml takes xml* + +The http_session type +--------------------- + +The http_session type is used whether you are writing to the low-level +or the high-level interface. An http_session object is created to +store data which persists beyond a single HTTP request: + + - Protocol options, e.g. (proxy) server details + - Authentication information + - Persistent connection + +A session is created with the 'ne_session_create' call. Before +creating a request for the session, the server details must be set, as +follows: + + ne_session *sess; + /* Initialize the socket library */ + sock_init(); + /* Create the session */ + sess = ne_session_create(); + /* Optionally, set a proxy server */ + ne_session_proxy(sess, "proxy.myisp.com", 8080); + /* Set the server */ + ne_session_server(sess, "my.server.com", 80); + +The proxy server should be set BEFORE the origin server; otherwise a +DNS lookup will be performed on the origin server by the +ne_session_server call, which may well fail if the client is +firewalled. ne_session_{proxy,server} will return NE_LOOKUP if +the DNS lookup fails; otherwise NE_OK. + +The 'ne_set_persist' call can be used to turn off persistent +connection handling: it is on by default. + +The 'ne_set_useragent' call can be used to set the User-Agent header +to be sent with requests. A product token of the form +"myhttpclient/0.1.2" should be passed, and will have "neon/x.y.z" +appended in the actual header sent. + +When a session has been finished with, it should be destroyed using +ne_session_destroy. Any subsequent operations on the session object +will have undefined results (i.e. will segfault). + +Low-level HTTP Request/Response Handling +---------------------------------------- + +... + + diff --git a/doc/using.xml b/doc/using.xml new file mode 100644 index 0000000..efb53d1 --- /dev/null +++ b/doc/using.xml @@ -0,0 +1,119 @@ + <sect1 id="using"> + <title>How to use neon from your application</title> + + <para>This section describes how to add &neon; support to an + application. If you just want to quickly try out &neon;, use + the <xref linkend="refconfig"/> script.</para> + + <para>The &neon; source code is designed to be easily embedded + into an application source tree. &neon; has no dependencies on + libraries other than an SSL toolkit and XML parser, though the + source tree can be configured to have no support for SSL or XML + if desired. To configure the &neon; source code some <ulink + url="http://www.gnu.org/software/autoconf/">GNU autoconf</ulink> + macros are supplied, which can be used in a number of ways, as + follows:</para> + + <itemizedlist> + <listitem> + + <para>autoconf macros are distributed in the 'macros' + subdirectory of the neon distribution. Use the NEON_LIBRARY + macro from your configure.in to check for the presence of + the neon library installed on the system. The macro adds an + '--with-neon=...' argument to configure, which allows the + user to specify a location for the library (the standard + /usr and /usr/local directories are checked automatically + without having to be specified).</para></listitem> + + <listitem><para>The 'src' directory of the neon package can be + imported directly into your application, if you do not wish + to add an external dependency. If you wish to bundle, use + the NEON_BUNDLED macro to configure neon in your application: + here, the neon sources are bundled in a directory called + 'libneon':</para> + + <programlisting>NEON_BUNDLED(libneon, ...)</programlisting> + + <para>If your application supports builds where srcdir != builddir, + you should use the NEON_VPATH_BUNDLED macro like this:</para> + + <programlisting>NEON_VPATH_BUNDLED(${srcdir}/libneon, libneon, ...)</programlisting> + + <para>If you use this macro, a '--with-included-neon' option + will be added to the generated configure script. This + allows the user to force the bundled neon to be used in the + application, rather than any neon library found on the + system. If you allow neon to be configured this way, you + must also configure an XML parser. Use the NEON_XML_PARSER + macro to do this.</para></listitem> + + <listitem><para>The final argument to the _BUNDLED macros is a + set of actions which are executed if the bundled build *is* + chosen (rather than an external neon which might have been + found on the user's system). In here, use either the + NEON_LIBTOOL_BUILD or NEON_NORMAL_BUILD macro to set up the + neon Makefile appropriately: including adding the neon source + directory to the recursive make.</para></listitem> + + </itemizedlist> + + <para>A full fragment might be:</para> + +<programlisting>NEON_BUNDLED(libneon, [ + NEON_NORMAL_BUILD + NEON_XML_PARSER + SUBDIRS="libneon $SUBDIRS" +])</programlisting> + + <para>This means the bundled neon source directory (called 'libneon') + is used if no neon is found on the system, and the standard XML + parser search is used.</para> + + </sect1> + + <sect1 id="compliance"> + <title>Protocol compliance</title> + + <para>&neon; is intended to be compliant with the IETF + protocol standards it implements, with a few exceptions where + real-world use has necessitated minor deviations. These + exceptions are documented in this section.</para> + + <sect2><title>RFC 2518, HTTP Extensions for Distributed Authoring—WebDAV</title> + + <para>&neon; is deliberately not compliant with section + 23.4.2, and treats property names as a (namespace-URI, name) + pair. This is <ulink + url="http://lists.w3.org/Archives/Public/w3c-dist-auth/1999OctDec/0343.html">generally + considered</ulink> to be correct behaviour by the WebDAV + working group, and is likely to formally adopted in a future + revision of the specification.</para></sect2> + + <sect2><title>RFC 2616, Hypertext Transfer Protocol—HTTP/1.1</title> + + <para>There is some confusion in this specification about the + use of the <quote>identity</quote> + <firstterm>transfer-coding</firstterm>. &neon; treats the + presence of <emphasis>any</emphasis> + <literal>Transfer-Encoding</literal> response header as an + indication that the response message uses the + <quote>chunked</quote> transfer-coding. This was the + suggested resolution <ulink + url="http://lists.w3.org/Archives/Public/ietf-http-wg-old/2001SepDec/0018.html">proposed + by Larry Masinter</ulink>.</para></sect2> + + <sect2> + <title>RFC 2617, HTTP Authentication: Basic and Digest Access Authentication</title> + + <para>&neon; is not strictly compliant with the quoting rules + given in the grammar for the <literal>Authorization</literal> + header. The grammar requires that the <literal>qop</literal> + and <literal>algorithm</literal> parameters are not quoted, + however one widely deployed server implementation + (Microsoft® IIS 5) rejects the request if these parameters + are not quoted. &neon; sends these parameters with + quotes—this is not known to cause any problems with + other server implementations.</para></sect2> + + </sect1> diff --git a/doc/xml.xml b/doc/xml.xml new file mode 100644 index 0000000..c001073 --- /dev/null +++ b/doc/xml.xml @@ -0,0 +1,207 @@ +<!-- neon XML interface -*- text -*- --> + +<sect1 id="xml"> + + <title>Parsing XML</title> + + <para>The &neon; XML interface is exposed by the + <filename>ne_xml.h</filename> header file. This interface gives a + wrapper around the standard <ulink + url="http://www.saxproject.org/">SAX</ulink> API used by XML + parsers, with an additional abstraction, <firstterm>stacked SAX + handlers</firstterm>, and also giving consistent <ulink + url="http://www.w3.org/TR/REC-xml-names">XML Namespace + support</ulink>.</para> + +<sect2 id="xml-sax"> + <title>Introduction to SAX</title> + + <para>A SAX-based parser works by emitting a sequence of + <firstterm>events</firstterm> to reflect the tokens being parsed + from the XML document. For example, parsing the following document + fragment: + +<programlisting><![CDATA[ +<hello>world</hello> +]]></programlisting> + + results in the following events: + + <orderedlist> + <listitem> + <simpara>&startelm; "hello"</simpara> + </listitem> + <listitem> + <simpara>&cdata; "world"</simpara> + </listitem> + <listitem> + <simpara>&endelm; "hello"</simpara> + </listitem> + </orderedlist> + + This example demonstrates the three event types used used in the + subset of SAX exposed by the &neon; XML interface: &startelm;, + &cdata; and &endelm;. In a C API, an <quote>event</quote> is + implemented as a function callback; three callback types are used in + &neon;, one for each type of event.</para> + +</sect2> + +<sect2 id="xml-stacked"> + <title>Stacked SAX handlers</title> + + <para>WebDAV property values are represented as fragments of XML, + transmitted as parts of larger XML documents over HTTP (notably in + the body of the response to a <literal>PROPFIND</literal> request). + When &neon; parses such documents, the SAX events generated for + these property value fragments may need to be handled by the + application, since &neon; has no knowledge of the structure of + properties used by the application.</para> + + <para>To solve this problem<footnote><para>This + <quote>problem</quote> only needs solving because the SAX interface + is so inflexible when implemented as C function callbacks; a better + approach would be to use an XML parser interface which is not based + on callbacks.</para></footnote> the &neon; XML interface introduces + the concept of a <firstterm>SAX handler</firstterm>. A SAX handler + comprises a &startelm;, &cdata; and &endelm; callback; the + &startelm; callback being defined such that each handler may + <emphasis>accept</emphasis> or <emphasis>decline</emphasis> the + &startelm; event. Handlers are composed into a <firstterm>handler + stack</firstterm> before parsing a document. When a new &startelm; + event is generated by the XML parser, &neon; invokes each &startelm; + callback in the handler stack in turn until one accepts the event. + The handler which accepts the event will then be subsequently be + passed &cdata; events if the element contains character data, + followed by an &endelm; event when the element is closed. If no + handler in the stack accepts a &startelm; event, the branch of the + tree is ignored.</para> + + <para>To illustrate, given a handler A, which accepts the + <literal>cat</literal> and <literal>age</literal> elements, and a + handler B, which accepts the <literal>name</literal> element, the + following document: + +<example id="xml-example"> +<title>An example XML document</title> +<programlisting><![CDATA[ +<cat> + <age>3</age> + <name>Bob</name> +</cat> +]]></programlisting></example> + + would be parsed as follows: + + <orderedlist> + <listitem> + <simpara>A &startelm; "cat" → <emphasis>accept</emphasis></simpara> + </listitem> + <listitem> + <simpara>A &startelm; "age" → <emphasis>accept</emphasis></simpara> + </listitem> + <listitem> + <simpara>A &cdata; "3"</simpara> + </listitem> + <listitem> + <simpara>A &endelm; "age"</simpara> + </listitem> + <listitem> + <simpara>A &startelm; "name" → <emphasis>decline</emphasis></simpara> + </listitem> + <listitem> + <simpara>B &startelm; "name" → <emphasis>accept</emphasis></simpara> + </listitem> + <listitem> + <simpara>B &cdata; "Bob"</simpara> + </listitem> + <listitem> + <simpara>B &endelm; "name"</simpara> + </listitem> + <listitem> + <simpara>A &endelm; "cat"</simpara> + </listitem> + </orderedlist></para> + + <para>The search for a handler which will accept a &startelm; event + begins at the handler of the parent element and continues toward the + top of the stack. For the root element, it begins at the base of + the stack. In the above example, handler A is at the base, and + handler B at the top; if the <literal>name</literal> element had any + children, only B's &startelm; would be invoked to accept + them.</para> + +</sect2> + +<sect2 id="xml-state"> + <title>Maintaining state</title> + + <para>To facilitate communication between independent handlers, a + <firstterm>state integer</firstterm> is associated with each element + being parsed. This integer is returned by &startelm; callback and + is passed to the subsequent &cdata; and &endelm; callbacks + associated with the element. The state integer of the parent + element is also passed to each &startelm; callback, the value zero + used for the root element (which by definition has no + parent).</para> + + <para>To further extend <xref linkend="xml-example"/>: if handler A + defines that the state of the root element <sgmltag>cat</sgmltag> + will be <literal>42</literal>, the event trace would be as + follows: + + <orderedlist> + <listitem> + <simpara>A &startelm; (parent = 0, "cat") → + <emphasis>accept</emphasis>, state = 42 + </simpara> + </listitem> + <listitem> + <simpara>A &startelm; (parent = 42, "age") → + <emphasis>accept</emphasis>, state = 50 + </simpara> + </listitem> + <listitem> + <simpara>A &cdata; (state = 50, "3")</simpara> + </listitem> + <listitem> + <simpara>A &endelm; (state = 50, "age")</simpara> + </listitem> + <listitem> + <simpara>A &startelm; (parent = 42, "name") → + <emphasis>decline</emphasis></simpara> + </listitem> + <listitem> + <simpara>B &startelm; (parent = 42, "name") → + <emphasis>accept</emphasis>, state = 99</simpara> + </listitem> + <listitem> + <simpara>B &cdata; (state = 99, "Bob")</simpara> + </listitem> + <listitem> + <simpara>B &endelm; (state = 99, "name")</simpara> + </listitem> + <listitem> + <simpara>A &endelm; (state = 42, "cat")</simpara> + </listitem> + </orderedlist></para> + + <para>To avoid collisions between state integers used by different + handlers, the interface definition of any handler includes the range + of integers it will use.</para> + +</sect2> + +<sect2 id="xml-ns"> + <title>XML namespaces</title> + + <para>To support XML namespaces, every element name is represented + as a <emphasis>(namespace, name)</emphasis> pair. The &startelm; + and &endelm; callbacks are passed namespace and name strings + accordingly. If an element in the XML document has no declared + namespace, the namespace given will be the empty string, + <literal>""</literal>.</para> + +</sect2> + +</sect1> diff --git a/install-sh b/install-sh new file mode 100755 index 0000000..ebc6691 --- /dev/null +++ b/install-sh @@ -0,0 +1,250 @@ +#! /bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/macros/ChangeLog b/macros/ChangeLog new file mode 100644 index 0000000..5a5ac37 --- /dev/null +++ b/macros/ChangeLog @@ -0,0 +1,929 @@ +Mon Apr 21 18:24:12 2003 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (HAVE_EXPAT): Fail if --with-expat is given + but expat.h is not found. + +Wed Mar 26 20:29:11 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SSL): And add ne_stubssl to NEON_EXTRAOBJS for + non-SSL build. + +Tue Mar 25 20:43:01 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SSL): Add ne_openssl to NEON_EXTRAOBJS. + +Mon Mar 17 20:34:55 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Check for netdb.h. + +Sun Mar 16 14:22:02 2003 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NE_XML_BUNDLED_EXPAT): Define + HAVE_XMLPARSE_H. + +Sun Mar 16 11:47:35 2003 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Also fix + non-included-expat build for when included expat is not an option. + +Sun Mar 16 11:20:23 2003 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Fix non-included-expat + build broken in previous commit. + +Sun Mar 16 09:06:41 2003 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Fix --with-included-expat + support. + +Sun Mar 9 08:55:04 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_SEARCH_LIBS): Bug fix to always compare against + `extras' if given. Add support for optional `actions-if-found' + argument. + + * neon-xml-parser.m4: Largely rewritten. Drop support for + libxml 1.x; require expat 1.95.x. + +Sun Mar 9 08:50:22 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Check for stpcpy. + +Mon Mar 3 22:15:56 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_CHECK_FUNCS): Check whether h_errno is declared. + +Wed Feb 19 21:35:20 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_FORMAT): Revert use of 'z' modifier; breaks on + CygWin. + +Tue Jan 14 17:06:07 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_FORMAT): Prefer the C99 'z' modifier to printf + size_t/ssize_t values where available. + +Fri Jan 3 23:12:20 2003 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_PKG_CONFIG): New macro. + (NEON_SSL): Use NE_PKG_CONFIG rather than PKG_CHECK_MODULES. + +Mon Dec 16 20:02:45 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SSL): Only check for OpenSSL <=0.9.6 if version is + known to be <=0.9.7. + +Mon Dec 16 19:01:57 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Use pkg-config data to determine location OpenSSL + libraries/headers, if available. + +Tue Nov 19 11:21:31 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-test.m4 (NEON_TEST): Suggest this macro is run before + NEON_XML_PARSER. + +Mon Oct 7 22:22:12 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_CHECK_SSLVER): New macro. + (NEON_SSL): Use NE_CHECK_SSLVER. Add --with-egd argument; + conditionally enable EGD, optionally using only a specific EGD + socket path, and only if using OpenSSL before 0.9.7. + +Tue Sep 24 21:36:01 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_ZLIB_VERSION): Removed macro. + (NEON_ZLIB): Removed --with-force-zlib flag; don't check zlib + version. + (NEON_SSL): Removed --with-force-ssl flag, only require OpenSSL + 0.9.6 or later. + +Sat Aug 31 17:28:15 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_FORMAT_PREP): Add check for gcc -Wformat -Werror + sanity. + (NEON_FORMAT): Only use gcc -Wformat -Werror if sanity is assured. + +Fri Aug 30 22:07:59 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Set NEON_INTERFACE_VERSION according to + NEON_VERSION_MINOR, NEON_VERSION_RELEASE, for the duration of neon + 0.x releases. + +Sun Aug 25 23:52:38 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_CHECK_FUNCS): New macro. + (LIBNEON_SOURCE_CHECKS): Use NE_CHECK_FUNCS rather than + AC_CHECK_FUNCS, so $NEON_LIBS are searched. + +Sun Aug 25 11:53:20 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Require inet_ntop as well for + USE_GETADDRINFO test. + +Sun Aug 18 22:50:09 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Remove redundant check for + SIGPIPE definition. + +Sun Aug 18 22:41:15 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_SEARCH_LIBS): Take optional fourth argument. + (LIBNEON_SOURCE_CHECKS): Define USE_GETADDRINFO if getaddrinfo and + gai_strerror are present. Otherwise, check for hstrerror, + possibly in -lresolv. + + * neon-test.m4: Don't check for hstrerror(). + +Sun Aug 18 22:31:13 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_FIND_PARSER_H, NEON_XML_LIBXML2): + Re-order checks to skip redundant tests, speeding up common case. + +Fri Aug 9 19:47:14 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SSL): Require OpenSSL 0.9.6f or later. + +Wed Jul 31 23:01:17 2002 Joe Orton <joe@manyfish.co.uk> + + * strftime.m4, readline.m4: Removed from neon/macros CVS module. + +Tue Jul 30 19:09:20 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON_CHECKS): Require AC_TYPE_SIZE_T, + AC_TYPE_OFF_T. + + * neon-test.m4 (NEON_TEST): Require AC_TYPE_PID_T. + +Tue Jul 30 19:06:48 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SSL): Require OpenSSL 0.9.6e or later for security + fixes; add --with-force-ssl to override version check. + +Fri Jul 26 00:01:07 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_MACOSX): Just check for any Darwin system. + +Sat Jul 20 10:40:29 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_WITH_LIBS): Allow a colon-separated list of + directories for --with-libs argument. + +Thu Jul 18 20:07:43 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Require AC_FUNC_STRERROR_R. + +Wed Jul 17 23:26:51 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_CHECK_VERSION): Rewrite to cache results, + simplify, and use new ne_version_match. + (NE_ZLIB_VERSION): Cache result. + +Fri Jul 5 12:57:56 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SSL): Fail if openssl/opensslv.h header is not + found, or if OpenSSL version is earlier than 0.9.6. + +Sun Jun 16 14:04:18 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_FORMAT): Take optional third argument giving + format string specifier to use. + (NEON_COMMON_CHECKS): Get format string for ssize_t; fix to + use 'u' specifier for size_t format string. + +Thu Jun 13 20:34:35 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Undocument use of =DIR + parameter to --with-expat, as it gives false expectations. + +Wed Jun 12 23:26:40 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_WITH_LIBS): New macro. + +Mon Jun 10 22:31:50 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_WARNINGS): Replace -ansi-pedantic with -pedantic. + +Sun May 26 19:08:12 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_LIBXML2): Check for + libxml/xmlversion.h header too. + +Wed May 22 09:54:42 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_MACOSX): Cache result. + (NE_COMMON_CHECKS): Simplify tm_gmtoff check further: use + AC_CHECK_MEMBERS. + +Mon May 20 21:18:06 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_SEARCH_LIBS): Cache results. + +Mon May 20 20:55:04 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Use AC_CACHE_CHECK to check for + SIGPIPE in signal.h; don't invade the ac_ namespace with cache + variables. Cache results of tm_gmtoff test too. + +Mon May 20 20:35:22 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_SNPRINTF): Simplify logic. + +Sun May 19 20:23:55 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_WARNINGS): Remove with_warnings variable; + simplify. + +Sun May 19 09:35:08 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_FIND_AR): Fix $PATH handling on some Linux + platforms. + +Sun May 19 09:05:22 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_FIND_AR): New macro. + (NEON_NORMAL_BUILD): Require NE_FIND_AR. + + * neon-test.m4: Require NE_FIND_AR. Check for hstrerror(). + +Fri May 17 23:37:19 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SSL): Allow --without-ssl again. + +Wed May 15 21:00:15 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_LIBXML2): sed '-L/usr/lib ' out of + xml2-config --libs output. + +Sat May 11 15:30:38 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SOCKS): Moved and simplified from neon-socks.m4. + Drop support for specifying directory argument; fail if + --with-socks is given and socks.h is not found. + + * neon-socks.m4: Removed file. + +Sat May 11 15:22:36 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_DEBUG): Moved and simplified from neon-debug.m4. + + * neon-debug.m4: Removed file. + +Sat May 11 13:40:29 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_WARNINGS): Moved and simplified from + neon-warnings.m4. + + * neon-warnings.m4: Removed file. + +Sat May 11 13:26:00 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_SSL): Simplified version of NEON_SSL from + neon-ssl.m4. Check for ssl.h; detect OpenSSL ENGINE correctly + when -lcrypto requries -ldl. + + * neon-ssl.m4: Removed file. + +Sat May 11 13:16:27 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_SEARCH_LIBS): Allow passing 'extralibs' to include + in library list when link against a specified library fails. + Prepend found library/libraries to $NEON_LIBS. + +Sat May 11 12:40:24 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER), + * neon.m4 (NEON_REPLACE_SNPRINTF): + Use AC_LIBOBJ rather than modify LIBOBJS directly, to appease + autoconf 2.53. + +Wed May 1 22:32:10 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_LIBXML1): Fix syntax error in + libxml 1.x detection causing spurious or missing warning message. + +Thu Apr 25 07:38:33 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_EXTERNAL_EXPAT): Check for expat.h + too, to support expat 1.95.x (Branko Èibej). + +Tue Apr 23 21:09:55 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_FIND_PARSER_H): New macro, factored out + from NEON_XML_LIBXML2. + (NEON_XML_LIBXML2, NEON_XML_LIBXML1): Use it. + +Tue Apr 23 20:54:30 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_LIBXML2): Check for parser.h or + libxml/parser.h, or fail. + +Sat Apr 13 22:35:01 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_SNPRINTF): Define NEON_TRIO in NEON_CFLAGS, export + it from 'neon-config --cflags' output. + +Fri Apr 5 23:40:00 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_ZLIB, NE_ZLIB_VERSION): Add --with-force-zlib, to + skip zlib version check. Simplify neon_zlib_message handling a + little. + +Tue Mar 12 00:18:00 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_ZLIB_VERSION): New macro. + (NEON_ZLIB): Use it to require zlib 1.1.4. + +Sun Mar 10 22:05:26 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_ZLIB): Don't add ne_compress to NEON_EXTRAOBJS. + +Mon Mar 4 21:04:28 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-ssl.m4 (NEON_SSL): Avoid adding -I/usr/include to CFLAGS + during build as well as not exporting it via neon-config. + +Tue Feb 19 21:30:50 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-socks.m4 (NEON_SOCKS): Fix configure argument name, export + -L argument in NEON_LIBS, check for socks.h not sock.h, define + NEON_SOCKS. + +Sun Jan 13 20:07:51 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_MACOSX): New macro. + (NEON_COMMON_CHECKS): Call it. + +Sun Jan 6 21:35:08 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Add XML_BYTE_ORDER to + CPPFLAGS; mini-expat doesn't pick up config.h. + +Tue Jan 1 23:30:03 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Set XML_BYTE_ORDER + appropriately. + +Tue Jan 1 22:50:15 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_CHECK_VERSION): Don't add libs for external neon + to NEON_LIBS here. + +Tue Jan 1 22:44:05 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_LIBXML2, NEON_XML_LIBXML1): Alter + CPPFLAGS only, not CFLAGS. + +Tue Jan 1 21:49:16 2002 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_USE_EXTERNAL): Factored out from NEON_COMMON. + (NEON_COMMON): Use NEON_USE_EXTERNAL; simplify, improve reporting. + + * neon.m4 (NEON_COMMON_CHECKS): Move check for common headers + here... (LIBNEON_SOURCE_CHECKS): from here. + +Tue Jan 1 21:44:33 2002 Joe Orton <joe@manyfish.co.uk> + + * neon-test.m4 (NEON_TEST): Check for AR and RANLIB. + +Fri Dec 14 22:39:57 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-test.m4 (NEON_TEST): Pick up time_t definition from + sys/time.h if present (fix for Solaris 2.6 and probably + elsewhere). + +Fri Dec 14 22:39:32 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_FORMAT): Allow passing in headers where type may + be defined. + +Mon Dec 10 07:36:26 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON): Fix --with-neon=PATH again. + +Sun Dec 9 21:40:47 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-test.m4 (NEON_TEST): Determine how to print time_t. + +Sun Dec 9 11:50:03 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_WITHOUT_ACL): New macro. + (LIBNEON_SOURCE_CHECKS): Conditionally build ACL support. + +Sun Dec 9 01:06:32 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_CHECK_VERSION): Use NEON_CONFIG as config script, + drop first argument. Better error message if the link failed. + (NEON_COMMON): Cleanup. Check for neon-config in PATH. Stop if + --with-neon was given, and the external neon wasn't good enough. + +Sun Dec 9 00:17:19 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-test.m4 (NEON_TEST): Requires NEON_COMMON_CHECKS. + + * neon-warnings.m4 (NEON_WARNINGS): Requires AC_PROG_CC. + +Sun Dec 9 00:13:47 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON_CHECKS): New macro; runs common C + language/compiler checks, which may be useful to neon applications + regardless of whether a bundled or external neon is being used. + Use AC_REQUIRE to prevent macros being expanded more than once. + (LIBNEON_SOURCE_CHECKS, NEON_COMMON): Require NEON_COMMON_CHECKS + to have been expanded. + +Sat Dec 8 00:56:34 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_FORMAT): Rewrite to use cache results (should fix + for cross-compiling), and for GCC, actually test for warnings - + fix for Linux. + +Sat Dec 8 00:15:44 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_CHECK_SUPPORT): Send --support output to + /dev/null, in case it is from pre-0.18 and prints the usage + message. + +Sat Dec 8 00:13:55 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON): Prepend -lneon to NEON_LIBS rather than + overwriting it when using bundled build. + +Mon Dec 3 19:49:28 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_FORMAT_PREP, NEON_FORMAT): New macros. + (LIBNEON_SOURCE_CHECKS): Call them. + +Mon Dec 3 19:43:11 2001 Joe Orton <joe@manyfish.co.uk> + + Fix gethostbyname() detection on Unixware 7: + + * neon.m4 (NEON_COMMON): Add -lneon to NEON_LIBS after performing + source checks. (NE_SEARCH_LIBS): Test using libraries from + NEON_LIBS too. + +Sat Nov 24 20:33:11 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_CHECK_SUPPORT): New macro. (NEON_COMMON): Define + NEON_SUPPORTS_{SSL,ZLIB} when using an external neon. + +Sat Nov 24 20:25:15 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_WITHOUT_ZLIB): New function. + (LIBNEON_SOURCE_CHECKS): Conditionally enable zlib support. + +Sun Nov 18 12:29:08 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-ssl.m4 (NEON_SSL): Don't add -I/usr/include to NEON_CFLAGS. + +Sat Oct 27 12:20:08 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4, neon-ssl.m4: Substitute NEON_SUPPORTS_ZLIB, + NEON_SUPPORTS_DAV, NEON_SUPPORTS_SSL as "yes" or "no" + appropriately. + +Thu Oct 25 14:29:53 2001 Mo DeJong <supermo@bayarea.net> + + * neon.m4 (NEON_NORMAL_BUILD): Use AC_CHECK_TOOL instead of + AC_PATH_PROG so that cross compilation works properly. + +Sat Oct 6 13:36:58 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_ZLIB): New macro. (LIBNEON_SOURCE_CHECKS): + print warning if struct tm lacks tm_gmtoff. + +Sat Oct 6 12:39:09 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Require autoconf 2.50. Use AC_HELP_STRING where + possible, and AC_MSG_NOTICE instead of 'echo'. + + * neon-ssl.m4, neon-xml-parser.m4, neon-socks.m4: Quoting fixes + for help strings. + +Tue Oct 2 21:13:24 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Check for tm_gmtoff in struct + tm. + +Sun Sep 30 23:35:03 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NE_SEARCH_LIBS): AC_SEARCH_LIBS replacement, adds found + libraries to NEON_LIBS instead. + +Sun Sep 30 11:11:19 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-test.m4: New file. + +Sun Sep 30 11:09:58 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4, neon-xml-parser.m4: Always add libs to $LIBS rather + than $NEONLIBS. + + * neon.m4: Export NEON_CFLAGS. + +Sat Sep 29 14:12:53 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Check for zlib (zlib.h, inflate + in -lz). Add ne_compress to NEON_EXTRAOBJS. + +Tue Sep 25 07:31:53 2001 Mo DeJong <supermo@bayarea.net> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Check for <signal.h> instead of + <sys/signal.h>. Define HAVE_SIGPIPE if SIGPIPE is defined in + <signal.h>. + +Mon Sep 24 20:16:47 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_EXTERNAL_EXPAT): Fix broken + AC_DEFINE (Mo DeJong). + +Mon Sep 24 17:24:42 2001 Joe Orton <joe@manyfish.co.uk> + + * ac_c_bigendian_cross.m4: New file. + + * neon.m4: Use AC_C_BIGENDIAN_CROSS rather than AC_C_BIGENDIAN. + +Mon Sep 17 23:29:11 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Check for setvbuf(). + +Sun Sep 16 20:39:05 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-ssl.m4 (NEON_SSL): Put SSL libs in LIBS rather than + NEONLIBS (and lib paths). + +Sun Sep 16 20:36:53 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON): Add library paths to NEONLIBS rather than + LDFLAGS. + +Sat Jun 9 22:06:25 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-debug.m4: New file. + +Thu May 31 00:04:51 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON_BUILD): Update filenames. + (NEON_CHECK_VERSION): Do simple AC_TRY_LINK and warn appropriately + before checking version. + +Thu May 31 00:03:40 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-warnings.m4: Add -Wbad-function-cast. + +Wed May 30 23:37:48 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4: Added --with-libxml1 and --with-libxml2 + arguments. + +Tue Apr 17 23:06:25 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-ssl.m4: Define neon_ssl_message for configure output. + +Wed Apr 11 23:14:33 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON_BUILD): Fix specifying a list of object + files. + +Fri Apr 6 23:09:58 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Call NEON_SOCKS. + +Fri Apr 6 23:08:39 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-socks.m4: Add basic SOCKSv5 support (untested). + +Mon Apr 2 21:42:40 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Version is 0.13.0, interface version 13:0:0. + +Mon Apr 2 00:27:37 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Move check for 'ar' program to NEON_NORMAL_BUILD, it's + not necessary for libtool build. + +Mon Apr 2 00:17:58 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Check for xmlversion.h + header from libxml2. + +Sun Apr 1 21:23:26 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4: Add expat2 support (Sam TH <sam@uchicago.edu>). + +Wed Mar 21 10:56:03 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4: Add libxml2 support. + +Sun Mar 4 15:45:21 2001 Joe Orton <joe@manyfish.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Use an m4 ifelse rather + than a shell test to code the conditional on whether an argument + was passed to the macro or not. + +Sun Mar 4 15:23:47 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Add "actions if not bundled" argument to *_BUNDLED + macros. + +Mon Feb 26 22:52:24 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Version is 0.12.0. + +Mon Feb 26 22:06:13 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Versionn is 0.12.0-dev. + +Sun Feb 25 17:12:49 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Add checks for endianness (for + md5 code), inline, and const. + +Sun Feb 25 17:00:07 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON): Rename NEON_IS_BUNDLED to + NEON_BUILD_BUNDLED. + +Sun Feb 25 16:52:19 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON): Define NEON_IS_BUNDLED to "yes" or "no" + appropriately. + +Sat Feb 24 00:06:09 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Don't set NEON_NEED_XML_PARSER + here. (NEON_COMMON): ... set it here instead. + (NEON_WITHOUT_WEBDAV): New macro to disable WebDAV support. + (NEON_COMMON_BUILD): Select default set of object files to build + depending on whether DAV is enabled or not. + +Fri Feb 23 23:28:09 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_COMMON_BUILD): Use an m4 'ifelse' for the number + of args test. + + * neon.m4 (NEON_LIBTOOL_BUILD, NEON_NORMAL_BUILD, + NEON_COMMON_BUILD): Set NEON_OBJEXT correctly (fixes + dependancies). + +Sun Feb 4 14:55:10 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Version is 0.11.0, interface version is 11:0:0. + +Sun Jan 28 17:16:27 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_CHECK_VERSION): Run actions-if-okay if + NEON_REQUIRE has not been called. + +Sun Jan 28 14:53:57 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4 (NEON_REQUIRE, NEON_CHECK_VERSION): New macros. + (NEON_COMMON): If a required version is defined, check that an + external neon library matches it. + +Sun Jan 28 10:39:31 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Define NEON_VERSION, NEON_VERSION_{MAJOR,MINOR} in + config.h rather than substituting into neon_config.h. + +Sat Jan 27 22:55:42 2001 Joe Orton <joe@manyfish.co.uk> + + * neon.m4: Include version string in library message. + +Tue Jan 23 23:14:33 2001 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_VERSIONS): New macro. (NEON_COMMON): Call it from + here. + +Mon Jan 15 22:26:54 2001 Joe Orton <joe@light.plus.com> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Append rather than + overwrite CFLAGS. + +Thu Jan 11 20:49:12 2001 Joe Orton <joe@light.plus.com> + + * neon-ssl.m4: Check for OpenSSL in /usr too. + +Thu Jan 11 20:05:34 2001 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_VPATH_BUNDLED): New macro. (NEON_BUNDLED): Call + NEON_COMMON_BUNDLED. (NEON_COMMON_BUNDLED): Abstracted from + NEON_BUNDLED. + +Wed Jan 10 22:44:37 2001 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_LIBTOOL_BUILD, NEON_NORMAL_BUILD): Pass optional + set of objects to build to these macros. Else, all objects go in + NEONOBJS. (NEON_COMMON_BUILD): Implement that. Also substitute + NEON_LINK_FLAGS. + +Mon Jan 8 22:23:51 2001 Joe Orton <joe@light.plus.com> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Put XML parser libs in + $NEONLIBS rather than $LIBS. + +Mon Jan 8 22:20:51 2001 Joe Orton <joe@light.plus.com> + + * neon-ssl.m4 (NEON_SSL): Put OpenSSL libs in $NEONLIBS rather + than LIBS. + +Sun Jan 7 17:30:54 2001 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_BUNDLED): Add optional second argument to specify + builddir of bundled sources (Peter Moulder <pjm@bofh.asn.au>). + +Wed Jan 3 21:33:05 2001 Joe Orton <joe@light.plus.com> + + * neon.m4: Place libraries needed to use neon in NEONLIBS. Adding + them to LIBS breaks bundled builds since libneon doesn't exist at + configure-time, and configure uses $LIBS. + +Wed Jan 3 21:17:00 2001 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_COMMON_BUILD): Don't set top_builddir using 'pwd'. + +Wed Jan 3 21:15:04 2001 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_COMMON): If using bundled neon, add -L<bundled + dir> to LDFLAGS, and -lneon to LIBS. + +Fri Dec 22 23:13:39 2000 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_NORMAL_BUILD, NEON_COMMON_BUILD): New macros. + +Tue Dec 19 22:13:18 2000 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_LIBTOOL_BUILD): New macro. + +Wed Dec 13 22:07:07 2000 Joe Orton <joe@light.plus.com> + + * neon.m4: Add a decent interface: NEON_LIBRARY for non-bundled + case, NEON_BUNDLED for the bundled case. (LIBNEON_SOURCE_CHECKS): + Always set NEON_NEED_XML_PARSER. + + * neon-xml-parser.m4 (NEON_XML_PARSER): Only execute if + "$NEON_NEED_XML_PARSER" = "yes". + +Sun Nov 19 22:52:56 2000 Joe Orton <joe@light.plus.com> + + * neon.m4, neon-ssl.m4, neon-warnings.m4, neon-xml-parser.m4: + Clarify that the more liberal license applies to the m4 files + only, not neon in general. + +Sun Nov 19 22:40:01 2000 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_LIBRARY): Don't call NEON_XML_PARSER, set + NEON_NEED_XML_PARSER to "yes" if it needs to be called. + +Sun Nov 19 22:31:26 2000 Joe Orton <joe@light.plus.com> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Switch to useing + NEON_INCLUDED_EXPAT m4 macro rather than passing arguments. + +Sun Nov 19 22:20:36 2000 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_LIBRARY): Switch to using NEON_INCLUDED_SOURCE m4 + macro rather than passing arguments to the NEON_LIBRARY macro. + +Sun Nov 5 23:26:18 2000 Joe Orton <joe@light.plus.com> + + * neon-xml-parser.m4: Never set LIBS if using a libtool-built + libexpat.la (Greg Stein). + +2000-10-10 Joe Orton <joe@monolith.orton.local> + + * neon-xml-parser.m4: If libexpat.la is included in the + --with-expat parameter, then use a libtool-friendly LIBS. (Greg + Stein) + +Sat Oct 7 19:16:08 2000 Joe Orton <joe@light.plus.com> + + * neon-xml-parser.m4: Link against a libexpat.la if found in + --with-expat location. (Greg Stein). + +Mon Sep 11 15:05:58 2000 Joe Orton <joe@light.plus.com> + + * neon.m4: Use AC_SEARCH_LIBS for finding gethostbyname() and + socket(). + +Mon Sep 11 15:03:45 2000 Joe Orton <joe@light.plus.com> + + * neon.m4 (NEON_REPLACE_SNPRINTF): New macro. + +Fri Sep 8 14:30:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.m4: Check for socket() in -lsocket, -linet. + +Thu Sep 7 00:11:51 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-xml-parser.m4: Added --with-expat flag (patch by Greg + Stein). + +Sun Aug 13 11:12:41 2000 Joe Orton <joe@orton.demon.co.uk> + + * strftime.m4: New file, from fileutils-4.0i. + +Thu Jul 27 19:59:18 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-ssl.m4: Append the SSL libs on the end of $LIBS rather than + redefining it completely. + +Thu Jul 27 19:43:38 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.m4: Define variable neon_library_message to describe what + neon library is being used. + +Mon Jul 24 16:56:34 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-ssl.m4: Put -lssl before -lcrypto in LIBS. + +Thu Jul 20 15:12:14 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Pass directory name + containing bundled expat sources as $1. + + * neon.m4 (NEON_LIBRARY): Pass directory name containing bundled + neon sources as $1, and $2 is passed to NEON_XML_PARSER for + similar use. + +Thu Jul 20 15:04:49 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-ssl.m4: Rewritten from scratch. Support OpenSSL only. + +Thu Jul 20 12:41:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.m4, neon-xml-parser.m4, neon_warnings.m4: Added licensing + information. + +Wed Jul 19 19:30:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-warnings.m4: New file. (NEON_WARNINGS): Macro for doing + --enable-warnings. + +Sun Jun 18 12:12:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.m4: Only add --with-included-neon flag if neon is bundled. + +Sun Jun 18 12:08:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * gnome-x-checks.m4: Imported from GNOME CVS macros module, + adding: descriptive args to AC_DEFINE HAVE_LIBSM call, requiring + Gtk 1.2.8 and the Gtk 'gthread' module. + +Mon May 29 15:10:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.m4 (LIBNEON_SOURCE_CHECKS): Call NEON_SSL. + +Tue May 23 19:11:29 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.m4: Renamed from neon-checks.m4. + +Sun May 21 23:52:27 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-ssl.m4: New file. + +Sat May 13 21:08:54 2000 Joe Orton <joe@orton.demon.co.uk> + + * acconfig.h: Added HAVE_LC_MESSAGE (my lcmessage.m4 is missing + the appropriate description arguments). + +Sat May 13 21:08:40 2000 Joe Orton <joe@orton.demon.co.uk> + + * acconfig.h: Added PACKAGE and VERSION. + +Sat May 13 21:02:29 2000 Joe Orton <joe@orton.demon.co.uk> + + * socklen-arg-type.m4: Added file, modified from a KDE + configure.in. + +Sat May 13 20:44:56 2000 Joe Orton <joe@orton.demon.co.uk> + + * gnome-x-checks.m4: Added description arguments to + AC_DEFINE(HAVE_LIBSM). + +Wed May 10 19:18:14 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-xml-parser.m4: Error if no XML parser is found. + +Wed May 10 14:33:21 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-checks.m4: New file. + +Wed May 10 14:26:57 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-xml-parser.m4 (NEON_XML_PARSER): Use "neon_" prefix for + variables. + +Wed May 10 13:47:04 2000 Joe Orton <joe@orton.demon.co.uk> + + * acconfig.h: New file. + +Wed May 10 13:42:16 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon-xml-parser.m4: New file. + +Sun May 7 21:57:32 2000 Joe Orton <joe@orton.demon.co.uk> + + * gnome-x-checks.m4 (GNOME_X_CHECKS): Check for Gtk 1.2.7 or + later, passing "gthread" module argument. + diff --git a/macros/neon-test.m4 b/macros/neon-test.m4 new file mode 100644 index 0000000..3e5f052 --- /dev/null +++ b/macros/neon-test.m4 @@ -0,0 +1,43 @@ +# Copyright (C) 2001-2002 Joe Orton <joe@manyfish.co.uk> -*- autoconf -*- +# +# This file is free software; you may copy and/or distribute it with +# or without modifications, as long as this notice is preserved. +# This software 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. + +# The above license applies to THIS FILE ONLY, the neon library code +# itself may be copied and distributed under the terms of the GNU +# LGPL, see COPYING.LIB for more details + +# This file is part of the neon HTTP/WebDAV client library. +# See http://www.webdav.org/neon/ for the latest version. +# Please send any feedback to <neon@webdav.org> + +# Tests needed for the neon-test common test code. + +AC_DEFUN([NEON_TEST], [ + +AC_REQUIRE([NEON_COMMON_CHECKS]) + +AC_REQUIRE([AC_TYPE_PID_T]) +AC_REQUIRE([AC_HEADER_TIME]) + +dnl NEON_XML_PARSER may add things (e.g. -I/usr/local/include) to +dnl CPPFLAGS which make "gcc -Werror" fail in NEON_FORMAT; suggest +dnl this macro is used first. +AC_BEFORE([$0], [NEON_XML_PARSER]) + +AC_CHECK_HEADERS(sys/time.h) + +AC_CHECK_FUNCS(pipe isatty usleep) + +AC_REQUIRE([NE_FIND_AR]) + +NEON_FORMAT(time_t, [ +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif]) + +]) diff --git a/macros/neon-xml-parser.m4 b/macros/neon-xml-parser.m4 new file mode 100644 index 0000000..0df534a --- /dev/null +++ b/macros/neon-xml-parser.m4 @@ -0,0 +1,144 @@ +# Copyright (C) 1998-2002 Joe Orton <joe@manyfish.co.uk> -*- autoconf -*- +# +# This file is free software; you may copy and/or distribute it with +# or without modifications, as long as this notice is preserved. +# This software 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. + +# The above license applies to THIS FILE ONLY, the neon library code +# itself may be copied and distributed under the terms of the GNU +# LGPL, see COPYING.LIB for more details + +# This file is part of the neon HTTP/WebDAV client library. +# See http://www.webdav.org/neon/ for the latest version. +# Please send any feedback to <neon@webdav.org> + +# Check for XML parser, supporting libxml 2.x and expat 1.95.x, +# or a bundled copy of expat. +# * Bundled expat if a directory name argument is passed +# -> expat dir must contain minimal expat sources, i.e. +# xmltok, xmlparse sub-directories. See sitecopy/cadaver for +# examples of how to do this. +# +# Usage: +# NEON_XML_PARSER() +# or +# NEON_XML_PARSER(expat-dir) + +dnl Find expat: run $1 if found, else $2 +AC_DEFUN([NE_XML_EXPAT], [ +AC_CHECK_HEADER(expat.h, + [AC_CHECK_LIB(expat, XML_SetXmlDeclHandler, [ + AC_DEFINE(HAVE_EXPAT, 1, [Define if you have expat]) + neon_xml_parser_message="expat" + NEON_LIBS="$NEON_LIBS -lexpat" + neon_xml_parser=expat + ], [$1])], [$1]) +]) + +dnl Find libxml2: run $1 if found, else $2 +AC_DEFUN([NE_XML_LIBXML2], [ +AC_CHECK_PROG(XML2_CONFIG, xml2-config, xml2-config) +if test -n "$XML2_CONFIG"; then + neon_xml_parser_message="libxml `$XML2_CONFIG --version`" + AC_DEFINE(HAVE_LIBXML, 1, [Define if you have libxml]) + # xml2-config in some versions erroneously includes -I/include + # in the --cflags output. + CPPFLAGS="$CPPFLAGS `$XML2_CONFIG --cflags | sed 's| -I/include||g'`" + NEON_LIBS="$NEON_LIBS `$XML2_CONFIG --libs | sed 's|-L/usr/lib ||g'`" + AC_CHECK_HEADERS(libxml/xmlversion.h libxml/parser.h,,[ + AC_MSG_ERROR([could not find parser.h, libxml installation problem?])]) + neon_xml_parser=libxml2 +else + $1 +fi +]) + +dnl Configure for a bundled expat build. +AC_DEFUN([NE_XML_BUNDLED_EXPAT], [ + +AC_REQUIRE([AC_C_BIGENDIAN]) +# Define XML_BYTE_ORDER for expat sources. +if test $ac_cv_c_bigendian = "yes"; then + ne_xml_border=21 +else + ne_xml_border=12 +fi + +# mini-expat doesn't pick up config.h +CPPFLAGS="$CPPFLAGS -DXML_BYTE_ORDER=$ne_xml_border -DXML_DTD -I$1/xmlparse -I$1/xmltok" + +# Use the bundled expat sources +AC_LIBOBJ($1/xmltok/xmltok) +AC_LIBOBJ($1/xmltok/xmlrole) +AC_LIBOBJ($1/xmlparse/xmlparse) +AC_LIBOBJ($1/xmlparse/hashtable) + +AC_DEFINE(HAVE_EXPAT) + +AC_DEFINE(HAVE_XMLPARSE_H, 1, [Define if using expat which includes xmlparse.h]) + +]) + +AC_DEFUN([NEON_XML_PARSER], [ + +dnl Switches to force choice of library +AC_ARG_WITH([libxml2], +AC_HELP_STRING([--with-libxml2], [force use of libxml 2.x])) +AC_ARG_WITH([expat], +AC_HELP_STRING([--with-expat], [force use of expat])) + +dnl Flag to force choice of included expat, if available. +ifelse($#, 1, [ +AC_ARG_WITH([included-expat], +AC_HELP_STRING([--with-included-expat], [use bundled expat sources]),, +with_included_expat=no)], +with_included_expat=no) + +if test "$NEON_NEED_XML_PARSER" = "yes"; then + # Find an XML parser + neon_xml_parser=none + + # Forced choice of expat: + case $with_expat in + yes) NE_XML_EXPAT([AC_MSG_ERROR([expat library not found, cannot proceed])]) ;; + no) ;; + */libexpat.la) + # Special case for Subversion + ne_expdir=`echo $with_expat | sed 's:/libexpat.la$::'` + AC_DEFINE(HAVE_EXPAT) + CPPFLAGS="$CPPFLAGS -I$ne_expdir" + # no dependency on libexpat => crippled libneon, so do partial install + ALLOW_INSTALL=lib + neon_xml_parser=expat + neon_xml_parser_message="expat in $ne_expdir" + ;; + /*) AC_MSG_ERROR([--with-expat does not take a directory argument]) ;; + esac + + # If expat wasn't specifically enabled and libxml was: + if test "${neon_xml_parser}-${with_libxml}-${with_included_expat}" = "none-yes-no"; then + NE_XML_LIBXML2( + [AC_MSG_ERROR([libxml2.x library not found, cannot proceed])]) + fi + + # Otherwise, by default search for libxml2 then expat: + if test "${neon_xml_parser}-${with_included_expat}" = "none-no"; then + NE_XML_LIBXML2([NE_XML_EXPAT([:])]) + fi + + # If an XML parser still has not been found, fail or use the bundled expat + if test "$neon_xml_parser" = "none"; then + m4_if($1, [], + [AC_MSG_ERROR([no XML parser was found: expat or libxml 2.x required])], + [# Configure the bundled copy of expat + NE_XML_BUNDLED_EXPAT($1) + neon_xml_parser_message="bundled expat in $1"]) + fi + + AC_MSG_NOTICE([XML parser used: $neon_xml_parser_message]) +fi + +]) diff --git a/macros/neon.m4 b/macros/neon.m4 new file mode 100644 index 0000000..6b0cee6 --- /dev/null +++ b/macros/neon.m4 @@ -0,0 +1,850 @@ +# Copyright (C) 1998-2002 Joe Orton <joe@manyfish.co.uk> -*- autoconf -*- +# +# This file is free software; you may copy and/or distribute it with +# or without modifications, as long as this notice is preserved. +# This software 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. + +# The above license applies to THIS FILE ONLY, the neon library code +# itself may be copied and distributed under the terms of the GNU +# LGPL, see COPYING.LIB for more details + +# This file is part of the neon HTTP/WebDAV client library. +# See http://www.webdav.org/neon/ for the latest version. +# Please send any feedback to <neon@webdav.org> + +# +# Usage: +# +# NEON_LIBRARY +# or NEON_BUNDLED(srcdir, [ACTIONS-IF-BUNDLED], [ACTIONS-IF-NOT_BUNDLED]) +# or NEON_VPATH_BUNDLED(srcdir, builddir, +# [ACTIONS-IF-BUNDLED], [ACTIONS-IF-NOT-BUNDLED]) +# +# where srcdir is the location of bundled neon 'src' directory. +# If using a VPATH-enabled build, builddir is the location of the +# build directory corresponding to srcdir. +# +# If a bundled build *is* being used, ACTIONS-IF-BUNDLED will be +# evaluated. These actions should ensure that 'make' is run +# in srcdir, and that one of NEON_NORMAL_BUILD or NEON_LIBTOOL_BUILD +# is called. +# +# After calling one of the above macros, if the NEON_NEED_XML_PARSER +# variable is set to "yes", then you must configure an XML parser +# too. You can do this your own way, or do it easily using the +# NEON_XML_PARSER() macro. Example usage for where we have bundled the +# neon sources in a directory called libneon, and bundled expat +# sources in a directory called 'expat'. +# +# NEON_BUNDLED(libneon, [ +# NEON_XML_PARSER(expat) +# NEON_NORMAL_BUILD +# ]) +# +# Alternatively, for a simple standalone app with neon as a +# dependancy, use just: +# +# NEON_LIBRARY +# +# and rely on the user installing neon correctly. +# +# You are free to configure an XML parser any other way you like, +# but the end result must be, either expat or libxml will get linked +# in, and HAVE_EXPAT or HAVE_LIBXML is defined appropriately. +# +# To set up the bundled build environment, call +# +# NEON_NORMAL_BUILD +# or +# NEON_LIBTOOL_BUILD +# +# depending on whether you are using libtool to build, or not. +# Both these macros take an optional argument specifying the set +# of object files you wish to build: if the argument is not given, +# all of neon will be built. + +AC_DEFUN([NEON_BUNDLED],[ + +neon_bundled_srcdir=$1 +neon_bundled_builddir=$1 + +NEON_COMMON_BUNDLED([$2], [$3]) + +]) + +AC_DEFUN([NEON_VPATH_BUNDLED],[ + +neon_bundled_srcdir=$1 +neon_bundled_builddir=$2 +NEON_COMMON_BUNDLED([$3], [$4]) + +]) + +AC_DEFUN([NEON_COMMON_BUNDLED],[ + +AC_PREREQ(2.50) + +AC_ARG_WITH(included-neon, +AC_HELP_STRING([--with-included-neon], [force use of included neon library]), +[neon_force_included="$withval"], [neon_force_included="no"]) + +NEON_COMMON + +# The colons are here so there is something to evaluate +# in case the argument was not passed. +if test "$neon_force_included" = "yes"; then + : + $1 +else + : + $2 +fi + +]) + +dnl Not got any bundled sources: +AC_DEFUN([NEON_LIBRARY],[ + +AC_PREREQ(2.50) +neon_force_included=no +neon_bundled_srcdir= +neon_bundled_builddir= + +NEON_COMMON + +]) + +AC_DEFUN([NEON_VERSIONS], [ + +# Define the current versions. +NEON_VERSION_MAJOR=0 +NEON_VERSION_MINOR=24 +NEON_VERSION_RELEASE=0 +NEON_VERSION_TAG= + +NEON_VERSION="${NEON_VERSION_MAJOR}.${NEON_VERSION_MINOR}.${NEON_VERSION_RELEASE}${NEON_VERSION_TAG}" + +# libtool library interface versioning. Release policy dictates that +# for neon 0.x.y, each x brings an incompatible interface change, and +# each y brings no interface change, and since this policy has been +# followed since 0.1, x == CURRENT, y == RELEASE, 0 == AGE. For +# 1.x.y, this will become N + x == CURRENT, y == RELEASE, x == AGE, +# where N is constant (and equal to CURRENT + 1 from the final 0.x +# release) +NEON_INTERFACE_VERSION="${NEON_VERSION_MINOR}:${NEON_VERSION_RELEASE}:0" + +AC_DEFINE_UNQUOTED(NEON_VERSION, "${NEON_VERSION}", + [Define to be the neon version string]) +AC_DEFINE_UNQUOTED(NEON_VERSION_MAJOR, [(${NEON_VERSION_MAJOR})], + [Define to be major number of neon version]) +AC_DEFINE_UNQUOTED(NEON_VERSION_MINOR, [(${NEON_VERSION_MINOR})], + [Define to be minor number of neon version]) + +]) + +dnl Define the minimum required version +AC_DEFUN([NEON_REQUIRE], [ +neon_require_major=$1 +neon_require_minor=$2 +]) + +dnl Check that the external library found in a given location +dnl matches the min. required version (if any). Requires that +dnl NEON_CONFIG be set the the full path of a valid neon-config +dnl script +dnl +dnl Usage: +dnl NEON_CHECK_VERSION(ACTIONS-IF-OKAY, ACTIONS-IF-FAILURE) +dnl +AC_DEFUN([NEON_CHECK_VERSION], [ +if test "x$neon_require_major" = "x"; then + # Nothing to check. + ne_goodver=yes + ne_libver="(version unknown)" +else + # Check whether the library is of required version + ne_save_LIBS="$LIBS" + ne_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS `$NEON_CONFIG --cflags`" + LIBS="$LIBS `$NEON_CONFIG --libs`" + ne_libver=`$NEON_CONFIG --version | sed -e "s/neon //g"` + # Check whether it's possible to link against neon + AC_CACHE_CHECK([linking against neon], [ne_cv_lib_neon], + AC_TRY_LINK_FUNC([ne_version_match], + [ne_cv_lib_neon=yes], [ne_cv_lib_neon=no])) + if test "$ne_cv_lib_neon" = "yes"; then + # Now check whether the neon library version is satisfactory + AC_CACHE_CHECK([neon library version], [ne_cv_lib_neonver], + AC_TRY_RUN([#include <ne_utils.h> +int main(int argc, char **argv) { +return ne_version_match($neon_require_major, $neon_require_minor); +}], ne_cv_lib_neonver=yes, ne_cv_lib_neonver=no)) + fi + ne_goodver=$ne_cv_lib_neonver + LIBS=$ne_save_LIBS + CFLAGS=$ne_save_CFLAGS +fi +if test "$ne_goodver" = "yes"; then + AC_MSG_NOTICE([using neon library $ne_libver]) + $1 +else + AC_MSG_NOTICE([incompatible neon library version $ne_libver: wanted $neon_require_major.$neon_require_minor]) + $2 +fi]) + +dnl NEON_CHECK_SUPPORT(feature, var) +AC_DEFUN([NEON_CHECK_SUPPORT], [ +if $NEON_CONFIG --support $1 >/dev/null; then + neon_$1_message="supported by neon" + $2=yes +else + neon_$1_message="not supported by neon" + $2=no +fi +]) + +AC_DEFUN([NEON_USE_EXTERNAL], [ +# Configure to use an external neon, given a neon-config script +# found at $NEON_CONFIG. +neon_prefix=`$NEON_CONFIG --prefix` +NEON_CHECK_VERSION([ + CFLAGS="$CFLAGS `$NEON_CONFIG --cflags`" + NEON_LIBS="$NEON_LIBS `$NEON_CONFIG --libs`" + neon_library_message="library in ${neon_prefix} (`$NEON_CONFIG --version`)" + neon_xml_parser_message="using whatever neon uses" + NEON_CHECK_SUPPORT([ssl], [NEON_SUPPORTS_SSL]) + NEON_CHECK_SUPPORT([zlib], [NEON_SUPPORTS_ZLIB]) + neon_got_library=yes +], [neon_got_library=no]) +]) + +AC_DEFUN([NEON_COMMON],[ + +AC_REQUIRE([NEON_COMMON_CHECKS]) + +NEON_VERSIONS + +AC_ARG_WITH(neon, +[ --with-neon[[=DIR]] specify location of neon library], +[case $withval in +yes|no) neon_force_external=$withval; neon_ext_path= ;; +*) neon_force_external=yes; neon_ext_path=$withval ;; +esac;], [ +neon_force_external=no +neon_ext_path= +]) + +if test "$neon_force_included" = "no"; then + # There is no included neon source directory, or --with-included-neon + # wasn't given (so we're not forced to use it). + + # Default to no external neon. + neon_got_library=no + if test "x$neon_ext_path" = "x"; then + AC_PATH_PROG([NEON_CONFIG], neon-config, none) + if test "x${NEON_CONFIG}" = "xnone"; then + AC_MSG_NOTICE([no external neon library found]) + elif test -x "${NEON_CONFIG}"; then + NEON_USE_EXTERNAL + else + AC_MSG_NOTICE([ignoring non-executable ${NEON_CONFIG}]) + fi + else + AC_MSG_CHECKING([for neon library in $neon_ext_path]) + NEON_CONFIG="$neon_ext_path/bin/neon-config" + if test -x ${NEON_CONFIG}; then + AC_MSG_RESULT([found]) + NEON_USE_EXTERNAL + else + AC_MSG_RESULT([not found]) + # ...will fail since force_external=yes + fi + fi + + if test "$neon_got_library" = "no"; then + if test $neon_force_external = yes; then + AC_MSG_ERROR([could not use external neon library]) + elif test -n "$neon_bundled_srcdir"; then + # Couldn't find external neon, forced to use bundled sources + neon_force_included="yes" + else + # Couldn't find neon, and don't have bundled sources + AC_MSG_ERROR(could not find neon) + fi + fi +fi + +# This isn't a simple 'else' branch, since neon_force_included +# is set to yes if the search fails. + +if test "$neon_force_included" = "yes"; then + AC_MSG_NOTICE([using bundled neon ($NEON_VERSION)]) + NEON_BUILD_BUNDLED="yes" + LIBNEON_SOURCE_CHECKS + CFLAGS="$CFLAGS -I$neon_bundled_srcdir" + NEON_LIBS="-L$neon_bundled_builddir -lneon $NEON_LIBS" + NEON_NEED_XML_PARSER=yes + neon_library_message="included libneon (${NEON_VERSION})" +else + # Don't need to configure an XML parser + NEON_NEED_XML_PARSER=no + NEON_BUILD_BUNDLED="yes" +fi + +AC_SUBST(NEON_BUILD_BUNDLED) + +]) + +dnl AC_SEARCH_LIBS done differently. Usage: +dnl NE_SEARCH_LIBS(function, libnames, [extralibs], [actions-if-not-found], +dnl [actions-if-found]) +dnl Tries to find 'function' by linking againt `-lLIB $NEON_LIBS' for each +dnl LIB in libnames. If link fails and 'extralibs' is given, will also +dnl try linking against `-lLIB extralibs $NEON_LIBS`. +dnl Once link succeeds, `-lLIB [extralibs]` is prepended to $NEON_LIBS, and +dnl `actions-if-found' are executed, if given. +dnl If link never succeeds, run `actions-if-not-found', if given, else +dnl give an error and fail configure. +AC_DEFUN([NE_SEARCH_LIBS], [ + +AC_CACHE_CHECK([for library containing $1], [ne_cv_libsfor_$1], [ +AC_TRY_LINK_FUNC($1, [ne_cv_libsfor_$1="none needed"], [ +ne_save_LIBS=$LIBS +ne_cv_libsfor_$1="not found" +for lib in $2; do + LIBS="$ne_save_LIBS -l$lib $NEON_LIBS" + AC_TRY_LINK_FUNC($1, [ne_cv_libsfor_$1="-l$lib"; break]) + m4_if($3, [], [], dnl If $3 is specified, then... + [LIBS="$ne_save_LIBS -l$lib $3 $NEON_LIBS" + AC_TRY_LINK_FUNC($1, [ne_cv_libsfor_$1="-l$lib $3"; break])]) +done +LIBS=$ne_save_LIBS])]) + +if test "$ne_cv_libsfor_$1" = "not found"; then + m4_if($4, [], [AC_MSG_ERROR([could not find library containing $1])], [$4]) +elif test "$ne_cv_libsfor_$1" != "none needed"; then + NEON_LIBS="$ne_cv_libsfor_$1 $NEON_LIBS" + $5 +fi]) + +dnl Check for presence and suitability of zlib library +AC_DEFUN([NEON_ZLIB], [ + +AC_ARG_WITH(zlib, AC_HELP_STRING([--without-zlib], [disable zlib support]), +ne_use_zlib=$withval, ne_use_zlib=yes) + +NEON_SUPPORTS_ZLIB=no +AC_SUBST(NEON_SUPPORTS_ZLIB) + +if test "$ne_use_zlib" = "yes"; then + AC_CHECK_HEADER(zlib.h, [ + AC_CHECK_LIB(z, inflate, [ + NEON_LIBS="$NEON_LIBS -lz" + NEON_CFLAGS="$NEON_CFLAGS -DNEON_ZLIB" + NEON_SUPPORTS_ZLIB=yes + neon_zlib_message="found in -lz" + ], [neon_zlib_message="zlib not found"]) + ], [neon_zlib_message="zlib not found"]) +else + neon_zlib_message="zlib disabled" +fi +]) + +AC_DEFUN([NE_MACOSX], [ +# Check for Darwin, which needs extra cpp and linker flags. +AC_CACHE_CHECK([for Darwin], ne_cv_os_macosx, [ +case `uname -s 2>/dev/null` in +Darwin) ne_cv_os_macosx=yes ;; +*) ne_cv_os_macosx=no ;; +esac]) +if test $ne_cv_os_macosx = yes; then + CPPFLAGS="$CPPFLAGS -no-cpp-precomp" + LDFLAGS="$LDFLAGS -flat_namespace" +fi +]) + +AC_DEFUN([NEON_COMMON_CHECKS], [ + +# These checks are done whether or not the bundled neon build +# is used. + +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_CC_STDC]) +AC_REQUIRE([AC_LANG_C]) +AC_REQUIRE([AC_ISC_POSIX]) +AC_REQUIRE([AC_C_INLINE]) +AC_REQUIRE([AC_C_CONST]) +AC_REQUIRE([AC_TYPE_SIZE_T]) +AC_REQUIRE([AC_TYPE_OFF_T]) + +AC_REQUIRE([NE_MACOSX]) + +AC_REQUIRE([AC_PROG_MAKE_SET]) + +AC_REQUIRE([AC_HEADER_STDC]) + +AC_CHECK_HEADERS([errno.h stdarg.h string.h stdlib.h]) + +NEON_FORMAT(size_t,,u) dnl size_t is unsigned; use %u formats +NEON_FORMAT(off_t) +NEON_FORMAT(ssize_t) + +]) + +AC_DEFUN([NEON_FORMAT_PREP], [ +AC_CHECK_SIZEOF(int) +AC_CHECK_SIZEOF(long) +AC_CHECK_SIZEOF(long long) +if test "$GCC" = "yes"; then + AC_CACHE_CHECK([for gcc -Wformat -Werror sanity], ne_cv_cc_werror, [ + # See whether a simple test program will compile without errors. + ne_save_CPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS -Wformat -Werror" + AC_TRY_COMPILE([#include <sys/types.h> + #include <stdio.h>], [int i = 42; printf("%d", i);], + [ne_cv_cc_werror=yes], [ne_cv_cc_werror=no]) + CPPFLAGS=$ne_save_CPPFLAGS]) + ne_fmt_trycompile=$ne_cv_cc_werror +else + ne_fmt_trycompile=no +fi +]) + +dnl NEON_FORMAT(TYPE[, HEADERS[, [SPECIFIER]]) +dnl +dnl This macro finds out which modifier is needed to create a +dnl printf format string suitable for printing integer type TYPE (which +dnl may be an int, long, or long long). +dnl The default specifier is 'd', if SPECIFIER is not given. +dnl TYPE may be defined in HEADERS; sys/types.h is always used first. +AC_DEFUN([NEON_FORMAT], [ + +AC_REQUIRE([NEON_FORMAT_PREP]) + +AC_CHECK_SIZEOF($1, [$2]) + +dnl Work out which specifier character to use +m4_ifdef([ne_spec], [m4_undefine([ne_spec])]) +m4_if($#, 3, [m4_define(ne_spec,$3)], [m4_define(ne_spec,d)]) + +AC_CACHE_CHECK([how to print $1], [ne_cv_fmt_$1], [ +ne_cv_fmt_$1=none +if test $ne_fmt_trycompile = yes; then + oflags="$CPPFLAGS" + # Consider format string mismatches as errors + CPPFLAGS="$CPPFLAGS -Wformat -Werror" + dnl obscured for m4 quoting: "for str in d ld qd; do" + for str in ne_spec l]ne_spec[ q]ne_spec[; do + AC_TRY_COMPILE([#include <sys/types.h> +$2 +#include <stdio.h>], [$1 i = 1; printf("%$str", i);], + [ne_cv_fmt_$1=$str; break]) + done + CPPFLAGS=$oflags +else + # Best guess. Don't have to be too precise since we probably won't + # get a warning message anyway. + case $ac_cv_sizeof_$1 in + $ac_cv_sizeof_int) ne_cv_fmt_$1="ne_spec" ;; + $ac_cv_sizeof_long) ne_cv_fmt_$1="l]ne_spec[" ;; + $ac_cv_sizeof_long_long) ne_cv_fmt_$1="ll]ne_spec[" ;; + esac +fi +]) + +if test "x$ne_cv_fmt_$1" = "xnone"; then + AC_MSG_ERROR([format string for $1 not found]) +fi + +AC_DEFINE_UNQUOTED([NE_FMT_]translit($1, a-z, A-Z), "$ne_cv_fmt_$1", + [Define to be printf format string for $1]) +]) + +dnl Wrapper for AC_CHECK_FUNCS; uses libraries from $NEON_LIBS. +AC_DEFUN([NE_CHECK_FUNCS], [ +ne_save_LIBS=$LIBS +LIBS="$LIBS $NEON_LIBS" +AC_CHECK_FUNCS($@) +LIBS=$ne_save_LIBS]) + +dnl Checks needed when compiling the neon source. +AC_DEFUN([LIBNEON_SOURCE_CHECKS], [ + +dnl Run all the normal C language/compiler tests +AC_REQUIRE([NEON_COMMON_CHECKS]) + +dnl Needed for building the MD5 code. +AC_REQUIRE([AC_C_BIGENDIAN]) +dnl Is strerror_r present; if so, which variant +AC_REQUIRE([AC_FUNC_STRERROR_R]) + +AC_CHECK_HEADERS([strings.h sys/time.h limits.h sys/select.h arpa/inet.h \ + signal.h sys/socket.h netinet/in.h netdb.h]) + +AC_REQUIRE([NE_SNPRINTF]) + +AC_REPLACE_FUNCS(strcasecmp) + +AC_CHECK_FUNCS(signal setvbuf setsockopt stpcpy) + +# Unixware 7 can only link gethostbyname with -lnsl -lsocket +# Pick up -lsocket first, then the gethostbyname check will work. +NE_SEARCH_LIBS(socket, socket inet) +NE_SEARCH_LIBS(gethostbyname, nsl) + +# Enable getaddrinfo() support only if all the necessary functions +# are found. +ne_enable_gai=yes +NE_CHECK_FUNCS(getaddrinfo gai_strerror inet_ntop,,[ne_enable_gai=no; break]) +if test $ne_enable_gai = yes; then + AC_DEFINE(USE_GETADDRINFO, 1, [Define if getaddrinfo() should be used]) +else + # Checks for non-getaddrinfo() based resolver interfaces. + NE_SEARCH_LIBS(hstrerror, resolv,,[:]) + NE_CHECK_FUNCS(hstrerror) + # Older Unixes don't declare h_errno. + AC_CHECK_DECL(h_errno,,,[#define _XOPEN_SOURCE_EXTENDED 1 +#include <netdb.h>]) +fi + +AC_CHECK_MEMBERS(struct tm.tm_gmtoff,, +AC_MSG_WARN([no timezone handling in date parsing on this platform]), +[#include <time.h>]) + +ifdef([neon_no_zlib], [ + neon_zlib_message="zlib disabled" + NEON_SUPPORTS_ZLIB=no +], [ + NEON_ZLIB() +]) + +# Conditionally enable ACL support +AC_MSG_CHECKING([whether to enable ACL support in neon]) +if test "x$neon_no_acl" = "xyes"; then + AC_MSG_RESULT(no) +else + AC_MSG_RESULT(yes) + NEON_EXTRAOBJS="$NEON_EXTRAOBJS ne_acl" +fi + +NEON_SSL() +NEON_SOCKS() + +AC_SUBST(NEON_CFLAGS) +AC_SUBST(NEON_LIBS) + +]) + +dnl Call to put lib/snprintf.o in LIBOBJS and define HAVE_SNPRINTF_H +dnl if snprintf isn't in libc. + +AC_DEFUN([NEON_REPLACE_SNPRINTF], [ +# Check for snprintf +AC_CHECK_FUNC(snprintf,,[ + AC_DEFINE(HAVE_SNPRINTF_H, 1, [Define if need to include snprintf.h]) + AC_LIBOBJ(lib/snprintf)]) +]) + +dnl turn off webdav, boo hoo. +AC_DEFUN([NEON_WITHOUT_WEBDAV], [ +neon_no_webdav=yes +neon_no_acl=yes +NEON_NEED_XML_PARSER=no +neon_xml_parser_message="none needed" +]) + +dnl Turn off zlib support +AC_DEFUN([NEON_WITHOUT_ZLIB], [ +define(neon_no_zlib, yes) +]) + +AC_DEFUN([NEON_WITHOUT_ACL], [ +# Turn off ACL support +neon_no_acl=yes +]) + +dnl Common macro to NEON_LIBTOOL_BUILD and NEON_NORMAL_BUILD +dnl Sets NEONOBJS appropriately if it has not already been set. +dnl +dnl NOT FOR EXTERNAL USE: use LIBTOOL_BUILD or NORMAL_BUILD. +dnl + +AC_DEFUN([NEON_COMMON_BUILD], [ + +# Using the default set of object files to build. +# Add the extension to EXTRAOBJS +ne="$NEON_EXTRAOBJS" +NEON_EXTRAOBJS= +for o in $ne; do + NEON_EXTRAOBJS="$NEON_EXTRAOBJS $o.$NEON_OBJEXT" +done + +AC_MSG_CHECKING(whether to enable WebDAV support in neon) + +dnl Did they want DAV support? +if test "x$neon_no_webdav" = "xyes"; then + # No WebDAV support + AC_MSG_RESULT(no) + NEONOBJS="$NEONOBJS \$(NEON_BASEOBJS)" + NEON_CFLAGS="$NEON_CFLAGS -DNEON_NODAV" + NEON_SUPPORTS_DAV=no + AC_DEFINE(NEON_NODAV, 1, [Enable if built without WebDAV support]) +else + # WebDAV support + NEON_SUPPORTS_DAV=yes + NEONOBJS="$NEONOBJS \$(NEON_DAVOBJS)" + # Turn on DAV locking please then. + AC_DEFINE(USE_DAV_LOCKS, 1, [Support WebDAV locking through the library]) + + AC_MSG_RESULT(yes) + +fi + +AC_SUBST(NEON_TARGET) +AC_SUBST(NEON_OBJEXT) +AC_SUBST(NEONOBJS) +AC_SUBST(NEON_EXTRAOBJS) +AC_SUBST(NEON_LINK_FLAGS) +AC_SUBST(NEON_SUPPORTS_DAV) + +]) + +# The libtoolized build case: +AC_DEFUN([NEON_LIBTOOL_BUILD], [ + +NEON_TARGET=libneon.la +NEON_OBJEXT=lo + +NEON_COMMON_BUILD($#, $*) + +]) + +dnl Find 'ar' and 'ranlib', fail if ar isn't found. +AC_DEFUN([NE_FIND_AR], [ + +# Search in /usr/ccs/bin for Solaris +ne_PATH=$PATH:/usr/ccs/bin +AC_PATH_TOOL(AR, ar, notfound, $ne_PATH) +if test "x$AR" = "xnotfound"; then + AC_MSG_ERROR([could not find ar tool]) +fi +AC_PATH_TOOL(RANLIB, ranlib, :, $ne_PATH) + +]) + +# The non-libtool build case: +AC_DEFUN([NEON_NORMAL_BUILD], [ + +NEON_TARGET=libneon.a +NEON_OBJEXT=o + +AC_REQUIRE([NE_FIND_AR]) + +NEON_COMMON_BUILD($#, $*) + +]) + +AC_DEFUN([NE_SNPRINTF], [ +AC_CHECK_FUNCS(snprintf vsnprintf,,[ + ne_save_LIBS=$LIBS + LIBS="$LIBS -lm" # Always need -lm + AC_CHECK_LIB(trio, trio_vsnprintf, + [AC_CHECK_HEADERS(trio.h,, + AC_MSG_ERROR([trio installation problem? libtrio found but not trio.h])) + AC_MSG_NOTICE(using trio printf replacement library) + NEON_LIBS="$NEON_LIBS -ltrio -lm" + NEON_CFLAGS="$NEON_CFLAGS -DNEON_TRIO"], + [AC_MSG_NOTICE([no vsnprintf/snprintf detected in C library]) + AC_MSG_ERROR([Install the trio library from http://daniel.haxx.se/trio/])]) + LIBS=$ne_save_LIBS + break +])]) + +dnl Usage: NE_CHECK_SSLVER(variable, version-string, version-hex) +dnl Define 'variable' to 'yes' if OpenSSL version is >= version-hex +AC_DEFUN([NE_CHECK_SSLVER], [ +AC_CACHE_CHECK([OpenSSL version is >= $2], $1, [ +AC_EGREP_CPP(good, [#include <openssl/opensslv.h> +#if OPENSSL_VERSION_NUMBER >= $3 +good +#endif], [$1=yes], [$1=no])])]) + +dnl Less noisy replacement for PKG_CHECK_MODULES +AC_DEFUN([NE_PKG_CONFIG], [ + +AC_PATH_PROG(PKG_CONFIG, pkg-config, no) +if test "$PKG_CONFIG" = "no"; then + : Not using pkg-config + $4 +else + AC_CACHE_CHECK([for $2 pkg-config data], ne_cv_pkg_$2, + [if $PKG_CONFIG $2; then + ne_cv_pkg_$2=yes + else + ne_cv_pkg_$2=no + fi]) + + if test "$ne_cv_pkg_$2" = "yes"; then + $1_CFLAGS=`$PKG_CONFIG --cflags $2` + $1_LIBS=`$PKG_CONFIG --libs $2` + : Using provided pkg-config data + $3 + else + : No pkg-config for $2 provided + $4 + fi +fi]) + +dnl Check for OpenSSL +AC_DEFUN([NEON_SSL], [ + +AC_ARG_WITH(ssl, [AC_HELP_STRING([--with-ssl], [enable OpenSSL support])]) + +AC_ARG_WITH(egd, +[[ --with-egd[=PATH] enable EGD support [using EGD socket at PATH]]]) + +case $with_ssl in +yes) + + NE_PKG_CONFIG(NE_SSL, openssl, + [AC_MSG_NOTICE(using SSL library configuration from pkg-config) + CPPFLAGS="$CPPFLAGS ${NE_SSL_CFLAGS}" + NEON_LIBS="$NEON_LIBS ${NE_SSL_LIBS}"], + [# libcrypto may require -ldl if using the OpenSSL ENGINE branch + NE_SEARCH_LIBS(RSA_new, crypto, -ldl) + NE_SEARCH_LIBS(SSL_library_init, ssl)]) + + AC_CHECK_HEADERS(openssl/ssl.h openssl/opensslv.h,, + [AC_MSG_ERROR([OpenSSL headers not found, cannot enable SSL support])]) + + # Enable EGD support if using 0.9.7 or newer + NE_CHECK_SSLVER(ne_cv_lib_ssl097, 0.9.7, 0x00907000L) + if test "$ne_cv_lib_ssl097" = "yes"; then + AC_MSG_NOTICE([OpenSSL >= 0.9.7; EGD support not needed in neon]) + neon_ssl_message="OpenSSL (0.9.7 or later)" + else + # Fail if OpenSSL is older than 0.9.6 + NE_CHECK_SSLVER(ne_cv_lib_ssl096, 0.9.6, 0x00906000L) + if test "$ne_cv_lib_ssl096" != "yes"; then + AC_MSG_ERROR([OpenSSL 0.9.6 or later is required]) + fi + neon_ssl_message="OpenSSL (0.9.6 or later)" + + case "$with_egd" in + yes|no) ne_cv_lib_sslegd=$with_egd ;; + /*) ne_cv_lib_sslegd=yes + AC_DEFINE_UNQUOTED([EGD_PATH], "$with_egd", + [Define to specific EGD socket path]) ;; + *) # Guess whether EGD support is needed + AC_CACHE_CHECK([whether to enable EGD support], [ne_cv_lib_sslegd], + [if test -r /dev/random || test -r /dev/urandom; then + ne_cv_lib_sslegd=no + else + ne_cv_lib_sslegd=yes + fi]) + ;; + esac + if test "$ne_cv_lib_sslegd" = "yes"; then + AC_MSG_NOTICE([EGD support enabled for seeding OpenSSL PRNG]) + AC_DEFINE([ENABLE_EGD], 1, [Define if EGD should be supported]) + fi + fi + + NEON_SUPPORTS_SSL=yes + NEON_CFLAGS="$NEON_CFLAGS -DNEON_SSL" + NEON_EXTRAOBJS="$NEON_EXTRAOBJS ne_openssl" + ;; +*) # Default to off; only create crypto-enabled binaries if requested. + neon_ssl_message="No SSL support" + NEON_SUPPORTS_SSL=no + NEON_EXTRAOBJS="$NEON_EXTRAOBJS ne_stubssl" + ;; +esac +AC_SUBST(NEON_SUPPORTS_SSL) +]) + +dnl Adds an --enable-warnings argument to configure to allow enabling +dnl compiler warnings +AC_DEFUN([NEON_WARNINGS],[ + +AC_REQUIRE([AC_PROG_CC]) dnl so that $GCC is set + +AC_ARG_ENABLE(warnings, +AC_HELP_STRING(--enable-warnings, [enable compiler warnings])) + +if test "$enable_warnings" = "yes"; then + case $GCC:`uname` in + yes:*) + CFLAGS="$CFLAGS -Wall -ansi-pedantic -Wmissing-declarations -Winline -Wshadow -Wreturn-type -Wsign-compare -Wundef -Wpointer-arith -Wcast-align -Wbad-function-cast -Wimplicit-prototypes" + if test -z "$with_ssl" -o "$with_ssl" = "no"; then + # OpenSSL headers fail strict prototypes checks + CFLAGS="$CFLAGS -Wstrict-prototypes" + fi + ;; + no:OSF1) CFLAGS="$CFLAGS -check -msg_disable returnchecks -msg_disable alignment -msg_disable overflow" ;; + no:IRIX) CFLAGS="$CFLAGS -fullwarn" ;; + no:UnixWare) CFLAGS="$CFLAGS -v" ;; + *) AC_MSG_WARN([warning flags unknown for compiler on this platform]) ;; + esac +fi +]) + +dnl Adds an --disable-debug argument to configure to allow disabling +dnl debugging messages. +dnl Usage: +dnl NEON_WARNINGS([actions-if-debug-enabled], [actions-if-debug-disabled]) +dnl +AC_DEFUN([NEON_DEBUG], [ + +AC_ARG_ENABLE(debug, +AC_HELP_STRING(--disable-debug,[disable runtime debugging messages])) + +# default is to enable debugging +case $enable_debug in +no) AC_MSG_NOTICE([debugging is disabled]) +$2 ;; +*) AC_MSG_NOTICE([debugging is enabled]) +AC_DEFINE(NE_DEBUGGING, 1, [Define to enable debugging]) +$1 +;; +esac]) + +dnl Macro to optionally enable socks support +AC_DEFUN([NEON_SOCKS], [ + +AC_ARG_WITH([socks], AC_HELP_STRING([--with-socks],[use SOCKSv5 library])) + +if test "$with_socks" = "yes"; then + ne_save_LIBS=$LIBS + + AC_CHECK_HEADERS(socks.h, + [AC_CHECK_LIB(socks5, connect, + [AC_MSG_NOTICE([SOCKSv5 support enabled])], + [AC_MSG_ERROR([could not find libsocks5 for SOCKS support])])], + [AC_MSG_ERROR([could not find socks.h for SOCKS support])]) + + CFLAGS="$CFLAGS -DNEON_SOCKS" + NEON_LIBS="$NEON_LIBS -lsocks5" + LIBS=$ne_save_LIBS + +fi]) + +AC_DEFUN([NEON_WITH_LIBS], [ +AC_ARG_WITH([libs], +[[ --with-libs=DIR[:DIR2...] look for support libraries in DIR/{bin,lib,include}]], +[case $with_libs in +yes|no) AC_MSG_ERROR([--with-libs must be passed a directory argument]) ;; +*) ne_save_IFS=$IFS; IFS=: + for dir in $with_libs; do + ne_add_CPPFLAGS="$ne_add_CPPFLAGS -I${dir}/include" + ne_add_LDFLAGS="$ne_add_LDFLAGS -L${dir}/lib" + ne_add_PATH="${ne_add_PATH}${dir}/bin:" + done + IFS=$ne_save_IFS + CPPFLAGS="${ne_add_CPPFLAGS} $CPPFLAGS" + LDFLAGS="${ne_add_LDFLAGS} $LDFLAGS" + PATH=${ne_add_PATH}$PATH ;; +esac])]) diff --git a/macros/socklen-arg-type.m4 b/macros/socklen-arg-type.m4 new file mode 100644 index 0000000..2d84ac4 --- /dev/null +++ b/macros/socklen-arg-type.m4 @@ -0,0 +1,43 @@ +dnl This function is (C) 1997,98,99 Stephan Kulow (coolo@kde.org) +dnl Modifications (C) Joe Orton 1999,2000 + +AC_DEFUN([SOCKLEN_ARG_TYPE],[ + +dnl Check for the type of the third argument of getsockname +AC_MSG_CHECKING(for the third argument of getsockname) +AC_CACHE_VAL(ac_cv_ksize_t, +[AC_TRY_COMPILE([ +#include <sys/types.h> +#include <sys/socket.h> +],[ +socklen_t a=0; +getsockname(0,(struct sockaddr*)0, &a); +], +ac_cv_ksize_t=socklen_t, +ac_cv_ksize_t=) +if test -z "$ac_cv_ksize_t"; then +ac_safe_cflags="$CFLAGS" +if test "$GCC" = "yes"; then + CFLAGS="-Werror $CFLAGS" +fi +AC_TRY_COMPILE([ +#include <sys/types.h> +#include <sys/socket.h> +],[ +int a=0; +getsockname(0,(struct sockaddr*)0, &a); +], +ac_cv_ksize_t=int, +ac_cv_ksize_t=size_t) +CFLAGS="$ac_safe_cflags" +fi +]) + +if test -z "$ac_cv_ksize_t"; then + ac_cv_ksize_t=int +fi + +AC_MSG_RESULT($ac_cv_ksize_t) +AC_DEFINE_UNQUOTED(ksize_t, $ac_cv_ksize_t, [Define to be the type of the third argument to getsockname]) + +])
\ No newline at end of file diff --git a/neon-config.in b/neon-config.in new file mode 100644 index 0000000..27b74d1 --- /dev/null +++ b/neon-config.in @@ -0,0 +1,105 @@ +#! /bin/sh +# Originally from libxml, Copyright (C) Daniel Veillard +# Modifications for neon Copyright (C) 2000-2002 Joe Orton. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +includedir=@includedir@ +libdir=@libdir@ + +usage() +{ + cat <<EOF +Usage: neon-config [OPTION] + +Known values for OPTION are: + + --prefix=DIR change neon prefix [default $prefix] + --libs print library linking information + --la-file print location of libtool .la file + --cflags print pre-processor and compiler flags + --help display this help and exit + --version output version information + --support FEATURE exit with success if feature is supported + Known features: dav [@NEON_SUPPORTS_DAV@], ssl [@NEON_SUPPORTS_SSL@], zlib [@NEON_SUPPORTS_ZLIB@] + +EOF + + exit $1 +} + +support() +{ + if test "$1" = "yes"; then + exit 0 + else + exit 1 + fi +} + +if test $# -eq 0; then + usage 1 +fi + +while test $# -gt 0; do + case "$1" in + -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) optarg= ;; + esac + + case "$1" in + --prefix=*) + prefix=$optarg + ;; + + --prefix) + echo $prefix + ;; + + --version) + echo neon @NEON_VERSION@ + exit 0 + ;; + + --help) + usage 0 + ;; + + --cflags) + echo -I${includedir}/neon @NEON_CFLAGS@ + ;; + + --libs) + LIBS="-lneon @NEON_LIBS@" + # Don't add standard library paths + case "${libdir}" in + /usr/lib|/lib) ;; + *) LIBS="-L${libdir} ${LIBS}" ;; + esac + echo @user_LDFLAGS@ ${LIBS} + ;; + + --la-file) + echo ${libdir}/libneon.la + ;; + + --support) + shift + + case "$1" in + ssl|SSL) support @NEON_SUPPORTS_SSL@ ;; + zlib|ZLIB) support @NEON_SUPPORTS_ZLIB@ ;; + dav|DAV) support @NEON_SUPPORTS_DAV@ ;; + *) support no ;; + esac + ;; + + *) + usage + exit 1 + ;; + esac + shift +done + +exit 0 diff --git a/neon.mak b/neon.mak new file mode 100644 index 0000000..14d0d30 --- /dev/null +++ b/neon.mak @@ -0,0 +1,186 @@ +#**** neon Win32 -*- Makefile -*- ******************************************** +# +# Define DEBUG_BUILD to create a debug version of the library. + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +######## +# Support for Expat integration +# IF EXPAT_SRC is set, then assume compiling against a pre-built +# binary Expat 1.95.X. If EXPAT_SRC is not set, then the user can +# still set EXPAT_FLAGS to specify very specific compile behavior. +# If both EXPAT_SRC and EXPAT_FLAGS are not set, disable WebDAV +# support. +BUILD_EXPAT = 1 +!IF "$(EXPAT_SRC)" == "" +!IF "$(EXPAT_FLAGS)" == "" +EXPAT_FLAGS = /D NEON_NODAV +BUILD_EXPAT = +!ENDIF +!ELSE +EXPAT_FLAGS = /I "$(EXPAT_SRC)\Source\Lib" /D HAVE_EXPAT /D HAVE_EXPAT_H +!ENDIF + + +######## +# Support for OpenSSL integration +!IF "$(OPENSSL_SRC)" == "" +OPENSSL_FLAGS = +!ELSE +OPENSSL_FLAGS = /I "$(OPENSSL_SRC)\inc32" /D NEON_SSL +!ENDIF + +######## +# Support for zlib integration +!IF "$(ZLIB_SRC)" == "" +ZLIB_FLAGS = +ZLIB_LIBS = +!ELSE +ZLIB_FLAGS = /I "$(ZLIB_SRC)" /D NEON_ZLIB +!IF "$(ZLIB_DLL)" == "" +ZLIB_LIBS = "$(ZLIB_SRC)\zlibstat.lib" +!ELSE +ZLIB_FLAGS = $(ZLIB_FLAGS) /D ZLIB_DLL +ZLIB_LIBS = "$(ZLIB_SRC)\zlibdll.lib" +!ENDIF +!ENDIF + + +!IF "$(DEBUG_BUILD)" == "" +INTDIR = Release +CFLAGS = /MD /W3 /GX /O2 /D "NDEBUG" +TARGET = .\libneon.lib +!ELSE +INTDIR = Debug +CFLAGS = /MDd /W3 /Gm /GX /Zi /Od /D "_DEBUG" +TARGET = .\libneonD.lib +!ENDIF + +# Exclude stuff we don't need from the Win32 headers +WIN32_DEFS = /D WIN32_LEAN_AND_MEAN /D NOUSER /D NOGDI /D NONLS /D NOCRYPT + +CPP=cl.exe +CPP_PROJ = /c /nologo $(CFLAGS) $(WIN32_DEFS) $(EXPAT_FLAGS) $(OPENSSL_FLAGS) $(ZLIB_FLAGS) /D "HAVE_CONFIG_H" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" +LIB32=link.exe -lib +LIB32_FLAGS=/nologo /out:"$(TARGET)" + +LIB32_OBJS= \ + "$(INTDIR)\ne_alloc.obj" \ + "$(INTDIR)\ne_auth.obj" \ + "$(INTDIR)\ne_basic.obj" \ + "$(INTDIR)\ne_compress.obj" \ + "$(INTDIR)\ne_cookies.obj" \ + "$(INTDIR)\ne_dates.obj" \ + "$(INTDIR)\ne_i18n.obj" \ + "$(INTDIR)\ne_md5.obj" \ + "$(INTDIR)\ne_redirect.obj" \ + "$(INTDIR)\ne_request.obj" \ + "$(INTDIR)\ne_session.obj" \ + "$(INTDIR)\ne_socket.obj" \ + "$(INTDIR)\ne_string.obj" \ + "$(INTDIR)\ne_uri.obj" \ + "$(INTDIR)\ne_utils.obj" + +!IF "$(BUILD_EXPAT)" != "" +LIB32_OBJS= \ + $(LIB32_OBJS) \ + "$(INTDIR)\ne_207.obj" \ + "$(INTDIR)\ne_xml.obj" \ + "$(INTDIR)\ne_acl.obj" \ + "$(INTDIR)\ne_props.obj" \ + "$(INTDIR)\ne_locks.obj" +!ENDIF + + +!IF "$(OPENSSL_SRC)" != "" +LIB32_OBJS = $(LIB32_OBJS) "$(INTDIR)\ne_openssl.obj" +!IFDEF OPENSSL_STATIC +LIB32_OBJS = $(LIB32_OBJS) $(OPENSSL_SRC)\out32\libeay32.lib \ + $(OPENSSL_SRC)\out32\ssleay32.lib +!ELSE +LIB32_OBJS = $(LIB32_OBJS) $(OPENSSL_SRC)\out32dll\libeay32.lib \ + $(OPENSSL_SRC)\out32dll\ssleay32.lib +!ENDIF +!ELSE +# Provide ABI-compatibility stubs for SSL interface +LIB32_OBJS = $(LIB32_OBJS) "$(INTDIR)\ne_stubssl.obj" +!ENDIF +!IF "$(ZLIB_SRC)" != "" +LIB32_OBJS = $(LIB32_OBJS) $(ZLIB_LIBS) +!ENDIF + + +ALL: ".\src\config.h" "$(TARGET)" + +CLEAN: + -@erase "$(INTDIR)\ne_207.obj" + -@erase "$(INTDIR)\ne_alloc.obj" + -@erase "$(INTDIR)\ne_acl.obj" + -@erase "$(INTDIR)\ne_auth.obj" + -@erase "$(INTDIR)\ne_basic.obj" + -@erase "$(INTDIR)\ne_compress.obj" + -@erase "$(INTDIR)\ne_cookies.obj" + -@erase "$(INTDIR)\ne_dates.obj" + -@erase "$(INTDIR)\ne_i18n.obj" + -@erase "$(INTDIR)\ne_locks.obj" + -@erase "$(INTDIR)\ne_md5.obj" + -@erase "$(INTDIR)\ne_props.obj" + -@erase "$(INTDIR)\ne_redirect.obj" + -@erase "$(INTDIR)\ne_request.obj" + -@erase "$(INTDIR)\ne_session.obj" + -@erase "$(INTDIR)\ne_openssl.obj" + -@erase "$(INTDIR)\ne_stubssl.obj" + -@erase "$(INTDIR)\ne_socket.obj" + -@erase "$(INTDIR)\ne_string.obj" + -@erase "$(INTDIR)\ne_uri.obj" + -@erase "$(INTDIR)\ne_utils.obj" + -@erase "$(INTDIR)\ne_xml.obj" + -@erase "$(TARGET)" + -@erase ".\src\config.h" + +"$(TARGET)": $(DEF_FILE) $(LIB32_OBJS) + -@if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)" + $(LIB32) @<< + $(LIB32_FLAGS) $(DEF_FLAGS) $(LIB32_OBJS) +<< + +{src}.c{$(INTDIR)}.obj:: + -@if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)" + $(CPP) @<< + $(CPP_PROJ) $< +<< + +".\src\config.h": config.hw + -@if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)" + <<tempfile.bat + @echo off + copy .\config.hw .\src\config.h > nul + echo Created config.h from config.hw +<< + +"$(INTDIR)\ne_207.obj": .\src\ne_207.c +"$(INTDIR)\ne_alloc.obj": .\src\ne_alloc.c +"$(INTDIR)\ne_acl.obj": .\src\ne_acl.c +"$(INTDIR)\ne_auth.obj": .\src\ne_auth.c +"$(INTDIR)\ne_basic.obj": .\src\ne_basic.c +"$(INTDIR)\ne_compress.obj": .\src\ne_compress.c +"$(INTDIR)\ne_cookies.obj": .\src\ne_cookies.c +"$(INTDIR)\ne_dates.obj": .\src\ne_dates.c +"$(INTDIR)\ne_i18n.obj": .\src\ne_i18n.c +"$(INTDIR)\ne_locks.obj": .\src\ne_locks.c +"$(INTDIR)\ne_md5.obj": .\src\ne_md5.c +"$(INTDIR)\ne_props.obj": .\src\ne_props.c +"$(INTDIR)\ne_redirect.obj": .\src\ne_redirect.c +"$(INTDIR)\ne_request.obj": .\src\ne_request.c +"$(INTDIR)\ne_session.obj": .\src\ne_session.c +"$(INTDIR)\ne_openssl.obj": .\src\ne_openssl.c +"$(INTDIR)\ne_stubssl.obj": .\src\ne_stubssl.c +"$(INTDIR)\ne_socket.obj": .\src\ne_socket.c +"$(INTDIR)\ne_string.obj": .\src\ne_string.c +"$(INTDIR)\ne_uri.obj": .\src\ne_uri.c +"$(INTDIR)\ne_utils.obj": .\src\ne_utils.c +"$(INTDIR)\ne_xml.obj": .\src\ne_xml.c diff --git a/neon.pc.in b/neon.pc.in new file mode 100644 index 0000000..9d431de --- /dev/null +++ b/neon.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: neon +Description: HTTP/WebDAV client library +Version: @NEON_VERSION@ +Libs: -L${libdir} -lneon @NEON_LIBS@ +Cflags: -I${includedir}/neon @NEON_CFLAGS@ diff --git a/src/.cvsignore b/src/.cvsignore new file mode 100644 index 0000000..5561c9e --- /dev/null +++ b/src/.cvsignore @@ -0,0 +1,15 @@ +*.lo +.libs +Makefile +libneon.la +libneon.a +neon_config.h +*.diff +c++.c +checkincl.c +*.*.* +*.out +*log +*.bb +*.da +*.bbg diff --git a/src/COPYING.LIB b/src/COPYING.LIB new file mode 100644 index 0000000..161a3d1 --- /dev/null +++ b/src/COPYING.LIB @@ -0,0 +1,482 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/src/ChangeLog b/src/ChangeLog new file mode 100644 index 0000000..2893290 --- /dev/null +++ b/src/ChangeLog @@ -0,0 +1,4929 @@ +Sat Jun 21 12:58:25 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_begin_request): Set or clear is_http11 flag + for each request. + +Wed Jun 18 20:54:44 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c: Add AI_ADDRCONFIG support; + [USE_CHECK_IPV6]: Define only if __linux__. + (init_ipv6) [USE_CHECK_IPV6]: New conditional. + (ne_addr_resolve) [USE_ADDRCONFIG]: Use AI_ADDRCONFIG. + +Wed Jun 18 20:03:13 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_create): New function (renamed from + create_sock). + (ne_sock_connect): Take an ne_socket *, return int. + (ne_sock_accept): Likewise. + (ne_sock_close): Only call ne_close if fd is non-negative. + + * ne_request.c (aborted): Handle NE_SOCK_* errors specially. + (do_connect): Adapt for ne_sock_create/connect interface. Set + sess->connected here on success. + (open_connection): Don't set sess->connected here. + +Sun Jun 15 12:14:22 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_ssl.h (ne_ssl_cert_digest): Pass digest as a pointer rather + than an array. + +Sun Jun 15 11:00:09 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_stubssl.c (ne_ssl_cert_cmp): Add stub. + +Wed May 28 21:37:27 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_context_create): Enable workarounds in + OpenSSL for better interop with buggy SSL servers. + +Fri May 23 23:13:30 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_stubssl.c (ne_ssl_set_clicert): Add stub. + +Sat May 10 17:05:26 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c: Rename struct ne_xml_handler to struct handler. + +Thu May 8 20:55:46 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_clicert_read): Pass "b" to fopen. + +Tue May 6 22:08:08 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (check_certificate): Re-order verify failure + handling to allow caller to set a custom session error string. + +Tue May 6 20:21:27 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_md5.c (md5_stream): Restore. + +Sat Apr 26 19:21:03 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (te_hdr_handler): Treat presence of any T-E + response header as implying the response is chunked, regardless of + value. + +Sat Apr 26 18:11:24 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c: Rename struct ne_xml_nspace to struct namespace. + +Wed Apr 23 22:19:29 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_cert_export): Don't bother checking for + i2d_X509() failure; no OpenSSL code ever checks, so everyone's + doomed if it really can fail. + +Wed Apr 23 22:01:23 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_cert_import, ne_ssl_cert_export, + ne_ssl_cert_write): Clear OpenSSL error stack on errors. + +Wed Apr 23 18:23:53 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_stubssl.c (ne_ssl_cert_write, ne_ssl_cert_import, + ne_ssl_cert_export): Add stubs. + +Wed Apr 23 14:05:32 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_cert_write): New function. + +Tue Apr 22 23:21:22 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_unbase64): Optimise out some redundant branches. + +Tue Apr 22 20:24:44 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_cert_export, ne_ssl_cert_import, + ne_ssl_cert_cmp): New functions. + +Tue Apr 22 18:31:55 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_unbase64): New function. + +Tue Apr 22 15:53:41 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_base64): Fix encoding binary data; take unsigned + argument. + +Tue Apr 22 13:07:48 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_stubssl.c (ne_ssl_cert_validity): Add stub. + +Tue Apr 22 09:22:26 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_cert_validity): New function. + (asn1time_to_string): Format into a fixed-size buffer. + +Tue Apr 22 08:38:30 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (ne_lock_discover, ne_lock): Don't leak the cdata + buffer. + + * ne_props.c (ne_propfind_destroy): Don't leak the value buffer. + +Mon Apr 21 23:52:25 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c (ne_xml_destroy): Free root element. + +Mon Apr 21 23:46:17 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (dup_client_cert): Set decrypted state; dup the + friendly name. + (ne_ssl_clicert_free): Free friendly name. + +Mon Apr 21 19:44:55 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_md5.h (ne_md5_buffer, ne_md5_stream): Remove unused + functions. + +Mon Apr 21 18:17:14 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c, ne_207.c: s/NE_ELM_/ELM_/ since no element ids are + exported. + +Mon Apr 21 16:38:14 2003 Joe Orton <joe@manyfish.co.uk> + + Redesign the XML interface: have startelm callback map {nspace, + name} onto a state integer or decline. Remove "valid"/"invalid"; + don't abort the parse if no handler accepts an element. Push + cdata accumulation down into the caller; drop collect mode, + stripws mode. + + * ne_xml.h (ne_xml_elmid, struct ne_xml_elm): Removed. + (ne_xml_startelm_cb): Return a state/acceptance integer, take a + state integer, nspace, name and atts. + (ne_xml_endelm_cb, ne_xml_cdata_cb): Take a state integer. + (ne_xml_push_mixed_handler): Removed. + (ne_xml_push_handler): Removed element list argument. + (struct ne_xml_idmap, ne_xml_mapid): New interface. + + * ne_xml.c (struct element): Replaces ne_xml_state. Add name, + nspace, state fields. + (friendly_name, find_handler, parse_element, + ne_xml_push_mixed_handler, push_handler): Removed functions. + (declare_nspaces, expand_qname): Factored out from find_handler + and parse_element. + (start_element): Use expand_qname, declare_nspaces. Find + appropriate handler here. Guarantee not to pass a NULL atts array + to the start-element callback. Drop collect mode. + (end_element): Drop collect mode + (ne_xml_push_handler): Fold push_handler back in. + (ne_xml_mapid): New function. + + * ne_207.h (NE_ELM_*): Don't export element id. + (NE_207_STATE_PROP, NE_207_STATE_TOP): Export state integers. + + * ne_207.c (struct ne_207_parser_s): Add cdata field. + (map207): Replace element list with idmap array. + (can_handle): New function, replacing check_context logic. + (start_element): Determine new state integer; only accept the + element in valid states. Clear cdata. + (end_element): Use state rather than element id. Do nothing for + end of 'response' element if element is incomplete. + (ne_207_create): Create cdata buffer. + (ne_207_destroy): Destroy cdata buffer. + (ne_207_ignore_unknown): Removed function. + (ne_simple_request): Don't call ne_207_ignore_unknown. + + * ne_props.h (NE_PROPS_STATE_TOP): Define state. + + * ne_props.c (struct ne_propfind_handler_s): Add value and depth + fields. + (ELM_flatprop): Define state. + (flat_elms): Removed array. + (chardata): Append to value field when in ELM_flatprop state. + (startelm): Decline everything other than elements within the + 'prop' state. Collect flatprop contents. + (endelm): Collect flatprop contents. + + * ne_locks.c (struct discover_ctx, struct lock_ctx): Store cdata. + (element_map): Replace element list with idmap array. + (can_accept): Replaces check_context callback. + (ld_startelm, lk_cdata, ld_cdata): New functions. + +Mon Apr 14 00:04:20 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_207.h (ne_207_start_response, ne_207_end_response, + ne_207_start_propstat, ne_207_end_propstat): Use ANSI-style + function pointers in typedefs. + + * ne_207.c (struct ne_207_parser_s): Updated accordingly. + +Mon Apr 14 00:02:10 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_block): Better error messages for + invalid chunks, don't use strncmp for a two-character comparison. + +Mon Apr 7 22:26:50 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_stubssl.c (ne_ssl_cert_identity): New function. + +Mon Apr 7 22:16:16 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (struct ne_ssl_certificate_s): Add identity field. + (check_identity): Add optional identity argument. + (populate_cert): Retrieve cert identity using check_identity. + (check_certificate): Pass extra NULL to check_identity. + (ne_ssl_cert_identity): New function. + (ne_ssl_cert_free): Free the identity field. + +Mon Apr 7 21:29:54 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (check_identity): Take a string hostname rather + than a session object. + (check_certificate): Adjust accordingly. + +Sun Apr 6 21:26:05 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h (NE_HEX2ASC): Cast result to char to avoid warnings + with some compilers. + +Sun Apr 6 20:11:42 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_readable_dname): Include commonName or + emailAddress in returned string if either is the only attribute. + +Sun Mar 30 10:54:20 2003 Joe Orton <joe@manyfish.co.uk> + + Split decryption of client certs into two steps + + * ne_openssl.c (ne_ssl_clicert_encrypted, ne_ssl_clicert_decrypt): + New functions. + (ne_ssl_client_cert_s): Add p12 and decrypted fields. + (find_friendly_name): New function. + (get_friendly_name): Removed function. + (ne_ssl_clicert_read): Drop password callback; on decrypt failure, + extract friendly name and set decrypted state of clicert. + +Sun Mar 30 10:54:01 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_stubssl.c (ne_ssl_clicert_encrypted, ne_ssl_clicert_decrypt): + New stubs. + (ne_ssl_clicert_read): Adjusted for API change. + +Sat Mar 29 14:23:37 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_dname_cmp): New function. + + * ne_stubssl.c (ne_ssl_dname_cmp): New function. + +Sat Mar 29 13:52:47 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (struct ne_ssl_client_cert_s): Add 'friendly_name' + field. + (get_friendly_name, ne_ssl_clicert_name): New functions. + (ne_ssl_clicert_read): Store the cert's friendly name. + + * ne_stubssl.c (ne_ssl_clicert_name): New function. + +Sat Mar 29 13:16:14 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_openssl.c (ne_ssl_clicert_owner): New function. + +Fri Mar 28 22:12:57 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_stubssl.c (ne_ssl_cert_digest): New function. + + * ne_openssl.c (ne_ssl_cert_digest): New function. + +Wed Mar 26 20:41:57 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_ssl_trust_cert) [NEON_SSL]: Only build when SSL + support is present. + +Wed Mar 26 20:01:00 2003 Joe Orton <joe@manyfish.co.uk> + + Begin abstraction of SSL layer to better isolate + SSL-library-specific code, and to improve certificate handling + interface. + + Drop support for PEM-encoded client certificates. + + * ne_session.h (ne_ssl_trust_cert): Replaces ne_ssl_load_ca, in + conjunction with ne_ssl_load_cert. + (ne_ssl_trust_default_ca): Replaces ne_ssl_load_default_ca. + (ne_ssl_keypw_prompt): Removed function, no longer needed. + (ne_ssl_set_clicert): Replaces ne_ssl_load_pkcs12, in conjunction + with ne_ssl_clicert_read. + (ne_ssl_provide_clicert): Replaces ne_ssl_provide_ccert, callback + type changed. + + * ne_openssl.c: New file; much code moved from ne_session.c. + + * ne_privssl.h: New file, private interface between ne_socket.c + and ne_openssl.c. + + * ne_ssl.h: New file. + + * ne_private.h (struct ne_session_s): Store pointers to + ne_ssl_client_cert etc opaque objects, not OpenSSL structures. + + * ne_session.c: Most of ne_ssl_* moved to ne_openssl.c. + (ne_session_create, ne_session_destroy): Use ne_ssl_cert_* etc to + manage cert objects. + + * ne_socket.c (struct ne_socket_s): Replace SSL *, SSL_CTX * + pointers with an ne_ssl_socket * pointer. + (readable_ossl, error_ossl, read_ossl, write_ossl, ne_sock_close): + Compensate for above change. + (ne_sock_use_ssl): Removed function. + (ne_sock_switch_ssl): Pass in SSL * as void for time being. + (ne_sock_connect_ssl): Renamed and cleaned up version of + ne_sock_use_ssl_os. + (ne_sock_sslsock): New function. + + * Makefile.in: Add deps for ne_openssl.c. + +Sun Mar 23 13:02:58 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_set_useragent): Use ne_malloc. + +Sat Mar 22 21:06:45 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (raw_connect): Fill in sin6_family or sin_family + since AIX 4.3 fails to do so. + +Wed Mar 19 20:44:11 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c [NEON_SSL] (free_client_cert): Build conditional on + NEON_SSL. + +Mon Mar 17 20:33:32 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c: Include netdb.h conditional on HAVE_NETDB_H. (fix + build for older versions of CygWin). + +Sun Mar 16 23:30:20 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (check_identity): Fix leak of subject alt. name + structures. + +Sun Mar 16 19:21:22 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (free_client_cert): New function. + (ne_session_destroy, ne_ssl_load_pem, ne_ssl_load_pkcs12): Call + it; prevent memory leak if ne_ssl_load_{pem,pkcs12} are called >1 + per session. + +Sun Mar 16 18:00:34 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (provide_client_cert): Free peer certificate after + use. + (ne_session_destroy): Free client cert and key if present. + +Sun Mar 16 14:23:05 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c [HAVE_EXPAT]: Include xmlparse.h for bundled expat + build. + + * ne_utils.c: Only include expat.h if HAVE_XMLPARSE_H is not + defined. + +Wed Mar 12 15:04:13 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c (struct redirect): Add 'valid' field. + (post_send): Set and clear 'valid' to keep track of whether stored + URI is valid. + (ne_redirect_location): Return NULL if stored URI is not valid. + +Wed Mar 12 14:52:49 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_uri_free): Zero-initialize structure after + free'ing. + +Tue Mar 11 22:01:11 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c (ne_redirect_location): Return NULL if no redirect + session is registered, rather than SEGV; Ralf Mattes + <rm@fabula.de>. + +Sun Mar 9 16:33:24 2003 Joe Orton <joe@manyfish.co.uk> + + Fix a memory leak if an XML parse error occurs during a PROPFIND + response: + + * ne_props.c (ne_propfind_current_private): Return NULL if no + propset is being processed. + (free_propset): Free propset href here. + (end_response): Don't free propset href here. Set current field + of handler to NULL after free'ing propset. + (ne_propfind_destroy): Free current propset if one was being + processed. + +Sun Mar 9 11:53:58 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_207.c (ne_207_destroy): Fix possible leak of reason_phrase + string. + +Sun Mar 9 11:01:15 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c (ne_parse_statusline): Use ne_strclean. + + * ne_session.c (ne_get_error): Use ne_strclean. + +Sun Mar 9 10:53:52 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c: Remove broken "UTF-8 decoding" support used for libxml + 1.x. + +Sun Mar 9 09:55:26 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c: Drop support for expat < 1.95.0 and libxml 1.x. + + * ne_utils.c (version_string): Include expat version string. + +Sun Mar 9 09:54:00 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c: Don't declare h_errno on Win32 either. + +Sun Mar 9 08:49:40 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (do_concat) [HAVE_STPCPY]: Use stpcpy rather than + strlen/memcpy, when available. + +Mon Mar 3 22:17:04 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c [!USE_GETADDRINFO && !HAVE_DECL_H_ERRNO): Declare + h_errno (fix build on SCO OpenServer 5.0). + +Sat Mar 1 21:22:19 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c (free_redirect): Fix once-per-session memory leak. + +Sat Mar 1 20:23:47 2003 Joe Orton <joe@manyfish.co.uk> + + Add implemention of simple memory leak tracking, for testing + purposes. + + * ne_alloc.c [NEON_MEMLEAK] (tracking_malloc, ne_free_ml, + ne_malloc_ml, ne_calloc_ml, ne_realloc_ml, ne_strdup_ml, + ne_strndup_ml, ne_memleak_dump): New functions. + + * memleak.h: New header. + +Sat Mar 1 13:44:26 2003 Joe Orton <joe@manyfish.co.uk> + + First step towards automated memory leak tests. + + * ne_alloc.c (ne_free): New function. + + * All files: replace use of free() with ne_free(). + +Sat Mar 1 09:48:39 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_path_unescape): Fix memory leak on invalid URI. + +Sat Mar 1 08:03:18 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_strclean): New function. + +Wed Feb 26 21:45:12 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_begin_request, proxy_tunnel, open_connection) + [NEON_SSL] Don't build CONNECT tunnel support code if SSL is not + supported. + +Wed Feb 26 21:44:18 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c (ne_debug_init): Allow ne_debug_init(NULL, 0) to turn + off debugging. Fix to produce debug output if the any of the + specified "channels" are active, not all. (also fixing + NE_DBG_FLUSH support). + +Tue Feb 25 23:12:31 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (process_footer): Mention number of extra bytes in + error message for oversized footer. + +Sun Feb 23 21:19:20 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (verify_response): Fix to parse nonce count as hex + string rather than decimal; fix verification of >9 responses. + +Thu Feb 13 20:35:45 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_set_useragent): Fix to append "neon/x.y.z" to + application-supplied token rather prepend. + +Thu Feb 13 09:06:22 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (open_connection): Fix for CONNECT tunnelling + (regression since 0.23.x), thanks to Nathan Hand + <Nathan.Hand@defence.gov.au>. + +Mon Feb 3 22:10:54 2003 Joe Orton <joe@manyfish.co.uk> + + Implement Daniel Stenberg's trick to avoid the slow AF_UNSPEC + lookups on Linux: + + * ne_socket.c (init_ipv6, ipv6_disabled): New function and global. + (ne_sock_init): Call init_ipv6. + (ne_addr_resolve) [USE_GETADDRINFO]: Pass AF_INET in hints if + ipv6_disabled is set. + +Mon Feb 3 20:55:47 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c [__hpux && USE_GETADDRINFO]: Undefine + USE_GETADDRINFO to work around broken implementation in HP-UX + 11.11. + +Mon Jan 27 21:39:31 2003 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (write_raw): Fix for handling EINTR during write(), + from Sergey N Ushakov. + +Thu Jan 16 21:59:03 2003 Joe Orton <joe@manyfish.co.uk> + + Allow _init after _finish to succeed: Sergey N Ushakov. + * ne_socket.c (init_result): New global variable. + (ne_sock_init): Use init_result global rather than result. + (ne_sock_finish): Clear init_result. + +Fri Dec 27 17:03:17 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (build_request): Remove redundant call to + ne_buffer_clear. + +Fri Dec 27 14:38:08 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_request_create): strdup the method string. + (ne_request_destroy): free the method. + +Mon Dec 23 17:04:32 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_write, ne_read, ne_close, ne_errno): Renamed + macros from NEON_WRITE, NEON_READ, NEON_CLOSE, NEON_ERRNO. + All callers changed. + +Mon Dec 23 16:58:43 2002 Joe Orton <joe@manyfish.co.uk> + + Add proper Win32 socket error handling, merged efforts of Johan + Lindh and Sergey N Ushakov <ushakov@int.com.ru>: + + * ne_socket.c (ne_errno, NE_ISINTR, NE_ISRESET, NE_ISCLOSED): New + macros. + [WIN32] (print_error): New function. + (set_strerror) [WIN32]: Use print_error. + (readable_raw, read_raw, write_raw): Use new error handling + macros. + (ne_addr_resolve) [WIN32]: Use WSAGetLastError() rather than + h_errno. + (ne_addr_error) [WIN32]: Use print_error. + +Tue Dec 10 21:41:26 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_iaddr_print): Renamed from ne_addr_print for + consistency with other ne_iaddr_ functions. + +Sun Dec 8 20:08:31 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (get_cnonce): Use GetCurrentThreadId() on Win32. + +Sun Nov 24 18:45:32 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c: Remove qop_values and algorithm_names arrays. + (request_digest): Inlined qop, algorithm name lookups accordingly. + +Sun Nov 24 16:45:39 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.h: Renamed ne_request_auth typedef to ne_auth_creds. + + * ne_auth.c (auth_session): Renamed reqcreds, recreds_ud fields to + creds, userdata. + (auth_register, ne_set_proxy_auth, ne_set_server_auth): Update for + ne_request_auth rename. + +Fri Nov 22 17:39:35 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (auth_challenge): Fix support for RFC2617-style digest + auth; notice the qop= parameter in challenge. Fix leak of parsed + qop array. + +Fri Nov 22 17:08:01 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (get_cnonce): Rewrite to use either pseudo-random data + from the SSL library (if available), or really-not-random data + from gettimeofday/getpid otherwise. + +Sun Nov 17 22:13:49 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_addr_print) [USE_GETADDRINFO]: Use the SACAST() + macro. + +Sun Nov 17 19:29:23 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_connect): Make address argument const. + (raw_connect): Make address argument const; adjust to use a copy + of the sockaddr structure, which is correct anyway. + (ne_addr_first, ne_addr_next): Make return pointer const. + + * ne_private.h (struct host_info): Store current address as const. + +Sun Nov 17 19:03:01 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_register_progress): Removed function. + + * ne_socket.h (ne_block_reader, ne_progress, + ne_register_progress): Removed. + + * ne_request.c (do_connect): Don't call ne_register_progress. + + * ne_request.h: Add ne_block_reader typedef. + + * ne_session.h: Include sys/types.h; add ne_progress typedef. + +Sun Nov 17 18:59:29 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_iaddr_make, ne_iaddr_cmp, ne_iaddr_free): + New functions. + +Mon Nov 11 19:51:24 2002 Joe Orton <joe@manyfish.co.uk> + + Allow discovery of document encoding. + + * ne_xml.c [HAVE_EXPAT]: (struct ne_xml_parser_s): Add encoding + field. (decl_handler): New function. + (ne_xml_doc_encoding): New function. + +Mon Nov 11 19:48:43 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c (sax_handler): Use sax_error for fatal error callback. + +Fri Oct 11 23:50:01 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h (struct ne_session_s): Change 'connected' to be a + simple boolean flag. + + * ne_session.c (ne_close_connection): Treat 'connected' as a + boolean. + + * ne_request.c (open_connection): Greatly simplified. + +Fri Oct 11 00:46:52 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c (end_propstat): Fix NULL pointer dereference + if no status object is given. + +Tue Oct 8 20:10:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c (ne_xml_create) [!HAVE_EXPAT]: Set 'replaceEntities' + flag in created parser so that entities are dereferenced in + attribute values. + +Mon Oct 7 22:08:46 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (init_ssl): Attempt to seed PRNG using EGD socket at + path EGD_PATH or a set of predetermined locations if EGD_PATH is + not defined. No longer try $EGDSOCKET or $HOME/.entropy. + +Mon Oct 7 21:32:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (register_hooks): Removed function. + (auth_register): Fold in register_hooks. + +Tue Sep 24 21:24:44 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_request_create): Pass Request-URI to + create_request hooks. + +Tue Sep 24 20:42:45 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c [__hpux]: Define _XOPEN_SOURCE_EXTENDED to 1, to + pick up h_errno definition on HP-UX 10.20. + +Wed Sep 18 21:46:28 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (struct ne_decompress_s): Add zstrinit field. + (gz_reader): Set zstrinit after inflateInit2 succeeds. + (ne_decompress_destroy): Only call inflateEnd if zstrinit is set. + +Wed Sep 18 19:56:00 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c: Remove incomplete domain support. + +Tue Sep 17 21:05:11 2002 Joe Orton <joe@manyfish.co.uk> + + Fix rejection of server certificates which have commonName as the + least specific attribute. + + * ne_session.c (check_identity): Don't ignore commonName if it is + the least specific attribute. + +Tue Sep 10 21:08:18 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (lookup_host): Destroy cached address if resolve + fails; fix segfault if a second request in the session is + dispatched after the DNS lookup fails on the first. + +Mon Sep 9 22:26:03 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (RETRY_RET): Treat SSL truncation as a legitimate + persistent connection timeout. + +Fri Aug 30 21:58:45 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_block): Clear can_persist flag if an + EOF was read (fix for read-till-EOF response terminated by an + unclean SSL shutdown). + +Mon Aug 26 18:05:00 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c: Fix HAVE_LIMITS check (Blair Zajac). + +Sun Aug 25 23:29:06 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (do_connect): Add debug message for connection + attempt. + +Sun Aug 25 22:54:04 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.h (ne_addr_print): Make address argument const. + +Sun Aug 25 11:52:32 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_addr_print): New function. + +Sun Aug 25 10:09:10 2002 Joe Orton <joe@manyfish.co.uk> + + Fix interop with Tomcat/3.2 SSL server, which performs an unclean + shutdown on an HTTP/1.0 response without a C-L header. + + * ne_request.c (read_response_block): Ignore SSL connection + truncation for a read-till-EOF response, where no reseponse + content has been read yet. + (ne_read_response_block): Always increase 'total' counter. + +Sun Aug 25 08:47:41 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (aborted): Handle code=0 case specifically, and + NE_SOCK_* as default. + +Sun Aug 25 08:24:48 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.h: Add `NE_SOCK_TRUNC' return value. + + * ne_socket.c (error_ossl): Return NE_SOCK_TRUNC when an EOF is + received without a close_notify. + +Sat Aug 24 17:37:14 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.h (ne_inet_addr): New type. + (ne_addr_first, ne_addr_next): New public interface. + (ne_sock_connect): Change first parameter to ne_inet_addr. + + * ne_socket.c: Predefine ne_inet_addr for ne_socket.h, replacing + ne_raw_addr. + (ne_addr_first, ne_addr_first): Renamed from addr_first, + addr_next; return type now ne_inet_addr; made public. + (ne_sock_connect): Fold in make_socket() macro; just connect to + single IP address passed in. + + * ne_private.h (struct host_info): Renamed 'addr' to 'address', + dded 'current' field, removed 'resolved' field. + + * ne_request.c (lookup_host): Adjust for addr->address rename. + (ne_begin_request): Call lookup_host if 'address' is NULL in + host_info structure, don't use 'resolved' flag. + (do_connect): Replaces init_socket; factor more code out from + open_connection. Loop over available addresses until an + ne_sock_connect call succeeds. + (open_connection): Moved code into do_connect. + + * ne_session.c (ne_session_destroy): Adjust for addr->address + rename. + +Sat Aug 24 13:45:26 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (count_concat, do_concat): Compact into while() + loops. + +Sat Aug 24 13:36:04 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h (VERSION_PRE11): Removed macro. + (struct ne_session_s): Add is_http11 field; removed version_major, + version_minor fields. + + * ne_request.c (add_fixed_headers): Use is_http11 flag rather than + VERSION_PRE11 macro. + (ne_begin_request): Set and use is_http11 flag. + + * ne_session.c (ne_version_pre_http11): Use is_http11 flag. + (ne_session_create): Don't set version_major, version_minor fields. + +Sat Aug 24 09:00:13 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (struct ne_request_s): Removed abs_path field. + (ne_set_request_uri): Removed function. + (ne_request_create): Set req->uri to be the actual Request-URI. + Don't use an absoluteURI in Request-URI if using SSL via a proxy + tunnel, or if passed-in path does not begin with a '/'. + (build_request): Use pre-determined Request-URI. + (proxy_tunnel): Pass true Request-URI to ne_request_create. + (ne_request_destroy): Don't free abs_path. + +Sat Aug 24 00:37:25 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (aborted): Fix handling of _CLOSED and _TIMEOUT + socket errors, and of non-socket errors. Presume ne_sock_error + cannot return NULL. + +Sat Aug 24 00:07:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_cookies.c (set_cookie_hdl): Ensure that each cookie field is + safe to free(). + +Fri Aug 23 23:46:58 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (aborted): Close the connection after setting the + session error, otherwise the socket error is lost. + +Fri Aug 23 22:50:30 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_init): Set SIGPIPE disposition before SSL + library initalization, so it happens even if SSL library + initialization fails. + +Fri Aug 23 22:03:26 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c [USE_GETADDRINFO] (make_socket): Pass SOCK_STREAM to + socket() rather than ai_socktype: on RHL6.2, ai_socktype is + returned as zero. + +Wed Aug 21 18:06:36 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c: Reinstate stdlib.h include. + + * ne_socket.h: Reinstate sys/socket.h include. + +Wed Aug 21 12:58:47 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_addr_resolve): Accept IPv6 addresses enclosed in + square brackets. + +Wed Aug 21 09:37:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_uri_parse): Parse literal IPv6 address using the + RFC2732 `[address]' syntax. + +Mon Aug 19 17:18:45 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_addr_error): Override a horribly generic error + message from gai_strerror(). + +Mon Aug 19 16:24:37 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.h: Remove netinet/in.h etc includes. + (ne_sock_addr): Add new opaque type. + (ne_addr_resolve, ne_addr_result, ne_addr_error, ne_addr_destroy): + New functions. + (ne_sock_connect): Changes address argument to `ne_sock_addr *'. + (ne_name_lookup): Removed function. + + * ne_socket.c: Added netinet/in.h etc includes. + (ne_sock_addr, ne_raw_addr): Define types. + (make_socket): New macro. + (ne_addr_resolve): Replace ne_name_lookup; store results (multiple + addresses if returned) in returned ne_sock_addr object. Use + getaddrinfo() if available. + (raw_connect, addr_first, addr_next, ne_addr_result, + ne_addr_error, ne_addr_destroy): New functions. + (ne_sock_connect): Re-implement to loop through available + addresses until a connect() succeeds; use make_socket, raw_connect + auxiliaries. + + * ne_private.h (struct host_info): Store an ne_sock_addr pointer. + + * ne_request.c (lookup_host): Use new ne_addr_* interface. + + * ne_session.c (ne_session_destroy): Destroy address objects. + +Mon Aug 19 00:19:49 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c: Move prng_seeded inside ifdef NEON_SSL region to + prevent unused variable warning for non-SSL build. + +Sun Aug 18 23:21:21 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h (ne_strerror): Return buffer. + +Sun Aug 18 23:17:56 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (set_error): Use ne_strnzcpy. + +Sun Aug 18 23:14:07 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_strerror): Use ne_strnzcpy. + +Sun Aug 18 23:11:45 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h (ne_strnzcpy): New macro. + +Sun Aug 18 22:48:27 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_init): Check directly for SIGPIPE + definition rather than HAVE_SIGPIPE. + +Sun Aug 18 13:49:49 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (set_hostport): Use %u for printing unsigned int. + +Sun Aug 18 13:47:43 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.h (NE_DBG_SSL): New constant. + + * ne_session.c [NEON_SSL] (everywhere): Use NE_DBG_SSL channel for + debugging messages. + +Sun Aug 18 08:17:19 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (match_hostname): Fix to use case-insensitive + string comparison. + +Sun Aug 18 08:10:12 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (check_identity): Check the commonName if no + alt. names of DNS type were found. + +Sun Aug 18 07:39:35 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (check_identity): Use the most specific commonName + attribute found, not the first. (for RFC2818 compliance) + +Sun Aug 18 01:54:53 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (match_hostname): Invert return value. + (check_identity): New function; split out commonName check from + check_certificate, check subjectAltName extension instead if + present. + (check_certificate): Use check_identity. + +Sat Aug 17 19:59:21 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (check_certificate): Extend debugging code to dump + the whole certificate chain, but #if 0 it by default. + +Mon Aug 12 12:04:51 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (aborted): Use NE_FMT_SSIZE_T to print ssize_t + value. + +Mon Aug 12 11:08:35 2002 Joe Orton <joe@manyfish.co.uk> + + Support PRNG seeding via EGD to make SSL work on platforms which + lack /dev/random: + + * ne_socket.c (init_ssl): New function. + (ne_sock_init): Call init_ssl, set prng_seeded global on success. + (ne_sock_use_ssl_os): Fail early if prng_seeded is not set, and + RAND_status returns false. + +Tue Aug 6 07:18:30 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_use_ssl_os): Remove goto-based error + handling. Don't call SSL_shutdown after SSL_connect fails. + +Mon Aug 5 23:18:55 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_ssl_keypw_prompt): Don't set SSL_CTX default + password callbacks, since these are never invoked. Implement + once, stub for !NEON_SSL is no longer needed. + +Mon Aug 5 21:01:54 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_ssl_load_pem): Pass private key prompt callback + to PEM_read_X509, PEM_read_PrivateKey (patch by Daniel Berlin). + Also handle errors properly; call ERR_get_error() to pop the + errors of the error stack. + +Mon Aug 5 20:15:10 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (provide_client_cert): Increase reference count on + key and certificate, to prevent them being free'd too early. + +Sun Aug 4 22:35:27 2002 Joe Orton <joe@manyfish.co.uk> + + Fix `retry_after_abort' test in request.c: + + * ne_request.c (send_request): Don't use the 'persisted' flag + until after a new connection has been opened, when it may have + been reset. + +Sun Aug 4 17:26:37 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (struct ne_request_s): Remove reqbuf field. + (ne_request_create, ne_request_destroy): Don't (de)allocate reqbuf. + (build_request): Allocate the returned buffer internally. + (ne_begin_request): Destroy the buffer after use. + +Sun Aug 4 15:36:01 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_ssl_load_pem): Close file after use. + +Sun Aug 4 12:55:49 2002 Joe Orton <joe@manyfish.co.uk> + + Factor out EPIPE, ECONNRESET handling from write_raw: + + * ne_socket.c (MAP_ERR): New macro. + (write_raw, error_ossl): Use MAP_ERR. + +Sun Aug 4 12:25:34 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_switch_ssl): New function. + +Sun Aug 4 12:24:23 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_switch_ssl): New function, really just for + test suite. + +Sat Aug 3 22:11:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (ne_forget_auth): Fix segfault if either server or + proxy auth is not in use. + +Sat Aug 3 22:06:32 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c (create, post_send, ne_redirect_register, + ne_redirect_location): Updated for new hook interface. + +Sat Aug 3 19:02:33 2002 Joe Orton <joe@manyfish.co.uk> + + Adjustment of hook interface and use: fixing a design flaw causing + a segfault in the auth hooks when two requests are used + concurrently for a single session during a CONNECT tunnel. + + * ne_request.h, ne_session.h: + (ne_get_request_private, ne_get_session_private): Replace + ne_request_hook_private, ne_session_hook_private. + (ne_set_session_private, ne_set_request_private): Replace + ne_hook_session_accessor, ne_hook_request_accessor. + + * ne_request.h (ne_create_request_fn, ne_pre_send_fn, + ne_post_send_fn): Add ne_request pointer as first argument. + (ne_hook_destroy_request): Take ne_destroy_req_fn function. + (ne_hook_destroy_session): Take ne_destroy_sess_fn function. + + * ne_request.c (struct ne_request_s): Renamed `accessor_hooks' + field to `private'. + (get_private): Renamed from call_access; don't invoke function. + (ne_null_accessor): Removed function. + + * ne_auth.c (struct auth_class): Store hook id. + (auth_session): Remove auth_request pointer. + (ah_create): Store auth_request pointer as request-private data. + (ah_pre_send, ah_post_send, ah_destroy): Retrieve auth_request + pointer from request-private data. + (register_hooks, ne_forget_auth): Use + ne_{get,set}_session_private. + + * ne_locks.c (struct lh_req_cookie): New structure. + (struct ne_lock_store_s): Remove submit_locks field. + (lk_create, lk_pre_send, submit_lock, ne_lock_using_resource, + ne_lock_using_parent, lk_destroy): Adjust to use lh_req_cookie + pointer as request-private data. + + * ne_cookies.c (create, pre_send): Adjust for hook prototype + changes. + +Wed Jul 31 23:46:17 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c [NEON_SSL]: Include limits.h for INT_MAX definition. + +Mon Jul 29 20:55:57 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (struct auth_class): New structure; abstracts out + proxy/server generic auth handling more cleanly. + (ah_server_class, ah_proxy_class): Declare variables. + (auth_session): Reference an auth_class structure. + (auth_register): Replaces auth_create. + (ne_set_server_auth, ne_set_proxy_auth): Simplify, use + auth_register. + (everywhere): Reference req_hdr etc via ->spec-> reference. + +Sun Jul 28 12:29:23 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (proxy_tunnel): Reset 'persisted' flag, so that a + newly tunnelled connection is not treated as persistent. + +Sun Jul 28 12:26:49 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h (CONCAT2, CONCAT3, CONCAT4): Removed macros. + +Thu Jul 25 23:16:00 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (send_request): Don't clear retry until a + status-line has been read. + +Thu Jul 25 00:03:17 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (basic_challenge, request_basic): Use ne_concat not + the CONCAT? macros. + + * ne_basic.c (ne_mkcol): Use ne_concat not the CONCAT2 macro. + +Wed Jul 24 00:16:39 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (count_concat, do_concat): Factored out from + ne_buffer_concat. + (ne_buffer_concat): Rewrite to use count_concat, do_concat. + (ne_concat): New (resurrected) function. + +Thu Jul 18 21:52:12 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (proxy_tunnel): Don't use server.hostport in + Request-URI; always include `:port' even if default port is used; + fix CONNECT through Inktomi Traffic-Server. + +Thu Jul 18 21:33:43 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (aborted, ne_set_request_body_fd): Use ne_strerror. + + * ne_session.c (ne_ssl_load_pem, ne_ssl_load_pkcs12): Use ne_strerror. + + * ne_basic.c (get_to_fd): Use ne_strerror. + +Thu Jul 18 20:19:30 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_strerror): New function. + + * ne_socket.c (set_strerror): Move portability logic to + ne_strerror; just use that here. + +Thu Jul 18 20:00:46 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (read_raw, write_raw): Don't re-evaluate 'errno', + per Ulrich Drepper's advice. + +Wed Jul 17 23:47:01 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (struct ne_socket_s): Store buffer for error string. + (set_error, set_strerror): New macros. + (everywhere): Use set_error, set_strerror or ne_snprintf to set + the socket error string. + +Wed Jul 17 23:19:18 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c (ne_version_match): Fix inverted minor version test. + +Sun Jul 14 20:13:59 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.h (ne_uri): Store port as unsigned. + + * ne_uri.c (ne_uri_defaultport): Return unsigned int, and zero for + undefined port. + +Sun Jul 14 20:07:35 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_session_proxy): Take port parameter as unsigned + int, as per ne_session_create. + +Sun Jul 14 20:03:21 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (strip_eol): Take ssize_t 'len' parameter. + (read_message_header): Use ssize_t for 'n'. + +Sun Jul 14 12:45:40 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_use_ssl_os): Unconditionally enable + SSL_MODE_AUTO_RETRY now OpenSSL 0.9.6 is required. + +Sun Jul 14 12:15:40 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.h (NE_XML_MEDIA_TYPE): New definition. + + * ne_acl.c (ne_acl_set), + * ne_props.c (ne_proppatch, propfind): Use NE_XML_MEDIA_TYPE, + rather than hard-coding the incorrect "text/xml" media type. + +Sun Jul 14 10:53:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c (ne_version_match): Replace ne_version_minimum. + +Sat Jul 13 11:40:37 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_negotiate_ssl): Include socket error string in + session error if SSL negotiation fails. + +Sat Jul 13 11:27:50 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (error_ossl): New function. + (ERROR_SSL_STRING): Removed macro. + (CAST2INT): New macro; safety harness for OpenSSL compatibility. + (read_ossl, write_ossl): Use error_ossl, CAST2INT. + (ne_sock_use_ssl_os): Use error_ssl. + +Sat Jul 13 11:16:07 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c: Define ECONNRESET as WSAECONNRESET on Win32. + +Sat Jul 13 10:10:03 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h (struct ne_session_s): Replace 'reqcount' with + 'persisted' flag. + + * ne_request.c (ne_end_request): Set 'persisted' flag if + connection is left open. + (send_request): Adjust to allow retry if 'persisted' flag is set. + (init_socket): Clear 'persisted' flag here... + (open_connection): ... rather than here. + +Wed Jul 10 22:51:39 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (RETRY_RET): Retry on an NE_SOCK_RESET too. + (send_request): Fix to only retry if not on the first request on a + connection (close_not_retried test). + +Sun Jul 7 20:49:09 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.h: Add NE_SOCK_RESET return value; improve comments. + + * ne_socket.c (read_raw, write_raw): Return NE_SOCK_RESET if an + ECONNRESET error is received when reading or writing. + +Sat Jul 6 13:30:15 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_status_line, discard_headers): New functions, + split out from send_request. + (send_request_body): Move debugging here from send_request. + (RETRY_RET): Renamed from CAN_RETRY. + (send_request): Simplify: remove complex 100-continue graceful + failure logic; use read_status_line, discard_headers, RETRY_RET. + Fix to only send request body once (expect_100_once test case). + Fix to not return NE_RETRY if reading status-line other than the + first fails (fail_eof_continued test case). + +Fri Jul 5 21:47:49 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (build_request): Fix from previous commit: clear + the buffer before building the request. + +Fri Jul 5 21:00:20 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (build_request): Fold Host header and Request-Line + into single ne_buffer_concat call. Don't set req->use_expect100 + here. Fold an if/else into an ?:. Optimise to use + ne_buffer_append to add 100-continue, user-supplied headers, and + trailing EOL, since they all have known lengths. + (send_request): Take request data as argument. + (ne_begin_request): Call build_request here; pass to send_request. + Move Expect100 logic here. + +Fri Jul 5 17:12:56 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_read_file): Removed function. + +Fri Jul 5 17:10:11 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (process_footer): Take unsigned char buffer. + Store calculated CRC in a uLong. + (do_inflate, gz_reader): Cast buffers to unsigned char for + strict compatibility with zlib interface. + +Wed Jul 3 19:21:17 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (struct ne_request_s): Use a fixed char array for + respbuf field. + (ne_request_create, ne_request_destroy): Don't allocate respbuf + dynamically. + (send_request): Move 'buffer' to appropriate scope. + (ne_request_dispatch): Remove 'buffer'; read into respbuf. + +Tue Jul 2 08:35:05 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (proxy_tunnel): Remove redundant sess->connected + assignment. + +Sun Jun 30 21:04:50 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_begin_request): Only set host->resolved if + lookup is successful. + +Sun Jun 30 18:25:51 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (SOCK_ERR): New macro. + (struct ne_request_s): Remove 'forced_close' field. + (aborted): Renamed from set_sockerr; also closes connection and + prints message in debug log. + (send_request_body): Don't use set_sockerr or set forced_close. + (read_response_block, read_message_header): Use SOCK_ERR; adjust + to use aborted(). + (ne_read_response_block, read_response_headers): Don't set + forced_close. + (CAN_RETRY): New macro. + (send_request): Adjust to use CAN_RETRY(); use aborted() to make + sure connection is closed in error cases. + (ne_begin_request): Don't close connection here in error cases; + don't use forced_close. + (open_connection): Adjust to use aborted() not set_sockerr(). + +Sun Jun 30 17:26:41 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_close_connection): Clarify debug messages. + +Sun Jun 30 14:36:11 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_block): Fail on chunk size lines + which contain no valid chunk size digits. + +Sun Jun 30 12:35:35 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_negotiate_ssl): Use ne_get_session rather + than req->session. + + * ne_request.c (struct header_handler, struct body_reader, + struct ne_request_s): Moved from ne_private.h. + +Sun Jun 30 12:13:58 2002 Joe Orton <joe@manyfish.co.uk> + + Cleanup of response handling: + + * ne_private.h (struct ne_response): Replace 'is_chunked' field with + 'mode' enum. + + * ne_request.c (te_hdr_handler): Set mode. + (connection_hdr_handler): Reset can_persist for 'close'. + (clength_hdr_handler): New function. + (ne_request_create): Use clength_hdr_handler to parse Content-Length + header. + (read_response_block, ne_read_response_block): Adapt for 'mode' enum; + simplify. + (normalize_response_length): Removed function. + (ne_begin_request): Fold in normalize_response_length logic. + (ne_end_request): Simplify logic. + +Sun Jun 30 11:08:26 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c: Remove X509_NAME workaround in favour of a + neon.mak change. + +Tue Jun 25 23:14:34 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c: Undefine X509_NAME if it is defined (by a Windows + header). + +Tue Jun 25 22:51:15 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c: Rename READ_BUFFER to RDBUFSIZ. + +Tue Jun 25 21:07:13 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h (struct host_info): Store port as unsigned int. + + * ne_session.c (set_hostport, set_hostinfo, ne_session_create): + Take port argument as unsigned int. + + * ne_socket.c (ne_sock_connect): Take port argument as unsigned + int. + +Tue Jun 25 20:59:14 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.h [__GNUCC__] (NE_DEBUG): Remove implementation using + GNU C extensions. + +Sun Jun 23 22:47:52 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (set_request_uri): Renamed from ne_set_request_uri; + made static. + (ne_request_create): Update accordingly. + + * ne_private.h (ne_set_request_uri): Removed prototype. + +Sun Jun 23 15:40:57 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (send_request, ne_request_destroy): Free + reason_phrase now it is malloc-allocated. + +Sun Jun 23 14:59:04 2002 Joe Orton <joe@manyfish.co.uk> + + Simplify given loss of const qualifier on ne_status.reason_phrase: + + * ne_props.c (struct propstat): Remove r_phrase field. + (end_propstat, free_propset): Use status.reason_phrase not + r_phrase field. + +Sun Jun 23 14:42:22 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_207.h (ne_207_end_response, ne_207_end_propstat): Remove + status_line parameter from callbacks. + + * ne_207.c (struct ne_207_parser_s): Remove status_line field. + (end_element): Don't store status_line. + (handle_error): Drop status_line argument, recreate dummy status + line from status object. + (end_response, end_propstat): Drop status_line arguments. + + * ne_props.c (end_propstat, end_response): Drop status_line + arguments. + +Sun Jun 23 14:39:00 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.h (ne_status): Remove const qualifier from + 'reason_phrase' field. + + * ne_utils.c (ne_parse_statusline): strdup the reason_phrase on + successful return. + +Sun Jun 23 11:39:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (struct ne_decompress_s): Replace footer union + with buffer. + (BUF2UINT): New macro. + (process_footer): Convert footer to integer in a portable manner, + using BUF2UINT. + +Sun Jun 23 09:05:25 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (ne_decompress_s): Use unsigned int for 32-bit + integers, not uLong (fix for 64-bit platforms). + +Wed Jun 19 18:46:40 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_session_destroy): Don't leak the proxy + hostname. + +Sun Jun 16 14:09:31 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_block): Use NE_FMT_SSIZE_T rather + than %d, cast field precision argument to int. + (ne_pull_request_body): Use ssize_t for store callback return + value, use NE_FMT_SSIZE_T rather than %d, cast field precision + argument to int. + +Sun Jun 16 12:15:19 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_negotiate_ssl): Don't leak peer certificate in + error cases. Fix spelling mistake in error message. + +Sun Jun 16 11:23:23 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (open_connection): When SSL negotation fails after + doing CONNECT request, use ne_close_connection so that + sess->connection is reset to 0, and ne_sock_close isn't called + twice for the socket. + +Wed Jun 12 23:22:20 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c (ne_proppatch): Add missing call to + ne_lock_using_resource. + +Mon Jun 10 20:45:27 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (verify_response): Remove redundant prototype, fix + sscanf format string to use signed integer. + +Mon Jun 10 20:13:57 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (do_inflate): Continue calling inflate() whilst + unconsumed input remains: fix from Justin Erenkrantz + <jerenkrantz@apache.org>. + +Mon Jun 10 19:53:59 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_readline): If a complete line is found in + the buffer, avoid the memmove() and simply copy the line directly + out of the buffer. + +Sun Jun 9 11:39:20 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c (post_send): Perform simple relative URI + resolution. + +Tue Jun 4 16:51:54 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_path_parent): Simplify. + +Mon Jun 3 17:50:27 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_cookies.c (set_cookie_hdl): Avoid free'ing cookie name/value, + thanks to Dan Mullen. + +Mon Jun 3 17:45:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_base64): Use size_t for outlen. + +Mon Jun 3 17:42:34 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.h, ne_socket.h [WIN32]: Move ssize_t definition to + ne_socket.h. + +Mon Jun 3 17:27:21 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_block): Use correct types for + passing to/from ne_sock_*. + +Mon Jun 3 11:32:20 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (ne_decompress_destroy): Don't fail if response + reader callback is never invoked. + +Sun Jun 2 12:51:35 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (read_ossl, read_raw): Call readable_{ossl,raw} + function here. + (ne_sock_read, ne_sock_peek, ne_sock_readline): Remove explicit + calls to ops->readable before ops->read. + +Thu May 30 22:00:07 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (ne_sock_readline): Optimise to use socket read + buffer directly, and use ->read (and ->readable) functions. + +Tue May 28 17:00:34 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_session_destroy): Don't free proxy.hostport, + since it's no longer set. + +Sun May 26 19:11:46 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c: #error if no expat header is configured; flatten + nested #if's, include libxml/xmlversion.h if present. + +Sun May 26 19:09:04 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c: Include libxml/xmlversion.h if present. + +Sun May 26 11:55:30 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (set_hostport): Renamed from get_hostport: set + host_info field directly; take defaultport argument. + (set_hostinfo): Don't use get_hostport. + (ne_session_create): Use set_hostinfo and set_hostport; pass + in default port correctly for http:/https:. + +Thu May 23 19:44:44 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c (resolve_nspace): Split out from parse_element. + (ne_xml_get_attr): Take parser object, and optional + namespace; resolve the namespace if necessary. + (parse_element): Use resolve_nspace. + + * ne_props.c (startelm): Use new ne_xml_get_attr interface. + +Wed May 22 22:29:05 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_dates.c: Renamed HAVE_TM_GMTOFF to HAVE_STRUCT_TM_TM_GMTOFF + from use of AC_CHECK_MEMBERS. + +Tue May 21 21:21:31 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c (ne_redirect_register): Drop confirm, notify, + userdata arguments. + (struct redirect): Drop most fields; add a uri structure. + (auto_redirect): Removed function. + (post_send): Remove functionality which retries a request with a + different URI to automatically follow redirects. Qualify the URI + if non-absolute. + (create): Remove now redundant code. + (ne_redirect_location): Return an ne_uri object. + +Sun May 19 18:53:22 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_set_useragent): Use strcat/malloc/strcpy + directly, rather than CONCAT2; allow compiler optimisations. + (AGENT): Renamed from NEON_USERAGENT, append space. + +Sun May 19 17:31:43 2002 Joe Orton <joe@manyfish.co.uk> + + Move everything exported by ne_socket.h into ne_*/NE_* namespace; + purge inappropriate and unused interfaces. Renaming done by Olof + Oberg. + + * ne_socket.h: + + (SOCK_FULL): Removed constant. + (sock_call_progress, sock_transfer, sock_sendline, + sock_send_string, sock_readfile_blocked): Removed functions. + + (NE_SOCK_ERROR, NE_SOCK_TIMEOUT, NE_SOCK_CLOSED): + Renamed constants. + + (ne_progress, ne_block_reader): Renamed types. + + (ne_register_progress, ne_sock_init, ne_sock_exit, ne_sock_read, + ne_sock_peek, ne_sock_block, ne_sock_fullwrite, ne_sock_readline, + ne_sock_connect, ne_sock_accept, ne_sock_fd, ne_sock_error, + ne_sock_read_timeout, ne_name_lookup, ne_service_lookup, + ne_sock_use_ssl, ne_sock_use_ssl_os): Renamed functions. + + * ne_private.h, ne_request.c, ne_session.c: Update accordingly. + + * ne_request.c (build_request): Return the ne_buffer pointer. + (send_request): Remove redundant strlen(), use known buffer + length. + + * ne_request.h: Drop ne_block_reader definition. + +Sun May 19 13:32:12 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_get_session, ne_get_request): Take const + request pointer. + +Sun May 19 13:21:17 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_buffer_ncreate): Renamed from + ne_buffer_create_sized. + + * ne_session.c (check_certificate, provide_client_cert): Update + accordingly. + + * ne_request.c (ne_request_create): Update accordingly. + +Sun May 19 13:12:14 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_token): Drop quotes parameter. + (ne_qtoken): Split out from ne_token. + + * ne_basic.c (dav_hdr_handler, ne_content_type_handler): Use + ne_qtoken. + + * ne_compress.c (find_token): Removed function. + (gz_reader): Compare header value directly against "gzip", + remove a stale comment. + +Sun May 19 09:45:28 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.h (sock_fullread): Return ssize_t; takes buflen as + size_t. + (sock_read, sock_peek): Fix prototypes to match actual definition. + + * ne_socket.c (write_raw): Return ssize_t. + +Sat May 18 14:53:45 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h (ne_buffer): Remove separate struct ne_buffer_s + definition. + + * ne_string.c (ne_buffer_create_sized): Don't use struct + ne_buffer_s. + +Sun May 12 11:33:02 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_base64): Moved from base64.c. + + * base64.c, base64.h: Removed files. + + * Makefile.in: Updated accordingly. + + * ne_auth.c: Don't include base64.h. + +Sun May 12 11:26:05 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h (ne_utf8_decode, ne_utf8_encode): Removed functions. + +Sat May 11 15:42:24 2002 Joe Orton <joe@manyfish.co.uk> + + As part of patch from Olof Oberg <mill@pedgr571.sn.umu.se>: + + * ne_request.h (ne_destroy_fn): Renamed from typo'ed + ne_destory_fn. + + * ne_request.c (ne_request_destroy, ne_hook_destroy_request, + ne_hook_destroy_session): Update accordingly. + + * ne_session.c (ne_session_destroy): Update accordingly. + +Thu May 9 21:44:15 2002 Joe Orton <joe@manyfish.co.uk> + + Major improvements to socket layer to incorporate socket read + buffering and rewrite sock_readline, and add an abstraction layer + to simplify SSL support. Grunt work by Jeff Johnson + <jbj@redhat.com> + + * ne_socket.c (struct iofns): New type. + (struct nsocket_s): Store 'ops' pointer to I/O functions in use + for the socket. Add buffer, bufpos, bufavail fields for read + buffering. + (sock_block, sock_read, sock_peek): Reimplement to add read + buffer, simplify to use I/O functions abstraction. + (readable_raw, read_raw, write_raw, readable_ossl, read_ossl, + write_ossl): Factored out from sock_read/fullwrite, avoiding + in-lined ifdefs. + (sock_fullwrite): Just use ops->write. + (sock_readline): Simplify greatly to exploit read-buffering, return + ssize_t. + (sock_fullread): Simplify, removing unnecessary local variables, + return ssize_t. + (create_sock, sock_enable_ssl_os): Set ops pointer. + +Wed May 8 11:54:48 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (sock_name_lookup): Avoid casts; use INADDR_NONE. + [!INADDR_NONE]: Define to (unsigned long) -1. + +Wed May 1 22:19:18 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.h (ne_lock): Document that ->token and ->owner fields + must be malloc-allocated if non-NULL. + +Wed May 1 22:15:41 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (get_ltoken_hdr): New function. + (ne_lock): Correctly parse Coded-URL from Lock-Token response + header. + +Wed May 1 22:03:08 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c (post_send): Adjust for ne_uri_parse handling of + unspecified port. + +Wed May 1 22:00:50 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (provide_client_cert): Fail if peer certificate not + known when client cert requested. + +Wed May 1 21:58:35 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.h (ne_ssl_provide_fn): Adjust callback typedef to + return void. + +Wed May 1 21:52:40 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.h: Remove NE_SERVERAUTH and NE_AUTHPROXY; fix + NE_PROXYAUTH description. + +Wed May 1 21:32:54 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_uri_parse): For consistency, port field is 0 if + unspecified. + +Tue Apr 30 10:05:48 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (shave_string): Removed function. + +Tue Apr 23 21:19:53 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c (start_propstat, startelm): Use ne_realloc not + realloc (thanks to Jeff Johnson). + +Tue Apr 23 20:55:56 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c: Include parser.h or libxml/parser.h, depending on + which is found. + +Mon Apr 15 00:37:43 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (build_request, send_request): Simplify logic. + +Sun Apr 14 16:59:50 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c: Remove unused accidental 'propstat' global. + (struct propstat): Add r_phrase field. + (end_propstat): Dup the reason_phrase string. + (free_propset): Free the reason_phrase. Avoid another possible + free(NULL) call. + +Sun Apr 14 12:00:54 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_content_type_handler): For text/* media types, + use default charset of ISO-8859-1. + +Sat Apr 13 23:11:07 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.h: Include trio.h if NEON_TRIO is defined. + +Sun Apr 7 17:38:14 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_headers): Don't zero-initialize hdr. + +Sun Apr 7 17:15:23 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_headers): Ignore whitespace between + header name and colon, simplify logic a little. + +Sun Apr 7 14:09:07 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_ssl_readable_dname): New function. + +Sun Apr 7 12:32:25 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_buffer_destroy): Remove redundant check for data + pointer being NULL. + +Wed Apr 3 19:44:59 2002 Joe Orton <joe@manyfish.co.uk> + + Optimisation/simplification of header name hashing. + + * ne_request.c (hash_and_lower): Renamed from hdr_hash; convert + string to lower-case in-place too. + (lower_string): Removed function. + (ne_add_response_header_handler): Use hash_and_lower rather than + lower_string. + (HH_ITERATE): Change parameter name to 'ch'. + +Fri Mar 29 23:00:57 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_uri_parse): Minor optimisation. + +Mon Mar 25 21:45:36 2002 Joe Orton <joe@manyfish.co.uk> + + Pass a lock context around during LOCK processing; search for the + correct <activelock> element in the response body. + + * ne_locks.c (ne_lock_create): Don't take a path argument. + (ne_unlock): Constify lock parameter. + (discover_results): Don't parse out href here... + (ld_create): do it here instead; renamed from create_private. + (lk_startelm): New function. + (lk_endelm): Renamed from end_element_lock. + (ne_lock): Require a Lock-Token response header; pass lock context + to callbacks. Copy lock back out. + +Mon Mar 25 21:35:42 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.h (NE_SSL_FAILMASK): New constant. + (NE_SSL_*): Shift right one bit. + +Mon Mar 25 21:21:18 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_close_connection): Return void. + +Mon Mar 25 20:09:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c (free_propset): Avoid free(NULL). + +Mon Mar 11 19:59:04 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (ne_lock_using_parent): Iterate over the lock list by + hand: check for infinite depth locks with cover the parent too + (fixing if_covered_child test). + +Mon Mar 11 19:25:44 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_request_dispatch): Move variable to scope in + which is is used. + +Sun Mar 10 22:04:58 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (NEON_BASEOBJS): Always build ne_compress.o. + +Sun Mar 10 22:01:54 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c [!NEON_ZLIB] (ne_decompress_reader, + ne_decompress_destroy): Add stubs. + +Sun Mar 10 21:42:11 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (struct discover_ctx): Store an ne_session pointer. + (discover_results): If lock URI is not an absoluteURI, qualify it + using the server host/port/scheme from the session. Don't leak + the lock object. + (create_private): Simplify, use ne_lock_create. + +Thu Mar 7 20:08:07 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_uri_defaultport): Fix default port number for https + scheme. + +Wed Mar 6 21:22:23 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (lk_pre_send): Use an absoluteURI in the If: header. + +Wed Mar 6 21:15:00 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_uri_unparse): New function. + +Tue Mar 5 22:57:00 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_uri_cmp): Compare hostnames and schemes + case-insensitively, and compare empty abspath and "/" as + equivalent, as per RFC 2616. + +Tue Mar 5 20:53:54 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (ne_uri_defaultport): New function. + +Mon Mar 4 21:10:29 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.h (ne_uri): Renamed from struct uri. + + * ne_uri.c (ne_path_parent): Renamed from uri_parent. + (ne_path_has_trailing_slash): Renamed from uri_has_trailing_slash. + (uri_abspath, uri_absolute): Removed. + (ne_uri_parse): Renamed from uri_parse, don't take a 'defaults' + parameter. + (ne_uri_free): Renamed from uri_free. + (ne_path_unescape): Renamed from uri_unescape. + (ne_path_escape): Renamed from uri_abspath_escape. + (ne_uri_cmp): Renamed from uri_cmp. + (ne_path_compare): Renamed from uri_compare. + (ne_path_childof): Renamed from uri_childof. + + * ne_basic.c, ne_locks.c, ne_uri.c, ne_redirect.c, ne_session.c, + ne_session.h: all callers changed. + +Mon Mar 4 01:03:23 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (strip_eol): Fix potential segfault. + +Mon Mar 4 00:38:10 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (insert_lock): New function. + (ne_lockstore_add, submit_lock): use insert_lock. + +Mon Mar 4 00:33:39 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (ne_lockstore_remove): Free list item. + +Mon Mar 4 00:31:08 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (free_list): Really destroy the lock. + (ne_lock_free): Don't free the lock object itself. + +Mon Mar 4 00:17:18 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_request_destroy): Free accessor hook list. + +Sun Mar 3 20:35:09 2002 Joe Orton <joe@manyfish.co.uk> + + Changes to lock interface; replacing "lock session" with a lock + store, which can be registered with an ne_session. Lock objects + now store URI as complete URI structure. + + * ne_locks.h (struct ne_lock): Store URI as complete URI + structure. Remove next/prev fields. + (ne_lock_store): New type. + + * ne_locks.c (struct lock_list): New type. + (struct ne_lock_store_s): Replaces ne_lock_session_s; store + lock_list pointers for stored locks, cursor, and locks to + submit. + (ne_lock_create): New function. + (lk_create): Renamed from create. + (lk_pre_send): Renamed from pre_send; adjust for lock list + type and to use URI path. + (free_list): New function; split out from old 'destroy'. + (lk_destroy): Renamed from destroy; use free_list. + (ne_lockstore_create, ne_lockstore_destroy, + ne_lockstore_first, ne_lockstore_next): New functions. + (ne_lockstore_register): Most of old ne_lock_register. + (submit_lock): Adjusted for lock_list type. + (ne_lockstore_findbyuri): Renamed from ne_lock_find; use + full URI structure. + (ne_lock_using_resource, ne_lock_using_parent): Adjusted + for lock_list/full URI changes. + (ne_lock_iterate): Removed function. + (ne_lockstore_add, ne_lockstore_remove): Renamed from + ne_lock_add, ne_lock_copy; adjusted for lock_list/full URI. + (ne_lock_copy): Adjusted for full URI. + (ne_lock_create, ne_lock_destroy): New function. + (ne_lock, ne_lock_refresh, ne_unlock): Adjusted for full URI. + +Sun Mar 3 15:23:40 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c (uri_cmp): New function. + +Sun Mar 3 11:01:30 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_fill_server_uri): New function. + +Mon Feb 25 21:25:27 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c (version_string): Add zlib version. + +Mon Feb 25 20:49:07 2002 Joe Orton <joe@manyfish.co.uk> + + * (everywhere): Replace use of snprintf, vsnprintf with + ne_snprintf, ne_vsnprintf so that trio replacements are used when + appropriate. + + * ne_dates.h: Pick up ne_utils.h for ne_{v,}snprintf defines. + +Sun Feb 24 11:23:05 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.h: Define ne_snprintf, ne_vsnprintf for trio or + non-trio builds. + +Sun Feb 24 11:20:42 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (check-incl): Add target to check that each header + file can be included standalone. + +Sun Feb 24 11:17:54 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.h: Add missing sys/types.h include. + +Sun Feb 24 11:12:22 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.h: Remove HTTP_QUOTES, HTTP_WHITESPACE globals. + + * ne_cookies.c (set_cookie_hdl): Don't use HTTP_QUOTES, + HTTP_WHITESPACE globals. + +Wed Feb 20 19:32:48 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (set_sockerr, ne_set_request_body_fd, + send_request): Use ne_set_error rather than accessing session + error directly. + +Tue Feb 19 21:34:59 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c (version_string) [NEON_SOCKS]: Mention SOCKSv5 + support. + + * ne_socket.c (sock_init) [NEON_SOCKS]: Call SOCKSinit. + +Tue Feb 19 19:21:07 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (open_connection): Remove notify_status call + duplicated with ne_negotiate_ssl. + +Tue Feb 19 19:16:44 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (sock_get_version): Removed function. + +Tue Feb 19 19:12:52 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_ssl_provide_ccert): Moved outside ifdef + NEON_SSL. + [!NEON_SSL] (ne_ssl_load_pem, ne_ssl_load_pkcs12, + ne_ssl_keypw_prompt): Added stubs. + +Sun Feb 17 21:15:34 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_session_create, ne_session_destroy): Only use + the SSL context is SSL is being used for the session. + +Sun Feb 17 20:19:05 2002 Joe Orton <joe@manyfish.co.uk> + + Add back client certificate support, much improved. + + * ne_private.h (struct ne_session_s): Add client cert/key fields, + provider, privkey password callbacks. + + * ne_socket.c (sock_init): Call PKCS12_PBE_add. + (sock_enable_ssl_os): Add optional 'appdata' argument. + (sock_enable_ssl): Adjust accordingly. + + * ne_session.c (provide_client_cert, privkey_prompt, + ne_ssl_keypw_prompt, ne_ssl_load_pkcs12, ne_ssl_load_pem, + ne_ssl_provide_ccert): New functions. + (ne_negotiate_ssl): Pass session as appdata to sock_enable_ssl_os. + +Sun Feb 17 12:32:34 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (make_dname): New function. + (check_certificate): Use make_dname. + +Sun Feb 17 11:29:10 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (struct get_context): Remove unused 'progress' field, + add 'session' field. + (get_to_fd, content_range_hdr_handler, clength_hdr_handler): Set + session error directly. + (clength_hdr_handler): Also fix check for expected range. + (everywhere): Initialize session field, don't set session error; + use NE_FMT_OFF_T to print off_t's rather than casting to long int. + +Sat Feb 16 23:24:10 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.h (NE_XML_STRIPWS): New element flag. + + * ne_xml.c (start_element): Clear cdata buffer if not in mixed + mode. (char_data): Only strip leading whitespace if + NE_XML_STRIPWS is set for the element. + +Sat Feb 16 14:52:59 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (enum state): New state NE_Z_AFTER_DATA. + (struct ne_decompress_s): Add fields for storing and parsing + stream footer; add checksum field for storing current crc. + (process_footer): New function. + (do_inflate): Compute checksum. Switch to AFTER_DATA state and + process footer after reading DEFLATE data. + (gz_reader): Fail on trailing content. Handle AFTER_DATA state. + (ne_decompress_destroy): Return error if final state was not + PASSTHROUGH, or FINISHED. + (ne_decompress_reader): Initialize crc. + +Sat Feb 16 14:26:54 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (ne_decompress_destroy): Fix potential segfault + with use-after-free. + +Thu Feb 14 16:50:40 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_headers): Ignore header lines + without a ':', rather than failing the request. + +Tue Feb 12 20:17:49 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_response_block): Read chunk size as unsigned + using strtoul; check that it fits within an unsigned int (and + hence, probably a size_t). + +Tue Feb 12 20:15:13 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h (STRIP_EOL): Removed macro. + +Mon Feb 11 22:11:03 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (match_hostname): Match fully-qualified hostnames + against commonName with leading "*." wildcard. + +Mon Feb 11 20:47:28 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (match_hostname): New function. + (check_certificate): Use it. + +Sun Feb 10 00:50:49 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (lookup_host): Set error string on lookup failure. + +Sun Feb 10 00:34:42 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (strip_eol): New function; more efficient + STRIP_EOL. + (send_request): Use strip_eol. + (read_message_header): Use strip_eol, simplify, remove redundant + variables. + +Sat Feb 9 21:02:31 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_set_error): Drop STRIP_EOL call. + +Sat Feb 9 21:01:01 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_set_error): Take printf-style format string + + varargs list. + +Sat Feb 9 16:15:09 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.h (SOCKET_READ_TIMEOUT): Moved to ne_socket.c. + + * ne_socket.c (struct nsocket_s): Add rdtimeout field. + (create_sock): Initialize rdtimeout to SOCKET_READ_TIMEOUT. + (sock_read, sock_recv): Use ->rdtimeout field for read timeout. + (sock_set_read_timeout): New function. + + * ne_private.h (struct ne_session_s): Add rdtimeout field. + + * ne_session.c (ne_set_read_timeout): New function. + + * ne_request.c (init_socket): New function. + (open_connection): Use init_socket. + +Sat Feb 9 15:11:59 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_session_destroy): Don't leak the server cert. + +Sat Feb 9 09:59:11 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (check_certificate): Only call verification + callback once per certificate; watch for the server cert + changing and fail if it does. + +Wed Feb 6 20:28:27 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (check_certificate): Only call verification + callback if failures is non-zero. + (ne_ssl_load_ca): Renamed from ne_ssl_add_ca. + (ne_ssl_load_default_ca): New function. + +Wed Feb 6 20:21:29 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (sock_init): Cache and return result of + initialization. + +Wed Feb 6 01:12:20 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (check_certificate): Ignore cert validity errors + from OpenSSL since these are duplicated. + +Wed Feb 6 01:08:57 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_negotiate_ssl): Fix for invalidating cached + SSL_SESSION. + +Wed Feb 6 01:03:37 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c [!NEON_SSL] (STUB): New function. + (ne_negotiate_ssl, ne_ssl_add_ca): Implement using STUB. + +Tue Feb 5 19:56:43 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.h (ne_ssl_certificate): New type. + + * ne_session.c (ne_session_create) [NEON_SSL]: Create the SSL_CTX + structure. + (ne_ssl_get_context): Return the SSL_CTX rather than setting it. + (ne_session_destroy): Free the SSL_CTX. + + (asn1time_to_string): Function moved in from sslcerts.c. + (check_certificate): Use OpenSSL's internal validity result. + Pass back an ne_ssl_certificate to the verification function; + including validity dates. + (ne_ssl_add_ca): New function, registers CA certs. + +Sat Feb 2 14:05:26 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (sock_enable_ssl_os): Take an optional SSL_SESSION + argument. + + * ne_private.h (struct ne_session_s): Add an SSL_SESSION field. + + * ne_session.c (ne_negotiate_ssl): Pass stored SSL session to + sock_enable_ssl_os, cache session after successful negotiation. + (ne_session_destroy): Free cached session. + +Sat Feb 2 10:45:46 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c, ne_utils.c: Globally replace ENABLE_SSL cpp symbol + with NEON_SSL. + +Sat Feb 2 09:43:27 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (check_certificate): Use 1K on-stack buffer. + +Sat Feb 2 08:27:08 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h (struct host_info): Add 'resolved' flag. + (struct ne_session_s): Add scheme field, rename have_proxy to + use_proxy, remove proxy_decider. + (struct ne_request_s): Remove use_proxy field. + + * ne_request.c (set_sockerr, ne_set_request_uri, build_request, + open_connection): Use session->use_proxy field to determine + whether proxy is used. + (ne_request_create): Drop use of proxy_decider callback. + (lookup_host): Moved here from ne_session.c. + (ne_begin_request): Lookup server/proxy hostname if not already + resolved. + + * ne_session.c (ne_session_create): Moved within file; takes + scheme, and server hostname, port as arguments. + (ne_ssl_enable, ne_session_decide_proxy, ne_session_server): + Removed functions. + (ne_get_scheme): Simply return scheme field. + +Fri Feb 1 23:12:38 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (add_fixed_headers): Remove last traces of TLS + upgrade support. + +Thu Jan 31 20:50:12 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h (struct ne_session_s): Rename use_secure to + use_ssl; removed nssl_context, added SSL_CTX, server cert, verify + callback pointers. + + * ne_request.c (send_request): Remove support for TLS upgrade. + (open_connection): Use ne_negotiate_ssl; close socket properly if + negotiation fails. + + * ne_session.c (ne_session_destroy): Free SSL_CTX stored in + session. + (ne_ssl_set_context, ne_ssl_set_verify, verify_err, getx509field, + check_context, ne_negotiate_ssl, ne_ssl_server_cert): New + functions. + (ne_set_secure_context, ne_set_request_secure_upgrade, + ne_set_accept_secure_upgrade): Removed functions. + (ne_ssl_enable): Renamed from ne_set_secure. + + * ne_socket.c (struct nssl_context_s): Removed type. + (sock_create_ssl_context, sock_destroy_ssl_context, + sock_disable_*, key_prompt_cb, sock_set_key_prompt, + sock_set_client_cert): Removed functions. + (sock_enable_ssl_os): Renamed from sock_make_secure; take an + SSL_CTX pointer, and optionally pass out the SSL structure. + (sock_enable_ssl): New function. + +Wed Jan 30 19:47:42 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_buffer_concat, ne_buffer_zappend, + ne_buffer_append, ne_buffer_grow): Don't return success value, + presume universe ends at OOM. + +Sat Jan 26 10:57:42 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c: Renamed enum state constants to have prefix + NE_Z_, to avoid conflict with Windows headers (Branko Èibej). + +Mon Jan 14 20:26:31 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_concat): Removed function - it didn't work, and + it wasn't used. + +Mon Jan 14 02:09:38 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_content_type_handler): Parse charset parameter. + +Sun Jan 13 14:29:00 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_content_type_handler): Remove trailing '/' from + parsed type, fix search for parms separator (Greg Stein). + +Sun Jan 13 12:07:51 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_207.c (ne_simple_request): Drop unused Content-Type handling. + +Thu Jan 10 00:39:17 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (hdr_hash): Mark as inline. + +Tue Jan 8 22:03:42 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (add_timeout_header): New function. (ne_lock, + ne_lock_refresh): Send a Timeout header if lock->timeout is set. + +Mon Jan 7 21:48:38 2002 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (parse_timeout): Fix parsing lock timeout (Arun + Garg). + +Mon Dec 17 22:46:36 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h (struct ne_session_s): Make expect100_works a plain + integer (rather than a bitfield). + +Sun Dec 9 14:04:27 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_buffer_grow, ne_buffer_create_sized): Don't + zero-fill new memory. (ne_buffer_concat): Zero terminate the + string as _grow doesn't do it. + +Sun Dec 9 13:31:55 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_buffer_zappend): Minor optimisation; implement + using ne_buffer_append. + +Sun Dec 9 13:18:35 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_buffer_concat): Optimise to use time O(n) [n == + total string length). + +Sun Dec 9 11:57:56 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (NEON_DAVOBJS): Remove ne_acl.o. + +Sat Dec 8 01:11:30 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_pull_request_body): Use NE_FMT_SIZE_T in + debugging message; cast size_t to int to avoid GCC warning for + field size parameter. (set_body_size): Use NE_FMT_SIZE_T. + + * ne_xml.c (ne_xml_parse): Similarly. + +Mon Dec 3 19:56:07 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c (ne_session_destroy): Return void. + +Sat Dec 1 18:37:43 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (ah_create): Reset attempt counter + here... (ah_post_send): ...rather than here. + +Tue Nov 27 21:26:01 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (send_with_progress): Actually call the callback; + fix for correct sock_fullwrite return codes. + +Tue Nov 27 20:20:40 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h (VERSION_PRE11): Define macro; as + ne_version_pre_http11. + + * ne_session.c (ne_version_pre_http11): Use VERSION_PRE11. + + * ne_request.c (add_fixed_headers, build_request, ne_end_request): + Use VERSION_PRE11. + +Sun Nov 18 19:32:56 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (discover_results): Check status is 2xx before + invoking callback; pass NULL lock and non-NULL status pointer in + failure cases. (create_private): Initialize lock to some "value + unspecified" defaults. + +Sun Nov 18 19:25:10 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (auth_session): Rename 'tries' field to 'attempt'. + (get_credentials, ah_pre_send, ah_post_send): Increment attempt + counter only when requesting credentials; reset it to zero when no + auth failure is signaled. + +Sun Nov 18 15:49:00 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.h (ne_request_auth): Pass username and password as + buffers of size NE_ABUFSIZ to callback. Add 'attempt' argument. + + * ne_auth.c (auth_session): Store username in buffer. + (get_credentials, basic_challenge, digest_challenge): Updated for + callback prototype changes. (ah_post_send): Request credentials, + and retry authentication until callback returns non-zero. + +Mon Nov 12 20:57:56 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (get_to_fd): Really cope with short writes (thanks to + rado <dzusto@yahoo.com>). + +Sun Nov 4 15:09:03 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_props.h: Define NE_ELM_PROPS_UNUSED for picking element ids + for use with the propfind XML parser. + +Sat Nov 3 19:06:04 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c (NSPACE): New macro. (set_body, pnamecmp, startelm, + free_propset): Handle property having NULL nspace element in + propfind code. + +Sun Oct 28 22:04:49 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c (parse_element): Prevent false matches of found prefix + "abcde" against stored prefix "abcdeFGH". Compare + case-sensitively. + +Fri Oct 26 20:28:03 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (send_request): Fix case where persistent + connection times out, and improve error handling. + +Thu Oct 25 20:42:24 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c (ne_proppatch): Really handle properties with a NULL + namespace correctly; use the "D:" prefix for elements in the + "DAV:" namespace; allow properties to have no namespace. + +Tue Oct 16 08:54:46 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c (parse_element): Fail the parse if a namespace prefix + definition is given with an empty value. + +Tue Oct 16 08:52:40 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_props.h, ne_207.h: Move ne_propname definition into + ne_props.h. + +Tue Oct 16 08:49:42 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c (ne_proppatch): Handle properties with a NULL nspace + field correctly. + +Sun Oct 7 19:31:06 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_acl.c (ne_acl_set) [USE_DAV_LOCKS]: Notify use of resource to + locking code. + +Sun Oct 7 17:45:01 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_acl.c, ne_acl.h: New files, contributed by Arun Garg + <arung@pspl.co.in>. + + * Makefile.in: Add ne_acl.* to build. + +Sun Oct 7 16:10:05 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_private (struct ne_session_s): Add 'reqcount' field. + + * ne_request.c (send_request): Refactor slightly; don't loop, but + return NE_RETRY when appropriate. Increment reqcount. + (ne_begin_request): Loop if send_request returns NE_RETRY. + (open_connection): Reset reqcount field. + +Tue Oct 2 21:11:39 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_dates.c (GMTOFF): New macro. (ne_iso8601_parse, + ne_rfc1123_parse, ne_rfc1036_parse, ne_asctime_parse): Use new + macro, fix up date handling on some platforms. + +Sat Sep 29 14:20:47 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (gz_reader): Fix tests 4 and 7: don't try to + inflate after reading header if no bytes are left in the buffer. + +Sat Sep 29 14:04:11 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c: Fix API; return an opaque object which must + be destroyed later. + + (ne_decompress_reader): Renamed from ne_gzip_response_body_reader. + Doesn't need the session object passed in any more. + (ne_decompress_destroy): Merge of co_destroy, co_post_end. + +Sat Sep 29 13:50:43 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_get_session): New function. + +Sat Sep 29 12:52:31 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c (parse_header): Bail if flags are set to something + unexpected. + +Sat Sep 29 11:15:30 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_compress.c, ne_compress.h: New files. + + * Makefile.in: Add deps for ne_compress. + +Thu Sep 27 09:05:24 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c: Adapted for new hooks interface. + + * ne_cookies.c: Adapted for new hooks interface. + (ne_cookie_register): New function. + +Thu Sep 27 09:01:03 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c, ne_locks.c: Adapted for new hooks interface. Store + pointer to per-request object in the per-session object. + +Thu Sep 27 08:48:16 2001 Joe Orton <joe@manyfish.co.uk> + + Re-write hooks interface to register callbacks individually rather + than as a block. Inspired by the Apache 2.0/APR hooks interface. + + * ne_private.h (struct hook): Store a callback, userdata, id. + (struct hook_request): Removed. (struct ne_session_s): Store + hooks lists for create_req, pre_send, post_send, destroy_req, + destroy_sess, accessor. (struct ne_request_s): Store accessor + hooks list. + + * ne_request.c (ne_add_hooks): Removed. + (ne_hook_create_request, ne_hook_pre_send, ne_hook_post_send, + ne_hook_destroy_request, ne_hook_destroy_session, + ne_hook_session_accessor, ne_hook_request_accessor, + ne_null_accessor, call_access, add_hook): New functions. + (ne_request_create, ne_request_destroy, build_request, + ne_end_request): Adapt for new interface. + + * ne_session.c (destroy_hooks): New function. + (ne_session_destroy): Use it to destroy hooks lists appropriately. + +Tue Sep 25 07:46:32 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.c: Only decode UTF-8 for parsers other than libxml 1.x. + +Tue Sep 25 07:33:09 2001 Mo DeJong <supermo@bayarea.net> + + * src/ne_socket.c: Include <signal.h> instead of <sys/signal.h>. + (sock_init): Only use signal() to ignore SIGPIPE if both + HAVE_SIGNAL and HAVE_SIGPIPE are defined. + +Tue Sep 25 07:09:53 2001 Mo DeJong <supermo@bayarea.net> + + * ne_socket.c (sock_init): Declare local variables before invoking + any instructions since that is not valid C code. + +Sun Sep 23 10:30:54 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c (struct auth_challenge): Make members const. + (clean_session): Free the realm string. (basic_challenge, + digest_challenge): strdup the realm string. (request_digest): + opaque is no longer stored quoted. (tokenize): New function. + (verify_response, auth_challenge): Rejig to use tokenize(). + +Sat Sep 22 20:17:00 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_shave): Fix possible memory corruption when + result should be the empty string. + +Thu Sep 20 21:27:57 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_pull_request_body): Add debugging dump of body + blocks. + +Thu Sep 20 21:23:43 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h: Remove obsolete 'if_locks' member from struct + ne_request_s. + +Tue Sep 18 23:35:30 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_get_range): Handle write errors too. + +Tue Sep 18 22:14:49 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.h (ne_xml_validate_cb): Take userdata parameter. + + * ne_xml.c (find_handler): Pass validate callback the handler's + userdata. + + * ne_207.c, ne_props.c, ne_locks.c: All users changed. + +Tue Sep 18 21:49:14 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c (ne_lock_refresh): New function. + +Tue Sep 18 21:17:29 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (copy_or_move): Take a depth parameter, add depth + header, for COPY requests. (ne_copy): Take depth parameter, pass + through. (ne_move): Adjusted accordingly. + +Mon Sep 17 23:29:58 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c (ne_debug_init): Set debug stream to be unbuffered if + setvbuf() is available. + +Mon Aug 27 00:36:37 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_207.c (start_element, end_element): Remember when context is + valid for a <propstat>, and only invoke callback then. + +Sun Aug 26 22:30:39 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_get_range): Better error handling. Cope with + Apache's 416 problem. + +Sun Aug 26 18:58:47 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c: Store unquoted challenge parameters in session + object, prevent having to unquote/free them >1 times. + +Sun Aug 26 18:57:51 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (sock_init): Do nothing on any calls after first. + +Sun Aug 26 12:45:04 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (server_hdr_handler): Remove function. (ne_options): + Don't add server_hdr_handler. + +Tue Jul 17 11:25:06 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (sock_init): Set signal dispostion for SIGPIPE to + ignore. + +Sat Jun 30 12:11:44 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c (ne_supports_ssl): New function. + +Tue Jun 19 21:57:49 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_dates.c (ne_iso8601_parse): Fix month off-by-one bug, use + separate vars for offsets. (ne_rfc1036_parse): Fix Y2K bug, + parsing problem. + +Tue Jun 19 21:57:42 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_dates.c (ne_iso8601_parse): New function. + +Sun Jun 10 15:39:40 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (send_with_progress): New function. + (send_request_body): Use send_with_progress to trigger progress + callbacks if necessary. + +Sat Jun 9 15:42:33 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h: Bring back NE_ASC2HEX/HEX2ASC. + + * ne_md5.c: Use them. + +Sat Jun 9 15:42:08 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_xml.h: Include ne_defs.h. + +Fri Jun 8 23:02:49 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.h, ne_socket.c: Update for includes (Mo DeJong). + +Fri Jun 8 21:34:00 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (dav_hdr_handler): Use ne_token. + +Sat Jun 2 14:37:07 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_private.h: Renamed from http_private.h. + +Sat Jun 2 14:35:23 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_auth.c, ne_auth.h: Renamed from http_auth.c, ne_auth.h. + +Sat Jun 2 14:35:02 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_cookies.c, ne_cookies.h: Renamed from http_cookies.c, + http_cookies.h. + +Sat Jun 2 14:34:51 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_dates.c, ne_dates.h: Renamed from dates.c, dates.h + +Sat Jun 2 14:22:49 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_redirect.c, ne_redirect.h: Renamed from http_redirect.c, + http_redirec.h. Big rename... s/http_/ne_g/ + +Sat Jun 2 12:54:51 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_md5.c (md5_process_block): Fix for word alignment issue on + Sparc from Kai Sommerfeld. + +Wed May 30 23:15:31 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_put, ne_get, ne_put_if_unmodified, ne_get_range, + ne_post): Take an integer fd rather than FILE * stream. + (get_to_fd): Write to fd rather than stream. + +Wed May 30 23:08:55 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_i18n.h, ne_i18n.c: Renamed from neon_i18n.h, neon_i18n.c. + + * *.c: All changed accordingly. + +Wed May 30 23:02:47 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_defs.h: Renamed from neon_defs.h. + + * *.h: All changed accordingly. + +Wed May 30 22:58:57 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_md5.c, ne_md5.h: Renamed from md5.c, neon_md5.h + +Wed May 30 22:55:19 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.h: In-line ne_debug for GCC which can cope with varargs + preprocessor macros. + +Wed May 30 00:43:05 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (ah_use_body): Removed function. (digest_body): New + function. (request_digest): Use ne_pull_request_body to find the + MD5 digest of the request body, when necessary. + +Wed May 30 00:30:52 2001 Joe Orton <joe@manyfish.co.uk> + + * http_redirect.c: Store Request-URI, session pointer, and method + string in redirect object. Avoid looking inside + ne_request/ne_session internals. + +Wed May 30 00:04:30 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c: Re-implement request body handling in terms of a + callback which provides the request body blocks on demand. Remove + 'use_body' hook, in favour of the hooks calling + ne_pull_request_body when necessary. (ne_pull_request_body, + body_fd_send, body_string_send): New functions. + (send_request_body): Re-implemented using ne_pull_request_body. + (run_set_body_hooks): Removed function. (ne_set_request_body_fd): + Replacement for ne_set_request_body_stream, using a raw fd rather + than a FILE *. + +Tue May 29 22:39:39 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_basic.h, dav_basic.h: Removed. + +Tue May 29 22:38:54 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_207.c (ne_simple_request, etc): Copied in from dav_basic.c. + +Tue May 29 22:12:23 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c, ne_locks.h, ne_props.c, ne_props.h, ne_207.c, + ne_207.h: Big rename. dav_* -> ne_*, and so on. + +Tue May 29 22:06:24 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_add_depth_header): Moved from dav_basic.c. + +Tue May 29 21:55:30 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_props.c, ne_props.h: Renamed from dav_props.c, dav_props.h. + +Tue May 29 21:43:15 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_207.c, ne_207.h: Renamed from dav_207.c, dav_207.h. + +Tue May 29 21:22:25 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_locks.c, ne_locks.h: Renamed from dav_locks.c, dav_locks.h. + +Tue May 29 21:21:44 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c (sock_fullwrite): Cast return value of SSL_write to + size_t to prevent comparison of signed with unsigned. + +Tue May 29 21:05:27 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c [!NEON_NODAV]: Move ne_copy, ne_mkcol, ne_move, + ne_delete in here. + +Tue May 29 20:12:50 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_uri.c, ne_uri.h: Renamed from uri.c, uri.h. + +Tue May 29 19:17:09 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_socket.c, ne_socket.h: Renamed from socket.c, nsocket.h. + +Tue May 29 18:58:51 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (ne_mkcol, ne_copy, ne_move, ne_delete): Renamed from + dav_*. + +Tue May 29 17:58:09 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c (copy_or_move, dav_copy, dav_move, dav_delete, + dav_mkcol): Copied in from dav_basic.c. + +Tue May 29 17:55:33 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_basic.c, ne_basic.h: Renamed from http_basic.c, http_basic.h. + +Tue May 29 17:47:50 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (ah_create, ah_pre_send): Add the response body + handler in pre_send, and only if qop=auth-int. + +Wed May 16 20:54:51 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (ne_get_request_headers): Removed function. + +Sat May 12 18:48:46 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c (read_message_header, read_response_headers): Use a + fixed-size char * buffer argument rather than an ne_buffer. + Append directly to it when header-folding. + +Mon May 7 10:42:38 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c (ne_token): Use an optimized search (strchr) if + quotes is NULL. + +Mon May 7 01:33:48 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (basic_challenge, get_cnonce): Updated for ne_base64 + change. + +Mon May 7 01:32:22 2001 Joe Orton <joe@manyfish.co.uk> + + * base64.c (ne_base64): Take length parameter. [BASE64_TEST] + (main): Remove function, obsoleted by test code. + +Wed May 2 12:06:59 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c, ne_string.h (ne_token, ne_shave): New functions, + destined to replace split_string, shave_string, etc. + + * ne_string.c [SPLIT_STRING_TEST, PAIR_STRING_TEST] (main): Remove + tests, functions are obsolete. + +Tue May 1 22:14:14 2001 Joe Orton <joe@manyfish.co.uk> + + * dates.c (ne_httpdate_parse): Moved from ne_utils.c. + +Tue May 1 21:55:45 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_utils.c, ne_utils.h: Renamed from http_utils.c, http_utils.h. + Big rename. http_* -> ne_*. neon_* -> ne_*. DEBUG() -> + NE_DEBUG(). DEBUG_* -> NE_DBG_*. + +Tue May 1 21:35:10 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_request.c: Updated for ne_buffer changes. + +Tue May 1 21:28:58 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.h (ne_buffer_size): Implement as macro. + + * ne_string.c (ne_buffer_size): Remove function. + +Tue May 1 21:23:47 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c, ne_string.h: Make ne_buffer a transparent type, and + no longer be an implicit pointer type. (ne_buffer_*): All + changed. (ne_buffer_data, NE_BUFFER_CAST): Removed. + +Tue May 1 21:17:40 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c, ne_string.h: Renamed sbuffer -> ne_buffer. + Implicit pointer removed ne_buffer type. + +Tue May 1 21:12:15 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_string.c, ne_string.h: Renamed from string_utils.c, + string_utils.h (CVS copy'n'delete). + +Tue May 1 20:49:46 2001 Joe Orton <joe@manyfish.co.uk> + + * md5.c (ASC2HEX, HEX2ASC): Moved here from string_utils.h. + + * string_utils.h: As above. + +Tue May 1 20:47:20 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c, http_request.h: Removed files. + + * ne_request.c, ne_request.h: Copied from old http_request.[ch]. + Renamed http_* -> ne_*. + +Tue May 1 20:43:11 2001 Joe Orton <joe@manyfish.co.uk> + + * hip_xml.c renamed to ne_xml.c, hip_xml.h renamed to ne_xml.h: + CVS repository copy'n'delete. + +Tue May 1 20:41:03 2001 Joe Orton <joe@manyfish.co.uk> + + * hip_xml.c, hip_xml.h: Big rename. hip_xml_* -> ne_xml_*. + +Tue May 1 20:37:13 2001 Joe Orton <joe@manyfish.co.uk> + + * http_basic.c, http_basic.h: Big rename. http_* -> ne_*. + +Tue May 1 19:59:01 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_session.c: Renamed http_* to ne_*. + +Tue May 1 19:55:47 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_alloc.h (NE_FREE): Renamed from HTTP_FREE() in http_utils.h + +Tue May 1 19:54:42 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c (sock_make_secure): Set SSL_MODE_AUTO_RETRY when + available. + +Mon Apr 30 00:36:34 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_session.[ch]: New files, split down from http_request.[ch]. + +Sun Apr 29 15:02:23 2001 Joe Orton <joe@manyfish.co.uk> + + * uri.c [URITEST] (main): Remove, obsoleted by new test suite. + +Sun Apr 29 15:01:30 2001 Joe Orton <joe@manyfish.co.uk> + + * uri.c (uri_has_trailing_slash): Return false if uri is "". + +Sun Apr 29 13:53:41 2001 Joe Orton <joe@manyfish.co.uk> + + * dates.c (ne_asctime_parse, ne_rfc1123_date, ne_rfc1036_parse): + Set tm_isdst to -1 in struct tm. + +Sun Apr 29 13:28:26 2001 Joe Orton <joe@manyfish.co.uk> + + * http_utils.c (http_parse_statusline): Skip leading whitespace. + (Johan Lindh). (http_parse_statusline): Ensure status-code is not + more than three digits. + +Sun Apr 29 13:26:47 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (build_request): Don't add "Content-Length: 0" + header if no body: Squid 2.3-STABLE1 doesn't like this. + +Sun Apr 29 13:25:16 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (everywhere): Renamed md5_* -> ne_md5_*. + +Sun Apr 29 13:24:12 2001 Joe Orton <joe@manyfish.co.uk> + + * md5.c: Renamed md5_* -> ne_md5_*. (ne_ascii_to_md5, + ne_md5_to_ascii): Moved from string_utils.c. + +Thu Apr 26 22:39:05 2001 Joe Orton <joe@manyfish.co.uk> + + * uri.c (uri_parse): A zero-length URI is invalid. + +Wed Apr 25 23:11:51 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (startelm): Check for xml:lang attribute and store + as prop->lang. (dav_propset_lang): New function. (free_propset): + Free lang. + +Wed Apr 25 23:08:52 2001 Joe Orton <joe@manyfish.co.uk> + + * hip_xml.c (hip_xml_get_attr): New function. + +Sun Apr 22 21:48:06 2001 Joe Orton <joe@manyfish.co.uk> + + * uri.c (uri_parse): Skip a userinfo@ segment if present (Johan + Lindh <johan@link-Data.com>). + +Wed Apr 18 13:29:46 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_locks.c (dav_lock_copy): Allow owner to be NULL. + +Tue Apr 17 22:57:50 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_alloc.h, dav_locks.h: Add C++ inclusion safety. + +Tue Apr 17 22:56:50 2001 Joe Orton <joe@manyfish.co.uk> + + * uri.c (uri_parse): Correctly handle URIs with no scheme or + hostport segments (i.e. just a path). + +Tue Apr 10 00:29:25 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c [HAVE_SOCKS_H]: Include socks.h for SOCKSv5 support. + +Wed Apr 4 21:41:47 2001 Joe Orton <joe@manyfish.co.uk> + + * http_utils.h [WIN32]: Define ssize_t (Kai). + +Tue Apr 3 21:03:28 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_locks.c (dav_lock_discover): Cope with below API change. + +Tue Apr 3 20:43:50 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (propfind): Register the flat element handler here, + to pick up *any* properties and store them as 'flat' if they are + not handled by a handler further down the stack. (make_elms, + free_elms, dav_propfind_set_flat, dav_propfind_set_complex): + Removed functions. (dav_propfind_named): Take the list of + property names, and call set_body here. + (dav_propfind_set_private): New function. + +Tue Apr 3 09:33:09 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.h: Added C++ inclusion safety. (Kai Sommerfeld) + +Mon Apr 2 02:39:18 2001 Joe Orton <joe@manyfish.co.uk> + + * hip_xml.c (sax_error): Add parse error callback for libxml. + +Mon Apr 2 02:23:06 2001 Joe Orton <joe@manyfish.co.uk> + + * http_redirect.c (post_send): Clean up and fix logic. Only check + for confirmation for same-server redirects. + +Mon Apr 2 02:13:48 2001 Joe Orton <joe@manyfish.co.uk> + + * http_redirect.c (http_redirect_location): New function. + (destroy): Removed function. (create): Free location. + (post_send): Only call notify callback for followed redirects. + +Mon Apr 2 01:55:27 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (http_set_request_uri): Allow using the '*' URI + even when using a proxy server. + +Mon Apr 2 01:32:06 2001 Joe Orton <joe@manyfish.co.uk> + + * http_redirect.c (post_send): Give up on trying to follow a + redirect to another server. Return HTTP_REDIRECT on such a + redirect. + + * http_redirect.c (post_send): Fix leaks of URI object (Kai + Sommerfeld). + +Mon Apr 2 01:08:33 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (read_response_headers): Don't read more than 100 + response headers. + +Mon Apr 2 00:54:43 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (http_request_dispatch): Remove mapping of auth + status codes to return values, the hook does it now. + +Mon Apr 2 00:53:20 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (http_set_request_uri): New function, split out + from http_request_create. (http_request_create): Use it. + +Mon Apr 2 00:51:23 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (http_set_proxy_auth, http_set_server_auth): Add + failure codes. (ah_post_send): Return failure code if + authentication fails. + +Mon Apr 2 00:19:17 2001 Joe Orton <joe@manyfish.co.uk> + + * http_utils.c: Pick up xmlversion.h from libxml2. + (neon_version_string): Include libxml2 version string if defined. + +Sun Apr 1 21:40:00 2001 Joe Orton <joe@manyfish.co.uk> + + * http_basic.c (http_get_range): Fix total length + calculation. (Johan Lindh <johan@linkdata.se>). + (clength_hdr_handler): Use range->total. + +Sun Apr 1 21:26:09 2001 Joe Orton <joe@manyfish.co.uk> + + * hip_xml.c: Add expat2 support (Sam TH <sam@uchicago.edu>). + +Sun Apr 1 21:07:19 2001 Joe Orton <joe@manyfish.co.uk> + + * string_utils.h (CONCAT2, CONCAT3, CONCAT4): Use ne_malloc. + +Sun Apr 1 20:59:09 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (propfind, dav_proppatch): As below. + +Sun Apr 1 20:32:29 2001 Joe Orton <joe@manyfish.co.uk> + + * http_basic.c (http_post): Changed as per + http_set_request_body_buffer change. + + * dav_locks.c (dav_lock): Likewise. + +Sun Apr 1 20:31:06 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (http_set_request_body_buffer): Pass in size + parameter too. (send_request_body): Use sized rather than + NUL-terminated buffer. + +Sun Apr 1 20:12:51 2001 Joe Orton <joe@manyfish.co.uk> + + * http_private.h: Added 'body_callback' request body type, and + body_cb, body_ud fields to http_req. + + * http_request.c (http_set_request_body_provider): New function. + (set_body_size): New function, factored out from old + http_set_request_body_*. (http_set_request_body_stream, + http_set_request_body_buffer): Use it. + +Sun Apr 1 19:56:17 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c: Replaced 'uri' and 'depth' fields in propfind + handler object with an 'http_req' pointer. (dav_propfind_create): + Create the request object here, and set the depth header. + (propfind): Changed accordingly. (dav_propfind_destroy): Destroy + request object too. + + * dav_props.c (dav_propfind_get_request): New function. + +Fri Mar 30 16:50:51 2001 Joe Orton <joe@manyfish.co.uk> + + * hip_xml.c (start_element): Quote attributes in collect (Kai + Sommerfeld). + +Fri Mar 30 16:36:08 2001 Joe Orton <joe@manyfish.co.uk> + + * http_basic.c (http_put_if_unmodified): Changed as below. + +Thu Mar 22 14:05:52 2001 Joe Orton <joe@manyfish.co.uk> + + * http_utils.c (http_dateparse): Changed as below. + + * http_auth.c (get_conce, basic_challenge): Likewise. + +Thu Mar 22 14:04:54 2001 Joe Orton <joe@manyfish.co.uk> + + * dates.c (ne_rfc1123_date, ne_rfc1123_date, ne_asctime_parse, + ne_rfc1036_parse): Added ne_ prefix. + +Thu Mar 22 14:03:12 2001 Joe Orton <joe@manyfish.co.uk> + + * base64.c (ne_base64): Renamed from base64. + +Tue Mar 20 20:34:44 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (make_elms): Don't request UTF-8 decoding of + property values. + +Tue Mar 20 20:33:39 2001 Joe Orton <joe@manyfish.co.uk> + + * string_utils.c (ne_utf8_decode): New function. + +Mon Mar 19 22:08:45 2001 Joe Orton <joe@manyfish.co.uk> + + * http_basic.c (get_callback): Removed function. (http_get, + http_read_file, http_post): Use callbacks directly rather than + indirectly through get_callback. + +Mon Mar 19 21:55:19 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (notify_status, http_set_status, + http_set_progress): New functions: request status and progress + callbacks. (open_connection, lookup_host): Use notify_status to + trigger status callbacks, and register socket progress callbacks. + +Mon Mar 19 21:53:07 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c (sock_register_notify): Removed function. + (sock_connect_u): Renamed to sock_connect. + (sock_register_progress): Per-socket progress callbacks rather + than global ones. (sock_call_progress): Take socket argument. + (all callers changed). + +Mon Mar 19 21:52:50 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c (sock_get_version): New function. + +Mon Mar 19 13:59:21 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (propfind): Destroy the handler. + +Mon Mar 19 13:36:55 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (dav_propnames): New function. + +Wed Mar 14 22:42:12 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.h (http_begin_request, http_end_request, + http_read_response_block): New functions. + (http_request_dispatch): Reimplemented using new caller-pulls + interface. + +Wed Mar 14 22:20:38 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (ah_post_send): If authentication fails (i.e. bad + credentials), clean the session details. + +Wed Mar 14 20:46:55 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c: Retry the request simply if it has not been tried + before with authentication details, otherwise, fail on 40[17]. + +Wed Mar 14 20:12:52 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (open_connection): Make sure to close the + connection, and return HTTP_CONNECT if the SSL negotiation fails. + +Tue Mar 6 18:37:43 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_alloc.c (ne_strndup): Allocate n+1 bytes not 'n' (Kai + Sommerfeld). + +Mon Mar 5 01:05:31 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c: Moved local sbuffer variables of + http_request_dispatch into http_req members 'reqbuf' and + 'respbuf'. (build_request): Return a const char * of the request. + (send_request): Call build_request directly, use req->respbuf. + (http_request_dispatch): Don't call build_request. Removed 'goto' + exception handling (hoorah). (http_request_create, + http_request_destroy): Create and destroy reqbuf and respbuf here. + +Mon Mar 5 00:43:40 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (http_set_request_body_stream): Set + req->body_size and Content-Length header here: return success + value. (http_set_request_body_buffer): Likewise (but no return + value). (get_request_bodysize): Removed function. + (build_request): Add Content-Length: 0 header if no request body. + +Mon Mar 5 00:27:24 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (http_forget_auth): New function. + +Mon Mar 5 00:25:15 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (http_request_hook_private): Renamed from + http_get_hook_private. (http_session_hook_private): New function. + + * dav_locks.c (dav_lock_using_resource, dav_lock_using_parent): + Renamed simiarly. + +Sun Mar 4 23:12:12 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c: Moved per-request state into struct auth_request. + (request_digest): Take struct auth_request argument. (free_auth): + New function. (http_add_hooks): Pass free_auth as cleanup + function for auth session. + +Sun Mar 4 23:08:46 2001 Joe Orton <joe@manyfish.co.uk> + + * http_redirect.c (auto_redirect): Auto-redirect OPTIONS requests. + (free_redirect): New function. (http_redirect_register): Pass + cleanup function. + +Sun Mar 4 23:07:01 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_locks.c (dav_lock_unregister): Removed function. + (free_locks): New function. (dav_lock_register): Pass free_locks + as cleanup function for hooks. + +Sun Mar 4 22:54:57 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.h (http_add_hooks): Added fourth argument to + register a cleanup function for the cookie. + +Sun Mar 4 19:53:03 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (request_digest): Use an sbuffer to create the + header value. + +Sun Mar 4 19:44:18 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c: Remove namespace protection for private + functions. s/http_auth/auth/g. + +Sun Mar 4 19:39:13 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (ah_pre_send): Increase attempt counter here, ... + (ah_post_send): instead of here. + +Sun Mar 4 18:40:03 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (http_request_dispatch): Simplify post_send hook + invocation: run them until one doesn't return HTTP_OK. Don't run + pre_send hooks here. Loop while a hook returns HTTP_RETRY. + (build_request): Run pre_send hooks here. + + * http_request.c (read_response_body): Call + normalize_response_length here. + +Sun Mar 4 18:12:26 2001 Joe Orton <joe@manyfish.co.uk> + + Re-implemented HTTP authentication using generic hooks interface. + + * http_auth.c: Added http_auth.h. (http_auth_init, + http_auth_set_creds_cb, http_auth_new_request, + http_auth_request_header): Removed functions, merged into new + hooks code. (ah_create, ah_post_send, ah_pre_send, ah_use_body, + ah_destroy): New functions. (auth_body_reader, + http_set_server_auth, http_set_proxy_auth): Moved over from + http_request.c and redone for hooks interface. + + * http_request.c (http_set_server_auth, http_set_proxy_auth, + give_creds, auth_body_reader): Moved to http_auth.c. + (http_accept_always): Renamed from always_accept_response and made + public. (http_request_create, build_request, + http_request_dispatch): Removed authentication code. + +Tue Feb 27 19:49:42 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (set_body): Remove UTF-8 encoding again. + +Mon Feb 26 22:38:41 2001 Joe Orton <joe@manyfish.co.uk> + + Patch from Kai Sommerfeld to remove URI escaping from inside neon. + + * dav_207.c (end_element): Don't unescape href elements. + + * http_request.c (http_request_create): Don't escape Request-URI. + + * dav_basic.c (copy_or_move): Don't escape destination URI. + +Mon Feb 26 21:44:56 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (set_body): UTF-8 encode the property name and + value. (make_elms): Request UTF-8 decoding of property values. + +Mon Feb 26 21:40:14 2001 Joe Orton <joe@manyfish.co.uk> + + * hip_xml.c: Do perform UTF-8 decoding when using libxml. + (char_data): Fix UTF-8 decoding bug where the byte after a + multi-byte encoded sequence would be skipped. + +Sun Feb 25 20:04:05 2001 Joe Orton <joe@manyfish.co.uk> + + * string_utils.c (ne_utf8_encode): New function. + +Sun Feb 25 19:52:01 2001 Joe Orton <joe@manyfish.co.uk> + + * ne_alloc.c (ne_realloc): New function. + +Sun Feb 25 17:00:32 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (all): Rename NEON_IS_BUNDLED to NEON_BUILD_BUNDLED. + +Sun Feb 25 16:52:43 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (all): Build according to NEON_IS_BUNDLED. + +Fri Feb 23 23:38:10 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Fix deps: neon_config.h has gone. + +Fri Feb 23 22:57:47 2001 Joe Orton <joe@manyfish.co.uk> + + * dav_props.c (dav_simple_propfind): Support a NULL 'props' + argument to do an allprop request, as per the advertising + literature. Register a catch-all handler in this case. + +Fri Feb 23 22:16:42 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (http_session_destroy): Free up hooks list. + +Thu Feb 22 21:54:36 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.h (http_add_hooks): Make request_hooks 'const'. + +Thu Feb 15 08:36:56 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (read_response_body): Accept SOCK_CLOSED as + end-of-response if we don't have a Content-Length (and not + chunked). (Kai Sommerfeld). + +Thu Feb 15 08:36:23 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (add_fixed_headers): Don't add the Host header + here. (http_request_dispatch): Add it here instead. + + * http_request.c (set_hostinfo): Dup the hostname. + (http_session_destroy): Free the hostname. (Kai Sommerfeld). + +Thu Feb 15 08:35:49 2001 Joe Orton <joe@manyfish.co.uk> + + * http_private.h: Make hostname in struct host_info char *. (Kai + Sommerfeld). + +Thu Feb 15 08:08:50 2001 Joe Orton <joe@manyfish.co.uk> + + * http_redirect.h: Add C++ header-inclusion safety macros (Kai + Sommerfeld <kai.sommerfeld@germany.sun.com>). + +Wed Feb 14 23:37:57 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (read_response_body): Use a size_t for readlen, + as read_response_block requires. + +Wed Feb 14 23:25:44 2001 Joe Orton <joe@manyfish.co.uk> + + * http_auth.c (request_digest): Fix incorrect signed-ness of + buffer. + +Wed Feb 14 23:22:13 2001 Joe Orton <joe@manyfish.co.uk> + + * uri.h, string_utils.h: Comment-out tokens after #endif. + +Sun Feb 4 14:36:11 2001 Joe Orton <joe@manyfish.co.uk> + + * http_redirect.c (post_send): Prototype change. + +Sun Feb 4 14:31:42 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c: Added key_prompt, key_userdata, key_file to + nssl_context. (sock_set_key_prompt, key_prompt_cb): New + functions. (sock_make_secure): Set ctx->key_file to private key + filename. + +Sun Feb 4 13:31:44 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.h: Make http_status argument of post_send a const + pointer. + +Sun Feb 4 10:38:12 2001 Joe Orton <joe@manyfish.co.uk> + + * http_request.c (set_sockerr): Only use the socket error if it is + NULL, else print generic "something went wrong"-type error + message. + +Sun Feb 4 10:29:37 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c (sock_set_client_cert): Call SSL_CTX_check_private_key + to ensure that the cert and private key match. + +Sun Feb 4 10:28:02 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c (sock_make_secure): In error cases after SSL_connect + succeeds, call SSL_shutdown and assign sock->ssl = NULL before + returning. + +Sat Feb 3 18:33:56 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c (sock_close): Call SSL_shutdown before close()'ing the + fd. + +Sat Feb 3 18:30:48 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c: Store an default SSL_CTX in nsocket, to be used when + no nssl_context is supplied. (create_socket): Create default + SSL_CTX (sock_close): Free it here. (sock_make_secure): Use it + here. + +Sat Feb 3 15:52:15 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c (sock_set_client_cert): New function. + +Sat Feb 3 15:48:51 2001 Joe Orton <joe@manyfish.co.uk> + + * socket.c: Rejig of nssl_context handling. An nssl_context is + now really a wrapper for an SSL_CTX. (sock_create_ssl_context): + Create the SSL_CTX here. (sock_disable_tlsv1, sock_disable_sslv2, + sock_disable_sslv3): Set the SSL_CTX option directly. + (sock_make_secure): Create an SSL_CTX if no nssl_context is + supplied, otherwise use SSL_CTX from nssl_context. + +Sun Jan 28 13:52:03 2001 Joe Orton <joe@manyfish.co.uk> + + * http_utils.c (neon_version_minimum): New function. + +Sun Jan 28 10:37:28 2001 Joe Orton <joe@manyfish.co.uk> + + * neon_config.h.in: Removed file. + + * http_request.c, http_utils.c: Don't include neon_config.h. + +Sat Jan 27 22:52:37 2001 Joe Orton <joe@light.plus.com> + + * socket.c: Use closesocket() as NEON_CLOSE (Markus Fleck + <fleck@isoc.de>). + +Sat Jan 27 22:35:16 2001 Joe Orton <joe@light.plus.com> + + * hip_xml.c: Add 'char_data' as libxml cdataBlock handler. + +Tue Jan 23 23:17:00 2001 Joe Orton <joe@light.plus.com> + + * neon_config.h.in: Renamed from neon_config.h. Define + NEON_VERSION_MAJOR and NEON_VERSION_MINOR too: all picked up from + the NEON_VERSIONS macro. + +Sun Jan 21 22:07:34 2001 Joe Orton <joe@light.plus.com> + + * string_utils.c (ne_concat): New function. + +Thu Jan 18 22:25:34 2001 Joe Orton <joe@light.plus.com> + + * ne_alloc.h: Added ne_oom_callback. + + * ne_alloc.c: Added DO_MALLOC macro to do the malloc and oom + callback test. (ne_malloc): Use DO_MALLOC. (ne_strdup, + ne_calloc, ne_strndup): Reimplement using DO_MALLOC rather than + calling ne_malloc(). + +Tue Jan 16 20:16:35 2001 Joe Orton <joe@light.plus.com> + + * neon_config.h: Bumped version to 0.10.1. + +Tue Jan 16 20:14:40 2001 Joe Orton <joe@light.plus.com> + + * http_request.c (http_session_create): Default expect-100 support + to OFF. + +Mon Jan 15 22:59:33 2001 Joe Orton <joe@light.plus.com> + + * neon_config.h: Bumped version to 0.10.0. + +Mon Jan 15 22:58:04 2001 Joe Orton <joe@light.plus.com> + + * dav_basic.c (dav_simple_request): Use dav_207_ignore_unknown. + +Sun Jan 14 22:52:31 2001 Joe Orton <joe@light.plus.com> + + * dav_props.c (propfind): Call dav_207_ignore_unknown. + +Sun Jan 14 22:49:06 2001 Joe Orton <joe@light.plus.com> + + * dav_207.c: Don't handle unknown elements in normal handler. + (ignore_cc, dav_207_ignore_unknown): New functions, for ignoring + any unknown elements in the parse. + +Sun Jan 14 21:53:00 2001 Joe Orton <joe@light.plus.com> + + * hip_xml.c: Renamed 'handlers' back to top_handlers. + (push_handler): Now, p->root->handlers points to BASE of stack, + p->top_handlers points to TOP of stack. (hip_xml_destroy): + Changed to start from new BASE of stack. + +Sun Jan 14 10:50:09 2001 Joe Orton <joe@light.plus.com> + + * http_request.c (http_session_server): Do perform the DNS lookup + if we have a proxy_decider function, since that means we MIGHT + need to know the IP address of the origin server. + (http_request_create): Pass the real scheme back to the proxy + decider callback. + +Wed Jan 10 22:43:16 2001 Joe Orton <joe@light.plus.com> + + * Makefile.in: Rename OBJ_EXT to NEON_OBJEXT. Remove + NEON_INTERFACE_VERSION, use NEON_LINK_FLAGS instead. + +Wed Jan 10 22:02:02 2001 Joe Orton <joe@light.plus.com> + + * dav_locks.c (create_private): New function. + (dav_lock_discover): Switch to using new dav_propfind_set_complex + API. + +Wed Jan 10 21:59:36 2001 Joe Orton <joe@light.plus.com> + + * dav_props.h (dav_propfind_set_complex): Switch the + 'sizeof_private' argument for a callback 'creator': the return + value of this callback is used as the 'private' field for the + current resource. + +Mon Jan 8 22:09:55 2001 Joe Orton <joe@light.plus.com> + + * dav_locks.h (dav_lock_result): Re-order arguments. Make lock + object const. + + * dav_locks.c (dav_lock_copy): New function. (discover_results): + Set lock->uri given href for current results. Free lock object + after passing to results. + +Sun Jan 7 21:55:14 2001 Joe Orton <joe@light.plus.com> + + * dav_locks.c (dav_lock): Destroy XML parser after use. Remove + handling of Lock-Token header: it wasn't used and it leaked. + +Sun Jan 7 19:58:29 2001 Joe Orton <joe@light.plus.com> + + * dav_props.c (free_propset): Free the property values, and the + result set URI too. + +Sun Jan 7 16:58:19 2001 Joe Orton <joe@light.plus.com> + + * http_request.c (read_response_block): Fix handling of + SOCK_CLOSED from sock_read as end-of-connection. + +Sat Jan 6 15:02:57 2001 Joe Orton <joe@light.plus.com> + + * hip_xml.c (struct hip_xml_parser): Renamed 'top_handlers' to + 'handlers' in hip_xml_parser. (push_handler): New function. + (hip_xml_push_handler, hip_xml_push_mixed_handler): Use + push_handler. + + * hip_xml.c (find_handler): Begin the search for a new handler + from the handler of the current (parent) element, and work up the + stack. + +Sat Jan 6 11:15:17 2001 Joe Orton <joe@light.plus.com> + + * hip_xml.c (find_handler): Display error message for unknown XML + element as 'elmname (in nspace)' rather than 'nspace:elmname' + since the latter produces confusing errors like + 'DAV::displayname'. + +Wed Jan 3 21:34:44 2001 Joe Orton <joe@light.plus.com> + + * Makefile.in: Hard-code top_builddir as '..' (possibly wrong, but + true for all neon apps so far). Remove INCLUDES, now unused. Add + top_srcdir. + +Fri Dec 22 22:51:27 2000 Joe Orton <joe@light.plus.com> + + * dav_props.c: Added 'has_props' field to propfind_handler struct. + (set_body): Only add leading 'prop' element if has_props is not + set. Set has_props. Don't add trailing </prop> element here. + (dav_propfind_named): ... add it here instead. + (dav_propfind_set_complex, dav_propfind_set_flat): New set_body + interface. + +Fri Dec 22 21:10:39 2000 Joe Orton <joe@light.plus.com> + + * socket.c (sock_transfer): Don't use NEON_READ here, this is + for reading from a non-socket fd. + +Wed Dec 20 00:19:34 2000 Joe Orton <joe@light.plus.com> + + * neon_config.h: Bumped version to 0.9.1. + +Wed Dec 20 00:19:09 2000 Joe Orton <joe@light.plus.com> + + * dav_props.c (free_propset): Don't free the private structure, + make this the caller's responsibility. + +Wed Dec 20 00:12:36 2000 Joe Orton <joe@light.plus.com> + + * dav_props.c (end_propstat): Rename propstat argument to + 'pstat_v' to kill warnings. + +Tue Dec 19 23:42:39 2000 Joe Orton <joe@light.plus.com> + + * dav_props.c (start_response): Zero-out the private structure on + creation. + +Tue Dec 19 22:54:06 2000 Joe Orton <joe@light.plus.com> + + * http_request.c (read_response_block): Rename local variable + 'socket' to 'sock'. + +Tue Dec 19 22:52:56 2000 Joe Orton <joe@light.plus.com> + + * http_request.h: Rename argument from 'stat' in post_send + definition. + +Tue Dec 19 22:52:32 2000 Joe Orton <joe@light.plus.com> + + * http_utils.h: Add 'const' to char * argument. + +Tue Dec 19 22:19:28 2000 Joe Orton <joe@light.plus.com> + + * neon_config.h: Bumped version to 0.9.0. + +Tue Dec 19 22:12:19 2000 Joe Orton <joe@light.plus.com> + + * Makefile.in: New file. + +Tue Dec 19 22:07:50 2000 Joe Orton <joe@light.plus.com> + + * Makefile.incl: Removed file. + +Tue Dec 19 22:06:06 2000 Joe Orton <joe@light.plus.com> + + * dav_locks.c (dav_lock_discover): New callback-based lock + discovery interface. Re-implemented using new propfind interface. + +Tue Dec 19 21:22:43 2000 Joe Orton <joe@light.plus.com> + + * dav_props.h: Replace old dav_propfind_* interface with better + one. (dav_simple_propfind): Renamed from dav_get_props. + (dav_propfind_current_private, dav_propfind_set_complex, + dav_propfind_set_flat): New functions. + (dav_propfind_get_current_resource): Removed function. + (dav_propfind_named, dav_propfind_allprop): Change second argument + to be the results callback. + + * dav_props.c: Replace implementatino of old interface with new + one. (dav_simple_propfind): Re-implemented on top of new + all-singing all-dancing dav_propfind_* interface. + +Sun Dec 17 18:24:50 2000 Joe Orton <joe@light.plus.com> + + * dav_props.c: Add dav_get_props, and all its auxiliaries. + +Sun Dec 17 15:43:55 2000 Joe Orton <joe@light.plus.com> + + * dav_props.c (propfind): Destroy the request after calling + http_get_status. + +Sun Dec 17 18:04:58 2000 Joe Orton <joe@light.plus.com> + + * hip_xml.c (find_handler): Allow using NULL as name and nspace in + HIP_ELM_unknown elements. + +Sun Dec 17 18:03:03 2000 Joe Orton <joe@light.plus.com> + + * dav_207.c (check_context): Don't handle the unknown element when + it is a child of the prop element, this prevents handling allprop + responses. + +Thu Dec 14 21:48:06 2000 Joe Orton <joe@light.plus.com> + + * neon_config.h: Bumped version to 0.8.0. + +Thu Dec 14 21:43:31 2000 Joe Orton <joe@light.plus.com> + + * dav_basic.c, dav_locks.c, dav_props.c (everywhere): Changed to + new response-status interface, and _class->klass change. + +Thu Dec 14 21:37:38 2000 Joe Orton <joe@light.plus.com> + + * http_request.c (http_get_status): Add new response-status + interface. + +Thu Dec 14 21:30:25 2000 Joe Orton <joe@light.plus.com> + + * http_basic.c (everywhere): Use new status interface. + +Thu Dec 14 21:25:03 2000 Joe Orton <joe@light.plus.com> + + * http_private.h: Made http_status pointer in http_req a declared + object. + + * http_request.h: Removed passing status pointer to + http_request_dispatch. + + * http_request.c (everywhere): Removed passing extra http_status * + to auxiliaries, use req->status instead. Renamed '_class' to + 'klass' everywhere. + +Thu Dec 14 21:15:54 2000 Joe Orton <joe@light.plus.com> + + * http_utils.h: Renamed '_class' member of http_status to 'klass'. + (http_parse_statusline): Change accordingly. + +Wed Dec 13 23:00:23 2000 Joe Orton <joe@light.plus.com> + + * socket.c: Changes for pre-BONE BeOS (David Reid + <dreid@jetnet.co.uk>). + +Wed Dec 13 21:29:36 2000 Joe Orton <joe@light.plus.com> + + * uri.c (ESCAPE): Explicitly cast the character to const unsigned + int. (uri_abspath_escape): Make 'pnt' a normal const char *. + +Wed Dec 13 21:17:31 2000 Joe Orton <joe@light.plus.com> + + * http_request.c: Remove netinet/in.h include, add limits.h + include. (Peter Boos) (read_response_block): Make readlen a size_t + (David Reid). + +Wed Dec 13 21:08:08 2000 Joe Orton <joe@light.plus.com> + + * ne_alloc.h [WIN32]: Include stdlib.h. (Peter Boos) + +Wed Dec 13 20:54:27 2000 Joe Orton <joe@light.plus.com> + + Patches from Peter Boos and David Reid for Win32 and + BeOS changes respectively: + + * socket.c: Add NEON_READ, NEON_WRITE, NEON_CLOSE macros to use + send/recv/closesocket for BeOS, send/recv/close for Win32, + write/read/close otherwise. Include WinSock2.h in Windows. Add + arpa/inet.h check. (sock_read, sock_write): Use the NEON_ macros. + (sock_connect, sock_close): Use NEON_CLOSE. (sock_init): Winsock + initialization. (sock_exit) Winsock cleanup. (sock_fullwrite): + Use size_t rather than ssize_t for 'sent'. (sock_connect, + sock_connect_u): Make 'port' parameter an unsigned short int. + +Wed Dec 13 20:42:18 2000 Joe Orton <joe@light.plus.com> + + * http_basic.c (clength_hdr_handler): Use an off_t for len, to + avoid comparison with size_t. + +Wed Dec 13 20:38:59 2000 Joe Orton <joe@light.plus.com> + + * hip_xml.c (char_data): Use an 'int' for wslen, avoid comparison + between size_t (which is signed) and int (which [is|might be?] + unsigned). + +Wed Dec 13 20:29:12 2000 Joe Orton <joe@light.plus.com> + + * nsocket.h [WIN32]: Use Windows headers rather than Unixy ones. + (sock_exit): New function. + +Wed Dec 13 20:21:22 2000 Joe Orton <joe@light.plus.com> + + * string_utils.c, string_utils.h, uri.h: Includes change (Peter + Boos). + +Wed Dec 13 20:20:09 2000 Joe Orton <joe@light.plus.com> + + * http_auth.c (http_auth_response_body): Don't make + inline. Includes change. (both by Peter Boos). + +Wed Dec 13 20:18:38 2000 Joe Orton <joe@light.plus.com> + + * uri.c (uri_unescape): Cast strtol return to (char). Includes + change as below (both by Peter Boos). + +Wed Dec 13 20:07:38 2000 Joe Orton <joe@light.plus.com> + + * base64.c, dates.c, dates.h, dav_207.c, dav_207.h, dav_basic.h, + dav_locks.h, hip_xml.h, http_auth.h, http_basic.h, http_cookies.c, + http_redirect.c, http_redirect.h, http_request.h, http_utils.c, + md5.c, ne_alloc.c: Use #include "..." rather than #include <...> + for neon headers. (Peter Boos <PediB@colorfullife.com>). + +Thu Dec 7 21:45:02 2000 Joe Orton <joe@light.plus.com> + + * socket.c (sock_read): Return zero immediately if a zero count + parameter is passed, following SUSv2 semantics. + +Thu Dec 7 21:41:36 2000 Joe Orton <joe@light.plus.com> + + * nsocket.h (sock_readfile_blocked): Define an interface, allow + taking -1 as the length parameter. Only return SOCK_CLOSED if + length == -1 is NOT passed. + +Sun Nov 26 09:46:53 2000 Joe Orton <joe@light.plus.com> + + * nsocket.h: Fix use of 'socket' in function prototypes. + +Sun Nov 19 00:29:48 2000 Joe Orton <joe@light.plus.com> + + * nsocket.h: Increase read timeout to 120 seconds. + +Sun Nov 5 14:42:46 2000 Joe Orton <joe@light.plus.com> + + * dav_locks.c: Fix element id's (fixes segfault when using locks). + +Thu Oct 26 22:28:17 2000 Joe Orton <joe@light.plus.com> + + * socket.c (sock_peek): Return SOCK_CLOSED if recv() returns zero. + +Thu Oct 26 22:24:14 2000 Joe Orton <joe@light.plus.com> + + * socket.c (sock_block): Return "got data" if SSL_pending + indicates data pending. Otherwise select on socket as normal. + +Thu Oct 26 22:15:14 2000 Joe Orton <joe@light.plus.com> + + * socket.c (sock_readline, sock_peek): Check whether SSL + connection has been closed if SSL_peek returns 0 (thanks to Jeff + Costlow <j.costlow@f5.com>). + +Thu Oct 14 19:57:31 2000 Joe Orton <joe@light.plus.com> + + * Makefile.incl: Fix spurius backslash at line 69 (thanks to + Dirk Bergstrom <dirk@juniper.net>). + +Sat Oct 14 19:51:44 2000 Joe Orton <joe@light.plus.com> + + * dav_basic.c (copy_or_move): Use http_get_scheme rather than + hard-coding "http". + +2000-10-02 Joe Orton <joe@light.plus.com> + + * http_request.c (http_get_scheme): New function. + +Tue Oct 10 19:56:42 2000 Joe Orton <joe@light.plus.com> + + * neon_config.h: Bumped version to 0.7.5. + +Sat Oct 7 19:26:58 2000 Joe Orton <joe@light.plus.com> + + * neon_config.h: Bumped version to 0.7.4. + +Sat Oct 7 19:19:37 2000 Joe Orton <joe@light.plus.com> + + * http_auth.c (request_digest): Quote algorithm and qop parameters + in digest header. + +Sat Oct 7 19:15:29 2000 Joe Orton <joe@light.plus.com> + + * socket.c (sock_connect_u): Don't leak the fd if connect fails + (David Sloat). + +Sat Sep 16 16:49:57 2000 Joe Orton <joe@light.plus.com> + + * hip_xml.h: Add 'HIP_ELM_UNUSED', defining lowest element ID + which should be used. + + * hip_xml.c, hip_xml.h (hip_xml_push_handler, + hip_xml_push_mixed_handler): Renamed from hip_xml_add_handler / + hip_xml_add_mixed_handler to reflect stack-like usage of handlers. + 'handlers' field of hip_xml_parser renamed to top_handler for same + reason (globally search'n'replaced). + + * hip_xml.h: Documentation update. + +Thu Sep 14 22:37:33 2000 Joe Orton <joe@light.plus.com> + + * http_auth.c (request_digest): Quote qop= value, fixes + IIS5 interop. + +Thu Sep 14 00:40:04 2000 Joe Orton <joe@light.plus.com> + + * socket.c (sock_connect_u): If connect() fails, close the socket + before returning: thanks to David Sloat <d.sloat@f5.com>. + +Tue Sep 12 20:08:40 2000 Joe Orton <joe@light.plus.com> + + * http_request.c (read_response_headers): Remove redundant + tolower(). + +Tue Sep 12 00:41:39 2000 Joe Orton <joe@light.plus.com> + + * neon_config.h: Bumped version to 0.7.3. + +Mon Sep 11 15:31:13 2000 Joe Orton <joe@light.plus.com> + + * http_request.c, http_auth.c: Include snprintf.h if + HAVE_SNPRINTF_H is defined. + +Fri Sep 8 10:46:53 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.7.2. + +Fri Sep 8 10:44:42 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_block): Return immediately if this is an SSL + socket. + +Thu Sep 7 00:31:12 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.h: Correct order of hip_xml_validate_cb arguments in + prototype (thanks to Greg Stein). + +Thu Sep 7 00:27:29 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c (propfind): Don't destroy the handler after use. + (dav_propfind_destroy): New function. + +Thu Sep 7 00:08:45 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.incl: Added targets for ne_alloc.o, string_utils.o, + uri.o, base64.o. + +Tue Aug 15 21:53:53 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.7.1. + +Tue Aug 15 21:16:34 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_request_create): Only add authentication + response body callbacks if a supply-credentials callback has been + set for the session. (http_request_dispatch): Similarly for + response header callbacks. + +Mon Aug 14 09:28:38 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.7.0. + +Mon Aug 14 09:23:54 2000 Joe Orton <joe@orton.demon.co.uk> + + * string_utils.h (SBUFFER_CAST): New macro. + +Mon Aug 14 09:13:05 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_private.h: Use a hash table for storing response header + handlers. Added response header 'catchers', which are passed ALL + response headers. + + * http_request.c (http_add_response_header_handler): Place the + handler in the correct hash bucket. (hdr_hash): New function. + (http_add_response_header_catcher): New function. + (http_request_destroy): Destroy the header catchers, and iterate + over the hash table to destroy the handlers. + (read_response_headers): Optimisation: hash and search for ':' in + a single loop. Remove another local variable. Iterate through + catchers too. + +Sun Aug 13 15:57:35 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.6.1. + +Sun Aug 13 15:50:42 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (send_request): Only go through the loop at most + twice. + +Sun Aug 13 15:49:52 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_redirect.c (destroy): Don't free the redirect structure. + +Sat Aug 12 17:10:32 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.6.0. + +Sat Aug 12 16:48:47 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_session_decide_proxy): New function. + (http_request_create): Call proxy "decider callback" to determine + whether to use the proxy server for a given request or not. + +Sat Aug 12 16:39:10 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.incl: Updated for http_private.h and + http_redirect.[ch]. + +Sat Aug 12 16:36:49 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c: Removed data structure definitions. + + * http_private.h: New file, contains data structure definitions. + Interface NOT exported. + +Sat Aug 12 16:31:32 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_redirect.c (destroy): No return value. + +Sat Aug 12 16:04:02 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_redirect.[ch]: First cut at HTTP redirect handling. + +Sat Aug 12 11:05:13 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.5.1. + +Sat Aug 12 02:04:15 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_locks.c (dav_lock_using_resource, dav_lock_using_parent): + Prevent segfault if locking is not in use. + +Fri Aug 11 17:19:06 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.5.0. + +Fri Aug 11 16:31:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (read_message_header): Take a buffer for storing + continuation lines. (read_response_headers): No need to strip EOL + since read_message_header does this already. Use one less + variable. + +Fri Aug 4 22:12:04 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (send_request): Don't retry sending the request + more than once. + +Wed Aug 2 11:08:31 2000 Joe Orton <joe@orton.demon.co.uk> + + * ne_alloc.[ch]: Renamed from xalloc.[ch]. + +Wed Aug 2 02:15:32 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_transfer): Return SOCK_CLOSED, or set sock->error + appropriately on read failure. + +Tue Aug 1 13:04:27 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c, nsocket.h (sock_progress, sock_call_progress, + sock_transfer, sock_readfile_blocked): Use 'off_t' not 'size_t' as + file size type. + +Fri Jul 28 13:32:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.4.2. + +Fri Jul 28 13:31:38 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (send_request): Fix sending request body after + getting 100-continue response. + +Fri Jul 28 11:26:47 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.4.1. + +Fri Jul 28 10:32:34 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.4.0. + +Fri Jul 28 10:28:21 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.[ch] (http_post): New function, from Sander Alberink + <sander.alberink@cmg.nl>. + +Thu Jul 27 18:55:49 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.c (neon_debug): No conditional compilation for + function body: compile it all regardless of whether debugging is + enabled or not, to allow applications to be debugged regardless of + whether debugging is compiled into the library or not. + +Thu Jul 27 16:59:26 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.c (clength_hdr_handler): Cast off_t to long int for + printing. + +Tue Jul 25 18:14:15 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (read_message_header): Iterate over header + handler list before placing zero-terminator at ':': if a handler + has a NULL name field, pass it the entire header value. + +Tue Jul 25 18:00:49 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_get_request_headers): New function. + +Mon Jul 24 16:55:29 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.3.9. + +Mon Jul 24 16:54:33 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.h: Use 'off_t' in http_content_range. + + * http_basic.c (http_get_range): Cast range values to (long int) + to prevent compiler warnings. + +Thu Jul 20 20:03:30 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.[ch], socket.c, http_basic.c: Include nsocket.h not + socket.h. + +Thu Jul 20 20:02:20 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.c (version_string): Indicate which XML parser is + supported. + +Thu Jul 20 20:01:12 2000 Joe Orton <joe@orton.demon.co.uk> + + * nsocket.h: Renamed from socket.h. + +Thu Jul 20 15:02:35 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.c, socket.c, http_request.c: SSL_ENABLE renamaed to + ENABLE_SSL. + +Thu Jul 20 12:20:13 2000 Joe Orton <joe@orton.demon.co.uk> + + * md5.c, http_auth.h: Include neon_md5.h. + +Thu Jul 20 12:19:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_md5.h: Renamed from md5.h. + +Wed Jul 19 22:33:46 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c (dav_simple_request): Don't leak ctype.value. + +Wed Jul 19 22:32:03 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c (check_context): Accept unknown elements. + +Wed Jul 19 22:31:10 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_locks.c (dav_lock_iterate): Allow passing func as NULL. + +Wed Jul 19 22:26:13 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.h (SOCKET_READ_TIMEOUT): Increase to 60. + +Wed Jul 19 22:25:51 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_locks.h: Include http_request.h. + +Mon Jul 17 11:41:16 2000 Joe Orton <joe@orton.demon.co.uk> + + * dates.c (asctime_parse, rfc1036_parse): Actually pass the string + to sscanf (thanks to lclint). (rfc1123_date): Check for gmtime() + returning NULL. + +Mon Jul 17 09:16:43 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.3.1. + +Mon Jul 17 09:07:58 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_locks.c: Include limits.h: thanks to Paul D'Anna. + +Sun Jul 16 18:47:15 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: Bumped version to 0.3.0. + +Sun Jul 16 16:44:25 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_i18n.c (neon_i18n_init) [ENABLE_NLS && NEON_IS_LIBRARY]: + New compilation conditions. + +Sun Jul 16 16:41:12 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_i18n.c: New file. + +Sun Jul 16 16:15:02 2000 Joe Orton <joe@orton.demon.co.uk> + + * string_utils.c (sbuffer_*): Change to assert postcondition + (buf->used == strlen(buf->data) + 1). (sbuffer_append): Fix + brokenness. + +Sun Jul 16 16:11:05 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_transfer): Increase sum length written correctly. + +Sun Jul 16 16:10:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_get_hook_private): New function. + +Sun Jul 16 16:07:11 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.c (find_handler): Call validate_cb for any handler which + recognizes the element. Allow return codes + HIP_XML_{VALID,INVALID,DECLINE} from validate_cb. If DECLINE is + returned, continue searching handlers until one returns + (IN)VALID. (start_element): Don't call validate_cb. + + * hip_xml.c (start_element, end_element): In collect mode, don't + print namespace prefix if present. + +Sun Jul 16 15:30:19 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_locks.[ch]: New file, code mainly taken from cadaver and + adapted for neon HTTP request/response handling. + +Sun Jul 16 15:28:25 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c (copy_or_move, dav_move, dav_copy): Pass overwrite + as parameter. + +Sun Jul 16 15:26:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * uri.c (uri_compare): Fixed to only return equal if *shorter* + string has no trailing slash. + +Sat Jul 15 20:14:07 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c (dav_207_get_current_response, + dav_207_get_current_propstat): New functions. + + * dav_props.c (dav_propfind_get_current_resource): Implement using + dav_207_get_current_response. + +Sat Jul 15 17:36:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * xalloc.c (xcalloc): New function. + +Sat Jul 15 14:11:14 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.[ch] and elsewhere: Replace 'class' field of + http_status with '_class' to be C++-safe. (patch from Tom + Bednarz). + +Thu Jul 6 18:48:52 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c (copy_or_move): Escape the destination URI. + +Thu Jul 6 18:45:51 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c (end_response): Added description parameter. + +Thu Jul 6 18:43:14 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.[ch] (end_element): Fix handling of responsedescription. + Add "description" parameter to dav_207_end_response callback, and + pass the contents of responsedescription. + + * dav_basic.c (handle_error, end_response, end_propstat): Pass + description and add to error string when present. + +Tue Jul 4 11:43:03 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c, dav_props.c, http_basic.c: Use + http_{add,print}_request_header rather than + http_get_request_header. + +Tue Jul 4 11:41:00 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.[ch] (http_add_request_header, + http_print_request_header): New functions. + (http_get_request_header): Removed function. + +Mon Jul 3 21:50:40 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c: Add basic support for TLS upgrade (RFC2817). + (http_set_request_secure_upgrade, http_set_accept_secure_upgrade): + New functions. (send_request): If upgrade is requested, and a 101 + response is received, negotiate the TLS connection. + (add_fixed_headers): Add Upgrade header if necessary. + +Mon Jul 3 21:46:00 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (send_request): Don't go into an infinite loop. + (read_message_header): Simplyify checking for end-of-line. + +Tue Jun 13 00:29:42 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_session_proxy, http_session_server): Allow + calling >1 time per session, to switch servers. (send_request): + Only retry sending request once. + +Mon Jun 12 21:50:41 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_add_hooks): New function. + +Mon Jun 12 21:37:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_cookies.[ch]: Added basic cookies support. + +Mon Jun 12 21:33:33 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_create_ssl_context, sock_destroy_ssl_context, + sock_disable_tlsv1, sock_disable_sslv2, sock_disable_sslv3, + sock_make_secure): Added nssl_context handling. + +Mon Jun 12 21:29:52 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_request_dispatch, http_request_create, + http_request_destroy, http_set_request_body_buffer, + http_set_request_body_stream): Added hook support. + +Mon Jun 12 21:04:00 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_set_secure): Store an nssl_context. + (open_connection): Give the nssl_context. + +Sun Jun 11 16:37:52 2000 Joe Orton <joe@orton.demon.co.uk> + + * sslcerts.c: Import of SSL support from mutt, relicensed under + the LGPL for use in neon by the author, Tommi Komulainen + <Tommi.Komulainen@iki.fi>. + +Sun Jun 11 11:30:16 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (set_sockerr): Updated to use sock_get_error. + +Sun Jun 11 11:29:29 2000 Joe Orton <joe@orton.demon.co.uk> + + * uri.c (uri_parse): Allow scheme to be omitted. + +Fri Jun 9 20:39:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_get_error): New function. (sock_*): Set + sock->error and SOCK_ERROR on error. + +Mon May 29 16:32:46 2000 Joe Orton <joe@orton.demon.co.uk> + + * uri.c (uri_abspath_escape): Allocate the exact amount of memory + needed. + +Mon May 29 15:53:33 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c (dav_simple_request): Correct invalid XML logic. + +Mon May 29 15:52:08 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (create_sock, sock_accept, sock_get_fd): New + functions. (sock_connect_u): Use create_sock. + +Sun May 28 21:00:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.c (neon_version_string): New function. + +Sun May 28 19:36:45 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_connect_u): Zero out allocated nsocket object. + +Thu May 25 01:27:04 2000 Joe Orton <joe@orton.demon.co.uk> + + * *.h: Include other neon headers with <braces>. + +Thu May 25 01:02:12 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c: Include stdlib.h for 'free'. + +Wed May 24 20:15:08 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (read_message_header): Return HTTP_RETRY if more + headers to read, HTTP_OK on end-of-headers. + (read_response_headers): Changed accordingly. + +Wed May 24 19:56:29 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (send_request_body): Return a SOCK_* code. + (send_request): Re-send request if socket has been closed (due to + persistent connection timeout). + +Wed May 24 19:00:01 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.c (find_element): Fix unknown element handling. + +Tue May 23 19:12:26 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c (propfind): Destroy the request body sbuffer after + use. + +Tue May 23 15:43:42 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_make_secure) [SSL_ENABLE]: Conditionally compile + SSL code. [!SSL_ENABLE]: Return failure. (sock_close) + [SSL_ENABLE]: Conditionally compile SSL code. + +Tue May 23 15:37:53 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_session_create): Renamed from + http_session_init. (http_session_destroy): Renamed frmo + http_session_finish. + +Sun May 21 23:50:58 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (lookup_host): Use sock_name_lookup. + +Sun May 21 23:40:39 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_request_create): Allow passing NULL uri + (internal use only). (http_set_secure): New function. + (read_response_block, read_message_headers): Redone for new socket + API. (build_request): Moved http_auth_new_request calls here + (from http_request_dispatch). (send_request): Always call + open_connection before sending the request. + (read_message_header, read_response_headers): Looser check for + empty line. (normalize_response_length): Set response body length + to zero on 2xx class response whilst in CONNECT, if no other + response body length is given. (http_request_dispatch): Don't + close the connection on a HTTP/1.0 2xx class response after a + CONNECT request. (proxy_tunnel): New function. + (open_connection): Use an SSL connection where appropriate. Use + proxy_tunnel for tunnelling through a proxy. + +Sun May 21 01:35:40 2000 Joe Orton <joe@orton.demon.co.uk> + + * src/socket.c: Added 'nsocket' ADT for handling sockets. + (sock_*): All changed to take 'nsocket *' argument rather than + integer file descriptor. Added 'sock_secure_details' to + sock_status enum. (sock_make_secure, sock_init): New function. + (sock_peek): Renamed from sock_recv. (send_file_*, recv_file_*): + Removed functions. (sock_name_lookup): Renamed from host_lookup. + (sock_service_lookup): Renamed from get_tcp_port. (sock_block, + sock_read, sock_fullwrite, sock_peek, sock_readline): Added SSL + support. (sock_transfer): Use sock_fullwrite and sock_read. + +Sun May 21 01:25:03 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_request_destroy): Free header handlers and + body readers. + +Sun May 21 01:24:30 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.h: Removed obsolte got_property callback type. + +Sun May 21 01:23:59 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c (propfind): Free handler object after use. + +Sun May 21 01:23:12 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c (dav_207_destroy): Don't try to free the 'response' + field. + +Sat May 20 21:45:32 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c: Changed 'te' enum of struct http_response to + 'is_chunked' boolean. + +Sun May 14 01:00:42 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c (propfind): Return error on parse error. + +Sun May 14 00:40:50 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h (NEON_VERSION): Bumped to 0.2.0. + +Sat May 13 23:31:28 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_defs.h (BEGIN_NEON_DECLS, END_NEON_DECLS): Added C++ safety + macros. + + * *.h: Surround with C++ safety macros. + +Sat May 13 22:36:06 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.c (hip_xml_destroy): Free handlers. + +Sat May 13 21:12:14 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.c (hip_xml_valid): Reversed return value. + +Sat May 13 21:11:17 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.c: Renamed http_debug_mask to neon_debug_mask, + similarly neon_debug_stream. (neon_debug_init): Renamed from + http_debug_init. + +Sat May 13 19:24:40 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.c: Initialize http_debug_mask to zero. + (http_debug_init): New function. + + * http_utils.h: Fixed #ifdef DEBUGGING. Only define relevant + DEBUG_* constants. + +Sat May 13 19:23:34 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_config.h: New file. + + * http_request.c: Include neon_config.h for NEON_VERSION. + +Sat May 13 18:28:05 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c (dav_propfind_create): Create a hip_xml_parser, a + 207 parser, register start+end response handlers with 207 layer. + (propfind): Fix allprop (Michael Sobolev). + + * dav_basic.c (dav_simple_request): Create and destroy + hip_xml_parser and 207 parser appropriately. + +Sat May 13 18:24:49 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c: Now takes an externally-declared hip_xml parser + pointer. (dav_207_create, dav_207_destroy): New functions. + (dav_207_init, dav_207_init_with_handler, dav_207_parse, + dav_207_error, dav_207_finish): Removed functions. + +Sat May 13 17:32:45 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.[ch]: Rewritten to use opaque hip_xml_parser pointer. + struct hip_xml_handler and struct hip_xml_state removed from + external interface. struct hip_xml_elm * passed to startelm_cb and + endelm_cb. (hip_xml_add_handler, hip_xml_valid, hip_xml_create, + hip_xml_destroy, hip_xml_set_error, hip_xml_get_error): New + functions. (hip_xml_init, hip_xml_destroy): Removed functions. + +Sat May 13 13:43:56 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.h: Removed. + +Sat May 13 13:42:20 2000 Joe Orton <joe@orton.demon.co.uk> + + * string_utils.h: Don't include config.h. (CONCAT*): Don't use + xmalloc, use malloc and abort manually. + +Sat May 13 13:32:46 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.h, dates.h, http_basic.h: Don't include config.h + +Sat May 13 13:31:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.[ch], dav_207.c: Use HIP_ERR_SIZE for size of parser + error string. + +Sat May 13 13:30:40 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.incl: Use obj_ext for object file extension. + +Thu May 11 18:21:53 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.h: Bumped version to 0.1.1. + +Thu May 11 18:16:08 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.c (get_to_fd): Fix short writes. + +Wed May 10 19:22:01 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.h: Bumped version to 0.1.0. + +Wed May 10 17:46:48 2000 Joe Orton <joe@orton.demon.co.uk> + + * uri.c (uri_parse, uri_free): New functions. + +Wed May 10 17:43:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.c (get_to_fd, http_get): Set error appropriately if + fwrite() fails. + +Wed May 10 14:25:38 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.c (http_debug): New function. + +Wed May 10 14:25:08 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.c (get_callback): Call sock_call_progress. + +Wed May 10 14:24:20 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_call_progress): New function. (many places): Use + it. + +Wed May 10 14:22:48 2000 Joe Orton <joe@orton.demon.co.uk> + + * uri.c (uri_has_trailing_slash): Moved from being inline. + +Tue May 9 23:34:25 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c: Use handler as userdata for 207 callbacks, unified + handler and context structures. (start_prop, end_prop, + start_propelm, end_propelm): Removed functions. + (dav_propfind_get_current_resource): New function. + +Tue May 9 23:29:44 2000 Joe Orton <joe@orton.demon.co.uk> + + * xalloc.[ch]: New files. + +Tue May 9 23:05:47 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.[ch]: Removed property and property element callbacks. + +Tue May 9 23:01:00 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c: Use separate name/namespace for element names. + (dav_207_init_with_handler): New function. (end_element): + Unescape URI in href element. + +Tue May 9 19:54:07 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c (dav_propfind_allprop, dav_propfind_named, propfind, + start_response, end_response, start_prop, end_prop, start_propelm, + end_propelm): New functions; PROPFIND support. + +Tue May 9 19:45:17 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (build_request): Renamed from make_request. + +Tue May 9 19:36:01 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.[ch]: Added sock_block_reader. + +Tue May 9 15:52:56 2000 Joe Orton <joe@orton.demon.co.uk> + + * uri.c (uri_childof): Return false when parent is the same length + as child. + +Sun May 7 15:07:49 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c: Separated element namespace/names. + +Tue May 2 16:40:59 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.[ch]: Added HIP_XML_UTF8DECODE flag. + +Tue May 2 16:16:57 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.[ch]: Separate element name and namespace. + +Mon May 1 00:21:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c (dav_accept_207): Moved function from dav_basic.c. + + * dav_basic.c (dav_accept_207, dav_parse_xml_block): Removed + functions. + +Sun Apr 30 22:47:47 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.[ch]: Renamed dav_proppatch_item to + dav_proppatch_operation. + +Sun Apr 30 22:45:04 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.c (start_element): Clearer error message. + +Sun Apr 30 19:12:07 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.c (http_content_type_handler, dav_hdr_handler): New + functions. (http_options): Handle DAV header. + +Sun Apr 30 18:08:53 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_props.c (dav_proppatch): New function. + +Sun Apr 30 18:05:55 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c (handle_error): New function. (end_response, + end_propstat): Use it. (dav_simple_request): Don't return the 207 + error string if we get all 2xx class status elements. + +Sun Apr 30 16:56:41 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c (dav_add_depth_header): New function. + +Sun Apr 30 14:49:06 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c (start_element): Unknown element is only a property if + the parent is DAV:propstat. + +Sun Apr 30 14:43:28 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c (end_response, end_propstat): Only write error line + if we have status information and the status is not a 424. + +Sun Apr 30 14:28:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.h: Added DAV_DEPTH_*. + +Sun Apr 30 12:47:50 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c (check_context): Allow (and ignore) unknown elements + anywhere other than as the root. + +Sun Apr 30 12:35:39 2000 Joe Orton <joe@orton.demon.co.uk> + + * string_utils.h (ASC2HEX, HEX2ASC): New macros. + +Sun Apr 30 12:34:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_auth.c [STANDALONE]: Removed. (everywhere): Switch to using + md5_to_ascii rather than md5_hexify. + +Sun Apr 30 12:32:35 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (read_response_block): Fixed to return errors + properly and block length to parameter. (read_response_body): + Changed accordingly. + +Sun Apr 30 12:29:45 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.c (friendly_name): New function, was PRETTY_NAME macro. + (start_element, end_element): Fix COLLECT handling. + (hip_xml_parse): Only write parse error if the document has not + already been marked invalid. + +Sun Apr 30 12:28:36 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_basic.c (dav_simple_request): Rewritten for new 207 + interface. (start_response, end_response, end_propstat): New + functions. + +Sun Apr 30 12:27:52 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c (dav_207_error): Return the parser error. + +Sat Apr 29 14:46:48 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c (sock_register_progress, sock_register_notify): New + functions. (everywhere): Use progress + notify callbacks rather + than fe_*. + +Sat Apr 29 14:15:23 2000 Joe Orton <joe@orton.demon.co.uk> + + * string_utils.c (md5_to_ascii, ascii_to_md5): New functions. + +Sat Apr 29 13:55:39 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.c (hip_xml_init): abort() on out-of-memory. + +Sat Apr 29 12:56:11 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon_i18n.h: New file. + +Sat Apr 29 12:55:24 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.[ch]: Re-implemented with sensible interface. + +Fri Apr 28 14:56:01 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_auth.c (http_auth_request_header): Renamed from + http_auth_request. + + * http_request.c (make_request): As above. + +Thu Apr 13 11:52:14 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.c (http_put): Switched URI and stream arguments. + +Thu Apr 13 09:51:21 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c: Added user_agent field to session structure. + (http_set_useragent): New function. (add_fixed_headers): Only set + user-agent if sess->user_agent is set. + +Thu Apr 13 09:49:32 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (lookup_host): New function, split from + set_hostinfo. (set_hostinfo): Doesn't perform DNS lookup. + (http_session_server): Don't do a DNS lookup if we have a proxy. + +Wed Apr 12 22:32:21 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_request_dispatch, http_request_create): + Store auth header values in local variables rather than request + structure. (http_request_create): Don't leak everything on error. + Handle http_auth_challenge return value. + +Wed Apr 12 22:30:06 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.c (http_options): Pass server capabilites object, + parse Server header to detect Apache/1.3.6 and before, indicating + broken 100-continue support. (server_hdr_handler): New function. + +Mon Apr 10 17:42:07 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.c: Use 'int' for return values. + +Mon Apr 10 17:41:40 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_auth.c (is_in_domain): Dummy implementation. + +Mon Apr 10 17:40:21 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c: Handle read() returning 0 when it shouldn't. + i18n'ized error messages. + +Mon Apr 10 14:45:09 2000 Joe Orton <joe@orton.demon.co.uk> + + * dates.[ch], md5.[ch], base64.[ch]: Imported date handling + utilities, MD5 checksum functions, and text->base64 converter. + +Mon Apr 10 14:44:08 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.incl: Dependancies updated for socket.[ch]. + +Mon Apr 10 14:43:36 2000 Joe Orton <joe@orton.demon.co.uk> + + * dav_207.c: Replaced malloc() calls with xmalloc() calls. + +Mon Apr 10 14:42:35 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_auth.c, uri.c, string_utils.h: Replaced malloc() calls with + xmalloc() calls. + +Mon Apr 10 14:41:40 2000 Joe Orton <joe@orton.demon.co.uk> + + * socket.[ch]: Imported socket handling utilities. + +Mon Apr 10 14:36:03 2000 Joe Orton <joe@orton.demon.co.uk> + + * string_utils.h (CONCAT*): Use xmalloc. + +Mon Apr 10 13:52:17 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (set_sockerr): Added handling for socket errors. + +Sat Apr 8 13:49:07 2000 Joe Orton <joe@orton.demon.co.uk> + + * string_utils.[ch]: Imported string utilites. + +Sat Apr 8 00:26:06 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_set_persist, http_set_expect100): New + functions. + +Sat Apr 8 00:25:37 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_basic.c (http_options): New function. + +Fri Apr 7 13:01:35 2000 Joe Orton <joe@orton.demon.co.uk> + + * neon.h: New file. + +Fri Apr 7 12:59:40 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (normalize_response_length, read_response_body): + New functions. (http_add_response_body_reader): Take a callback + to determine whether the body reader wants to read the response + body. + +Fri Apr 7 11:46:41 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (http_set_server_auth, http_set_proxy_auth): New + functions. (give_creds): Use supplied callbacks for + authentication. (get_request_bodysize): Send Content-Length: 0 if + no entity-body is being sent with a request. (te_hdr_handler, + connection_hdr_handler): New functions. (make_request): Don't use + Expect: 100-continue if server is not HTTP/1.1 compliant. + (read_message_header): Only read until HTTP_MAXIMUM_HEADER_LENGTH + bytes of header have been read. (read_response_headers): No + hard-coded header handling. (http_request_create): Set + req->method_is_head here. + +Thu Apr 6 14:39:28 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.c [HIP_XML_DECODE_UTF8] (decode_utf8_double): New + function. (char_data) [HIP_XML_DECODE_UTF8]: Decode UTF-8. + +Tue Mar 28 13:54:51 2000 Joe Orton <joe@orton.demon.co.uk> + + * Makefile.incl: Imported makefile fragment. + +Tue Mar 28 13:54:09 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.[ch] (http_get_error): New function. + +Thu Mar 23 18:48:42 2000 Joe Orton <joe@orton.demon.co.uk> + + * hip_xml.[ch]: Imported generic XML parsing layer. + + * dav_207.[ch]: Imported generic WebDAV 207 response handling. + + * dav_basic.[ch]: Imported/implemented DAV response handling and + basic Class 1 namespace methods. + +Thu Mar 23 18:46:14 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.c (add_hooks, run_hooks, http_add_destroy_hook): + Adding hooks support. (add_fixed_headers): Send TE token in + Connection header. Only send Keep-Alive header & token to pre-1.1 + origin servers (i.e., not proxies). + +Thu Mar 23 12:49:01 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_auth.[ch], uri.[ch]: Imported HTTP authentication and URI + handling modules. + +Thu Mar 23 12:47:05 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_utils.c: Imported HTTP utility functions. + +Thu Mar 23 12:44:38 2000 Joe Orton <joe@orton.demon.co.uk> + + * http_request.[ch]: Implemented modular HTTP request handling. + + * http_basic.[ch]: Implemented basic HTTP methods GET, PUT, and + PUT with If-Unmodified. + diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..04b308e --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,147 @@ +# +# neon source directory Makefile +# +# Use the NEON_NORMAL_BUILD or NEON_LIBTOOL_BUILD autoconf +# macros to set up this Makefile correctly. +# + +SHELL = @SHELL@ + +# Installation paths +prefix = @prefix@ +exec_prefix = @exec_prefix@ +libdir = @libdir@ + +# Build paths +VPATH = @srcdir@ +top_builddir = .. +top_srcdir = @top_srcdir@ + +# Toolchain settings. +CC = @CC@ +AR = @AR@ +RANLIB = @RANLIB@ +LIBTOOL = @LIBTOOL@ + +# Flags +CPPFLAGS = @DEFS@ @CPPFLAGS@ +CFLAGS = @CFLAGS@ @NEON_CFLAGS@ +LDFLAGS = @LDFLAGS@ +NEON_LINK_FLAGS = @NEON_LINK_FLAGS@ +# Note: don't substitute @LIBS@ in here; during a bundled +# build of this directory, @LIBS@ may include -lneon. +LIBS = @NEON_LIBS@ + +COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS) +LINK = $(LIBTOOL) --quiet --mode=link $(CC) $(LDFLAGS) + +NEON_BASEOBJS = ne_request.@NEON_OBJEXT@ ne_session.@NEON_OBJEXT@ \ + ne_basic.@NEON_OBJEXT@ ne_string.@NEON_OBJEXT@ \ + ne_uri.@NEON_OBJEXT@ ne_dates.@NEON_OBJEXT@ ne_alloc.@NEON_OBJEXT@ \ + ne_md5.@NEON_OBJEXT@ ne_utils.@NEON_OBJEXT@ \ + ne_socket.@NEON_OBJEXT@ ne_auth.@NEON_OBJEXT@ \ + ne_cookies.@NEON_OBJEXT@ ne_redirect.@NEON_OBJEXT@ \ + ne_compress.@NEON_OBJEXT@ + +NEON_DAVOBJS = $(NEON_BASEOBJS) \ + ne_207.@NEON_OBJEXT@ ne_xml.@NEON_OBJEXT@ \ + ne_props.@NEON_OBJEXT@ ne_locks.@NEON_OBJEXT@ + +OBJECTS = @NEONOBJS@ @NEON_EXTRAOBJS@ + +.SUFFIXES: +.SUFFIXES: .c .lo .o + +NEON_TARGET = @NEON_TARGET@ + +# Thanks to gettext for this neat trick. +all: all-@NEON_BUILD_BUNDLED@ + +all-yes: $(NEON_TARGET) +all-no: + @echo "Bundled neon build not being used." + +.c.lo: + $(LIBTOOL) --quiet --mode=compile $(COMPILE) -c $< -o $@ +.c.o: + $(COMPILE) -c $< -o $@ + +libneon.la: $(OBJECTS) + $(LINK) -rpath $(libdir) $(NEON_LINK_FLAGS) -o $@ $(OBJECTS) $(LIBS) + +libneon.a: $(OBJECTS) + $(AR) cru $@ $(OBJECTS) + $(RANLIB) $@ + +clean: + rm -f $(NEON_TARGET) *.o *.lo + rm -rf .libs + +c++.c: + find . -name ne_\*.h -print | sed 's/.*/#include "&"/;/ne_private/d' > $@ + echo "int main(void) {}" >> $@ + +check-c++: c++.c + c++ -I. c++.c + +check-incl: + @for f in ne_*.h; do \ + echo Checking $$f...; \ + echo "#include \"$$f\"" > checkincl.c; \ + $(COMPILE) -c checkincl.c -o checkincl.o || exit 1; done + +neonreq = ne_request.h ne_session.h ne_utils.h ne_string.h ne_socket.h \ + ne_alloc.h $(top_builddir)/config.h ne_private.h + +ne_request.@NEON_OBJEXT@: ne_request.c $(neonreq) ne_i18n.h ne_private.h \ + ne_uri.h + +ne_session.@NEON_OBJEXT@: ne_session.c ne_session.h ne_alloc.h \ + ne_utils.h ne_private.h $(top_builddir)/config.h + +ne_openssl.@NEON_OBJEXT@: ne_openssl.c ne_session.h ne_ssl.h ne_privssl.h \ + ne_private.h $(top_builddir)/config.h + +ne_socket.@NEON_OBJEXT@: ne_socket.c ne_socket.h $(top_builddir)/config.h \ + ne_privssl.h ne_string.h + +ne_auth.@NEON_OBJEXT@: ne_auth.c ne_auth.h $(neonreq) \ + ne_dates.h ne_md5.h ne_uri.h + +ne_basic.@NEON_OBJEXT@: ne_basic.c ne_basic.h $(neonreq) + +ne_utils.@NEON_OBJEXT@: ne_utils.c $(top_builddir)/config.h \ + ne_utils.h ne_dates.h + +ne_xml.@NEON_OBJEXT@: ne_xml.c ne_xml.h ne_string.h ne_utils.h \ + $(top_builddir)/config.h + +ne_207.@NEON_OBJEXT@: ne_207.c ne_207.h ne_xml.h \ + $(top_builddir)/config.h ne_utils.h ne_i18n.h + +ne_string.@NEON_OBJEXT@: ne_string.c ne_string.h ne_alloc.h \ + $(top_builddir)/config.h + +ne_alloc.@NEON_OBJEXT@: ne_alloc.c ne_alloc.h $(top_builddir)/config.h + +ne_dates.@NEON_OBJEXT@: ne_dates.c ne_dates.h $(top_builddir)/config.h + +ne_uri.@NEON_OBJEXT@: ne_uri.c ne_uri.h ne_utils.h ne_string.h ne_alloc.h \ + $(top_builddir)/config.h + +ne_md5.@NEON_OBJEXT@: ne_md5.c ne_md5.h $(top_builddir)/config.h + +ne_props.@NEON_OBJEXT@: ne_props.c $(top_builddir)/config.h \ + ne_props.h ne_207.h ne_xml.h $(neonreq) + +ne_locks.@NEON_OBJEXT@: ne_locks.c $(neonreq) ne_locks.h ne_207.h ne_xml.h + +ne_redirect.@NEON_OBJEXT@: ne_redirect.c $(neonreq) ne_redirect.h \ + ne_uri.h ne_private.h + +ne_cookies.@NEON_OBJEXT@: ne_cookies.c $(neonreq) ne_cookies.h ne_uri.h \ + ne_private.h + +ne_compress.@NEON_OBJEXT@: ne_compress.c $(neonreq) ne_compress.h + +ne_acl.@NEON_OBJEXT@: ne_acl.c ne_acl.h $(neonreq) diff --git a/src/README b/src/README new file mode 100644 index 0000000..0cb9a3f --- /dev/null +++ b/src/README @@ -0,0 +1,15 @@ + +This is the source directory of the 'neon' HTTP/WebDAV client library, +which can be bundled inside other packages. For the complete neon +package, see: + + http://www.webdav.org/neon/ + +This source directory may be distributed and/or modified under the +terms of the GNU Library General Public License, as given in +COPYING.LIB. + +Please send questions, bug reports, feature requests, etc, regarding +the neon library, to the mailing list at: + + neon@webdav.org diff --git a/src/memleak.h b/src/memleak.h new file mode 100644 index 0000000..8d77dcb --- /dev/null +++ b/src/memleak.h @@ -0,0 +1,55 @@ +/* + Memory leak wrappers + Copyright (C) 2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* WARNING: THIS IS AN INTERNAL NEON INTERFACE AND MUST NOT BE USED + * from NEON APPLICATIONS. */ + +/* This file contains an alternate interface to the memory allocation + * wrappers in ne_alloc.c, which perform simple leak detection. It + * MUST NOT BE INSTALLED, or used from neon applications. */ + +#ifndef MEMLEAK_H +#define MEMLEAK_H + +#include <stdio.h> + +#define ne_malloc(s) ne_malloc_ml(s, __FILE__, __LINE__) +#define ne_calloc(s) ne_calloc_ml(s, __FILE__, __LINE__) +#define ne_realloc(p, s) ne_realloc_ml(p, s, __FILE__, __LINE__) +#define ne_strdup(s) ne_strdup_ml(s, __FILE__, __LINE__) +#define ne_strndup(s, n) ne_strndup_ml(s, n, __FILE__, __LINE__) +#define ne_free ne_free_ml + +/* Prototypes of allocation functions: */ +void *ne_malloc_ml(size_t size, const char *file, int line); +void *ne_calloc_ml(size_t size, const char *file, int line); +void *ne_realloc_ml(void *ptr, size_t s, const char *file, int line); +char *ne_strdup_ml(const char *s, const char *file, int line); +char *ne_strndup_ml(const char *s, size_t n, const char *file, int line); +void ne_free_ml(void *ptr); + +/* Dump the list of currently allocated blocks to 'f'. */ +void ne_alloc_dump(FILE *f); + +/* Current number of bytes in allocated but not free'd. */ +extern size_t ne_alloc_used; + +#endif /* MEMLEAK_H */ diff --git a/src/ne_207.c b/src/ne_207.c new file mode 100644 index 0000000..ad5e6a1 --- /dev/null +++ b/src/ne_207.c @@ -0,0 +1,345 @@ +/* + WebDAV 207 multi-status response handling + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* Generic handling for WebDAV 207 Multi-Status responses. */ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "ne_alloc.h" +#include "ne_utils.h" +#include "ne_xml.h" +#include "ne_207.h" +#include "ne_uri.h" +#include "ne_basic.h" + +#include "ne_i18n.h" + +struct ne_207_parser_s { + ne_207_start_response *start_response; + ne_207_end_response *end_response; + ne_207_start_propstat *start_propstat; + ne_207_end_propstat *end_propstat; + ne_xml_parser *parser; + void *userdata; + + ne_buffer *cdata; + + /* remember whether we are in a response: the validation + * doesn't encapsulate this since we only count as being + * "in a response" when we've seen the href element. */ + int in_response; + + /* current position */ + void *response, *propstat; + /* caching */ + ne_status status; + char *description, *href; +}; + +#define ELM_multistatus 1 +#define ELM_response 2 +#define ELM_responsedescription 3 +#define ELM_href 4 +#define ELM_prop (NE_207_STATE_PROP) +#define ELM_status 6 +#define ELM_propstat 7 + +static const struct ne_xml_idmap map207[] = { + { "DAV:", "multistatus", ELM_multistatus }, + { "DAV:", "response", ELM_response }, + { "DAV:", "responsedescription", ELM_responsedescription }, + { "DAV:", "href", ELM_href }, + { "DAV:", "propstat", ELM_propstat }, + { "DAV:", "prop", ELM_prop }, + { "DAV:", "status", ELM_status } +}; + +/* Set the callbacks for the parser */ +void ne_207_set_response_handlers(ne_207_parser *p, + ne_207_start_response start, + ne_207_end_response end) +{ + p->start_response = start; + p->end_response = end; +} + +void ne_207_set_propstat_handlers(ne_207_parser *p, + ne_207_start_propstat start, + ne_207_end_propstat end) +{ + p->start_propstat = start; + p->end_propstat = end; +} + +void *ne_207_get_current_response(ne_207_parser *p) +{ + return p->response; +} + +void *ne_207_get_current_propstat(ne_207_parser *p) +{ + return p->propstat; +} + +/* return non-zero if (child, parent) is an interesting element */ +static int can_handle(int parent, int child) +{ + return (parent == 0 && child == ELM_multistatus) || + (parent == ELM_multistatus && child == ELM_response) || + (parent == ELM_response && + (child == ELM_href || child == ELM_status || + child == ELM_propstat || child == ELM_responsedescription)) || + (parent == ELM_propstat && + (child == ELM_prop || child == ELM_status || + child == ELM_responsedescription)); +} + +static int cdata_207(void *userdata, int state, const char *buf, size_t len) +{ + ne_207_parser *p = userdata; + + if ((state == ELM_href || state == ELM_responsedescription || + state == ELM_status) && p->cdata->used + len < 2048) + ne_buffer_append(p->cdata, buf, len); + + return 0; +} + +static int start_element(void *userdata, int parent, + const char *nspace, const char *name, + const char **atts) +{ + ne_207_parser *p = userdata; + int state = ne_xml_mapid(map207, NE_XML_MAPLEN(map207), nspace, name); + + if (!can_handle(parent, state)) + return NE_XML_DECLINE; + + /* if not in a response, ignore everything. */ + if (!p->in_response && state != ELM_response && state != ELM_multistatus && + state != ELM_href) + return NE_XML_DECLINE; + + if (state == ELM_propstat && p->start_propstat) + p->propstat = p->start_propstat(p->userdata, p->response); + + ne_buffer_clear(p->cdata); + + return state; +} + +#define GIVE_STATUS(p) ((p)->status.reason_phrase?&(p)->status:NULL) + +#define HAVE_CDATA(p) ((p)->cdata->used > 1) + +static int +end_element(void *userdata, int state, const char *nspace, const char *name) +{ + ne_207_parser *p = userdata; + const char *cdata = p->cdata->data; + + switch (state) { + case ELM_responsedescription: + if (HAVE_CDATA(p)) { + NE_FREE(p->description); + p->description = ne_strdup(cdata); + } + break; + case ELM_href: + /* Now we have the href, begin the response */ + if (p->start_response && HAVE_CDATA(p)) { + p->response = p->start_response(p->userdata, cdata); + p->in_response = 1; + } + break; + case ELM_status: + if (HAVE_CDATA(p)) { + NE_FREE(p->status.reason_phrase); + if (ne_parse_statusline(cdata, &p->status)) { + char buf[500]; + NE_DEBUG(NE_DBG_HTTP, "Status line: %s\n", cdata); + ne_snprintf(buf, 500, + _("Invalid HTTP status line in status element " + "at line %d of response:\nStatus line was: %s"), + ne_xml_currentline(p->parser), cdata); + ne_xml_set_error(p->parser, buf); + return -1; + } else { + NE_DEBUG(NE_DBG_XML, "Decoded status line: %s\n", cdata); + } + } + break; + case ELM_propstat: + if (p->end_propstat) + p->end_propstat(p->userdata, p->propstat, GIVE_STATUS(p), + p->description); + p->propstat = NULL; + NE_FREE(p->description); + NE_FREE(p->status.reason_phrase); + break; + case ELM_response: + if (!p->in_response) break; + if (p->end_response) + p->end_response(p->userdata, p->response, GIVE_STATUS(p), + p->description); + p->response = NULL; + p->in_response = 0; + NE_FREE(p->status.reason_phrase); + NE_FREE(p->description); + break; + } + return 0; +} + +ne_207_parser *ne_207_create(ne_xml_parser *parser, void *userdata) +{ + ne_207_parser *p = ne_calloc(sizeof *p); + + p->parser = parser; + p->userdata = userdata; + p->cdata = ne_buffer_create(); + + /* Add handler for the standard 207 elements */ + ne_xml_push_handler(parser, start_element, cdata_207, end_element, p); + + return p; +} + +void ne_207_destroy(ne_207_parser *p) +{ + if (p->status.reason_phrase) ne_free(p->status.reason_phrase); + ne_buffer_destroy(p->cdata); + ne_free(p); +} + +int ne_accept_207(void *userdata, ne_request *req, const ne_status *status) +{ + return (status->code == 207); +} + +/* Handling of 207 errors: we keep a string buffer, and append + * messages to it as they come down. + * + * Note, 424 means it would have worked but something else went wrong. + * We will have had the error for "something else", so we display + * that, and skip 424 errors. */ + +/* This is passed as userdata to the 207 code. */ +struct context { + char *href; + ne_buffer *buf; + unsigned int is_error; +}; + +static void *start_response(void *userdata, const char *href) +{ + struct context *ctx = userdata; + NE_FREE(ctx->href); + ctx->href = ne_strdup(href); + return NULL; +} + +static void handle_error(struct context *ctx, const ne_status *status, + const char *description) +{ + if (status && status->klass != 2 && status->code != 424) { + char buf[50]; + ctx->is_error = 1; + sprintf(buf, "%d", status->code); + ne_buffer_concat(ctx->buf, ctx->href, ": ", + buf, " ", status->reason_phrase, "\n", NULL); + if (description != NULL) { + /* TODO: these can be multi-line. Would be good to + * word-wrap this at col 80. */ + ne_buffer_concat(ctx->buf, " -> ", description, "\n", NULL); + } + } + +} + +static void end_response(void *userdata, void *response, + const ne_status *status, const char *description) +{ + struct context *ctx = userdata; + handle_error(ctx, status, description); +} + +static void +end_propstat(void *userdata, void *propstat, + const ne_status *status, const char *description) +{ + struct context *ctx = userdata; + handle_error(ctx, status, description); +} + +/* Dispatch a DAV request and handle a 207 error response appropriately */ +/* TODO: hook up Content-Type parsing; passing charset to XML parser */ +int ne_simple_request(ne_session *sess, ne_request *req) +{ + int ret; + struct context ctx = {0}; + ne_207_parser *p207; + ne_xml_parser *p; + + p = ne_xml_create(); + p207 = ne_207_create(p, &ctx); + /* The error string is progressively written into the + * ne_buffer by the element callbacks */ + ctx.buf = ne_buffer_create(); + + ne_207_set_response_handlers(p207, start_response, end_response); + ne_207_set_propstat_handlers(p207, NULL, end_propstat); + + ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v, p); + + ret = ne_request_dispatch(req); + + if (ret == NE_OK) { + if (ne_get_status(req)->code == 207) { + if (!ne_xml_valid(p)) { + /* The parse was invalid */ + ne_set_error(sess, ne_xml_get_error(p)); + ret = NE_ERROR; + } else if (ctx.is_error) { + /* If we've actually got any error information + * from the 207, then set that as the error */ + ne_set_error(sess, ctx.buf->data); + ret = NE_ERROR; + } + } else if (ne_get_status(req)->klass != 2) { + ret = NE_ERROR; + } + } + + ne_207_destroy(p207); + ne_xml_destroy(p); + ne_buffer_destroy(ctx.buf); + NE_FREE(ctx.href); + + ne_request_destroy(req); + + return ret; +} + diff --git a/src/ne_207.h b/src/ne_207.h new file mode 100644 index 0000000..890ce84 --- /dev/null +++ b/src/ne_207.h @@ -0,0 +1,93 @@ +/* + WebDAV 207 multi-status response handling + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef DAV207_H +#define DAV207_H + +#include "ne_xml.h" +#include "ne_request.h" /* for ne_request */ + +BEGIN_NEON_DECLS + +/* The defined state integer for the '{DAV:}prop' element. */ +#define NE_207_STATE_PROP (50) +/* This interface reserves the state integers 'x' where 0 < x < 100 */ +#define NE_207_STATE_TOP (100) + +/* Handling of 207 multistatus XML documents. A "multistatus" + * document is made up of a set of responses, each concerned with a + * particular resource. Each response may have an associated result + * status and failure description. A response is made up of a set of + * propstats, each of which again may have an associated result status + * and failure description. */ + +/* Start and end response callbacks trigger at the start and end of + * each "response" within the multistatus body. 'href' gives the URI + * of the resource which is subject of this response. The return + * value of a 'start_response' callback is passed as the 'response' + * parameter to the corresponding 'end_response' parameter. */ +typedef void *ne_207_start_response(void *userdata, const char *href); +typedef void ne_207_end_response(void *userdata, void *response, + const ne_status *status, + const char *description); + +/* Similarly, start and end callbacks for each propstat within the + * response. The return value of the 'start_response' callback for + * the response in which this propstat is contains is passed as the + * 'response' parameter. The return value of each 'start_propstat' is + * passed as the 'propstat' parameter' to the corresponding + * 'end_propstat' callback. */ +typedef void *ne_207_start_propstat(void *userdata, void *response); +typedef void ne_207_end_propstat(void *userdata, void *propstat, + const ne_status *status, + const char *description); + +typedef struct ne_207_parser_s ne_207_parser; + +/* Create 207 parser an add the handlers the the given parser's + * handler stack. */ +ne_207_parser *ne_207_create(ne_xml_parser *parser, void *userdata); + +/* Register response handling callbacks. */ +void ne_207_set_response_handlers(ne_207_parser *p, + ne_207_start_response *start, + ne_207_end_response *end); + +/* Register propstat handling callbacks. */ +void ne_207_set_propstat_handlers(ne_207_parser *p, + ne_207_start_propstat *start, + ne_207_end_propstat *end); + +/* Destroy the parser */ +void ne_207_destroy(ne_207_parser *p); + +/* An acceptance function which only accepts 207 responses */ +int ne_accept_207(void *userdata, ne_request *req, const ne_status *status); + +void *ne_207_get_current_propstat(ne_207_parser *p); +void *ne_207_get_current_response(ne_207_parser *p); + +/* Dispatch a DAV request and handle a 207 error response appropriately */ +int ne_simple_request(ne_session *sess, ne_request *req); + +END_NEON_DECLS + +#endif /* DAV207_H */ diff --git a/src/ne_acl.c b/src/ne_acl.c new file mode 100644 index 0000000..c540a0e --- /dev/null +++ b/src/ne_acl.c @@ -0,0 +1,130 @@ +/* + Access control + Copyright (C) 2001, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* Contributed by Arun Garg <arung@pspl.co.in> */ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "ne_request.h" +#include "ne_locks.h" +#include "ne_alloc.h" +#include "ne_string.h" +#include "ne_acl.h" +#include "ne_uri.h" +#include "ne_xml.h" /* for NE_XML_MEDIA_TYPE */ + +static ne_buffer *acl_body(ne_acl_entry *right, int count) +{ + ne_buffer *body = ne_buffer_create(); + int m; + + ne_buffer_zappend(body, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL + "<acl xmlns='DAV:'>" EOL); + + for (m = 0; m < count; m++) { + const char *type; + + type = (right[m].type == ne_acl_grant ? "grant" : "deny"); + + ne_buffer_concat(body, "<ace>" EOL "<principal>", NULL); + + switch (right[m].apply) { + case ne_acl_all: + ne_buffer_zappend(body, "<all/>" EOL); + break; + case ne_acl_property: + ne_buffer_concat(body, "<property><", right[m].principal, + "/></property>" EOL, NULL); + break; + case ne_acl_href: + ne_buffer_concat(body, "<href>", right[m].principal, + "</href>" EOL, NULL); + break; + } + + ne_buffer_concat(body, "</principal>" EOL "<", type, ">" EOL, NULL); + + if (right[m].read == 0) + ne_buffer_concat(body, + "<privilege>" "<read/>" "</privilege>" EOL, + NULL); + if (right[m].read_acl == 0) + ne_buffer_concat(body, + "<privilege>" "<read-acl/>" "</privilege>" EOL, + NULL); + if (right[m].write == 0) + ne_buffer_concat(body, + "<privilege>" "<write/>" "</privilege>" EOL, + NULL); + if (right[m].write_acl == 0) + ne_buffer_concat(body, + "<privilege>" "<write-acl/>" "</privilege>" EOL, + NULL); + if (right[m].read_cuprivset == 0) + ne_buffer_concat(body, + "<privilege>" + "<read-current-user-privilege-set/>" + "</privilege>" EOL, NULL); + ne_buffer_concat(body, "</", type, ">" EOL, NULL); + ne_buffer_zappend(body, "</ace>" EOL); + } + ne_buffer_zappend(body, "</acl>" EOL); + + return body; +} + +int ne_acl_set(ne_session *sess, const char *uri, + ne_acl_entry *entries, int numentries) +{ + int ret; + ne_request *req = ne_request_create(sess, "ACL", uri); + ne_buffer *body = acl_body(entries, numentries); + +#ifdef USE_DAV_LOCKS + ne_lock_using_resource(req, uri, 0); +#endif + + ne_set_request_body_buffer(req, body->data, ne_buffer_size(body)); + ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE); + ret = ne_request_dispatch(req); + + ne_buffer_destroy(body); + + if (ret == NE_OK && ne_get_status(req)->code == 207) { + ret = NE_ERROR; + } + + ne_request_destroy(req); + return ret; +} diff --git a/src/ne_acl.h b/src/ne_acl.h new file mode 100644 index 0000000..bf1a1ec --- /dev/null +++ b/src/ne_acl.h @@ -0,0 +1,56 @@ +/* + Access control + Copyright (C) 2001, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_ACL_H +#define NE_ACL_H + +#include "ne_session.h" + +BEGIN_NEON_DECLS + +typedef struct +{ + enum { + ne_acl_href, + ne_acl_property, + ne_acl_all + } apply; + + enum { + ne_acl_grant, + ne_acl_deny + } type; + + char *principal; + int read; + int read_acl; + int write; + int write_acl; + int read_cuprivset; +} ne_acl_entry; + +/* Set the ACL for the given resource to the list of ACL entries. */ +int ne_acl_set(ne_session *sess, const char *uri, + ne_acl_entry entries[], int numentries); + +END_NEON_DECLS + +#endif /* NE_ACL_H */ diff --git a/src/ne_alloc.c b/src/ne_alloc.c new file mode 100644 index 0000000..1bee5aa --- /dev/null +++ b/src/ne_alloc.c @@ -0,0 +1,208 @@ +/* + Replacement memory allocation handling etc. + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <stdio.h> + +#include "ne_alloc.h" + +static void (*oom)(void); + +void ne_oom_callback(void (*callback)(void)) +{ + oom = callback; +} + +#ifndef NEON_MEMLEAK + +#define DO_MALLOC(ptr, len) do { \ + ptr = malloc((len)); \ + if (!ptr) { \ + if (oom != NULL) \ + oom(); \ + abort(); \ + } \ +} while(0); + +void *ne_malloc(size_t len) +{ + void *ptr; + DO_MALLOC(ptr, len); + return ptr; +} + +void *ne_calloc(size_t len) +{ + void *ptr; + DO_MALLOC(ptr, len); + return memset(ptr, 0, len); +} + +void *ne_realloc(void *ptr, size_t len) +{ + void *ret = realloc(ptr, len); + if (!ret) { + if (oom) + oom(); + abort(); + } + return ret; +} + +char *ne_strdup(const char *s) +{ + char *ret; + DO_MALLOC(ret, strlen(s) + 1); + return strcpy(ret, s); +} + +char *ne_strndup(const char *s, size_t n) +{ + char *new; + DO_MALLOC(new, n+1); + new[n] = '\0'; + memcpy(new, s, n); + return new; +} + +#else /* NEON_MEMLEAK */ + +/* Memory-leak detection implementation: ne_malloc and friends are + * #defined to ne_malloc_ml etc by memleak.h, which is conditionally + * included by config.h. */ + +/* memory allocated be ne_*alloc, but not freed. */ +size_t ne_alloc_used = 0; + +static struct block { + void *ptr; + size_t len; + const char *file; + int line; + struct block *next; +} *blocks = NULL; + +void ne_alloc_dump(FILE *f) +{ + struct block *b; + + for (b = blocks; b != NULL; b = b->next) + fprintf(f, "%" NE_FMT_SIZE_T "b@%s:%d%s", b->len, b->file, b->line, + b->next?", ":""); +} + +static void *tracking_malloc(size_t len, const char *file, int line) +{ + void *ptr = malloc((len)); + struct block *block; + + if (!ptr) { + if (oom) oom(); + abort(); + } + + block = malloc(sizeof *block); + if (block != NULL) { + block->ptr = ptr; + block->len = len; + block->file = file; + block->line = line; + block->next = blocks; + blocks = block; + ne_alloc_used += len; + } + + return ptr; +} + +void *ne_malloc_ml(size_t size, const char *file, int line) +{ + return tracking_malloc(size, file, line); +} + +void *ne_calloc_ml(size_t size, const char *file, int line) +{ + return memset(tracking_malloc(size, file, line), 0, size); +} + +void *ne_realloc_ml(void *ptr, size_t s, const char *file, int line) +{ + void *ret = realloc(ptr, s); + struct block *b; + + if (!ret) { + if (oom) oom(); + abort(); + } + + for (b = blocks; b != NULL; b = b->next) { + if (b->ptr == ptr) { + ne_alloc_used = ne_alloc_used + s - b->len; + b->ptr = ret; + b->len = s; + break; + } + } + + return ret; +} + +char *ne_strdup_ml(const char *s, const char *file, int line) +{ + return strcpy(tracking_malloc(strlen(s) + 1, file, line), s); +} + +char *ne_strndup_ml(const char *s, size_t n, const char *file, int line) +{ + char *ret = tracking_malloc(n + 1, file, line); + ret[n] = '\0'; + return memcpy(ret, s, n); +} + +void ne_free_ml(void *ptr) +{ + struct block *b, *last = NULL; + + for (b = blocks; b != NULL; last = b, b = b->next) { + if (b->ptr == ptr) { + ne_alloc_used -= b->len; + if (last) + last->next = b->next; + else + blocks = b->next; + free(b); + break; + } + } + + free(ptr); +} + +#endif /* NEON_MEMLEAK */ diff --git a/src/ne_alloc.h b/src/ne_alloc.h new file mode 100644 index 0000000..1da4a38 --- /dev/null +++ b/src/ne_alloc.h @@ -0,0 +1,58 @@ +/* + Replacement memory allocation handling etc. + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_ALLOC_H +#define NE_ALLOC_H + +#ifdef WIN32 +#include <stdlib.h> +#else +#include <sys/types.h> +#endif + +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +/* Set callback which is called if malloc() returns NULL. */ +void ne_oom_callback(void (*callback)(void)); + +#ifndef NEON_MEMLEAK +/* Replacements for standard C library memory allocation functions, + * which never return NULL. If the C library malloc() returns NULL, + * neon will abort(); calling an OOM callback beforehand if one is + * registered. The C library will only ever return NULL if the + * operating system does not use optimistic memory allocation. */ +void *ne_malloc(size_t size); +void *ne_calloc(size_t size); +void *ne_realloc(void *ptr, size_t s); +char *ne_strdup(const char *s); +char *ne_strndup(const char *s, size_t n); +#define ne_free free +#endif + +/* Handy macro to free things: takes an lvalue, and sets to NULL + * afterwards. */ +#define NE_FREE(x) do { if ((x) != NULL) ne_free((x)); (x) = NULL; } while (0) + +END_NEON_DECLS + +#endif /* NE_ALLOC_H */ diff --git a/src/ne_auth.c b/src/ne_auth.c new file mode 100644 index 0000000..ba0095c --- /dev/null +++ b/src/ne_auth.c @@ -0,0 +1,1026 @@ +/* + HTTP Authentication routines + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + + +/* HTTP Authentication, as per RFC2617. + * + * TODO: + * - Test auth-int support + */ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* for getpid() */ +#endif + +#ifdef NEON_SSL +#include <openssl/rand.h> +#endif + +#include <time.h> + +#include "ne_md5.h" +#include "ne_dates.h" +#include "ne_request.h" +#include "ne_auth.h" +#include "ne_string.h" +#include "ne_utils.h" +#include "ne_alloc.h" +#include "ne_uri.h" +#include "ne_i18n.h" + +/* TODO: should remove this eventually. Need it for + * ne_pull_request_body. */ +#include "ne_private.h" + +#define HOOK_SERVER_ID "http://webdav.org/neon/hooks/server-auth" +#define HOOK_PROXY_ID "http://webdav.org/neon/hooks/proxy-auth" + +/* The authentication scheme we are using */ +typedef enum { + auth_scheme_basic, + auth_scheme_digest +} auth_scheme; + +typedef enum { + auth_alg_md5, + auth_alg_md5_sess, + auth_alg_unknown +} auth_algorithm; + +/* Selected method of qop which the client is using */ +typedef enum { + auth_qop_none, + auth_qop_auth, + auth_qop_auth_int +} auth_qop; + +/* A challenge */ +struct auth_challenge { + auth_scheme scheme; + const char *realm; + const char *nonce; + const char *opaque; + unsigned int stale:1; /* if stale=true */ + unsigned int got_qop:1; /* we were given a qop directive */ + unsigned int qop_auth:1; /* "auth" token in qop attrib */ + unsigned int qop_auth_int:1; /* "auth-int" token in qop attrib */ + auth_algorithm alg; + struct auth_challenge *next; +}; + +static const struct auth_class { + const char *id, *req_hdr, *resp_hdr, *resp_info_hdr, *fail_msg; + int status_code, fail_code; +} ah_server_class = { + HOOK_SERVER_ID, + "Authorization", "WWW-Authenticate", "Authentication-Info", + N_("Server was not authenticated correctly."), 401, NE_AUTH +}, ah_proxy_class = { + HOOK_PROXY_ID, + "Proxy-Authorization", "Proxy-Authenticate", "Proxy-Authentication-Info", + N_("Proxy server was not authenticated correctly."), 407, NE_PROXYAUTH +}; + +/* Authentication session state. */ +typedef struct { + ne_session *sess; + + /* Specifics for server/proxy auth. FIXME: need a better field + * name! */ + const struct auth_class *spec; + + /* The scheme used for this authentication session */ + auth_scheme scheme; + /* The callback used to request new username+password */ + ne_auth_creds creds; + void *userdata; + + /*** Session details ***/ + + /* The username and password we are using to authenticate with */ + char username[NE_ABUFSIZ]; + /* Whether we CAN supply authentication at the moment */ + unsigned int can_handle:1; + /* This used for Basic auth */ + char *basic; + /* These all used for Digest auth */ + char *realm; + char *nonce; + char *cnonce; + char *opaque; + auth_qop qop; + auth_algorithm alg; + int nonce_count; + /* The ASCII representation of the session's H(A1) value */ + char h_a1[33]; + + /* Temporary store for half of the Request-Digest + * (an optimisation - used in the response-digest calculation) */ + struct ne_md5_ctx stored_rdig; + + /* Details of server... needed to reconstruct absoluteURI's when + * necessary */ + const char *host; + const char *uri_scheme; + unsigned int port; + + int attempt; +} auth_session; + +struct auth_request { + /*** Per-request details. ***/ + ne_request *request; /* the request object. */ + + /* The method and URI we are using for the current request */ + const char *uri; + const char *method; + /* Whether we WILL supply authentication for this request or not */ + unsigned int will_handle:1; + + /* Used for calculation of H(entity-body) of the response */ + struct ne_md5_ctx response_body; + + /* Results of response-header callbacks */ + char *auth_hdr, *auth_info_hdr; +}; + +static void clean_session(auth_session *sess) +{ + sess->can_handle = 0; + NE_FREE(sess->basic); + NE_FREE(sess->nonce); + NE_FREE(sess->cnonce); + NE_FREE(sess->opaque); + NE_FREE(sess->realm); +} + +/* Returns client nonce string. */ +static char *get_cnonce(void) +{ + char data[256], ret[33]; + unsigned char tmp[16]; + struct ne_md5_ctx hash; + + ne_md5_init_ctx(&hash); + +#ifdef NEON_SSL + if (RAND_pseudo_bytes(data, sizeof data) >= 0) + ne_md5_process_bytes(data, sizeof data, &hash); + else { +#endif + /* Fallback sources of random data: all bad, but no good sources + * are available. */ + + /* Uninitialized stack data; yes, happy valgrinders, this is + * supposed to be here. */ + ne_md5_process_bytes(data, sizeof data, &hash); + +#ifdef HAVE_GETTIMEOFDAY + { + struct timeval tv; + if (gettimeofday(&tv, NULL) == 0) + ne_md5_process_bytes(&tv, sizeof tv, &hash); + } +#else /* HAVE_GETTIMEOFDAY */ + { + time_t t = time(NULL); + ne_md5_process_bytes(&t, sizeof t, &hash); + } +#endif + { +#ifdef WIN32 + DWORD pid = GetCurrentThreadId(); +#else + pid_t pid = getpid(); +#endif + ne_md5_process_bytes(&pid, sizeof pid, &hash); + } + +#ifdef NEON_SSL + } +#endif + + ne_md5_finish_ctx(&hash, tmp); + ne_md5_to_ascii(tmp, ret); + + return ne_strdup(ret); +} + +static int get_credentials(auth_session *sess, char *pwbuf) +{ + return sess->creds(sess->userdata, sess->realm, sess->attempt++, + sess->username, pwbuf); +} + +/* Examine a Basic auth challenge. + * Returns 0 if an valid challenge, else non-zero. */ +static int basic_challenge(auth_session *sess, struct auth_challenge *parms) +{ + char *tmp, password[NE_ABUFSIZ]; + + /* Verify challenge... must have a realm */ + if (parms->realm == NULL) { + return -1; + } + + NE_DEBUG(NE_DBG_HTTPAUTH, "Got Basic challenge with realm [%s]\n", + parms->realm); + + clean_session(sess); + + sess->realm = ne_strdup(parms->realm); + + if (get_credentials(sess, password)) { + /* Failed to get credentials */ + return -1; + } + + sess->scheme = auth_scheme_basic; + + tmp = ne_concat(sess->username, ":", password, NULL); + sess->basic = ne_base64(tmp, strlen(tmp)); + ne_free(tmp); + + /* Paranoia. */ + memset(password, 0, sizeof password); + + return 0; +} + +/* Add Basic authentication credentials to a request */ +static char *request_basic(auth_session *sess) +{ + return ne_concat("Basic ", sess->basic, "\r\n", NULL); +} + +/* Examine a digest challenge: return 0 if it is a valid Digest challenge, + * else non-zero. */ +static int digest_challenge(auth_session *sess, struct auth_challenge *parms) +{ + struct ne_md5_ctx tmp; + unsigned char tmp_md5[16]; + char password[NE_ABUFSIZ]; + + /* Verify they've given us the right bits. */ + if (parms->alg == auth_alg_unknown || + ((parms->alg == auth_alg_md5_sess) && + !(parms->qop_auth || parms->qop_auth_int)) || + parms->realm==NULL || parms->nonce==NULL) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Invalid challenge."); + return -1; + } + + if (parms->stale) { + /* Just a stale response, don't need to get a new username/password */ + NE_DEBUG(NE_DBG_HTTPAUTH, "Stale digest challenge.\n"); + } else { + /* Forget the old session details */ + NE_DEBUG(NE_DBG_HTTPAUTH, "In digest challenge.\n"); + + clean_session(sess); + + sess->realm = ne_strdup(parms->realm); + + /* Not a stale response: really need user authentication */ + if (get_credentials(sess, password)) { + /* Failed to get credentials */ + return -1; + } + } + sess->alg = parms->alg; + sess->scheme = auth_scheme_digest; + sess->nonce = ne_strdup(parms->nonce); + sess->cnonce = get_cnonce(); + /* TODO: add domain handling. */ + if (parms->opaque != NULL) { + sess->opaque = ne_strdup(parms->opaque); /* don't strip the quotes */ + } + + if (parms->got_qop) { + /* What type of qop are we to apply to the message? */ + NE_DEBUG(NE_DBG_HTTPAUTH, "Got qop directive.\n"); + sess->nonce_count = 0; + if (parms->qop_auth_int) { + sess->qop = auth_qop_auth_int; + } else { + sess->qop = auth_qop_auth; + } + } else { + /* No qop at all/ */ + sess->qop = auth_qop_none; + } + + if (!parms->stale) { + /* Calculate H(A1). + * tmp = H(unq(username-value) ":" unq(realm-value) ":" passwd) + */ + NE_DEBUG(NE_DBG_HTTPAUTH, "Calculating H(A1).\n"); + ne_md5_init_ctx(&tmp); + ne_md5_process_bytes(sess->username, strlen(sess->username), &tmp); + ne_md5_process_bytes(":", 1, &tmp); + ne_md5_process_bytes(sess->realm, strlen(sess->realm), &tmp); + ne_md5_process_bytes(":", 1, &tmp); + ne_md5_process_bytes(password, strlen(password), &tmp); + memset(password, 0, sizeof password); /* done with that. */ + ne_md5_finish_ctx(&tmp, tmp_md5); + if (sess->alg == auth_alg_md5_sess) { + unsigned char a1_md5[16]; + struct ne_md5_ctx a1; + char tmp_md5_ascii[33]; + /* Now we calculate the SESSION H(A1) + * A1 = H(...above...) ":" unq(nonce-value) ":" unq(cnonce-value) + */ + ne_md5_to_ascii(tmp_md5, tmp_md5_ascii); + ne_md5_init_ctx(&a1); + ne_md5_process_bytes(tmp_md5_ascii, 32, &a1); + ne_md5_process_bytes(":", 1, &a1); + ne_md5_process_bytes(sess->nonce, strlen(sess->nonce), &a1); + ne_md5_process_bytes(":", 1, &a1); + ne_md5_process_bytes(sess->cnonce, strlen(sess->cnonce), &a1); + ne_md5_finish_ctx(&a1, a1_md5); + ne_md5_to_ascii(a1_md5, sess->h_a1); + NE_DEBUG(NE_DBG_HTTPAUTH, "Session H(A1) is [%s]\n", sess->h_a1); + } else { + ne_md5_to_ascii(tmp_md5, sess->h_a1); + NE_DEBUG(NE_DBG_HTTPAUTH, "H(A1) is [%s]\n", sess->h_a1); + } + + } + + NE_DEBUG(NE_DBG_HTTPAUTH, "I like this Digest challenge.\n"); + + return 0; +} + +/* callback for ne_pull_request_body. */ +static int digest_body(void *userdata, const char *buf, size_t count) +{ + struct ne_md5_ctx *ctx = userdata; + ne_md5_process_bytes(buf, count, ctx); + return 0; +} + +/* Return Digest authentication credentials header value for the given + * session. */ +static char *request_digest(auth_session *sess, struct auth_request *req) +{ + struct ne_md5_ctx a2, rdig; + unsigned char a2_md5[16], rdig_md5[16]; + char a2_md5_ascii[33], rdig_md5_ascii[33]; + char nc_value[9] = {0}; + const char *qop_value; /* qop-value */ + ne_buffer *ret; + + /* Increase the nonce-count */ + if (sess->qop != auth_qop_none) { + sess->nonce_count++; + ne_snprintf(nc_value, 9, "%08x", sess->nonce_count); + NE_DEBUG(NE_DBG_HTTPAUTH, "Nonce count is %d, nc is [%s]\n", + sess->nonce_count, nc_value); + } + + qop_value = sess->qop == auth_qop_auth_int ? "auth-int" : "auth"; + + /* Calculate H(A2). */ + ne_md5_init_ctx(&a2); + ne_md5_process_bytes(req->method, strlen(req->method), &a2); + ne_md5_process_bytes(":", 1, &a2); + ne_md5_process_bytes(req->uri, strlen(req->uri), &a2); + + if (sess->qop == auth_qop_auth_int) { + struct ne_md5_ctx body; + char tmp_md5_ascii[33]; + unsigned char tmp_md5[16]; + + ne_md5_init_ctx(&body); + + /* Calculate H(entity-body): pull in the request body from + * where-ever it is coming from, and calculate the digest. */ + + NE_DEBUG(NE_DBG_HTTPAUTH, "Digesting request body...\n"); + ne_pull_request_body(req->request, digest_body, &body); + NE_DEBUG(NE_DBG_HTTPAUTH, "Digesting request body done.\n"); + + ne_md5_finish_ctx(&body, tmp_md5); + ne_md5_to_ascii(tmp_md5, tmp_md5_ascii); + + NE_DEBUG(NE_DBG_HTTPAUTH, "H(entity-body) is [%s]\n", tmp_md5_ascii); + + /* Append to A2 */ + ne_md5_process_bytes(":", 1, &a2); + ne_md5_process_bytes(tmp_md5_ascii, 32, &a2); + } + ne_md5_finish_ctx(&a2, a2_md5); + ne_md5_to_ascii(a2_md5, a2_md5_ascii); + NE_DEBUG(NE_DBG_HTTPAUTH, "H(A2): %s\n", a2_md5_ascii); + + NE_DEBUG(NE_DBG_HTTPAUTH, "Calculating Request-Digest.\n"); + /* Now, calculation of the Request-Digest. + * The first section is the regardless of qop value + * H(A1) ":" unq(nonce-value) ":" */ + ne_md5_init_ctx(&rdig); + + /* Use the calculated H(A1) */ + ne_md5_process_bytes(sess->h_a1, 32, &rdig); + + ne_md5_process_bytes(":", 1, &rdig); + ne_md5_process_bytes(sess->nonce, strlen(sess->nonce), &rdig); + ne_md5_process_bytes(":", 1, &rdig); + if (sess->qop != auth_qop_none) { + /* Add on: + * nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" + */ + NE_DEBUG(NE_DBG_HTTPAUTH, "Have qop directive, digesting: [%s:%s:%s]\n", + nc_value, sess->cnonce, qop_value); + ne_md5_process_bytes(nc_value, 8, &rdig); + ne_md5_process_bytes(":", 1, &rdig); + ne_md5_process_bytes(sess->cnonce, strlen(sess->cnonce), &rdig); + ne_md5_process_bytes(":", 1, &rdig); + /* Store a copy of this structure (see note below) */ + sess->stored_rdig = rdig; + ne_md5_process_bytes(qop_value, strlen(qop_value), &rdig); + ne_md5_process_bytes(":", 1, &rdig); + } else { + /* Store a copy of this structure... we do this because the + * calculation of the rspauth= field in the Auth-Info header + * is the same as this digest, up to this point. */ + sess->stored_rdig = rdig; + } + /* And finally, H(A2) */ + ne_md5_process_bytes(a2_md5_ascii, 32, &rdig); + ne_md5_finish_ctx(&rdig, rdig_md5); + ne_md5_to_ascii(rdig_md5, rdig_md5_ascii); + + ret = ne_buffer_create(); + + ne_buffer_concat(ret, + "Digest username=\"", sess->username, "\", " + "realm=\"", sess->realm, "\", " + "nonce=\"", sess->nonce, "\", " + "uri=\"", req->uri, "\", " + "response=\"", rdig_md5_ascii, "\", " + "algorithm=\"", sess->alg == auth_alg_md5 ? "MD5" : "MD5-sess", "\"", + NULL); + + if (sess->opaque != NULL) { + ne_buffer_concat(ret, ", opaque=\"", sess->opaque, "\"", NULL); + } + + if (sess->qop != auth_qop_none) { + /* Add in cnonce and nc-value fields */ + ne_buffer_concat(ret, ", cnonce=\"", sess->cnonce, "\", " + "nc=", nc_value, ", " + "qop=\"", qop_value, "\"", NULL); + } + + ne_buffer_zappend(ret, "\r\n"); + + NE_DEBUG(NE_DBG_HTTPAUTH, "Digest request header is %s\n", ret->data); + + return ne_buffer_finish(ret); +} + +/* Parse line of comma-separated key-value pairs. If 'ischall' == 1, + * then also return a leading space-separated token, as *value == NULL. + * Otherwise, if return value is 0, *key and *value will be non-NULL. + * If return value is non-zero, parsing has ended. */ +static int tokenize(char **hdr, char **key, char **value, int ischall) +{ + char *pnt = *hdr; + enum { BEFORE_EQ, AFTER_EQ, AFTER_EQ_QUOTED } state = BEFORE_EQ; + + if (**hdr == '\0') + return 1; + + *key = NULL; + + do { + switch (state) { + case BEFORE_EQ: + if (*pnt == '=') { + if (*key == NULL) + return -1; + *pnt = '\0'; + *value = pnt + 1; + state = AFTER_EQ; + } else if (*pnt == ' ' && ischall && *key != NULL) { + *value = NULL; + *pnt = '\0'; + *hdr = pnt + 1; + return 0; + } else if (*key == NULL && strchr(" \r\n\t", *pnt) == NULL) { + *key = pnt; + } + break; + case AFTER_EQ: + if (*pnt == ',') { + *pnt = '\0'; + *hdr = pnt + 1; + return 0; + } else if (*pnt == '\"') { + state = AFTER_EQ_QUOTED; + } + break; + case AFTER_EQ_QUOTED: + if (*pnt == '\"') { + state = AFTER_EQ; + } + break; + } + } while (*++pnt != '\0'); + + *hdr = pnt; + + /* End of string: */ + return 0; +} + +/* Pass this the value of the 'Authentication-Info:' header field, if + * one is received. + * Returns: + * 0 if it gives a valid authentication for the server + * non-zero otherwise (don't believe the response in this case!). + */ +static int verify_response(struct auth_request *req, auth_session *sess, + const char *value) +{ + char *hdr, *pnt, *key, *val; + auth_qop qop = auth_qop_none; + char *nextnonce = NULL, /* for the nextnonce= value */ + *rspauth = NULL, /* for the rspauth= value */ + *cnonce = NULL, /* for the cnonce= value */ + *nc = NULL, /* for the nc= value */ + *qop_value = NULL; + int nonce_count, okay; + + if (!req->will_handle) { + /* Ignore it */ + return 0; + } + + if (sess->scheme != auth_scheme_digest) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Found Auth-Info header not in response " + " to Digest credentials - dodgy.\n"); + return -1; + } + + pnt = hdr = ne_strdup(value); + + NE_DEBUG(NE_DBG_HTTPAUTH, "Auth-Info header: %s\n", value); + + while (tokenize(&pnt, &key, &val, 0) == 0) { + val = ne_shave(val, "\""); + NE_DEBUG(NE_DBG_HTTPAUTH, "Pair: [%s] = [%s]\n", key, val); + if (strcasecmp(key, "qop") == 0) { + qop_value = val; + if (strcasecmp(val, "auth-int") == 0) { + qop = auth_qop_auth_int; + } else if (strcasecmp(val, "auth") == 0) { + qop = auth_qop_auth; + } else { + qop = auth_qop_none; + } + } else if (strcasecmp(key, "nextnonce") == 0) { + nextnonce = val; + } else if (strcasecmp(key, "rspauth") == 0) { + rspauth = val; + } else if (strcasecmp(key, "cnonce") == 0) { + cnonce = val; + } else if (strcasecmp(key, "nc") == 0) { + nc = val; + if (sscanf(val, "%x", &nonce_count) != 1) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Couldn't find nonce count.\n"); + } else { + NE_DEBUG(NE_DBG_HTTPAUTH, "Got nonce_count: %d\n", nonce_count); + } + } + } + + /* Presume the worst */ + okay = -1; + + if ((qop != auth_qop_none) && (qop_value != NULL)) { + if ((rspauth == NULL) || (cnonce == NULL) || (nc == NULL)) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Missing rspauth, cnonce or nc with qop.\n"); + } else { /* Have got rspauth, cnonce and nc */ + if (strcmp(cnonce, sess->cnonce) != 0) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Response cnonce doesn't match.\n"); + } else if (nonce_count != sess->nonce_count) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Response nonce count doesn't match.\n"); + } else { + /* Calculate and check the response-digest value. + * joe: IMO the spec is slightly ambiguous as to whether + * we use the qop which WE sent, or the qop which THEY + * sent... */ + struct ne_md5_ctx a2; + unsigned char a2_md5[16], rdig_md5[16]; + char a2_md5_ascii[33], rdig_md5_ascii[33]; + + NE_DEBUG(NE_DBG_HTTPAUTH, "Calculating response-digest.\n"); + + /* First off, H(A2) again. */ + ne_md5_init_ctx(&a2); + ne_md5_process_bytes(":", 1, &a2); + ne_md5_process_bytes(req->uri, strlen(req->uri), &a2); + if (qop == auth_qop_auth_int) { + unsigned char heb_md5[16]; + char heb_md5_ascii[33]; + /* Add on ":" H(entity-body) */ + ne_md5_finish_ctx(&req->response_body, heb_md5); + ne_md5_to_ascii(heb_md5, heb_md5_ascii); + ne_md5_process_bytes(":", 1, &a2); + ne_md5_process_bytes(heb_md5_ascii, 32, &a2); + NE_DEBUG(NE_DBG_HTTPAUTH, "Digested [:%s]\n", heb_md5_ascii); + } + ne_md5_finish_ctx(&a2, a2_md5); + ne_md5_to_ascii(a2_md5, a2_md5_ascii); + + /* We have the stored digest-so-far of + * H(A1) ":" unq(nonce-value) + * [ ":" nc-value ":" unq(cnonce-value) ] for qop + * in sess->stored_rdig, to save digesting them again. + * + */ + if (qop != auth_qop_none) { + /* Add in qop-value */ + NE_DEBUG(NE_DBG_HTTPAUTH, "Digesting qop-value [%s:].\n", + qop_value); + ne_md5_process_bytes(qop_value, strlen(qop_value), + &sess->stored_rdig); + ne_md5_process_bytes(":", 1, &sess->stored_rdig); + } + /* Digest ":" H(A2) */ + ne_md5_process_bytes(a2_md5_ascii, 32, &sess->stored_rdig); + /* All done */ + ne_md5_finish_ctx(&sess->stored_rdig, rdig_md5); + ne_md5_to_ascii(rdig_md5, rdig_md5_ascii); + + NE_DEBUG(NE_DBG_HTTPAUTH, "Calculated response-digest of: " + "[%s]\n", rdig_md5_ascii); + NE_DEBUG(NE_DBG_HTTPAUTH, "Given response-digest of: " + "[%s]\n", rspauth); + + /* And... do they match? */ + okay = (strcasecmp(rdig_md5_ascii, rspauth) == 0)?0:-1; + NE_DEBUG(NE_DBG_HTTPAUTH, "Matched: %s\n", okay?"nope":"YES!"); + } + } + } else { + NE_DEBUG(NE_DBG_HTTPAUTH, "No qop directive, auth okay.\n"); + okay = 0; + } + + /* Check for a nextnonce */ + if (nextnonce != NULL) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Found nextnonce of [%s].\n", nextnonce); + if (sess->nonce != NULL) + ne_free(sess->nonce); + sess->nonce = ne_strdup(nextnonce); + } + + ne_free(hdr); + + return okay; +} + +/* Passed the value of a "(Proxy,WWW)-Authenticate: " header field. + * Returns 0 if valid challenge was accepted; non-zero if no valid + * challenge was found. */ +static int auth_challenge(auth_session *sess, const char *value) +{ + char *pnt, *key, *val, *hdr; + struct auth_challenge *chall = NULL, *challenges = NULL; + int success; + + pnt = hdr = ne_strdup(value); + + NE_DEBUG(NE_DBG_HTTPAUTH, "Got new auth challenge: %s\n", value); + + /* The header value may be made up of one or more challenges. We + * split it down into attribute-value pairs, then search for + * schemes in the pair keys. */ + + while (!tokenize(&pnt, &key, &val, 1)) { + + if (val == NULL) { + /* We have a new challenge */ + NE_DEBUG(NE_DBG_HTTPAUTH, "New challenge for scheme [%s]\n", key); + chall = ne_calloc(sizeof *chall); + + chall->next = challenges; + challenges = chall; + /* Initialize the challenge parameters */ + /* Which auth-scheme is it (case-insensitive matching) */ + if (strcasecmp(key, "basic") == 0) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Basic scheme.\n"); + chall->scheme = auth_scheme_basic; + } else if (strcasecmp(key, "digest") == 0) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Digest scheme.\n"); + chall->scheme = auth_scheme_digest; + } else { + NE_DEBUG(NE_DBG_HTTPAUTH, "Unknown scheme.\n"); + ne_free(chall); + challenges = NULL; + break; + } + continue; + } else if (chall == NULL) { + /* If we haven't got an auth-scheme, and we're + * haven't yet found a challenge, skip this pair. + */ + continue; + } + + /* Strip quotes of value. */ + val = ne_shave(val, "\"'"); + + NE_DEBUG(NE_DBG_HTTPAUTH, "Got pair: [%s] = [%s]\n", key, val); + + if (strcasecmp(key, "realm") == 0) { + chall->realm = val; + } else if (strcasecmp(key, "nonce") == 0) { + chall->nonce = val; + } else if (strcasecmp(key, "opaque") == 0) { + chall->opaque = val; + } else if (strcasecmp(key, "stale") == 0) { + /* Truth value */ + chall->stale = (strcasecmp(val, "true") == 0); + } else if (strcasecmp(key, "algorithm") == 0) { + if (strcasecmp(val, "md5") == 0) { + chall->alg = auth_alg_md5; + } else if (strcasecmp(val, "md5-sess") == 0) { + chall->alg = auth_alg_md5_sess; + } else { + chall->alg = auth_alg_unknown; + } + } else if (strcasecmp(key, "qop") == 0) { + char **qops; + int qop; + qops = split_string(val, ',', NULL, " \r\n\t"); + chall->got_qop = 1; + for (qop = 0; qops[qop] != NULL; qop++) { + if (strcasecmp(qops[qop], "auth") == 0) { + chall->qop_auth = 1; + } else if (strcasecmp(qops[qop], "auth-int") == 0) { + chall->qop_auth_int = 1; + } + } + split_string_free(qops); + } + } + + NE_DEBUG(NE_DBG_HTTPAUTH, "Finished parsing parameters.\n"); + + /* Did we find any challenges */ + if (challenges == NULL) { + ne_free(hdr); + return -1; + } + + success = 0; + + NE_DEBUG(NE_DBG_HTTPAUTH, "Looking for Digest challenges.\n"); + + /* Try a digest challenge */ + for (chall = challenges; chall != NULL; chall = chall->next) { + if (chall->scheme == auth_scheme_digest) { + if (!digest_challenge(sess, chall)) { + success = 1; + break; + } + } + } + + if (!success) { + NE_DEBUG(NE_DBG_HTTPAUTH, + "No good Digest challenges, looking for Basic.\n"); + for (chall = challenges; chall != NULL; chall = chall->next) { + if (chall->scheme == auth_scheme_basic) { + if (!basic_challenge(sess, chall)) { + success = 1; + break; + } + } + } + + if (!success) { + /* No good challenges - record this in the session state */ + NE_DEBUG(NE_DBG_HTTPAUTH, "Did not understand any challenges.\n"); + } + + } + + /* Remember whether we can now supply the auth details */ + sess->can_handle = success; + + while (challenges != NULL) { + chall = challenges->next; + ne_free(challenges); + challenges = chall; + } + + ne_free(hdr); + + return !success; +} + +/* The body reader callback. */ +static void auth_body_reader(void *cookie, const char *block, size_t length) +{ + struct ne_md5_ctx *ctx = cookie; + NE_DEBUG(NE_DBG_HTTPAUTH, + "Digesting %" NE_FMT_SIZE_T " bytes of response body.\n", length); + ne_md5_process_bytes(block, length, ctx); +} + +static void ah_create(ne_request *req, void *session, const char *method, + const char *uri) +{ + auth_session *sess = session; + struct auth_request *areq = ne_calloc(sizeof *areq); + + NE_DEBUG(NE_DBG_HTTPAUTH, "ah_create, for %s\n", sess->spec->resp_hdr); + + areq->method = method; + areq->uri = uri; + areq->request = req; + + ne_add_response_header_handler(req, sess->spec->resp_hdr, + ne_duplicate_header, &areq->auth_hdr); + + + ne_add_response_header_handler(req, sess->spec->resp_info_hdr, + ne_duplicate_header, + &areq->auth_info_hdr); + + sess->attempt = 0; + + ne_set_request_private(req, sess->spec->id, areq); +} + + +static void ah_pre_send(ne_request *r, void *cookie, ne_buffer *request) +{ + auth_session *sess = cookie; + struct auth_request *req = ne_get_request_private(r, sess->spec->id); + + if (!sess->can_handle) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Not handling session.\n"); + } else { + char *value; + + NE_DEBUG(NE_DBG_HTTPAUTH, "Handling."); + req->will_handle = 1; + + if (sess->qop == auth_qop_auth_int) { + /* Digest mode / qop=auth-int: take an MD5 digest of the + * response body. */ + ne_md5_init_ctx(&req->response_body); + ne_add_response_body_reader(req->request, ne_accept_always, + auth_body_reader, &req->response_body); + } + + switch(sess->scheme) { + case auth_scheme_basic: + value = request_basic(sess); + break; + case auth_scheme_digest: + value = request_digest(sess, req); + break; + default: + value = NULL; + break; + } + + if (value != NULL) { + ne_buffer_concat(request, sess->spec->req_hdr, ": ", value, NULL); + ne_free(value); + } + + } + +} + +#define SAFELY(x) ((x) != NULL?(x):"null") + +static int ah_post_send(ne_request *req, void *cookie, const ne_status *status) +{ + auth_session *sess = cookie; + struct auth_request *areq = ne_get_request_private(req, sess->spec->id); + int ret = NE_OK; + + NE_DEBUG(NE_DBG_HTTPAUTH, + "ah_post_send (#%d), code is %d (want %d), %s is %s\n", + sess->attempt, status->code, sess->spec->status_code, + sess->spec->resp_hdr, SAFELY(areq->auth_hdr)); + if (areq->auth_info_hdr != NULL && + verify_response(areq, sess, areq->auth_info_hdr)) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Response authentication invalid.\n"); + ne_set_error(sess->sess, _(sess->spec->fail_msg)); + ret = NE_ERROR; + } else if (status->code == sess->spec->status_code && + areq->auth_hdr != NULL) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Got challenge with code %d.\n", status->code); + if (!auth_challenge(sess, areq->auth_hdr)) { + ret = NE_RETRY; + } else { + clean_session(sess); + ret = sess->spec->fail_code; + } + } + + NE_FREE(areq->auth_info_hdr); + NE_FREE(areq->auth_hdr); + + return ret; +} + +static void ah_destroy(ne_request *req, void *session) +{ + auth_session *sess = session; + struct auth_request *areq = ne_get_request_private(req, sess->spec->id); + ne_free(areq); +} + +static void free_auth(void *cookie) +{ + auth_session *sess = cookie; + + clean_session(sess); + ne_free(sess); +} + +static void auth_register(ne_session *sess, + const struct auth_class *ahc, const char *id, + ne_auth_creds creds, void *userdata) +{ + auth_session *ahs = ne_calloc(sizeof *ahs); + + ahs->creds = creds; + ahs->userdata = userdata; + ahs->sess = sess; + ahs->spec = ahc; + + /* Register hooks */ + ne_hook_create_request(sess, ah_create, ahs); + ne_hook_pre_send(sess, ah_pre_send, ahs); + ne_hook_post_send(sess, ah_post_send, ahs); + ne_hook_destroy_request(sess, ah_destroy, ahs); + ne_hook_destroy_session(sess, free_auth, ahs); + + ne_set_session_private(sess, id, ahs); +} + +void ne_set_server_auth(ne_session *sess, ne_auth_creds creds, void *userdata) +{ + auth_register(sess, &ah_server_class, HOOK_SERVER_ID, creds, userdata); +} + +void ne_set_proxy_auth(ne_session *sess, ne_auth_creds creds, void *userdata) +{ + auth_register(sess, &ah_proxy_class, HOOK_PROXY_ID, creds, userdata); +} + +void ne_forget_auth(ne_session *sess) +{ + auth_session *as; + if ((as = ne_get_session_private(sess, HOOK_SERVER_ID)) != NULL) + clean_session(as); + if ((as = ne_get_session_private(sess, HOOK_PROXY_ID)) != NULL) + clean_session(as); +} + diff --git a/src/ne_auth.h b/src/ne_auth.h new file mode 100644 index 0000000..fe1370c --- /dev/null +++ b/src/ne_auth.h @@ -0,0 +1,62 @@ +/* + HTTP authentication routines + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_AUTH_H +#define NE_AUTH_H + +#include "ne_session.h" /* for ne_session */ + +BEGIN_NEON_DECLS + +/* Size of username/password buffers passed to ne_auth_creds + * callback. */ +#define NE_ABUFSIZ (256) + +/* The callback used to request the username and password in the given + * realm. The username and password must be copied into the buffers + * which are both of size NE_ABUFSIZ. The 'attempt' parameter is zero + * on the first call to the callback, and increases by one each time + * an attempt to authenticate fails. + * + * The callback must return zero to indicate that authentication + * should be attempted with the username/password, or non-zero to + * cancel the request. (if non-zero, username and password are + * ignored.) */ +typedef int (*ne_auth_creds)(void *userdata, const char *realm, int attempt, + char *username, char *password); + +/* TOP TIP: if you just wish to try authenticating once (even if the + * user gets the username/password wrong), have your implementation of + * the callback return the 'attempt' value. */ + +/* Set callbacks to provide credentials for server and proxy + * authentication. userdata is passed as the first argument to the + * callback. The callback is called *indefinitely* until either it + * returns non-zero, or authentication is successful. */ +void ne_set_server_auth(ne_session *sess, ne_auth_creds creds, void *userdata); +void ne_set_proxy_auth(ne_session *sess, ne_auth_creds creds, void *userdata); + +/* Clear any stored authentication details for the given session. */ +void ne_forget_auth(ne_session *sess); + +END_NEON_DECLS + +#endif /* NE_AUTH_H */ diff --git a/src/ne_basic.c b/src/ne_basic.c new file mode 100644 index 0000000..d3b4eb3 --- /dev/null +++ b/src/ne_basic.c @@ -0,0 +1,554 @@ +/* + Basic HTTP and WebDAV methods + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <errno.h> + +#include "ne_request.h" +#include "ne_alloc.h" +#include "ne_utils.h" +#include "ne_basic.h" +#include "ne_207.h" + +#ifndef NEON_NODAV +#include "ne_uri.h" +#endif + +#ifdef USE_DAV_LOCKS +#include "ne_locks.h" +#endif +#include "ne_dates.h" +#include "ne_i18n.h" + +/* Header parser to retrieve Last-Modified date */ +static void get_lastmodified(void *userdata, const char *value) { + time_t *modtime = userdata; + *modtime = ne_httpdate_parse(value); +} + +int ne_getmodtime(ne_session *sess, const char *uri, time_t *modtime) +{ + ne_request *req = ne_request_create(sess, "HEAD", uri); + int ret; + + ne_add_response_header_handler(req, "Last-Modified", get_lastmodified, + modtime); + + *modtime = -1; + + ret = ne_request_dispatch(req); + + if (ret == NE_OK && ne_get_status(req)->klass != 2) { + *modtime = -1; + ret = NE_ERROR; + } + + ne_request_destroy(req); + + return ret; +} + +/* PUT's from fd to URI */ +int ne_put(ne_session *sess, const char *uri, int fd) +{ + ne_request *req = ne_request_create(sess, "PUT", uri); + int ret; + +#ifdef USE_DAV_LOCKS + ne_lock_using_resource(req, uri, 0); + ne_lock_using_parent(req, uri); +#endif + + ne_set_request_body_fd(req, fd); + + ret = ne_request_dispatch(req); + + if (ret == NE_OK && ne_get_status(req)->klass != 2) + ret = NE_ERROR; + + ne_request_destroy(req); + + return ret; +} + +/* Conditional HTTP put. + * PUTs from fd to uri, returning NE_FAILED if resource as URI has + * been modified more recently than 'since'. + */ +int +ne_put_if_unmodified(ne_session *sess, const char *uri, int fd, + time_t since) +{ + ne_request *req; + char *date; + int ret; + + if (ne_version_pre_http11(sess)) { + time_t modtime; + /* Server is not minimally HTTP/1.1 compliant. Do a HEAD to + * check the remote mod time. Of course, this makes the + * operation very non-atomic, but better than nothing. */ + ret = ne_getmodtime(sess, uri, &modtime); + if (ret != NE_OK) return ret; + if (modtime != since) + return NE_FAILED; + } + + req = ne_request_create(sess, "PUT", uri); + + date = ne_rfc1123_date(since); + /* Add in the conditionals */ + ne_add_request_header(req, "If-Unmodified-Since", date); + ne_free(date); + +#ifdef USE_DAV_LOCKS + ne_lock_using_resource(req, uri, 0); + /* FIXME: this will give 412 if the resource doesn't exist, since + * PUT may modify the parent... does that matter? */ +#endif + + ne_set_request_body_fd(req, fd); + + ret = ne_request_dispatch(req); + + if (ret == NE_OK) { + if (ne_get_status(req)->code == 412) { + ret = NE_FAILED; + } else if (ne_get_status(req)->klass != 2) { + ret = NE_ERROR; + } + } + + ne_request_destroy(req); + + return ret; +} + +struct get_context { + int error; + ne_session *session; + off_t total; + int fd; /* used in get_to_fd */ + ne_content_range *range; +}; + +static void get_to_fd(void *userdata, const char *block, size_t length) +{ + struct get_context *ctx = userdata; + ssize_t ret; + + if (!ctx->error) { + while (length > 0) { + ret = write(ctx->fd, block, length); + if (ret < 0) { + char err[200]; + ctx->error = 1; + ne_strerror(errno, err, sizeof err); + ne_set_error(ctx->session, _("Could not write to file: %s"), + err); + break; + } else { + length -= ret; + block += ret; + } + } + } +} + +static int accept_206(void *ud, ne_request *req, const ne_status *st) +{ + return (st->code == 206); +} + +static void clength_hdr_handler(void *ud, const char *value) +{ + struct get_context *ctx = ud; + off_t len = strtol(value, NULL, 10); + + if (ctx->range->end == -1) { + ctx->range->end = ctx->range->start + len - 1; + ctx->range->total = len; + } + else if (len != ctx->total) { + NE_DEBUG(NE_DBG_HTTP, + "Expecting %" NE_FMT_OFF_T " bytes, " + "got entity of length %" NE_FMT_OFF_T "\n", + ctx->total, len); + ne_set_error(ctx->session, _("Response not of expected length")); + ctx->error = 1; + } +} + +static void content_range_hdr_handler(void *ud, const char *value) +{ + struct get_context *ctx = ud; + + if (strncmp(value, "bytes ", 6) != 0) { + ne_set_error(ctx->session, ("Response range using unrecognized unit")); + ctx->error = 1; + } + + /* TODO: verify against requested range. */ +} + +int ne_get_range(ne_session *sess, const char *uri, + ne_content_range *range, int fd) +{ + ne_request *req = ne_request_create(sess, "GET", uri); + struct get_context ctx; + const ne_status *status; + int ret; + + if (range->end == -1) { + ctx.total = -1; + } + else { + ctx.total = (range->end - range->start) + 1; + } + + NE_DEBUG(NE_DBG_HTTP, "Range total: %" NE_FMT_OFF_T "\n", ctx.total); + + ctx.fd = fd; + ctx.error = 0; + ctx.range = range; + ctx.session = sess; + + ne_add_response_header_handler(req, "Content-Length", + clength_hdr_handler, &ctx); + ne_add_response_header_handler(req, "Content-Range", + content_range_hdr_handler, + &ctx); + + ne_add_response_body_reader(req, accept_206, get_to_fd, &ctx); + + if (range->end == -1) { + ne_print_request_header(req, "Range", "bytes=%" NE_FMT_OFF_T "-", + range->start); + } + else { + ne_print_request_header(req, "Range", + "bytes=%" NE_FMT_OFF_T "-%" NE_FMT_OFF_T, + range->start, range->end); + } + ne_add_request_header(req, "Accept-Ranges", "bytes"); + + ret = ne_request_dispatch(req); + + status = ne_get_status(req); + + if (ctx.error) { + ret = NE_ERROR; + } else if (status && status->code == 416) { + /* connection is terminated too early with Apache/1.3, so we check + * this even if ret == NE_ERROR... */ + ne_set_error(sess, _("Range is not satisfiable")); + ret = NE_ERROR; + } + else if (ret == NE_OK) { + if (status->klass == 2 && status->code != 206) { + ne_set_error(sess, _("Resource does not support ranged GETs.")); + ret = NE_ERROR; + } + else if (status->klass != 2) { + ret = NE_ERROR; + } + } + + ne_request_destroy(req); + + return ret; +} + + +/* Get to given fd */ +int ne_get(ne_session *sess, const char *uri, int fd) +{ + ne_request *req = ne_request_create(sess, "GET", uri); + struct get_context ctx; + int ret; + + ctx.total = -1; + ctx.fd = fd; + ctx.error = 0; + ctx.session = sess; + + /* Read the value of the Content-Length header into ctx.total */ + ne_add_response_header_handler(req, "Content-Length", + ne_handle_numeric_header, + &ctx.total); + + ne_add_response_body_reader(req, ne_accept_2xx, get_to_fd, &ctx); + + ret = ne_request_dispatch(req); + + if (ctx.error) { + ret = NE_ERROR; + } else if (ret == NE_OK && ne_get_status(req)->klass != 2) { + ret = NE_ERROR; + } + + ne_request_destroy(req); + + return ret; +} + + +/* Get to given fd */ +int ne_post(ne_session *sess, const char *uri, int fd, const char *buffer) +{ + ne_request *req = ne_request_create(sess, "POST", uri); + struct get_context ctx; + int ret; + + ctx.total = -1; + ctx.fd = fd; + ctx.error = 0; + ctx.session = sess; + + /* Read the value of the Content-Length header into ctx.total */ + ne_add_response_header_handler(req, "Content-Length", + ne_handle_numeric_header, &ctx.total); + + ne_add_response_body_reader(req, ne_accept_2xx, get_to_fd, &ctx); + + ne_set_request_body_buffer(req, buffer, strlen(buffer)); + + ret = ne_request_dispatch(req); + + if (ctx.error) { + ret = NE_ERROR; + } + else if (ret == NE_OK && ne_get_status(req)->klass != 2) { + ret = NE_ERROR; + } + + ne_request_destroy(req); + + return ret; +} + +void ne_content_type_handler(void *userdata, const char *value) +{ + ne_content_type *ct = userdata; + char *sep, *stype; + + ct->value = ne_strdup(value); + + stype = strchr(ct->value, '/'); + if (!stype) { + NE_FREE(ct->value); + return; + } + + *stype++ = '\0'; + ct->type = ct->value; + + sep = strchr(stype, ';'); + + if (sep) { + char *tok; + /* look for the charset parameter. TODO; probably better to + * hand-carve a parser than use ne_token/strstr/shave here. */ + *sep++ = '\0'; + do { + tok = ne_qtoken(&sep, ';', "\"\'"); + if (tok) { + tok = strstr(tok, "charset="); + if (tok) + ct->charset = ne_shave(tok+8, "\"\'"); + } else { + break; + } + } while (sep != NULL); + } + + /* set subtype, losing any trailing whitespace */ + ct->subtype = ne_shave(stype, " \t"); + + /* 2616#3.7.1: subtypes of text/ default to charset ISO-8859-1. */ + if (ct->charset == NULL && strcasecmp(ct->type, "text") == 0) + ct->charset = "ISO-8859-1"; +} + +static void dav_hdr_handler(void *userdata, const char *value) +{ + char *tokens = ne_strdup(value), *pnt = tokens; + ne_server_capabilities *caps = userdata; + + do { + char *tok = ne_qtoken(&pnt, ',', "\"'"); + if (!tok) break; + + tok = ne_shave(tok, " \r\t\n"); + + if (strcmp(tok, "1") == 0) { + caps->dav_class1 = 1; + } else if (strcmp(tok, "2") == 0) { + caps->dav_class2 = 1; + } else if (strcmp(tok, "<http://apache.org/dav/propset/fs/1>") == 0) { + caps->dav_executable = 1; + } + } while (pnt != NULL); + + ne_free(tokens); + +} + +int ne_options(ne_session *sess, const char *uri, + ne_server_capabilities *caps) +{ + ne_request *req = ne_request_create(sess, "OPTIONS", uri); + + int ret; + + ne_add_response_header_handler(req, "DAV", dav_hdr_handler, caps); + + ret = ne_request_dispatch(req); + + if (ret == NE_OK && ne_get_status(req)->klass != 2) { + ret = NE_ERROR; + } + + ne_request_destroy(req); + + return ret; +} + +#ifndef NEON_NODAV + +void ne_add_depth_header(ne_request *req, int depth) +{ + const char *value; + switch(depth) { + case NE_DEPTH_ZERO: + value = "0"; + break; + case NE_DEPTH_ONE: + value = "1"; + break; + default: + value = "infinity"; + break; + } + ne_add_request_header(req, "Depth", value); +} + +static int copy_or_move(ne_session *sess, int is_move, int overwrite, + int depth, const char *src, const char *dest) +{ + ne_request *req = ne_request_create( sess, is_move?"MOVE":"COPY", src ); + + /* 2518 S8.9.2 says only use Depth: infinity with MOVE. */ + if (!is_move) { + ne_add_depth_header(req, depth); + } + +#ifdef USE_DAV_LOCKS + if (is_move) { + ne_lock_using_resource(req, src, NE_DEPTH_INFINITE); + } + ne_lock_using_resource(req, dest, NE_DEPTH_INFINITE); + /* And we need to be able to add members to the destination's parent */ + ne_lock_using_parent(req, dest); +#endif + + ne_print_request_header(req, "Destination", "%s://%s%s", + ne_get_scheme(sess), + ne_get_server_hostport(sess), dest); + + ne_add_request_header(req, "Overwrite", overwrite?"T":"F"); + + return ne_simple_request(sess, req); +} + +int ne_copy(ne_session *sess, int overwrite, int depth, + const char *src, const char *dest) +{ + return copy_or_move(sess, 0, overwrite, depth, src, dest); +} + +int ne_move(ne_session *sess, int overwrite, + const char *src, const char *dest) +{ + return copy_or_move(sess, 1, overwrite, 0, src, dest); +} + +/* Deletes the specified resource. (and in only two lines of code!) */ +int ne_delete(ne_session *sess, const char *uri) +{ + ne_request *req = ne_request_create(sess, "DELETE", uri); + +#ifdef USE_DAV_LOCKS + ne_lock_using_resource(req, uri, NE_DEPTH_INFINITE); + ne_lock_using_parent(req, uri); +#endif + + /* joe: I asked on the DAV WG list about whether we might get a + * 207 error back from a DELETE... conclusion, you shouldn't if + * you don't send the Depth header, since we might be an HTTP/1.1 + * client and a 2xx response indicates success to them. But + * it's all a bit unclear. In any case, DAV servers today do + * return 207 to DELETE even if we don't send the Depth header. + * So we handle 207 errors appropriately. */ + + return ne_simple_request(sess, req); +} + +int ne_mkcol(ne_session *sess, const char *uri) +{ + ne_request *req; + char *real_uri; + int ret; + + if (ne_path_has_trailing_slash(uri)) { + real_uri = ne_strdup(uri); + } else { + real_uri = ne_concat(uri, "/", NULL); + } + + req = ne_request_create(sess, "MKCOL", real_uri); + +#ifdef USE_DAV_LOCKS + ne_lock_using_resource(req, real_uri, 0); + ne_lock_using_parent(req, real_uri); +#endif + + ret = ne_simple_request(sess, req); + + ne_free(real_uri); + + return ret; +} + +#endif /* NEON_NODAV */ diff --git a/src/ne_basic.h b/src/ne_basic.h new file mode 100644 index 0000000..1953fc2 --- /dev/null +++ b/src/ne_basic.h @@ -0,0 +1,140 @@ +/* + HTTP/1.1 methods + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_BASIC_H +#define NE_BASIC_H + +#include <sys/types.h> /* for time_t */ + +#include "ne_request.h" + +BEGIN_NEON_DECLS + +/* Perform a GET request on resource at 'path', writing the entity + * body which is returned to 'fd'. */ +int ne_get(ne_session *sess, const char *path, int fd); + +/* Perform a PUT request on resource at 'path', reading the entity + * body to submit from 'fd'. */ +int ne_put(ne_session *sess, const char *path, int fd); + +#ifndef NEON_NODAV + +#define NE_DEPTH_ZERO (0) +#define NE_DEPTH_ONE (1) +#define NE_DEPTH_INFINITE (2) + + +/* For ne_copy and ne_move: + * + * If a resource exists at "dest" and overwrite is zero, the operation + * will fail; if overwrite is non-zero, any existing resource will + * be over-written. + */ + +/* Copy resource from 'src to 'dest' paths. If 'src' identifies a + * collection resource, depth may be NE_DEPTH_ZERO to request that the + * collection and its properties are to be copied, or + * NE_DEPTH_INFINITE to request that the collection and its contents + * are to be copied. */ +int ne_copy(ne_session *sess, int overwrite, int depth, + const char *src, const char *dest); + +/* Move resource from 'src' to dest 'path'. */ +int ne_move(ne_session *sess, int overwrite, + const char *src, const char *dest); + +/* Delete resource at 'path'. */ +int ne_delete(ne_session *sess, const char *path); +/* Create a collection at 'path', which MUST have a trailing slash. */ +int ne_mkcol(ne_session *sess, const char *path); + +/* Adds a Depth: header to a request */ +void ne_add_depth_header(ne_request *req, int depth); + +#endif /* NEON_NODAV */ + +/* PUT resource at location as above, only if it has not been modified + * since given modtime. If server is HTTP/1.1, uses If-Unmodified-Since + * header; guaranteed failure if resource is modified after 'modtime'. + * If server is HTTP/1.0, HEAD's the resource first to fetch current + * modtime; race condition if resource is modified between HEAD and PUT. + */ +int ne_put_if_unmodified(ne_session *sess, + const char *path, int fd, time_t modtime); + +/* Retrieve modification time of resource at location 'path', place in + * *modtime. (uses HEAD) */ +int ne_getmodtime(ne_session *sess, const char *path, time_t *modtime); + +typedef struct { + const char *type, *subtype; + const char *charset; + char *value; +} ne_content_type; + +/* Sets (*ne_content_type)userdata appropriately. + * Caller must free ->value after use */ +void ne_content_type_handler(void *userdata, const char *value); + +/* Server capabilities: */ +typedef struct { + unsigned int dav_class1; /* True if Class 1 WebDAV server */ + unsigned int dav_class2; /* True if Class 2 WebDAV server */ + unsigned int dav_executable; /* True if supports the 'executable' + * property a. la. mod_dav */ +} ne_server_capabilities; + +/* Determines server capabilities (using OPTIONS). Pass 'path' as "*" + * to determine proxy server capabilities if using a proxy server. */ +int ne_options(ne_session *sess, const char *path, + ne_server_capabilities *caps); + +/* Defines a range of bytes, starting at 'start' and ending + * at 'end'. 'total' is the number of bytes in the range. + */ +typedef struct { + off_t start, end, total; +} ne_content_range; + +/* Partial GET. range->start must be >= 0. range->total is ignored. + * + * If range->end is -1, then the rest of the resource from start is + * requested, and range->total and end are filled in on success. + * + * Otherwise, bytes from range->start to range->end are requested. + * + * This will write to the CURRENT position of f; so if you want + * to do a resume download, use: + * struct ne_content_range range; + * range.start = resume_from; + * range.end = range.start + 999; (= 1000 bytes) + * fseek(myfile, resume_from, SEEK_SET); + * ne_get_range(sess, path, &range, myfile); */ +int ne_get_range(ne_session *sess, const char *path, + ne_content_range *range, int fd); + +/* Post using buffer as request-body: stream response into f */ +int ne_post(ne_session *sess, const char *path, int fd, const char *buffer); + +END_NEON_DECLS + +#endif /* NE_BASIC_H */ diff --git a/src/ne_compress.c b/src/ne_compress.c new file mode 100644 index 0000000..b84b46d --- /dev/null +++ b/src/ne_compress.c @@ -0,0 +1,414 @@ +/* + Handling of compressed HTTP responses + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "ne_request.h" +#include "ne_compress.h" +#include "ne_utils.h" + +#ifdef NEON_ZLIB + +#include <zlib.h> + +/* Adds support for the 'gzip' Content-Encoding in HTTP. gzip is a + * file format which wraps the DEFLATE compression algorithm. zlib + * implements DEFLATE: we have to unwrap the gzip format (specified in + * RFC1952) as it comes off the wire, and hand off chunks of data to + * be inflated. */ + +struct ne_decompress_s { + ne_session *session; /* associated session. */ + /* temporary buffer for holding inflated data. */ + char outbuf[BUFSIZ]; + z_stream zstr; + int zstrinit; /* non-zero if zstr has been initialized */ + char *enchdr; /* value of Content-Enconding response header. */ + + /* pass blocks back to this. */ + ne_block_reader reader; + void *userdata; + + /* buffer for gzip header bytes. */ + union { + unsigned char buf[10]; + struct header { + unsigned char id1; + unsigned char id2; + unsigned char cmeth; /* compression method. */ + unsigned char flags; + unsigned int mtime; /* breaks when sizeof int != 4 */ + unsigned char xflags; + unsigned char os; + } hdr; + } in; + size_t incount; /* bytes in in.buf */ + + unsigned char footer[8]; + size_t footcount; /* bytes in footer. */ + + /* CRC32 checksum: odd that zlib uses uLong for this since it is a + * 64-bit integer on LP64 platforms. */ + uLong checksum; + + /* current state. */ + enum state { + NE_Z_BEFORE_DATA, /* not received any response blocks yet. */ + NE_Z_PASSTHROUGH, /* response not compressed: passing through. */ + NE_Z_IN_HEADER, /* received a few bytes of response data, but not + * got past the gzip header yet. */ + NE_Z_POST_HEADER, /* waiting for the end of the NUL-terminated bits. */ + NE_Z_INFLATING, /* inflating response bytes. */ + NE_Z_AFTER_DATA, /* after data; reading CRC32 & ISIZE */ + NE_Z_FINISHED, /* stream is finished. */ + NE_Z_ERROR /* inflate bombed. */ + } state; +}; + +#define ID1 0x1f +#define ID2 0x8b + +#define HDR_DONE 0 +#define HDR_EXTENDED 1 +#define HDR_ERROR 2 + +/* parse_header parses the gzip header, sets the next state and returns + * HDR_DONE: all done, bytes following are raw DEFLATE data. + * HDR_EXTENDED: all done, expect a NUL-termianted string + * before the DEFLATE data + * HDR_ERROR: invalid header, give up. + */ +static int parse_header(ne_decompress *ctx) +{ + struct header *h = &ctx->in.hdr; + + NE_DEBUG(NE_DBG_HTTP, "ID1: %d ID2: %d, cmeth %d, flags %d\n", + h->id1, h->id2, h->cmeth, h->flags); + + if (h->id1 != ID1 || h->id2 != ID2 || h->cmeth != 8) { + ctx->state = NE_Z_ERROR; + ne_set_error(ctx->session, "Compressed stream invalid"); + return HDR_ERROR; + } + + NE_DEBUG(NE_DBG_HTTP, "mtime: %d, xflags: %d, os: %d\n", + h->mtime, h->xflags, h->os); + + /* TODO: we can only handle one NUL-terminated extensions field + * currently. Really, we should count the number of bits set, and + * skip as many fields as bits set (bailing if any reserved bits + * are set. */ + if (h->flags == 8) { + ctx->state = NE_Z_POST_HEADER; + return HDR_EXTENDED; + } else if (h->flags != 0) { + ctx->state = NE_Z_ERROR; + ne_set_error(ctx->session, "Compressed stream not supported"); + return HDR_ERROR; + } + + NE_DEBUG(NE_DBG_HTTP, "compress: Good stream.\n"); + + ctx->state = NE_Z_INFLATING; + return HDR_DONE; +} + +/* Convert 'buf' to unsigned int; 'buf' must be 'unsigned char *' */ +#define BUF2UINT(buf) ((buf[3]<<24) + (buf[2]<<16) + (buf[1]<<8) + buf[0]) + +/* Process extra 'len' bytes of 'buf' which were received after the + * DEFLATE data. */ +static void process_footer(ne_decompress *ctx, + const unsigned char *buf, size_t len) +{ + if (len + ctx->footcount > 8) { + ne_set_error(ctx->session, + "Too many bytes (%" NE_FMT_SIZE_T ") in gzip footer", + len); + ctx->state = NE_Z_ERROR; + } else { + memcpy(ctx->footer + ctx->footcount, buf, len); + ctx->footcount += len; + if (ctx->footcount == 8) { + uLong crc = BUF2UINT(ctx->footer) & 0xFFFFFFFF; + if (crc == ctx->checksum) { + ctx->state = NE_Z_FINISHED; + NE_DEBUG(NE_DBG_HTTP, "compress: Checksum match.\n"); + } else { + NE_DEBUG(NE_DBG_HTTP, "compress: Checksum mismatch: " + "given %lu vs computed %lu\n", crc, ctx->checksum); + ne_set_error(ctx->session, + "Checksum invalid for compressed stream"); + ctx->state = NE_Z_ERROR; + } + } + } +} + +/* Inflate response buffer 'buf' of length 'len'. */ +static void do_inflate(ne_decompress *ctx, const char *buf, size_t len) +{ + int ret; + + ctx->zstr.avail_in = len; + ctx->zstr.next_in = (unsigned char *)buf; + ctx->zstr.total_in = 0; + + do { + ctx->zstr.avail_out = sizeof ctx->outbuf; + ctx->zstr.next_out = (unsigned char *)ctx->outbuf; + ctx->zstr.total_out = 0; + + ret = inflate(&ctx->zstr, Z_NO_FLUSH); + + NE_DEBUG(NE_DBG_HTTP, + "compress: inflate %d, %ld bytes out, %d remaining\n", + ret, ctx->zstr.total_out, ctx->zstr.avail_in); +#if 0 + NE_DEBUG(NE_DBG_HTTPBODY, + "Inflated body block (%ld):\n[%.*s]\n", + ctx->zstr.total_out, (int)ctx->zstr.total_out, + ctx->outbuf); +#endif + /* update checksum. */ + ctx->checksum = crc32(ctx->checksum, (unsigned char *)ctx->outbuf, + ctx->zstr.total_out); + + /* pass on the inflated data */ + ctx->reader(ctx->userdata, ctx->outbuf, ctx->zstr.total_out); + + } while (ret == Z_OK && ctx->zstr.avail_in > 0); + + if (ret == Z_STREAM_END) { + NE_DEBUG(NE_DBG_HTTP, "compress: end of data stream, remaining %d.\n", + ctx->zstr.avail_in); + /* process the footer. */ + ctx->state = NE_Z_AFTER_DATA; + process_footer(ctx, ctx->zstr.next_in, ctx->zstr.avail_in); + } else if (ret != Z_OK) { + ctx->state = NE_Z_ERROR; + ne_set_error(ctx->session, "Error reading compressed data."); + NE_DEBUG(NE_DBG_HTTP, "compress: inflate failed (%d): %s\n", + ret, ctx->zstr.msg?ctx->zstr.msg:"(no message)"); + } +} + +/* Callback which is passed blocks of the response body. */ +static void gz_reader(void *ud, const char *buf, size_t len) +{ + ne_decompress *ctx = ud; + const char *zbuf; + size_t count; + + switch (ctx->state) { + case NE_Z_PASSTHROUGH: + /* move along there. */ + ctx->reader(ctx->userdata, buf, len); + return; + + case NE_Z_ERROR: + /* beyond hope. */ + break; + + case NE_Z_FINISHED: + /* Could argue for tolerance, and ignoring trailing content; + * but it could mean something more serious. */ + if (len > 0) { + ctx->state = NE_Z_ERROR; + ne_set_error(ctx->session, + "Unexpected content received after compressed stream"); + } + break; + + case NE_Z_BEFORE_DATA: + /* work out whether this is a compressed response or not. */ + if (ctx->enchdr && strcasecmp(ctx->enchdr, "gzip") == 0) { + NE_DEBUG(NE_DBG_HTTP, "compress: got gzipped stream.\n"); + + /* This is the magic bit: using plain inflateInit() + * doesn't work, and this does, but I have no idea why.. + * Google showed me the way. */ + if (inflateInit2(&ctx->zstr, -MAX_WBITS) != Z_OK) { + ne_set_error(ctx->session, ctx->zstr.msg); + ctx->state = NE_Z_ERROR; + return; + } + ctx->zstrinit = 1; + + } else { + /* No Content-Encoding header: pass it on. TODO: we could + * hack it and register the real callback now. But that + * would require add_resp_body_rdr to have defined + * ordering semantics etc etc */ + ctx->state = NE_Z_PASSTHROUGH; + ctx->reader(ctx->userdata, buf, len); + return; + } + + ctx->state = NE_Z_IN_HEADER; + /* FALLTHROUGH */ + + case NE_Z_IN_HEADER: + /* copy as many bytes as possible into the buffer. */ + if (len + ctx->incount > 10) { + count = 10 - ctx->incount; + } else { + count = len; + } + memcpy(ctx->in.buf + ctx->incount, buf, count); + ctx->incount += count; + /* have we got the full header yet? */ + if (ctx->incount != 10) { + return; + } + + buf += count; + len -= count; + + switch (parse_header(ctx)) { + case HDR_ERROR: + return; + case HDR_EXTENDED: + if (len == 0) + return; + break; + case HDR_DONE: + if (len > 0) { + do_inflate(ctx, buf, len); + } + return; + } + + /* FALLTHROUGH */ + + case NE_Z_POST_HEADER: + /* eating the filename string. */ + zbuf = memchr(buf, '\0', len); + if (zbuf == NULL) { + /* not found it yet. */ + return; + } + + NE_DEBUG(NE_DBG_HTTP, + "compresss: skipped %" NE_FMT_SIZE_T " header bytes.\n", + zbuf - buf); + /* found end of string. */ + len -= (1 + zbuf - buf); + buf = zbuf + 1; + ctx->state = NE_Z_INFLATING; + if (len == 0) { + /* end of string was at end of buffer. */ + return; + } + + /* FALLTHROUGH */ + + case NE_Z_INFLATING: + do_inflate(ctx, buf, len); + break; + + case NE_Z_AFTER_DATA: + process_footer(ctx, (unsigned char *)buf, len); + break; + } + +} + +int ne_decompress_destroy(ne_decompress *ctx) +{ + int ret; + + if (ctx->zstrinit) + /* inflateEnd only fails if it's passed NULL etc; ignore + * return value. */ + inflateEnd(&ctx->zstr); + + if (ctx->enchdr) + ne_free(ctx->enchdr); + + switch (ctx->state) { + case NE_Z_BEFORE_DATA: + case NE_Z_PASSTHROUGH: + case NE_Z_FINISHED: + ret = NE_OK; + break; + case NE_Z_ERROR: + /* session error already set. */ + ret = NE_ERROR; + break; + default: + /* truncated response. */ + ne_set_error(ctx->session, "Compressed response was truncated"); + ret = NE_ERROR; + break; + } + + ne_free(ctx); + return ret; +} + +ne_decompress *ne_decompress_reader(ne_request *req, ne_accept_response acpt, + ne_block_reader rdr, void *userdata) +{ + ne_decompress *ctx = ne_calloc(sizeof *ctx); + + ne_add_request_header(req, "Accept-Encoding", "gzip"); + + ne_add_response_header_handler(req, "Content-Encoding", + ne_duplicate_header, &ctx->enchdr); + + ne_add_response_body_reader(req, acpt, gz_reader, ctx); + + ctx->state = NE_Z_BEFORE_DATA; + ctx->reader = rdr; + ctx->userdata = userdata; + ctx->session = ne_get_session(req); + /* initialize the checksum. */ + ctx->checksum = crc32(0L, Z_NULL, 0); + + return ctx; +} + +#else /* !NEON_ZLIB */ + +/* Pass-through interface present to provide ABI compatibility. */ + +ne_decompress *ne_decompress_reader(ne_request *req, ne_accept_response acpt, + ne_block_reader rdr, void *userdata) +{ + ne_add_response_body_reader(req, acpt, rdr, userdata); + /* an arbitrary return value: don't confuse them by returning NULL. */ + return (ne_decompress *)req; +} + +int ne_decompress_destroy(ne_decompress *dc) +{ + return 0; +} + +#endif /* NEON_ZLIB */ diff --git a/src/ne_compress.h b/src/ne_compress.h new file mode 100644 index 0000000..9fa6b73 --- /dev/null +++ b/src/ne_compress.h @@ -0,0 +1,44 @@ +/* + Compressed HTPT request/response Handling + Copyright (C) 2001, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_COMPRESS_H +#define NE_COMPRESS_H + +#include "ne_request.h" + +typedef struct ne_decompress_s ne_decompress; + +/* Call this to register a 'reader' callback which will be passed + * blocks of response body (if the 'acceptance' callback is + * successful). If the response body is returned compressed by the + * server, this reader will receive UNCOMPRESSED blocks. + * + * Returns pointer to context object which must be passed to + * ne_decompress_destroy after the request has been dispatched, to + * free any internal state. */ +ne_decompress *ne_decompress_reader(ne_request *req, ne_accept_response accpt, + ne_block_reader rdr, void *userdata); + +/* Free's up internal state. Returns non-zero if errors occured during + * decompression: the session error string will have the error. */ +int ne_decompress_destroy(ne_decompress *ctx); + +#endif /* NE_COMPRESS_H */ diff --git a/src/ne_cookies.c b/src/ne_cookies.c new file mode 100644 index 0000000..f84f2fa --- /dev/null +++ b/src/ne_cookies.c @@ -0,0 +1,135 @@ +/* + Basic cookie support for neon + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* A nice demo of hooks, since it doesn't need any external + * interface to muck with the stored states. + */ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <time.h> + +#include "ne_cookies.h" + +#include "ne_request.h" +#include "ne_string.h" +#include "ne_alloc.h" + +#define COOKIE_ID "http://www.webdav.org/neon/hooks/cookies" + +static void set_cookie_hdl(void *userdata, const char *value) +{ + char **pairs = pair_string(value, ';', '=', "\"'", " \r\n\t"); + ne_cookie *cook; + ne_cookie_cache *cache = userdata; + int n; + + /* Check sanity */ + if (pairs[0] == NULL || pairs[1] == NULL) { + /* yagaboo */ + return; + } + + NE_DEBUG(NE_DBG_HTTP, "Got cookie name=%s\n", pairs[0]); + + /* Search for a cookie of this name */ + NE_DEBUG(NE_DBG_HTTP, "Searching for existing cookie...\n"); + for (cook = cache->cookies; cook != NULL; cook = cook->next) { + if (strcasecmp(cook->name, pairs[0]) == 0) { + break; + } + } + + if (cook == NULL) { + NE_DEBUG(NE_DBG_HTTP, "New cookie.\n"); + cook = ne_malloc(sizeof *cook); + memset(cook, 0, sizeof *cook); + cook->name = ne_strdup(pairs[0]); + cook->next = cache->cookies; + cache->cookies = cook; + } else { + /* Free the old value */ + ne_free(cook->value); + } + + cook->value = ne_strdup(pairs[1]); + + for (n = 2; pairs[n] != NULL; n+=2) { + NE_DEBUG(NE_DBG_HTTP, "Cookie parm %s=%s\n", pairs[n], pairs[n+1]); + if (strcasecmp(pairs[n], "path") == 0) { + cook->path = ne_strdup(pairs[n+1]); + } else if (strcasecmp(pairs[n], "max-age") == 0) { + int t = atoi(pairs[n+1]); + cook->expiry = time(NULL) + (time_t)t; + } else if (strcasecmp(pairs[n], "domain") == 0) { + cook->domain = ne_strdup(pairs[n+1]); + } + } + + NE_DEBUG(NE_DBG_HTTP, "End of parms.\n"); + + pair_string_free(pairs); +} + +static void create(ne_request *req, void *session, + const char *method, const char *uri) +{ + ne_cookie_cache *cache = session; + ne_add_response_header_handler(req, "Set-Cookie", set_cookie_hdl, cache); +} + +/* Just before sending the request: let them add headers if they want */ +static void pre_send(ne_request *req, void *session, ne_buffer *request) +{ + ne_cookie_cache *cache = session; + ne_cookie *cook; + + if (cache->cookies == NULL) { + return; + } + + ne_buffer_zappend(request, "Cookie: "); + + for (cook = cache->cookies; cook != NULL; cook=cook->next) { + ne_buffer_concat(request, cook->name, "=", cook->value, NULL); + if (cook->next != NULL) { + ne_buffer_zappend(request, "; "); + } + } + + ne_buffer_zappend(request, "\r\n"); +} + +/* Register cookie handling for given session using given cache. */ +void ne_cookie_register(ne_session *sess, ne_cookie_cache *cache) +{ + ne_hook_create_request(sess, create, cache); + ne_hook_pre_send(sess, pre_send, cache); +} + diff --git a/src/ne_cookies.h b/src/ne_cookies.h new file mode 100644 index 0000000..feb3a37 --- /dev/null +++ b/src/ne_cookies.h @@ -0,0 +1,51 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_COOKIES_H +#define NE_COOKIES_H + +#include "ne_request.h" +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +struct ne_cookie_s; +typedef struct ne_cookie_s ne_cookie; + +struct ne_cookie_s { + char *name, *value; + unsigned int secure:1; + unsigned int discard:1; + char *domain, *path; + time_t expiry; /* time at which the cookie expires */ + ne_cookie *next; +}; + +typedef struct { + ne_cookie *cookies; +} ne_cookie_cache; + +/* Register cookie handling for given session using given cache. */ +void ne_cookie_register(ne_session *sess, ne_cookie_cache *cache); + +END_NEON_DECLS + +#endif /* NE_COOKIES_H */ diff --git a/src/ne_dates.c b/src/ne_dates.c new file mode 100644 index 0000000..6896bd2 --- /dev/null +++ b/src/ne_dates.c @@ -0,0 +1,251 @@ +/* + Date manipulation routines + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <time.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#include <stdio.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "ne_alloc.h" +#include "ne_dates.h" +#include "ne_utils.h" + +/* Generic date manipulation routines. */ + +/* ISO8601: 2001-01-01T12:30:00Z */ +#define ISO8601_FORMAT_Z "%04d-%02d-%02dT%02d:%02d:%lfZ" +#define ISO8601_FORMAT_M "%04d-%02d-%02dT%02d:%02d:%lf-%02d:%02d" +#define ISO8601_FORMAT_P "%04d-%02d-%02dT%02d:%02d:%lf+%02d:%02d" + +/* RFC1123: Sun, 06 Nov 1994 08:49:37 GMT */ +#define RFC1123_FORMAT "%3s, %02d %3s %4d %02d:%02d:%02d GMT" +/* RFC850: Sunday, 06-Nov-94 08:49:37 GMT */ +#define RFC1036_FORMAT "%s %2d-%3s-%2d %2d:%2d:%2d GMT" +/* asctime: Wed Jun 30 21:49:08 1993 */ +#define ASCTIME_FORMAT "%3s %3s %2d %2d:%2d:%2d %4d" + +static const char *rfc1123_weekdays[7] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; +static const char *short_months[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +#if defined(HAVE_STRUCT_TM_TM_GMTOFF) +#define GMTOFF(t) ((t).tm_gmtoff) +#else +/* FIXME: work out the offset anyway. */ +#define GMTOFF(t) (0) +#endif + +/* Returns the time/date GMT, in RFC1123-type format: eg + * Sun, 06 Nov 1994 08:49:37 GMT. */ +char *ne_rfc1123_date(time_t anytime) { + struct tm *gmt; + char *ret; + gmt = gmtime(&anytime); + if (gmt == NULL) + return NULL; + ret = ne_malloc(29 + 1); /* dates are 29 chars long */ +/* it goes: Sun, 06 Nov 1994 08:49:37 GMT */ + ne_snprintf(ret, 30, RFC1123_FORMAT, + rfc1123_weekdays[gmt->tm_wday], gmt->tm_mday, + short_months[gmt->tm_mon], 1900 + gmt->tm_year, + gmt->tm_hour, gmt->tm_min, gmt->tm_sec); + + return ret; +} + +/* Takes an ISO-8601-formatted date string and returns the time_t. + * Returns (time_t)-1 if the parse fails. */ +time_t ne_iso8601_parse(const char *date) +{ + struct tm gmt = {0}; + int off_hour, off_min; + double sec; + off_t fix; + int n; + + /* it goes: ISO8601: 2001-01-01T12:30:00+03:30 */ + if ((n = sscanf(date, ISO8601_FORMAT_P, + &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, + &gmt.tm_hour, &gmt.tm_min, &sec, + &off_hour, &off_min)) == 8) { + gmt.tm_sec = (int)sec; + fix = - off_hour * 3600 - off_min * 60; + } + /* it goes: ISO8601: 2001-01-01T12:30:00-03:30 */ + else if ((n = sscanf(date, ISO8601_FORMAT_M, + &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, + &gmt.tm_hour, &gmt.tm_min, &sec, + &off_hour, &off_min)) == 8) { + gmt.tm_sec = (int)sec; + fix = off_hour * 3600 + off_min * 60; + } + /* it goes: ISO8601: 2001-01-01T12:30:00Z */ + else if ((n = sscanf(date, ISO8601_FORMAT_Z, + &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, + &gmt.tm_hour, &gmt.tm_min, &sec)) == 6) { + gmt.tm_sec = (int)sec; + fix = 0; + } + else { + return (time_t)-1; + } + + gmt.tm_year -= 1900; + gmt.tm_isdst = -1; + gmt.tm_mon--; + + return mktime(&gmt) + fix + GMTOFF(gmt); +} + +/* Takes an RFC1123-formatted date string and returns the time_t. + * Returns (time_t)-1 if the parse fails. */ +time_t ne_rfc1123_parse(const char *date) +{ + struct tm gmt = {0}; + static char wkday[4], mon[4]; + int n; +/* it goes: Sun, 06 Nov 1994 08:49:37 GMT */ + n = sscanf(date, RFC1123_FORMAT, + wkday, &gmt.tm_mday, mon, &gmt.tm_year, &gmt.tm_hour, + &gmt.tm_min, &gmt.tm_sec); + /* Is it portable to check n==7 here? */ + gmt.tm_year -= 1900; + for (n=0; n<12; n++) + if (strcmp(mon, short_months[n]) == 0) + break; + /* tm_mon comes out as 12 if the month is corrupt, which is desired, + * since the mktime will then fail */ + gmt.tm_mon = n; + gmt.tm_isdst = -1; + return mktime(&gmt) + GMTOFF(gmt); +} + +/* Takes a string containing a RFC1036-style date and returns the time_t */ +time_t ne_rfc1036_parse(const char *date) +{ + struct tm gmt = {0}; + int n; + static char wkday[10], mon[4]; + /* RFC850/1036 style dates: Sunday, 06-Nov-94 08:49:37 GMT */ + n = sscanf(date, RFC1036_FORMAT, + wkday, &gmt.tm_mday, mon, &gmt.tm_year, + &gmt.tm_hour, &gmt.tm_min, &gmt.tm_sec); + if (n != 7) { + return (time_t)-1; + } + + /* portable to check n here? */ + for (n=0; n<12; n++) + if (strcmp(mon, short_months[n]) == 0) + break; + /* tm_mon comes out as 12 if the month is corrupt, which is desired, + * since the mktime will then fail */ + + /* Defeat Y2K bug. */ + if (gmt.tm_year < 50) + gmt.tm_year += 100; + + gmt.tm_mon = n; + gmt.tm_isdst = -1; + return mktime(&gmt) + GMTOFF(gmt); +} + + +/* (as)ctime dates are like: + * Wed Jun 30 21:49:08 1993 + */ +time_t ne_asctime_parse(const char *date) +{ + struct tm gmt = {0}; + int n; + static char wkday[4], mon[4]; + n = sscanf(date, ASCTIME_FORMAT, + wkday, mon, &gmt.tm_mday, + &gmt.tm_hour, &gmt.tm_min, &gmt.tm_sec, + &gmt.tm_year); + /* portable to check n here? */ + for (n=0; n<12; n++) + if (strcmp(mon, short_months[n]) == 0) + break; + /* tm_mon comes out as 12 if the month is corrupt, which is desired, + * since the mktime will then fail */ + gmt.tm_mon = n; + gmt.tm_isdst = -1; + return mktime(&gmt) + GMTOFF(gmt); +} + +/* HTTP-date parser */ +time_t ne_httpdate_parse(const char *date) +{ + time_t tmp; + tmp = ne_rfc1123_parse(date); + if (tmp == -1) { + tmp = ne_rfc1036_parse(date); + if (tmp == -1) + tmp = ne_asctime_parse(date); + } + return tmp; +} + +#undef RFC1036_FORMAT +#undef ASCTIME_FORMAT +#undef RFC1123_FORMAT + +#ifdef RFC1123_TEST + +int main(int argc, char **argv) { + time_t now, in; + char *out; + if (argc > 1) { + printf("Got: %s\n", argv[1]); + in = ne_rfc1123_parse(argv[1]); + printf("Parsed: %d\n", in); + out = ne_rfc1123_date(in); + printf("Back again: %s\n", out); + } else { + now = time(NULL); + out = ne_rfc1123_date(now); + in = ne_rfc1123_parse(out); + printf("time(NULL) = %d\n", now); + printf("RFC1123 Time: [%s]\n", out); + printf("Parsed = %d\n", in); + out = ne_rfc1123_date(in); + printf("Back again: [%s]\n", out); + } + return 0; +} + +#endif + + diff --git a/src/ne_dates.h b/src/ne_dates.h new file mode 100644 index 0000000..ed628fd --- /dev/null +++ b/src/ne_dates.h @@ -0,0 +1,54 @@ +/* + Date manipulation routines + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef DATES_H +#define DATES_H + +#include <sys/types.h> + +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +/* Date manipulation routines as per RFC1123 and RFC1036 */ + +/* Return current date/time in RFC1123 format */ +char *ne_rfc1123_date(time_t anytime); + +/* Returns time from date/time using the subset of the ISO8601 format + * referenced in RFC2518 (e.g as used in the creationdate property in + * the DAV: namespace). */ +time_t ne_iso8601_parse(const char *date); + +/* Returns time from date/time in RFC1123 format */ +time_t ne_rfc1123_parse(const char *date); + +time_t ne_rfc1036_parse(const char *date); + +/* Parses asctime date string */ +time_t ne_asctime_parse(const char *date); + +/* Parse an HTTP-date as per RFC2616 */ +time_t ne_httpdate_parse(const char *date); + +END_NEON_DECLS + +#endif /* DATES_H */ diff --git a/src/ne_defs.h b/src/ne_defs.h new file mode 100644 index 0000000..f029edf --- /dev/null +++ b/src/ne_defs.h @@ -0,0 +1,10 @@ + +#undef BEGIN_NEON_DECLS +#undef END_NEON_DECLS +#ifdef __cplusplus +# define BEGIN_NEON_DECLS extern "C" { +# define END_NEON_DECLS } +#else +# define BEGIN_NEON_DECLS /* empty */ +# define END_NEON_DECLS /* empty */ +#endif diff --git a/src/ne_i18n.c b/src/ne_i18n.c new file mode 100644 index 0000000..afbb3f9 --- /dev/null +++ b/src/ne_i18n.c @@ -0,0 +1,32 @@ +/* + Internationalization of neon + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +void neon_i18n_init(void) +{ +#if defined(ENABLE_NLS) && defined(NEON_IS_LIBRARY) + /* if neon is build bundled in (i.e., not as a standalone + * library), then there is probably no point in this, since the + * messages won't be pointing in the right direction. + * there's not really any point in doing this if neon is + * a library since the messages aren't i18n'ized, but... */ + bindtextdomain("neon", LOCALEDIR); +#endif +} diff --git a/src/ne_i18n.h b/src/ne_i18n.h new file mode 100644 index 0000000..2b715a8 --- /dev/null +++ b/src/ne_i18n.h @@ -0,0 +1,37 @@ +/* + Internationalization of neon + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NEON_I18N_H +#define NEON_I18N_H + +#undef _ +#ifdef ENABLE_NLS +#include <libintl.h> +#define _(str) gettext(str) +#else +#define _(str) (str) +#endif /* ENABLE_NLS */ +#define N_(str) (str) + +/* Initialize i18n in neon */ +void neon_i18n_init(void); + +#endif /* NEON_I18N_H */ diff --git a/src/ne_locks.c b/src/ne_locks.c new file mode 100644 index 0000000..6df2f2c --- /dev/null +++ b/src/ne_locks.c @@ -0,0 +1,819 @@ +/* + WebDAV Class 2 locking operations + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include <ctype.h> /* for isdigit() */ + +#include "ne_alloc.h" + +#include "ne_request.h" +#include "ne_xml.h" +#include "ne_locks.h" +#include "ne_uri.h" +#include "ne_basic.h" +#include "ne_props.h" +#include "ne_207.h" +#include "ne_i18n.h" + +#define HOOK_ID "http://webdav.org/neon/hooks/webdav-locking" + +/* A list of lock objects. */ +struct lock_list { + struct ne_lock *lock; + struct lock_list *next, *prev; +}; + +struct ne_lock_store_s { + struct lock_list *locks; + struct lock_list *cursor; /* current position in 'locks' */ +}; + +struct lh_req_cookie { + const ne_lock_store *store; + struct lock_list *submit; +}; + +/* Context for PROPFIND/lockdiscovery callbacks */ +struct discover_ctx { + ne_session *session; + ne_lock_result results; + void *userdata; + ne_buffer *cdata; +}; + +/* Context for handling LOCK response */ +struct lock_ctx { + struct ne_lock active; /* activelock */ + char *token; /* the token we're after. */ + int found; + ne_buffer *cdata; +}; + +/* use the "application" state space. */ +#define ELM_LOCK_FIRST (NE_PROPS_STATE_TOP + 66) + +#define ELM_lockdiscovery (ELM_LOCK_FIRST) +#define ELM_activelock (ELM_LOCK_FIRST + 1) +#define ELM_lockscope (ELM_LOCK_FIRST + 2) +#define ELM_locktype (ELM_LOCK_FIRST + 3) +#define ELM_depth (ELM_LOCK_FIRST + 4) +#define ELM_owner (ELM_LOCK_FIRST + 5) +#define ELM_timeout (ELM_LOCK_FIRST + 6) +#define ELM_locktoken (ELM_LOCK_FIRST + 7) +#define ELM_lockinfo (ELM_LOCK_FIRST + 8) +#define ELM_write (ELM_LOCK_FIRST + 9) +#define ELM_exclusive (ELM_LOCK_FIRST + 10) +#define ELM_shared (ELM_LOCK_FIRST + 11) +#define ELM_href (ELM_LOCK_FIRST + 12) +#define ELM_prop (NE_207_STATE_PROP) + +static const struct ne_xml_idmap element_map[] = { +#define ELM(x) { "DAV:", #x, ELM_ ## x } + ELM(lockdiscovery), ELM(activelock), ELM(prop), ELM(lockscope), + ELM(locktype), ELM(depth), ELM(owner), ELM(timeout), ELM(locktoken), + ELM(lockinfo), ELM(lockscope), ELM(locktype), ELM(write), ELM(exclusive), + ELM(shared), ELM(href) + /* no "lockentry" */ +#undef ELM +}; + +static const ne_propname lock_props[] = { + { "DAV:", "lockdiscovery" }, + { NULL } +}; + +/* this simply registers the accessor for the function. */ +static void lk_create(ne_request *req, void *session, + const char *method, const char *uri) +{ + struct lh_req_cookie *lrc = ne_malloc(sizeof *lrc); + lrc->store = session; + lrc->submit = NULL; + ne_set_request_private(req, HOOK_ID, lrc); +} + +static void lk_pre_send(ne_request *r, void *userdata, ne_buffer *req) +{ + struct lh_req_cookie *lrc = ne_get_request_private(r, HOOK_ID); + + if (lrc->submit != NULL) { + struct lock_list *item; + + /* Add in the If header */ + ne_buffer_zappend(req, "If:"); + for (item = lrc->submit; item != NULL; item = item->next) { + char *uri = ne_uri_unparse(&item->lock->uri); + ne_buffer_concat(req, " <", uri, "> (<", + item->lock->token, ">)", NULL); + ne_free(uri); + } + ne_buffer_zappend(req, EOL); + } +} + +/* Insert 'lock' into lock list *list. */ +static void insert_lock(struct lock_list **list, struct ne_lock *lock) +{ + struct lock_list *item = ne_malloc(sizeof *item); + if (*list != NULL) { + (*list)->prev = item; + } + item->prev = NULL; + item->next = *list; + item->lock = lock; + *list = item; +} + +static void free_list(struct lock_list *list, int destroy) +{ + struct lock_list *next; + + while (list != NULL) { + next = list->next; + if (destroy) + ne_lock_destroy(list->lock); + ne_free(list); + list = next; + } +} + +static void lk_destroy(ne_request *req, void *userdata) +{ + struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID); + free_list(lrc->submit, 0); + ne_free(lrc); +} + +void ne_lockstore_destroy(ne_lock_store *store) +{ + free_list(store->locks, 1); + ne_free(store); +} + +ne_lock_store *ne_lockstore_create(void) +{ + return ne_calloc(sizeof(ne_lock_store)); +} + +#define CURSOR_RET(s) ((s)->cursor?(s)->cursor->lock:NULL) + +struct ne_lock *ne_lockstore_first(ne_lock_store *store) +{ + store->cursor = store->locks; + return CURSOR_RET(store); +} + +struct ne_lock *ne_lockstore_next(ne_lock_store *store) +{ + store->cursor = store->cursor->next; + return CURSOR_RET(store); +} + +void ne_lockstore_register(ne_lock_store *store, ne_session *sess) +{ + /* Register the hooks */ + ne_hook_create_request(sess, lk_create, store); + ne_hook_pre_send(sess, lk_pre_send, store); + ne_hook_destroy_request(sess, lk_destroy, store); +} + +/* Submit the given lock for the given URI */ +static void submit_lock(struct lh_req_cookie *lrc, struct ne_lock *lock) +{ + struct lock_list *item; + + /* Check for dups */ + for (item = lrc->submit; item != NULL; item = item->next) { + if (strcasecmp(item->lock->token, lock->token) == 0) + return; + } + + insert_lock(&lrc->submit, lock); +} + +struct ne_lock *ne_lockstore_findbyuri(ne_lock_store *store, + const ne_uri *uri) +{ + struct lock_list *cur; + + for (cur = store->locks; cur != NULL; cur = cur->next) { + if (ne_uri_cmp(&cur->lock->uri, uri) == 0) { + return cur->lock; + } + } + + return NULL; +} + +void ne_lock_using_parent(ne_request *req, const char *path) +{ + struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID); + ne_uri u; + struct lock_list *item; + char *parent; + + if (lrc == NULL) + return; + + parent = ne_path_parent(path); + if (parent == NULL) + return; + + u.authinfo = NULL; + ne_fill_server_uri(ne_get_session(req), &u); + + for (item = lrc->store->locks; item != NULL; item = item->next) { + + /* Only care about locks which are on this server. */ + u.path = item->lock->uri.path; + if (ne_uri_cmp(&u, &item->lock->uri)) + continue; + + /* This lock is needed if it is an infinite depth lock which + * covers the parent, or a lock on the parent itself. */ + if ((item->lock->depth == NE_DEPTH_INFINITE && + ne_path_childof(item->lock->uri.path, parent)) || + ne_path_compare(item->lock->uri.path, parent) == 0) { + NE_DEBUG(NE_DBG_LOCKS, "Locked parent, %s on %s\n", + item->lock->token, item->lock->uri.path); + submit_lock(lrc, item->lock); + } + } + + u.path = parent; /* handy: makes u.path valid and ne_free(parent). */ + ne_uri_free(&u); +} + +void ne_lock_using_resource(ne_request *req, const char *uri, int depth) +{ + struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID); + struct lock_list *item; + int match; + + if (lrc == NULL) + return; + + /* Iterate over the list of stored locks to see if any of them + * apply to this resource */ + for (item = lrc->store->locks; item != NULL; item = item->next) { + + match = 0; + + if (depth == NE_DEPTH_INFINITE && + ne_path_childof(uri, item->lock->uri.path)) { + /* Case 1: this is a depth-infinity request which will + * modify a lock somewhere inside the collection. */ + NE_DEBUG(NE_DBG_LOCKS, "Has child: %s\n", item->lock->token); + match = 1; + } + else if (ne_path_compare(uri, item->lock->uri.path) == 0) { + /* Case 2: this request is directly on a locked resource */ + NE_DEBUG(NE_DBG_LOCKS, "Has direct lock: %s\n", item->lock->token); + match = 1; + } + else if (item->lock->depth == NE_DEPTH_INFINITE && + ne_path_childof(item->lock->uri.path, uri)) { + /* Case 3: there is a higher-up infinite-depth lock which + * covers the resource that this request will modify. */ + NE_DEBUG(NE_DBG_LOCKS, "Is child of: %s\n", item->lock->token); + match = 1; + } + + if (match) { + submit_lock(lrc, item->lock); + } + } + +} + +void ne_lockstore_add(ne_lock_store *store, struct ne_lock *lock) +{ + insert_lock(&store->locks, lock); +} + +void ne_lockstore_remove(ne_lock_store *store, struct ne_lock *lock) +{ + struct lock_list *item; + + /* Find the lock */ + for (item = store->locks; item != NULL; item = item->next) + if (item->lock == lock) + break; + + if (item->prev != NULL) { + item->prev->next = item->next; + } else { + store->locks = item->next; + } + if (item->next != NULL) { + item->next->prev = item->prev; + } + ne_free(item); +} + +struct ne_lock *ne_lock_copy(const struct ne_lock *lock) +{ + struct ne_lock *ret = ne_calloc(sizeof *ret); + + ret->uri.path = ne_strdup(lock->uri.path); + ret->uri.host = ne_strdup(lock->uri.host); + ret->uri.scheme = ne_strdup(lock->uri.scheme); + ret->uri.port = lock->uri.port; + ret->token = ne_strdup(lock->token); + ret->depth = lock->depth; + ret->type = lock->type; + ret->scope = lock->scope; + if (lock->owner) ret->owner = ne_strdup(lock->owner); + ret->timeout = lock->timeout; + + return ret; +} + +struct ne_lock *ne_lock_create(void) +{ + struct ne_lock *lock = ne_calloc(sizeof *lock); + lock->depth = NE_DEPTH_ZERO; + lock->type = ne_locktype_write; + lock->scope = ne_lockscope_exclusive; + lock->timeout = NE_TIMEOUT_INVALID; + return lock; +} + +void ne_lock_free(struct ne_lock *lock) +{ + ne_uri_free(&lock->uri); + NE_FREE(lock->owner); + NE_FREE(lock->token); +} + +void ne_lock_destroy(struct ne_lock *lock) +{ + ne_lock_free(lock); + ne_free(lock); +} + +int ne_unlock(ne_session *sess, const struct ne_lock *lock) +{ + ne_request *req = ne_request_create(sess, "UNLOCK", lock->uri.path); + int ret; + + ne_print_request_header(req, "Lock-Token", "<%s>", lock->token); + + /* UNLOCK of a lock-null resource removes the resource from the + * parent collection; so an UNLOCK may modify the parent + * collection. (somewhat counter-intuitive, and not easily derived + * from 2518.) */ + ne_lock_using_parent(req, lock->uri.path); + + ret = ne_request_dispatch(req); + + if (ret == NE_OK && ne_get_status(req)->klass == 2) { + ret = NE_OK; + } else { + ret = NE_ERROR; + } + + ne_request_destroy(req); + + return ret; +} + +static int parse_depth(const char *depth) +{ + if (strcasecmp(depth, "infinity") == 0) { + return NE_DEPTH_INFINITE; + } else if (isdigit(depth[0])) { + return atoi(depth); + } else { + return -1; + } +} + +static long parse_timeout(const char *timeout) +{ + if (strcasecmp(timeout, "infinite") == 0) { + return NE_TIMEOUT_INFINITE; + } else if (strncasecmp(timeout, "Second-", 7) == 0) { + long to = strtol(timeout+7, NULL, 10); + if (to == LONG_MIN || to == LONG_MAX) + return NE_TIMEOUT_INVALID; + return to; + } else { + return NE_TIMEOUT_INVALID; + } +} + +static void discover_results(void *userdata, const char *href, + const ne_prop_result_set *set) +{ + struct discover_ctx *ctx = userdata; + struct ne_lock *lock = ne_propset_private(set); + const ne_status *status = ne_propset_status(set, &lock_props[0]); + + /* Require at least that the lock has a token. */ + if (lock->token) { + if (status && status->klass != 2) { + ctx->results(ctx->userdata, NULL, lock->uri.path, status); + } else { + ctx->results(ctx->userdata, lock, lock->uri.path, NULL); + } + } + else if (status) { + ctx->results(ctx->userdata, NULL, href, status); + } + + ne_lock_destroy(lock); + + NE_DEBUG(NE_DBG_LOCKS, "End of response for %s\n", href); +} + +static int +end_element_common(struct ne_lock *l, int state, const char *cdata) +{ + switch (state) { + case ELM_write: + l->type = ne_locktype_write; + break; + case ELM_exclusive: + l->scope = ne_lockscope_exclusive; + break; + case ELM_shared: + l->scope = ne_lockscope_shared; + break; + case ELM_depth: + NE_DEBUG(NE_DBG_LOCKS, "Got depth: %s\n", cdata); + l->depth = parse_depth(cdata); + if (l->depth == -1) { + return -1; + } + break; + case ELM_timeout: + NE_DEBUG(NE_DBG_LOCKS, "Got timeout: %s\n", cdata); + l->timeout = parse_timeout(cdata); + if (l->timeout == NE_TIMEOUT_INVALID) { + return -1; + } + break; + case ELM_owner: + l->owner = strdup(cdata); + break; + case ELM_href: + l->token = strdup(cdata); + break; + } + return 0; +} + +/* End-element handler for lock discovery PROPFIND response */ +static int end_element_ldisc(void *userdata, int state, + const char *nspace, const char *name) +{ + struct ne_lock *lock = ne_propfind_current_private(userdata); + struct discover_ctx *ctx = userdata; + + return end_element_common(lock, state, ctx->cdata->data); +} + +static inline int can_accept(int parent, int id) +{ + return (parent == NE_XML_STATEROOT && id == ELM_prop) || + (parent == ELM_prop && id == ELM_lockdiscovery) || + (parent == ELM_lockdiscovery && id == ELM_activelock) || + (parent == ELM_activelock && + (id == ELM_lockscope || id == ELM_locktype || + id == ELM_depth || id == ELM_owner || + id == ELM_timeout || id == ELM_locktoken)) || + (parent == ELM_lockscope && + (id == ELM_exclusive || id == ELM_shared)) || + (parent == ELM_locktype && id == ELM_write) || + (parent == ELM_locktoken && id == ELM_href); +} + +static int ld_startelm(void *userdata, int parent, + const char *nspace, const char *name, + const char **atts) +{ + struct discover_ctx *ctx = userdata; + int id = ne_xml_mapid(element_map, NE_XML_MAPLEN(element_map), + nspace, name); + + ne_buffer_clear(ctx->cdata); + + if (can_accept(parent, id)) + return id; + else + return NE_XML_DECLINE; +} + +#define MAX_CDATA (256) + +static int lk_cdata(void *userdata, int state, + const char *cdata, size_t len) +{ + struct lock_ctx *ctx = userdata; + + if (ctx->cdata->used + len < MAX_CDATA) + ne_buffer_append(ctx->cdata, cdata, len); + + return 0; +} + +static int ld_cdata(void *userdata, int state, + const char *cdata, size_t len) +{ + struct discover_ctx *ctx = userdata; + + if (ctx->cdata->used + len < MAX_CDATA) + ne_buffer_append(ctx->cdata, cdata, len); + + return 0; +} + +static int lk_startelm(void *userdata, int parent, + const char *nspace, const char *name, + const char **atts) +{ + struct lock_ctx *ctx = userdata; + int id; + + id = ne_xml_mapid(element_map, NE_XML_MAPLEN(element_map), nspace, name); + + NE_DEBUG(NE_DBG_LOCKS, "lk_startelm: %s => %d\n", name, id); + + /* Lock-Token header is a MUST requirement: if the token + * is not known, bail out. */ + if (id == 0 || ctx->token == NULL) + return NE_XML_DECLINE; + + /* TODO: only accept 'prop' as root for LOCK response */ + if (!can_accept(parent, id)) + return NE_XML_DECLINE; + + if (id == ELM_activelock && !ctx->found) { + /* a new activelock */ + ne_lock_free(&ctx->active); + memset(&ctx->active, 0, sizeof ctx->active); + } + + ne_buffer_clear(ctx->cdata); + + return id; +} + +/* End-element handler for LOCK response */ +static int lk_endelm(void *userdata, int state, + const char *nspace, const char *name) +{ + struct lock_ctx *ctx = userdata; + + if (ctx->found) + return 0; + + if (end_element_common(&ctx->active, state, ctx->cdata->data)) + return -1; + + if (state == ELM_activelock) { + if (ctx->active.token && strcmp(ctx->active.token, ctx->token) == 0) { + ctx->found = 1; + } + } + + return 0; +} + +static void *ld_create(void *userdata, const char *href) +{ + struct discover_ctx *ctx = userdata; + struct ne_lock *lk = ne_lock_create(); + + if (ne_uri_parse(href, &lk->uri) != 0) { + ne_lock_destroy(lk); + return NULL; + } + + if (!lk->uri.host) + ne_fill_server_uri(ctx->session, &lk->uri); + + return lk; +} + +/* Discover all locks on URI */ +int ne_lock_discover(ne_session *sess, const char *uri, + ne_lock_result callback, void *userdata) +{ + ne_propfind_handler *handler; + struct discover_ctx ctx = {0}; + int ret; + + ctx.results = callback; + ctx.userdata = userdata; + ctx.session = sess; + ctx.cdata = ne_buffer_create(); + + handler = ne_propfind_create(sess, uri, NE_DEPTH_ZERO); + + ne_propfind_set_private(handler, ld_create, &ctx); + + ne_xml_push_handler(ne_propfind_get_parser(handler), + ld_startelm, ld_cdata, end_element_ldisc, handler); + + ret = ne_propfind_named(handler, lock_props, discover_results, &ctx); + + ne_buffer_destroy(ctx.cdata); + ne_propfind_destroy(handler); + + return ret; +} + +static void add_timeout_header(ne_request *req, long timeout) +{ + if (timeout == NE_TIMEOUT_INFINITE) { + ne_add_request_header(req, "Timeout", "Infinite"); + } + else if (timeout != NE_TIMEOUT_INVALID && timeout > 0) { + ne_print_request_header(req, "Timeout", "Second-%ld", timeout); + } + /* just ignore it if timeout == 0 or invalid. */ +} + +/* Parse a Lock-Token response header. */ +static void get_ltoken_hdr(void *ud, const char *value) +{ + struct lock_ctx *ctx = ud; + + if (value[0] == '<') value++; + ctx->token = ne_strdup(value); + ne_shave(ctx->token, ">"); +} + +int ne_lock(ne_session *sess, struct ne_lock *lock) +{ + ne_request *req = ne_request_create(sess, "LOCK", lock->uri.path); + ne_buffer *body = ne_buffer_create(); + ne_xml_parser *parser = ne_xml_create(); + int ret, parse_failed; + struct lock_ctx ctx; + + memset(&ctx, 0, sizeof ctx); + ctx.cdata = ne_buffer_create(); + + ne_xml_push_handler(parser, lk_startelm, lk_cdata, lk_endelm, &ctx); + + /* Create the body */ + ne_buffer_concat(body, "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL + "<lockinfo xmlns='DAV:'>" EOL " <lockscope>", + lock->scope==ne_lockscope_exclusive? + "<exclusive/>":"<shared/>", + "</lockscope>" EOL + "<locktype><write/></locktype>", NULL); + + if (lock->owner) { + ne_buffer_concat(body, "<owner>", lock->owner, "</owner>" EOL, NULL); + } + ne_buffer_zappend(body, "</lockinfo>" EOL); + + ne_set_request_body_buffer(req, body->data, ne_buffer_size(body)); + ne_add_response_body_reader(req, ne_accept_2xx, + ne_xml_parse_v, parser); + ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE); + ne_add_depth_header(req, lock->depth); + add_timeout_header(req, lock->timeout); + + ne_add_response_header_handler(req, "Lock-Token", get_ltoken_hdr, &ctx); + + /* TODO: + * By 2518, we need this only if we are creating a lock-null resource. + * Since we don't KNOW whether the lock we're given is a lock-null + * or not, we cover our bases. + */ + ne_lock_using_parent(req, lock->uri.path); + /* This one is clearer from 2518 sec 8.10.4. */ + ne_lock_using_resource(req, lock->uri.path, lock->depth); + + ret = ne_request_dispatch(req); + + ne_buffer_destroy(body); + ne_buffer_destroy(ctx.cdata); + parse_failed = !ne_xml_valid(parser); + + if (ret == NE_OK && ne_get_status(req)->klass == 2) { + if (ctx.token == NULL) { + ret = NE_ERROR; + ne_set_error(sess, _("No Lock-Token header given")); + } + else if (parse_failed) { + ret = NE_ERROR; + ne_set_error(sess, ne_xml_get_error(parser)); + } + else if (ne_get_status(req)->code == 207) { + ret = NE_ERROR; + /* TODO: set the error string appropriately */ + } + else if (ctx.found) { + /* it worked: copy over real lock details if given. */ + NE_FREE(lock->token); + lock->token = ctx.token; + ctx.token = NULL; + if (ctx.active.timeout != NE_TIMEOUT_INVALID) + lock->timeout = ctx.active.timeout; + lock->scope = ctx.active.scope; + lock->type = ctx.active.type; + if (ctx.active.depth >= 0) + lock->depth = ctx.active.depth; + if (ctx.active.owner) { + NE_FREE(lock->owner); + lock->owner = ctx.active.owner; + ctx.active.owner = NULL; + } + } else { + ret = NE_ERROR; + ne_set_error(sess, _("Response missing activelock for %s"), + ctx.token); + } + } else { + ret = NE_ERROR; + } + + if (ctx.token) + ne_free(ctx.token); + ne_lock_free(&ctx.active); + + ne_request_destroy(req); + ne_xml_destroy(parser); + + return ret; +} + +int ne_lock_refresh(ne_session *sess, struct ne_lock *lock) +{ + ne_request *req = ne_request_create(sess, "LOCK", lock->uri.path); + ne_xml_parser *parser = ne_xml_create(); + int ret, parse_failed; + + /* Handle the response and update *lock appropriately. */ + ne_xml_push_handler(parser, lk_startelm, NULL, lk_endelm, lock); + + ne_add_response_body_reader(req, ne_accept_2xx, + ne_xml_parse_v, parser); + + /* Probably don't need to submit any other lock-tokens for this + * resource? If it's an exclusive lock, then there can be no other + * locks covering the resource. If it's a shared lock, then this + * lock (which is being refreshed) is sufficient to modify the + * resource state? */ + ne_print_request_header(req, "If", "(<%s>)", lock->token); + add_timeout_header(req, lock->timeout); + + ret = ne_request_dispatch(req); + + parse_failed = !ne_xml_valid(parser); + + if (ret == NE_OK && ne_get_status(req)->klass == 2) { + if (parse_failed) { + ret = NE_ERROR; + ne_set_error(sess, ne_xml_get_error(parser)); + } + else if (ne_get_status(req)->code == 207) { + ret = NE_ERROR; + /* TODO: set the error string appropriately */ + } + } else { + ret = NE_ERROR; + } + + ne_request_destroy(req); + ne_xml_destroy(parser); + + return ret; +} diff --git a/src/ne_locks.h b/src/ne_locks.h new file mode 100644 index 0000000..702e1fb --- /dev/null +++ b/src/ne_locks.h @@ -0,0 +1,166 @@ +/* + WebDAV Class 2 locking operations + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_LOCKS_H +#define NE_LOCKS_H + +#include "ne_request.h" /* for ne_session + ne_request */ +#include "ne_uri.h" /* for ne_uri */ + +BEGIN_NEON_DECLS + +/* The scope of a lock */ +enum ne_lock_scope { + ne_lockscope_exclusive, + ne_lockscope_shared +}; + +/* Lock type. Only write locks are defined in RFC2518. */ +enum ne_lock_type { + ne_locktype_write +}; + +/* A lock object. */ +struct ne_lock { + ne_uri uri; + int depth; /* the depth of the lock (NE_DEPTH_*). */ + enum ne_lock_type type; + enum ne_lock_scope scope; + char *token; /* the lock token: uniquely identifies this lock. */ + char *owner; /* string describing the owner of the lock. */ + long timeout; /* timeout in seconds. (or NE_TIMEOUT_*) */ +}; +/* NB: struct ne_lock Would be typedef'ed to ne_lock except lock is + * a verb and a noun, so we already have ne_lock the function. Damn + * the English language. */ + +#define NE_TIMEOUT_INFINITE -1 +#define NE_TIMEOUT_INVALID -2 + +/* Create a depth zero, exclusive write lock, with default timeout + * (allowing a server to pick a default). token, owner and uri are + * unset. */ +struct ne_lock *ne_lock_create(void); + +/* HINT: to initialize uri host/port/scheme for the lock's URI, use + * ne_fill_server_uri from ne_session.h. */ + +/* Deep-copy a lock structure: strdup's any of path, token, owner, + * hostport which are set. */ +struct ne_lock *ne_lock_copy(const struct ne_lock *lock); + +/* Free a lock structure; free's any of any of the URI, token and + * owner which are set, but not the lock object itself. */ +void ne_lock_free(struct ne_lock *lock); + +/* Like ne_lock_free; but free's the lock object itself too. */ +void ne_lock_destroy(struct ne_lock *lock); + +/* ne_lock_store: an opaque type which is used to store a set of lock + * objects. */ +typedef struct ne_lock_store_s ne_lock_store; + +/* Create a lock store. */ +ne_lock_store *ne_lockstore_create(void); + +/* Register the lock store 'store' with the HTTP session 'sess': any + * operations made using 'sess' which operate on a locked resource, + * can use the locks from 'store' if needed. */ +void ne_lockstore_register(ne_lock_store *store, ne_session *sess); + +/* Destroy a lock store, free'ing any locks remaining inside. */ +void ne_lockstore_destroy(ne_lock_store *store); + +/* Add a lock to the store: the store then "owns" the lock object, and + * you must not free it. The lock MUST have all of: + * - a completed URI structure: scheme, host, port, and path all set + * - a valid lock token + * - a valid depth + */ +void ne_lockstore_add(ne_lock_store *store, struct ne_lock *lock); + +/* Remove given lock object from store: 'lock' MUST point to a lock + * object which is known to be in the store. */ +void ne_lockstore_remove(ne_lock_store *store, struct ne_lock *lock); + +/* Returns the first lock in the lock store, or NULL if the store is + * empty. */ +struct ne_lock *ne_lockstore_first(ne_lock_store *store); + +/* After ne_lockstore_first has been called; returns the next lock in + * the lock store, or NULL if there are no more locks stored. + * Behaviour is undefined if ne_lockstore_first has not been called on + * 'store' since the store was created, or the last time this function + * returned NULL for the store.. */ +struct ne_lock *ne_lockstore_next(ne_lock_store *store); + +/* Find a lock in the store for the given server, and with the given + * path. */ +struct ne_lock *ne_lockstore_findbyuri(ne_lock_store *store, + const ne_uri *uri); + +/* Issue a LOCK request for the given lock. Requires that the uri, + * depth, type, scope, and timeout members of 'lock' are filled in. + * owner and token must be malloc-allocated if not NULL; and may be + * free()d by this function. On successful return, lock->token will + * contain the lock token. */ +int ne_lock(ne_session *sess, struct ne_lock *lock); + +/* Issue an UNLOCK request for the given lock */ +int ne_unlock(ne_session *sess, const struct ne_lock *lock); + +/* Refresh a lock. Updates lock->timeout appropriately. */ +int ne_lock_refresh(ne_session *sess, struct ne_lock *lock); + +/* Callback for lock discovery. If 'lock' is NULL, something went + * wrong performing lockdiscovery for the resource, look at 'status' + * for the details. + * + * If lock is non-NULL, at least lock->uri and lock->token will be + * filled in; and status will be NULL. */ +typedef void (*ne_lock_result)(void *userdata, const struct ne_lock *lock, + const char *uri, const ne_status *status); + +/* Perform lock discovery on the given path. 'result' is called with + * the results (possibly >1 times). */ +int ne_lock_discover(ne_session *sess, const char *path, + ne_lock_result result, void *userdata); + + +/* The ne_lock_using_* functions should be used before dispatching a + * request which modify resources. If a lock store has been + * registered with the session associated with the request, and locks + * are present in the lock store which cover the resources which are + * being modified by the request, then the appropriate lock tokens are + * submitted in the request headers. */ + +/* Indicate that request 'req' will modify the resource at 'path', and + * is an operation of given 'depth'. */ +void ne_lock_using_resource(ne_request *req, const char *path, int depth); + +/* Indicate that request 'req' will modify the parent collection of + * the resource found at 'path' (for instance when removing the + * resource from the collection). */ +void ne_lock_using_parent(ne_request *req, const char *path); + +END_NEON_DECLS + +#endif /* NE_LOCKS_H */ diff --git a/src/ne_md5.c b/src/ne_md5.c new file mode 100644 index 0000000..8a33054 --- /dev/null +++ b/src/ne_md5.c @@ -0,0 +1,432 @@ +/* md5.c - Functions to compute MD5 message digest of files or memory blocks + according to the definition of MD5 in RFC 1321 from April 1992. + Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995. */ + +#include "config.h" + +#include <sys/types.h> + +#if STDC_HEADERS || defined _LIBC +# include <stdlib.h> +# include <string.h> +#else +#ifdef HAVE_STRING_H +#include <string.h> +#endif +# ifndef HAVE_MEMCPY +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#include <ctype.h> /* for tolower */ + +#include "ne_md5.h" +#include "ne_string.h" /* for NE_ASC2HEX */ + +#ifdef _LIBC +# include <endian.h> +# if __BYTE_ORDER == __BIG_ENDIAN +# define WORDS_BIGENDIAN 1 +# endif +#endif + +#define md5_init_ctx ne_md5_init_ctx +#define md5_process_block ne_md5_process_block +#define md5_process_bytes ne_md5_process_bytes +#define md5_finish_ctx ne_md5_finish_ctx +#define md5_read_ctx ne_md5_read_ctx +#define md5_stream ne_md5_stream +#define md5_ctx ne_md5_ctx + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) \ + (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) +#else +# define SWAP(n) (n) +#endif + + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (RFC 1321, 3.1: Step 1) */ +static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; + + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +void +md5_init_ctx (ctx) + struct md5_ctx *ctx; +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Put result from CTX in first 16 bytes following RESBUF. The result + must be in little endian byte order. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +void * +md5_read_ctx (ctx, resbuf) + const struct md5_ctx *ctx; + void *resbuf; +{ + ((md5_uint32 *) resbuf)[0] = SWAP (ctx->A); + ((md5_uint32 *) resbuf)[1] = SWAP (ctx->B); + ((md5_uint32 *) resbuf)[2] = SWAP (ctx->C); + ((md5_uint32 *) resbuf)[3] = SWAP (ctx->D); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +void * +md5_finish_ctx (ctx, resbuf) + struct md5_ctx *ctx; + void *resbuf; +{ + /* Take yet unprocessed bytes into account. */ + md5_uint32 bytes = ctx->buflen; + size_t pad; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if (ctx->total[0] < bytes) + ++ctx->total[1]; + + pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; + memcpy (&ctx->buffer[bytes], fillbuf, pad); + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3); + *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) | + (ctx->total[0] >> 29)); + + /* Process last bytes. */ + md5_process_block (ctx->buffer, bytes + pad + 8, ctx); + + return md5_read_ctx (ctx, resbuf); +} + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +int +md5_stream (stream, resblock) + FILE *stream; + void *resblock; +{ + /* Important: BLOCKSIZE must be a multiple of 64. */ +#define BLOCKSIZE 4096 + struct md5_ctx ctx; + char buffer[BLOCKSIZE + 72]; + size_t sum; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Iterate over full file contents. */ + while (1) + { + /* We read the file in blocks of BLOCKSIZE bytes. One call of the + computation function processes the whole buffer so that with the + next round of the loop another block can be read. */ + size_t n; + sum = 0; + + /* Read block. Take care for partial reads. */ + do + { + n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); + + sum += n; + } + while (sum < BLOCKSIZE && n != 0); + if (n == 0 && ferror (stream)) + return 1; + + /* If end of file is reached, end the loop. */ + if (n == 0) + break; + + /* Process buffer with BLOCKSIZE bytes. Note that + BLOCKSIZE % 64 == 0 + */ + md5_process_block (buffer, BLOCKSIZE, &ctx); + } + + /* Add the last bytes if necessary. */ + if (sum > 0) + md5_process_bytes (buffer, sum, &ctx); + + /* Construct result in desired memory. */ + md5_finish_ctx (&ctx, resblock); + return 0; +} + +void +md5_process_bytes (buffer, len, ctx) + const void *buffer; + size_t len; + struct md5_ctx *ctx; +{ + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if (ctx->buflen != 0) + { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy (&ctx->buffer[left_over], buffer, add); + ctx->buflen += add; + + if (left_over + add > 64) + { + md5_process_block (ctx->buffer, (left_over + add) & ~63, ctx); + /* The regions in the following copy operation cannot overlap. */ + memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63], + (left_over + add) & 63); + ctx->buflen = (left_over + add) & 63; + } + + buffer = (const char *) buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if (len > 64) + { + md5_process_block (buffer, len & ~63, ctx); + buffer = (const char *) buffer + (len & ~63); + len &= 63; + } + + /* Move remaining bytes in internal buffer. */ + if (len > 0) + { + memcpy (ctx->buffer, buffer, len); + ctx->buflen = len; + } +} + + +/* These are the four functions used in the four steps of the MD5 algorithm + and defined in the RFC 1321. The first function is a little bit optimized + (as found in Colin Plumbs public domain implementation). */ +/* #define FF(b, c, d) ((b & c) | (~b & d)) */ +#define FF(b, c, d) (d ^ (b & (c ^ d))) +#define FG(b, c, d) FF (d, b, c) +#define FH(b, c, d) (b ^ c ^ d) +#define FI(b, c, d) (c ^ (b | ~d)) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. */ + +void +md5_process_block (buffer, len, ctx) + const void *buffer; + size_t len; + struct md5_ctx *ctx; +{ + md5_uint32 correct_words[16]; + const unsigned char *words = buffer; + const unsigned char *endp = words + len; + md5_uint32 A = ctx->A; + md5_uint32 B = ctx->B; + md5_uint32 C = ctx->C; + md5_uint32 D = ctx->D; + + /* First increment the byte count. RFC 1321 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += len; + if (ctx->total[0] < len) + ++ctx->total[1]; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + while (words < endp) + { + md5_uint32 *cwp = correct_words; + md5_uint32 A_save = A; + md5_uint32 B_save = B; + md5_uint32 C_save = C; + md5_uint32 D_save = D; + + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ + +#define OP(a, b, c, d, s, T) \ + do \ + { \ + md5_uint32 WORD_ = (md5_uint32)words[0] | ((md5_uint32)words[1] << 8) \ + | ((md5_uint32)words[2] << 16) | ((md5_uint32)words[3] << 24); \ + a += FF (b, c, d) + (*cwp++ = WORD_) + T; \ + words += 4; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* It is unfortunate that C does not provide an operator for + cyclic rotation. Hope the C compiler is smart enough. */ +#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + */ + + /* Round 1. */ + OP (A, B, C, D, 7, 0xd76aa478); + OP (D, A, B, C, 12, 0xe8c7b756); + OP (C, D, A, B, 17, 0x242070db); + OP (B, C, D, A, 22, 0xc1bdceee); + OP (A, B, C, D, 7, 0xf57c0faf); + OP (D, A, B, C, 12, 0x4787c62a); + OP (C, D, A, B, 17, 0xa8304613); + OP (B, C, D, A, 22, 0xfd469501); + OP (A, B, C, D, 7, 0x698098d8); + OP (D, A, B, C, 12, 0x8b44f7af); + OP (C, D, A, B, 17, 0xffff5bb1); + OP (B, C, D, A, 22, 0x895cd7be); + OP (A, B, C, D, 7, 0x6b901122); + OP (D, A, B, C, 12, 0xfd987193); + OP (C, D, A, B, 17, 0xa679438e); + OP (B, C, D, A, 22, 0x49b40821); + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +#undef OP +#define OP(f, a, b, c, d, k, s, T) \ + do \ + { \ + a += f (b, c, d) + correct_words[k] + T; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Round 2. */ + OP (FG, A, B, C, D, 1, 5, 0xf61e2562); + OP (FG, D, A, B, C, 6, 9, 0xc040b340); + OP (FG, C, D, A, B, 11, 14, 0x265e5a51); + OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP (FG, A, B, C, D, 5, 5, 0xd62f105d); + OP (FG, D, A, B, C, 10, 9, 0x02441453); + OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP (FG, D, A, B, C, 14, 9, 0xc33707d6); + OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP (FG, B, C, D, A, 8, 20, 0x455a14ed); + OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP (FG, C, D, A, B, 7, 14, 0x676f02d9); + OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); + + /* Round 3. */ + OP (FH, A, B, C, D, 5, 4, 0xfffa3942); + OP (FH, D, A, B, C, 8, 11, 0x8771f681); + OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP (FH, B, C, D, A, 14, 23, 0xfde5380c); + OP (FH, A, B, C, D, 1, 4, 0xa4beea44); + OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP (FH, B, C, D, A, 6, 23, 0x04881d05); + OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); + + /* Round 4. */ + OP (FI, A, B, C, D, 0, 6, 0xf4292244); + OP (FI, D, A, B, C, 7, 10, 0x432aff97); + OP (FI, C, D, A, B, 14, 15, 0xab9423a7); + OP (FI, B, C, D, A, 5, 21, 0xfc93a039); + OP (FI, A, B, C, D, 12, 6, 0x655b59c3); + OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP (FI, C, D, A, B, 10, 15, 0xffeff47d); + OP (FI, B, C, D, A, 1, 21, 0x85845dd1); + OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP (FI, C, D, A, B, 6, 15, 0xa3014314); + OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP (FI, A, B, C, D, 4, 6, 0xf7537e82); + OP (FI, D, A, B, C, 11, 10, 0xbd3af235); + OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP (FI, B, C, D, A, 9, 21, 0xeb86d391); + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + } + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} + +/* Writes the ASCII representation of the MD5 digest into the + * given buffer, which must be at least 33 characters long. */ +void ne_md5_to_ascii(const unsigned char md5_buf[16], char *buffer) +{ + int count; + for (count = 0; count<16; count++) { + buffer[count*2] = NE_HEX2ASC(md5_buf[count] >> 4); + buffer[count*2+1] = NE_HEX2ASC(md5_buf[count] & 0x0f); + } + buffer[32] = '\0'; +} + +/* Reads the ASCII representation of an MD5 digest. The buffer must + * be at least 32 characters long. */ +void ne_ascii_to_md5(const char *buffer, unsigned char md5_buf[16]) +{ + int count; + for (count = 0; count<16; count++) { + md5_buf[count] = ((NE_ASC2HEX(buffer[count*2])) << 4) | + NE_ASC2HEX(buffer[count*2+1]); + } +} diff --git a/src/ne_md5.h b/src/ne_md5.h new file mode 100644 index 0000000..9714ec3 --- /dev/null +++ b/src/ne_md5.h @@ -0,0 +1,144 @@ +/* Declaration of functions and data types used for MD5 sum computing + library functions. + Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifndef NEON_MD5_H +#define NEON_MD5_H 1 + +#include <stdio.h> + +#if defined HAVE_LIMITS_H || _LIBC +# include <limits.h> +#endif + +/* The following contortions are an attempt to use the C preprocessor + to determine an unsigned integral type that is 32 bits wide. An + alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but + doing that would require that the configure script compile and *run* + the resulting executable. Locally running cross-compiled executables + is usually not possible. */ + +#ifdef _LIBC +# include <sys/types.h> +typedef u_int32_t md5_uint32; +#else +# if defined __STDC__ && __STDC__ +# define UINT_MAX_32_BITS 4294967295U +# else +# define UINT_MAX_32_BITS 0xFFFFFFFF +# endif + +/* If UINT_MAX isn't defined, assume it's a 32-bit type. + This should be valid for all systems GNU cares about because + that doesn't include 16-bit systems, and only modern systems + (that certainly have <limits.h>) have 64+-bit integral types. */ + +# ifndef UINT_MAX +# define UINT_MAX UINT_MAX_32_BITS +# endif + +# if UINT_MAX == UINT_MAX_32_BITS + typedef unsigned int md5_uint32; +# else +# if USHRT_MAX == UINT_MAX_32_BITS + typedef unsigned short md5_uint32; +# else +# if ULONG_MAX == UINT_MAX_32_BITS + typedef unsigned long md5_uint32; +# else + /* The following line is intended to evoke an error. + Using #error is not portable enough. */ + "Cannot determine unsigned 32-bit data type." +# endif +# endif +# endif +#endif + +#undef __P +#if defined (__STDC__) && __STDC__ +# define __P(x) x +#else +# define __P(x) () +#endif + +/* Structure to save state of computation between the single steps. */ +struct ne_md5_ctx +{ + md5_uint32 A; + md5_uint32 B; + md5_uint32 C; + md5_uint32 D; + + md5_uint32 total[2]; + md5_uint32 buflen; + char buffer[128]; +}; + +/* + * The following three functions are build up the low level used in + * the functions `md5_stream' and `md5_buffer'. + */ + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +extern void ne_md5_init_ctx __P ((struct ne_md5_ctx *ctx)); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void ne_md5_process_block __P ((const void *buffer, size_t len, + struct ne_md5_ctx *ctx)); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void ne_md5_process_bytes __P ((const void *buffer, size_t len, + struct ne_md5_ctx *ctx)); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 16 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +extern void *ne_md5_finish_ctx __P ((struct ne_md5_ctx *ctx, void *resbuf)); + + +/* Put result from CTX in first 16 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +extern void *ne_md5_read_ctx __P ((const struct ne_md5_ctx *ctx, void *resbuf)); + + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +extern int ne_md5_stream __P ((FILE *stream, void *resblock)); + +/* MD5 ascii->binary conversion */ +void ne_md5_to_ascii(const unsigned char md5_buf[16], char *buffer); +void ne_ascii_to_md5(const char *buffer, unsigned char md5_buf[16]); + +#endif /* NEON_MD5_H */ diff --git a/src/ne_openssl.c b/src/ne_openssl.c new file mode 100644 index 0000000..5d21ef1 --- /dev/null +++ b/src/ne_openssl.c @@ -0,0 +1,790 @@ +/* + neon SSL/TLS support using OpenSSL + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + Portions are: + Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <stdio.h> + +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/pkcs12.h> +#include <openssl/x509v3.h> +#include <openssl/rand.h> + +#include "ne_ssl.h" +#include "ne_string.h" +#include "ne_session.h" +#include "ne_i18n.h" + +#include "ne_private.h" +#include "ne_privssl.h" + +struct ne_ssl_dname_s { + X509_NAME *dn; +}; + +struct ne_ssl_certificate_s { + ne_ssl_dname subj_dn, issuer_dn; + X509 *subject; + ne_ssl_certificate *issuer; + char *identity; +}; + +struct ne_ssl_client_cert_s { + PKCS12 *p12; + int decrypted; /* non-zero if successfully decrypted. */ + ne_ssl_certificate cert; + EVP_PKEY *pkey; + char *friendly_name; +}; + +char *ne_ssl_readable_dname(const ne_ssl_dname *name) +{ + int n, flag = 0; + ne_buffer *dump = ne_buffer_create(); + const ASN1_OBJECT * const cname = OBJ_nid2obj(NID_commonName), + * const email = OBJ_nid2obj(NID_pkcs9_emailAddress); + + for (n = X509_NAME_entry_count(name->dn); n > 0; n--) { + X509_NAME_ENTRY *ent = X509_NAME_get_entry(name->dn, n-1); + + /* Skip commonName or emailAddress except if there is no other + * attribute in dname. */ + if ((OBJ_cmp(ent->object, cname) && OBJ_cmp(ent->object, email)) || + (!flag && n == 1)) { + if (flag) + ne_buffer_append(dump, ", ", 2); + ne_buffer_append(dump, ent->value->data, ent->value->length); + flag = 1; + } + } + + return ne_buffer_finish(dump); +} + +int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2) +{ + return X509_NAME_cmp(dn1->dn, dn2->dn); +} + +void ne_ssl_clicert_free(ne_ssl_client_cert *cc) +{ + if (cc->p12) + PKCS12_free(cc->p12); + if (cc->decrypted) { + if (cc->cert.identity) ne_free(cc->cert.identity); + EVP_PKEY_free(cc->pkey); + X509_free(cc->cert.subject); + } + if (cc->friendly_name) ne_free(cc->friendly_name); + ne_free(cc); +} + +/* Map a server cert verification into a string. */ +static void verify_err(ne_session *sess, int failures) +{ + struct { + int bit; + const char *str; + } reasons[] = { + { NE_SSL_NOTYETVALID, N_("certificate is not yet valid") }, + { NE_SSL_EXPIRED, N_("certificate has expired") }, + { NE_SSL_IDMISMATCH, N_("certificate issued for a different hostname") }, + { NE_SSL_UNTRUSTED, N_("issuer is not trusted") }, + { 0, NULL } + }; + int n, flag = 0; + + strcpy(sess->error, _("Server certificate verification failed: ")); + + for (n = 0; reasons[n].bit; n++) { + if (failures & reasons[n].bit) { + if (flag) strncat(sess->error, ", ", sizeof sess->error); + strncat(sess->error, _(reasons[n].str), sizeof sess->error); + flag = 1; + } + } + +} + +/* Format an ASN1 time to a string. 'buf' must be at least of size + * 'NE_SSL_VDATELEN'. */ +static void asn1time_to_string(ASN1_TIME *tm, char *buf) +{ + BIO *bio; + + strncpy(buf, _("[invalid date]"), NE_SSL_VDATELEN-1); + + bio = BIO_new(BIO_s_mem()); + if (bio) { + if (ASN1_TIME_print(bio, tm)) + BIO_read(bio, buf, NE_SSL_VDATELEN-1); + BIO_free(bio); + } +} + +void ne_ssl_cert_validity(const ne_ssl_certificate *cert, + char *from, char *until) +{ + ASN1_TIME *notBefore = X509_get_notBefore(cert->subject); + ASN1_TIME *notAfter = X509_get_notAfter(cert->subject); + + if (from) asn1time_to_string(notBefore, from); + if (until) asn1time_to_string(notAfter, until); +} + +/* Return non-zero if hostname from certificate (cn) matches hostname + * used for session (hostname). TODO: could do more advanced wildcard + * matching using fnmatch() here, if fnmatch is present. */ +static int match_hostname(char *cn, const char *hostname) +{ + const char *dot; + NE_DEBUG(NE_DBG_SSL, "Match %s on %s...\n", cn, hostname); + dot = strchr(hostname, '.'); + if (dot == NULL) { + char *pnt = strchr(cn, '.'); + /* hostname is not fully-qualified; unqualify the cn. */ + if (pnt != NULL) { + *pnt = '\0'; + } + } + else if (strncmp(cn, "*.", 2) == 0) { + hostname = dot + 1; + cn += 2; + } + return !strcasecmp(cn, hostname); +} + +/* Check certificate identity. Returns zero if identity matches; 1 if + * identity does not match, or <0 if the certificate had no identity. + * If 'identity' is non-NULL, store the malloc-allocated identity in + * *identity. */ +static int check_identity(const char *hostname, X509 *cert, char **identity) +{ + STACK_OF(GENERAL_NAME) *names; + int match = 0, found = 0; + + names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (names) { + /* Got a subject alt. name extension. */ + int n; + + for (n = 0; n < sk_GENERAL_NAME_num(names) && !match; n++) { + GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, n); + + /* only care about this if it is a DNS name. */ + if (nm->type == GEN_DNS) { + char *name = ne_strndup(nm->d.ia5->data, nm->d.ia5->length); + if (identity && !found) *identity = ne_strdup(name); + match = match_hostname(name, hostname); + ne_free(name); + found = 1; + } + } + /* free the whole stack. */ + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + } + + /* Check against the commonName if no DNS alt. names were found, + * as per RFC2818. */ + if (!found) { + X509_NAME *subj = X509_get_subject_name(cert); + X509_NAME_ENTRY *entry; + ASN1_STRING *str; + int idx = -1, lastidx; + char *name; + + /* find the most specific commonName attribute. */ + do { + lastidx = idx; + idx = X509_NAME_get_index_by_NID(subj, NID_commonName, lastidx); + } while (idx >= 0); + + if (lastidx < 0) + return -1; + + /* extract the string from the entry */ + entry = X509_NAME_get_entry(subj, lastidx); + str = X509_NAME_ENTRY_get_data(entry); + + name = ne_strndup(str->data, str->length); + if (identity) *identity = ne_strdup(name); + match = match_hostname(name, hostname); + ne_free(name); + } + + NE_DEBUG(NE_DBG_SSL, "Identity match: %s\n", match ? "good" : "bad"); + return match ? 0 : 1; +} + +/* Populate an ne_ssl_certificate structure from an X509 object. */ +static ne_ssl_certificate *populate_cert(ne_ssl_certificate *cert, X509 *x5) +{ + cert->subj_dn.dn = X509_get_subject_name(x5); + cert->issuer_dn.dn = X509_get_issuer_name(x5); + cert->issuer = NULL; + cert->subject = x5; + /* Retrieve the cert identity; pass a dummy hostname to match. */ + cert->identity = NULL; + check_identity("", x5, &cert->identity); + return cert; +} + +/* Return a linked list of certificate objects from an OpenSSL chain. */ +static ne_ssl_certificate *make_chain(STACK_OF(X509) *chain) +{ + int n, count = sk_X509_num(chain); + ne_ssl_certificate *top = NULL, *current = NULL; + + NE_DEBUG(NE_DBG_SSL, "Chain depth: %d\n", count); + + for (n = 0; n < count; n++) { + ne_ssl_certificate *cert = ne_malloc(sizeof *cert); + populate_cert(cert, X509_dup(sk_X509_value(chain, n))); +#if NE_DEBUGGING + if (ne_debug_mask & NE_DBG_SSL) { + fprintf(ne_debug_stream, "Cert #%d:\n", n); + X509_print_fp(ne_debug_stream, cert->subject); + } +#endif + if (top == NULL) { + current = top = cert; + } else { + current->issuer = cert; + current = cert; + } + } + + return top; +} + +/* Verifies an SSL server certificate. */ +static int check_certificate(ne_session *sess, SSL *ssl, ne_ssl_certificate *chain) +{ + X509 *cert = chain->subject; + ASN1_TIME *notBefore = X509_get_notBefore(cert); + ASN1_TIME *notAfter = X509_get_notAfter(cert); + int ret, failures = 0; + long result; + + /* check expiry dates */ + if (X509_cmp_current_time(notBefore) >= 0) + failures |= NE_SSL_NOTYETVALID; + else if (X509_cmp_current_time(notAfter) <= 0) + failures |= NE_SSL_EXPIRED; + + /* Check certificate was issued to this server. */ + ret = check_identity(sess->server.hostname, cert, NULL); + if (ret < 0) { + ne_set_error(sess, _("Server certificate was missing commonName " + "attribute in subject name")); + return NE_ERROR; + } else if (ret > 0) failures |= NE_SSL_IDMISMATCH; + + /* get the result of the cert verification out of OpenSSL */ + result = SSL_get_verify_result(ssl); + + NE_DEBUG(NE_DBG_SSL, "Verify result: %ld = %s\n", result, + X509_verify_cert_error_string(result)); + + switch (result) { + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + /* TODO: and probably more result codes here... */ + failures |= NE_SSL_UNTRUSTED; + break; + /* ignore these, since we've already noticed them: */ + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + /* cert was trusted: */ + case X509_V_OK: + break; + default: + /* TODO: tricky to handle the 30-odd failure cases OpenSSL + * presents here (see x509_vfy.h), and present a useful API to + * the application so it in turn can then present a meaningful + * UI to the user. The only thing to do really would be to + * pass back the error string, but that's not localisable. So + * just fail the verification here - better safe than + * sorry. */ + ne_set_error(sess, _("Certificate verification error: %s"), + X509_verify_cert_error_string(result)); + return NE_ERROR; + } + + if (failures == 0) { + /* verified OK! */ + ret = NE_OK; + } else { + /* Set up the error string. */ + verify_err(sess, failures); + ret = NE_ERROR; + /* Allow manual override */ + if (sess->ssl_verify_fn && + sess->ssl_verify_fn(sess->ssl_verify_ud, failures, chain) == 0) + ret = NE_OK; + } + + return ret; +} + +/* Duplicate a client certificate, which must be in the decrypted state. */ +static ne_ssl_client_cert *dup_client_cert(const ne_ssl_client_cert *cc) +{ + ne_ssl_client_cert *newcc = ne_calloc(sizeof *newcc); + + newcc->decrypted = 1; + newcc->pkey = cc->pkey; + if (cc->friendly_name) + newcc->friendly_name = ne_strdup(cc->friendly_name); + + populate_cert(&newcc->cert, cc->cert.subject); + + cc->cert.subject->references++; + cc->pkey->references++; + return newcc; +} + +/* Callback invoked when the SSL server requests a client certificate. */ +static int provide_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey) +{ + ne_ssl_context *ctx = SSL_get_app_data(ssl); + ne_session *sess = SSL_CTX_get_app_data(ctx->ctx); + + if (!sess->client_cert && sess->ssl_provide_fn) { + ne_ssl_dname **dnames = NULL; + int n, count = 0; + STACK_OF(X509_NAME) *ca_list = SSL_get_client_CA_list(ssl); + + if (ca_list == NULL) { + /* no list of acceptable CA names provided. */ + dnames = NULL; + count = 0; + } else { + count = sk_X509_NAME_num(ca_list); + + dnames = ne_malloc(count * sizeof(ne_ssl_dname *)); + + for (n = 0; n < count; n++) { + dnames[n] = ne_malloc(sizeof(ne_ssl_dname)); + dnames[n]->dn = sk_X509_NAME_value(ca_list, n); + } + } + + NE_DEBUG(NE_DBG_SSL, "Calling client certificate provider...\n"); + sess->ssl_provide_fn(sess->ssl_provide_ud, sess, + (const ne_ssl_dname *const *)dnames, count); + if (count) { + for (n = 0; n < count; n++) + ne_free(dnames[n]); + ne_free(dnames); + } + } + + if (sess->client_cert) { + ne_ssl_client_cert *const cc = sess->client_cert; + NE_DEBUG(NE_DBG_SSL, "Supplying client certificate.\n"); + cc->pkey->references++; + cc->cert.subject->references++; + *cert = cc->cert.subject; + *pkey = cc->pkey; + return 1; + } else { + NE_DEBUG(NE_DBG_SSL, "No client certificate supplied.\n"); + return 0; + } +} + +void ne_ssl_set_clicert(ne_session *sess, const ne_ssl_client_cert *cc) +{ + sess->client_cert = dup_client_cert(cc); +} + +ne_ssl_context *ne_ssl_context_create(void) +{ + ne_ssl_context *ctx = ne_malloc(sizeof *ctx); + ctx->ctx = SSL_CTX_new(SSLv23_client_method()); + ctx->sess = NULL; + /* set client cert callback. */ + SSL_CTX_set_client_cert_cb(ctx->ctx, provide_client_cert); + /* enable workarounds for buggy SSL server implementations */ + SSL_CTX_set_options(ctx->ctx, SSL_OP_ALL); + return ctx; +} + +void ne_ssl_context_destroy(ne_ssl_context *ctx) +{ + SSL_CTX_free(ctx->ctx); + if (ctx->sess) + SSL_SESSION_free(ctx->sess); + ne_free(ctx); +} + +/* For internal use only. */ +int ne_negotiate_ssl(ne_request *req) +{ + ne_session *sess = ne_get_session(req); + ne_ssl_context *ctx = sess->ssl_context; + ne_ssl_socket *sock; + STACK_OF(X509) *chain; + + NE_DEBUG(NE_DBG_SSL, "Doing SSL negotiation.\n"); + + /* Rather a hack: link the ssl_context back to the ne_session, so + * provide_client_cert can get to the ne_session. */ + SSL_CTX_set_app_data(ctx->ctx, sess); + + if (ne_sock_connect_ssl(sess->socket, ctx)) { + if (ctx->sess) { + /* remove cached session. */ + SSL_SESSION_free(ctx->sess); + ctx->sess = NULL; + } + ne_set_error(sess, _("SSL negotiation failed: %s"), + ne_sock_error(sess->socket)); + return NE_ERROR; + } + + sock = ne_sock_sslsock(sess->socket); + + chain = SSL_get_peer_cert_chain(sock->ssl); + if (chain == NULL || sk_X509_num(chain) == 0) { + ne_set_error(sess, _("SSL server did not present certificate")); + return NE_ERROR; + } + + if (sess->server_cert) { + if (X509_cmp(sk_X509_value(chain, 0), sess->server_cert->subject)) { + /* This could be a MITM attack: fail the request. */ + ne_set_error(sess, _("Server certificate changed: " + "connection intercepted?")); + return NE_ERROR; + } + /* certificate has already passed verification: no need to + * verify it again. */ + } else { + /* new connection: create the chain. */ + ne_ssl_certificate *cert = make_chain(chain); + + if (check_certificate(sess, sock->ssl, cert)) { + NE_DEBUG(NE_DBG_SSL, "SSL certificate checks failed: %s\n", + sess->error); + ne_ssl_cert_free(cert); + return NE_ERROR; + } + /* remember the chain. */ + sess->server_cert = cert; + } + + if (!ctx->sess) { + /* store the session. */ + ctx->sess = SSL_get1_session(sock->ssl); + } + + if (sess->notify_cb) { + sess->notify_cb(sess->notify_ud, ne_conn_secure, + SSL_get_version(sock->ssl)); + } + + return NE_OK; +} + +const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert) +{ + return &cert->issuer_dn; +} + +const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert) +{ + return &cert->subj_dn; +} + +const ne_ssl_certificate *ne_ssl_cert_signedby(const ne_ssl_certificate *cert) +{ + return cert->issuer; +} + +const char *ne_ssl_cert_identity(const ne_ssl_certificate *cert) +{ + return cert->identity; +} + +void ne_ssl_ctx_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert) +{ + X509_STORE *store = SSL_CTX_get_cert_store(ctx->ctx); + + X509_STORE_add_cert(store, cert->subject); +} + +void ne_ssl_trust_default_ca(ne_session *sess) +{ + X509_STORE *store = SSL_CTX_get_cert_store(sess->ssl_context->ctx); + + X509_STORE_set_default_paths(store); +} + +/* Find a friendly name in a PKCS12 structure the hard way, without + * decrypting the parts which are encrypted.. */ +static char *find_friendly_name(PKCS12 *p12) +{ + STACK_OF(PKCS7) *safes = PKCS12_unpack_authsafes(p12); + int n, m; + char *name = NULL; + + if (safes == NULL) return NULL; + + /* Iterate over the unpacked authsafes: */ + for (n = 0; n < sk_PKCS7_num(safes) && !name; n++) { + PKCS7 *safe = sk_PKCS7_value(safes, n); + STACK_OF(PKCS12_SAFEBAG) *bags; + + /* Only looking for unencrypted authsafes. */ + if (OBJ_obj2nid(safe->type) != NID_pkcs7_data) continue; + + bags = PKCS12_unpack_p7data(safe); + if (!bags) continue; + + /* Iterate through the bags, picking out a friendly name */ + for (m = 0; m < sk_PKCS12_SAFEBAG_num(bags) && !name; m++) { + PKCS12_SAFEBAG *bag = sk_PKCS12_SAFEBAG_value(bags, m); + name = PKCS12_get_friendlyname(bag); + } + + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + } + + sk_PKCS7_pop_free(safes, PKCS7_free); + return name; +} + +ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename) +{ + PKCS12 *p12; + FILE *fp; + X509 *cert; + EVP_PKEY *pkey; + ne_ssl_client_cert *cc; + + fp = fopen(filename, "rb"); + if (fp == NULL) + return NULL; + + p12 = d2i_PKCS12_fp(fp, NULL); + + fclose(fp); + + if (p12 == NULL) { + ERR_clear_error(); + return NULL; + } + + /* Try parsing with no password. */ + if (PKCS12_parse(p12, NULL, &pkey, &cert, NULL) == 1) { + /* Success - no password needed for decryption. */ + unsigned int len = 0; + unsigned char *name = X509_alias_get0(cert, &len); + + cc = ne_calloc(sizeof *cc); + cc->pkey = pkey; + cc->decrypted = 1; + if (name && len) + cc->friendly_name = ne_strndup((char *)name, len); + populate_cert(&cc->cert, cert); + PKCS12_free(p12); + return cc; + } else { + /* Failed to parse the file */ + int err = ERR_get_error(); + ERR_clear_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 && + ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) { + /* Decryption error due to bad password. */ + cc = ne_calloc(sizeof *cc); + cc->friendly_name = find_friendly_name(p12); + cc->p12 = p12; + return cc; + } else { + /* Some parse error, give up. */ + PKCS12_free(p12); + return NULL; + } + } +} + +int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *cc) +{ + return !cc->decrypted; +} + +int ne_ssl_clicert_decrypt(ne_ssl_client_cert *cc, const char *password) +{ + X509 *cert; + EVP_PKEY *pkey; + + if (PKCS12_parse(cc->p12, password, &pkey, &cert, NULL) != 1) { + ERR_clear_error(); + return -1; + } + + PKCS12_free(cc->p12); + populate_cert(&cc->cert, cert); + cc->pkey = pkey; + cc->decrypted = 1; + cc->p12 = NULL; + return 0; +} + +const ne_ssl_certificate *ne_ssl_clicert_owner(const ne_ssl_client_cert *cc) +{ + return &cc->cert; +} + +const char *ne_ssl_clicert_name(ne_ssl_client_cert *ccert) +{ + return ccert->friendly_name; +} + +ne_ssl_certificate *ne_ssl_cert_read(const char *filename) +{ + FILE *fp = fopen(filename, "r"); + X509 *cert; + + if (fp == NULL) + return NULL; + + cert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + + if (cert == NULL) { + NE_DEBUG(NE_DBG_SSL, "d2i_X509_fp failed: %s\n", + ERR_reason_error_string(ERR_get_error())); + ERR_clear_error(); + return NULL; + } + + return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), cert); +} + +int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename) +{ + FILE *fp = fopen(filename, "w"); + + if (fp == NULL) return -1; + + if (PEM_write_X509(fp, cert->subject) != 1) { + fclose(fp); + return -1; + } + + if (fclose(fp) != 0) + return -1; + + return 0; +} + +void ne_ssl_cert_free(ne_ssl_certificate *cert) +{ + X509_free(cert->subject); + if (cert->issuer) + ne_ssl_cert_free(cert->issuer); + if (cert->identity) + ne_free(cert->identity); + ne_free(cert); +} + +int ne_ssl_cert_cmp(const ne_ssl_certificate *c1, const ne_ssl_certificate *c2) +{ + return X509_cmp(c1->subject, c2->subject); +} + +/* The certificate import/export format is the base64 encoding of the + * raw DER; PEM without the newlines and wrapping. */ + +ne_ssl_certificate *ne_ssl_cert_import(const char *data) +{ + unsigned char *der, *p; + size_t len; + X509 *x5; + + /* decode the base64 to get the raw DER representation */ + len = ne_unbase64(data, &der); + if (len == 0) return NULL; + + p = der; + x5 = d2i_X509(NULL, &p, len); /* p is incremented */ + ne_free(der); + if (x5 == NULL) { + ERR_clear_error(); + return NULL; + } + + return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), x5); +} + +char *ne_ssl_cert_export(const ne_ssl_certificate *cert) +{ + int len; + unsigned char *der, *p; + + /* find the length of the DER encoding. */ + len = i2d_X509(cert->subject, NULL); + + p = der = ne_malloc(len); + i2d_X509(cert->subject, &p); /* p is incremented */ + + p = ne_base64(der, len); + ne_free(der); + return p; +} + +#if SHA_DIGEST_LENGTH != 20 +# error SHA digest length is not 20 bytes +#endif + +int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest) +{ + unsigned char sha1[EVP_MAX_MD_SIZE]; + unsigned int len, j; + char *p; + + if (!X509_digest(cert->subject, EVP_sha1(), sha1, &len) || len != 20) { + ERR_clear_error(); + return -1; + } + + for (j = 0, p = digest; j < 20; j++) { + *p++ = NE_HEX2ASC((sha1[j] >> 4) & 0x0f); + *p++ = NE_HEX2ASC(sha1[j] & 0x0f); + *p++ = ':'; + } + + *--p = '\0'; + return 0; +} diff --git a/src/ne_private.h b/src/ne_private.h new file mode 100644 index 0000000..964800d --- /dev/null +++ b/src/ne_private.h @@ -0,0 +1,118 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* THIS IS NOT A PUBLIC INTERFACE. You CANNOT include this header file + * from an application. */ + +#ifndef NE_PRIVATE_H +#define NE_PRIVATE_H + +#include "ne_request.h" +#include "ne_socket.h" +#include "ne_ssl.h" + +struct host_info { + char *hostname; + unsigned int port; + ne_sock_addr *address; /* if non-NULL, result of resolving 'hostname'. */ + /* current network address obtained from 'address' being used. */ + const ne_inet_addr *current; + char *hostport; /* URI hostport segment */ +}; + +/* Store every registered callback in a generic container, and cast + * the function pointer when calling it. */ +struct hook { + void (*fn)(void); + void *userdata; + const char *id; /* non-NULL for accessors. */ + struct hook *next; +}; + +#define HAVE_HOOK(st,func) (st->hook->hooks->func != NULL) +#define HOOK_FUNC(st, func) (*st->hook->hooks->func) + +/* Session support. */ +struct ne_session_s { + /* Connection information */ + ne_socket *socket; + + /* non-zero if connection has been established. */ + int connected; + + /* non-zero if connection has persisted beyond one request. */ + int persisted; + + int is_http11; /* >0 if connected server is known to be + * HTTP/1.1 compliant. */ + + char *scheme; + struct host_info server, proxy; + + /* Settings */ + unsigned int use_proxy:1; /* do we have a proxy server? */ + unsigned int no_persist:1; /* set to disable persistent connections */ + unsigned int use_ssl:1; /* whether a secure connection is required */ + unsigned int in_connect:1; /* doing a proxy CONNECT */ + + int expect100_works; /* known state of 100-continue support */ + + ne_progress progress_cb; + void *progress_ud; + + ne_notify_status notify_cb; + void *notify_ud; + + int rdtimeout; /* read timeout. */ + + struct hook *create_req_hooks, *pre_send_hooks, *post_send_hooks; + struct hook *destroy_req_hooks, *destroy_sess_hooks, *private; + + char *user_agent; /* full User-Agent string */ + +#ifdef NEON_SSL + ne_ssl_client_cert *client_cert; + ne_ssl_certificate *server_cert; + ne_ssl_context *ssl_context; +#endif + + /* Server cert verification callback: */ + ne_ssl_verify_fn ssl_verify_fn; + void *ssl_verify_ud; + /* Client cert provider callback: */ + ne_ssl_provide_fn ssl_provide_fn; + void *ssl_provide_ud; + + /* Error string */ + char error[BUFSIZ]; +}; + +typedef int (*ne_push_fn)(void *userdata, const char *buf, size_t count); + +/* Pulls the request body for the given request, passing blocks to the + * given callback. + */ +int ne_pull_request_body(ne_request *req, ne_push_fn fn, void *ud); + +/* Do the SSL negotiation. */ +int ne_negotiate_ssl(ne_request *req); + +#endif /* HTTP_PRIVATE_H */ diff --git a/src/ne_privssl.h b/src/ne_privssl.h new file mode 100644 index 0000000..1f63759 --- /dev/null +++ b/src/ne_privssl.h @@ -0,0 +1,48 @@ +/* + SSL interface definitions internal to neon. + Copyright (C) 2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* THIS IS NOT A PUBLIC INTERFACE. You CANNOT include this header file + * from an application. */ + +#ifndef NE_PRIVSSL_H +#define NE_PRIVSSL_H + +/* This is the private interface between ne_socket and ne_openssl. */ + +#ifdef NEON_SSL + +#include <openssl/ssl.h> + +#include "ne_ssl.h" + +/* SSL context */ +struct ne_ssl_context_s { + SSL_CTX *ctx; + SSL_SESSION *sess; +}; + +struct ne_ssl_socket_s { + SSL *ssl; +}; + +#endif /* NEON_SSL */ + +#endif diff --git a/src/ne_props.c b/src/ne_props.c new file mode 100644 index 0000000..9b7da0f --- /dev/null +++ b/src/ne_props.c @@ -0,0 +1,620 @@ +/* + WebDAV property manipulation + Copyright (C) 2000-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "ne_alloc.h" +#include "ne_xml.h" +#include "ne_props.h" +#include "ne_basic.h" +#include "ne_locks.h" + +/* don't store flat props with a value > 10K */ +#define MAX_FLATPROP_LEN (102400) + +struct ne_propfind_handler_s { + ne_session *sess; + ne_request *request; + + int has_props; /* whether we've already written some + * props to the body. */ + ne_buffer *body; + + ne_207_parser *parser207; + ne_xml_parser *parser; + + /* Callback to create the private structure. */ + ne_props_create_complex private_creator; + void *private_userdata; + + /* Current propset, or NULL if none being processed. */ + ne_prop_result_set *current; + + ne_buffer *value; /* current flat property value */ + int depth; /* nesting depth within a flat property */ + + ne_props_result callback; + void *userdata; +}; + +#define ELM_flatprop (NE_207_STATE_TOP - 1) + +/* We build up the results of one 'response' element in memory. */ +struct prop { + char *name, *nspace, *value, *lang; + /* Store a ne_propname here too, for convienience. pname.name = + * name, pname.nspace = nspace, but they are const'ed in pname. */ + ne_propname pname; +}; + +#define NSPACE(x) ((x) ? (x) : "") + +struct propstat { + struct prop *props; + int numprops; + ne_status status; +}; + +/* Results set. */ +struct ne_prop_result_set_s { + struct propstat *pstats; + int numpstats; + void *private; + char *href; +}; + + +static int +startelm(void *userdata, int state, const char *name, const char *nspace, + const char **atts); +static int +endelm(void *userdata, int state, const char *name, const char *nspace); + +/* Handle character data; flat property value. */ +static int chardata(void *userdata, int state, const char *data, size_t len) +{ + ne_propfind_handler *hdl = userdata; + + if (state == ELM_flatprop && hdl->value->length < MAX_FLATPROP_LEN) + ne_buffer_append(hdl->value, data, len); + + return 0; +} + +ne_xml_parser *ne_propfind_get_parser(ne_propfind_handler *handler) +{ + return handler->parser; +} + +ne_request *ne_propfind_get_request(ne_propfind_handler *handler) +{ + return handler->request; +} + +static int propfind(ne_propfind_handler *handler, + ne_props_result results, void *userdata) +{ + int ret; + ne_request *req = handler->request; + + /* Register the flat property handler to catch any properties + * which the user isn't handling as 'complex'. */ + ne_xml_push_handler(handler->parser, startelm, chardata, endelm, handler); + + handler->callback = results; + handler->userdata = userdata; + + ne_set_request_body_buffer(req, handler->body->data, + ne_buffer_size(handler->body)); + + ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE); + + ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v, + handler->parser); + + ret = ne_request_dispatch(req); + + if (ret == NE_OK && ne_get_status(req)->klass != 2) { + ret = NE_ERROR; + } else if (!ne_xml_valid(handler->parser)) { + ne_set_error(handler->sess, ne_xml_get_error(handler->parser)); + ret = NE_ERROR; + } + + return ret; +} + +static void set_body(ne_propfind_handler *hdl, const ne_propname *names) +{ + ne_buffer *body = hdl->body; + int n; + + if (!hdl->has_props) { + ne_buffer_zappend(body, "<prop>" EOL); + hdl->has_props = 1; + } + + for (n = 0; names[n].name != NULL; n++) { + ne_buffer_concat(body, "<", names[n].name, " xmlns=\"", + NSPACE(names[n].nspace), "\"/>" EOL, NULL); + } + +} + +int ne_propfind_allprop(ne_propfind_handler *handler, + ne_props_result results, void *userdata) +{ + ne_buffer_zappend(handler->body, "<allprop/></propfind>" EOL); + return propfind(handler, results, userdata); +} + +int ne_propfind_named(ne_propfind_handler *handler, const ne_propname *props, + ne_props_result results, void *userdata) +{ + set_body(handler, props); + ne_buffer_zappend(handler->body, "</prop></propfind>" EOL); + return propfind(handler, results, userdata); +} + + +/* The easy one... PROPPATCH */ +int ne_proppatch(ne_session *sess, const char *uri, + const ne_proppatch_operation *items) +{ + ne_request *req = ne_request_create(sess, "PROPPATCH", uri); + ne_buffer *body = ne_buffer_create(); + int n, ret; + + /* Create the request body */ + ne_buffer_zappend(body, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" EOL + "<D:propertyupdate xmlns:D=\"DAV:\">"); + + for (n = 0; items[n].name != NULL; n++) { + const char *elm = (items[n].type == ne_propset) ? "set" : "remove"; + + /* <set><prop><prop-name>value</prop-name></prop></set> */ + ne_buffer_concat(body, "<D:", elm, "><D:prop>" + "<", items[n].name->name, NULL); + + if (items[n].name->nspace) { + ne_buffer_concat(body, " xmlns=\"", items[n].name->nspace, "\"", NULL); + } + + if (items[n].type == ne_propset) { + ne_buffer_concat(body, ">", items[n].value, NULL); + } else { + ne_buffer_append(body, ">", 1); + } + + ne_buffer_concat(body, "</", items[n].name->name, "></D:prop></D:", elm, ">" + EOL, NULL); + } + + ne_buffer_zappend(body, "</D:propertyupdate>" EOL); + + ne_set_request_body_buffer(req, body->data, ne_buffer_size(body)); + ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE); + +#ifdef USE_DAV_LOCKS + ne_lock_using_resource(req, uri, NE_DEPTH_ZERO); +#endif + + ret = ne_simple_request(sess, req); + + ne_buffer_destroy(body); + + return ret; +} + +/* Compare two property names. */ +static int pnamecmp(const ne_propname *pn1, const ne_propname *pn2) +{ + if (pn1->nspace == NULL && pn2->nspace != NULL) { + return 1; + } else if (pn1->nspace != NULL && pn2->nspace == NULL) { + return -1; + } else if (pn1->nspace == NULL) { + return strcmp(pn1->name, pn2->name); + } else { + return (strcmp(pn1->nspace, pn2->nspace) || + strcmp(pn1->name, pn2->name)); + } +} + +/* Find property in 'set' with name 'pname'. If found, set pstat_ret + * to the containing propstat, likewise prop_ret, and returns zero. + * If not found, returns non-zero. */ +static int findprop(const ne_prop_result_set *set, const ne_propname *pname, + struct propstat **pstat_ret, struct prop **prop_ret) +{ + + int ps, p; + + for (ps = 0; ps < set->numpstats; ps++) { + for (p = 0; p < set->pstats[ps].numprops; p++) { + struct prop *prop = &set->pstats[ps].props[p]; + + if (pnamecmp(&prop->pname, pname) == 0) { + if (pstat_ret != NULL) + *pstat_ret = &set->pstats[ps]; + if (prop_ret != NULL) + *prop_ret = prop; + return 0; + } + } + } + + return -1; +} + +const char *ne_propset_value(const ne_prop_result_set *set, + const ne_propname *pname) +{ + struct prop *prop; + + if (findprop(set, pname, NULL, &prop)) { + return NULL; + } else { + return prop->value; + } +} + +const char *ne_propset_lang(const ne_prop_result_set *set, + const ne_propname *pname) +{ + struct prop *prop; + + if (findprop(set, pname, NULL, &prop)) { + return NULL; + } else { + return prop->lang; + } +} + +void *ne_propfind_current_private(ne_propfind_handler *handler) +{ + return handler->current ? handler->current->private : NULL; +} + +void *ne_propset_private(const ne_prop_result_set *set) +{ + return set->private; +} + +int ne_propset_iterate(const ne_prop_result_set *set, + ne_propset_iterator iterator, void *userdata) +{ + int ps, p; + + for (ps = 0; ps < set->numpstats; ps++) { + for (p = 0; p < set->pstats[ps].numprops; p++) { + struct prop *prop = &set->pstats[ps].props[p]; + int ret = iterator(userdata, &prop->pname, prop->value, + &set->pstats[ps].status); + if (ret) + return ret; + + } + } + + return 0; +} + +const ne_status *ne_propset_status(const ne_prop_result_set *set, + const ne_propname *pname) +{ + struct propstat *pstat; + + if (findprop(set, pname, &pstat, NULL)) { + /* TODO: it is tempting to return a dummy status object here + * rather than NULL, which says "Property result was not given + * by server." but I'm not sure if this is best left to the + * client. */ + return NULL; + } else { + return &pstat->status; + } +} + +static void *start_response(void *userdata, const char *href) +{ + ne_prop_result_set *set = ne_calloc(sizeof(*set)); + ne_propfind_handler *hdl = userdata; + + set->href = ne_strdup(href); + + if (hdl->private_creator != NULL) { + set->private = hdl->private_creator(hdl->private_userdata, href); + } + + hdl->current = set; + + return set; +} + +static void *start_propstat(void *userdata, void *response) +{ + ne_prop_result_set *set = response; + int n; + struct propstat *pstat; + + n = set->numpstats; + set->pstats = ne_realloc(set->pstats, sizeof(struct propstat) * (n+1)); + set->numpstats = n+1; + + pstat = &set->pstats[n]; + memset(pstat, 0, sizeof(*pstat)); + + /* And return this as the new pstat. */ + return &set->pstats[n]; +} + +static int startelm(void *userdata, int parent, + const char *nspace, const char *name, const char **atts) +{ + ne_propfind_handler *hdl = userdata; + struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207); + struct prop *prop; + int n; + const char *lang; + + /* Just handle all children of propstat and their descendants. */ + if ((parent != NE_207_STATE_PROP && parent != ELM_flatprop) + || pstat == NULL) + return NE_XML_DECLINE; + + if (parent == ELM_flatprop) { + /* collecting the flatprop value. */ + hdl->depth++; + if (hdl->value->used < MAX_FLATPROP_LEN) + ne_buffer_concat(hdl->value, "<", name, ">", NULL); + return ELM_flatprop; + } + + /* Add a property to this propstat */ + n = pstat->numprops; + + pstat->props = ne_realloc(pstat->props, sizeof(struct prop) * (n + 1)); + pstat->numprops = n+1; + + /* Fill in the new property. */ + prop = &pstat->props[n]; + + prop->pname.name = prop->name = ne_strdup(name); + if (nspace[0] == '\0') { + prop->pname.nspace = prop->nspace = NULL; + } else { + prop->pname.nspace = prop->nspace = ne_strdup(nspace); + } + prop->value = NULL; + + NE_DEBUG(NE_DBG_XML, "Got property #%d: {%s}%s.\n", n, + NSPACE(prop->nspace), prop->name); + + /* This is under discussion at time of writing (April '01), and it + * looks like we need to retrieve the xml:lang property from any + * element here or above. + * + * Also, I think we might need attribute namespace handling here. */ + lang = ne_xml_get_attr(hdl->parser, atts, NULL, "xml:lang"); + if (lang != NULL) { + prop->lang = ne_strdup(lang); + NE_DEBUG(NE_DBG_XML, "Property language is %s\n", prop->lang); + } else { + prop->lang = NULL; + } + + hdl->depth = 0; + + return ELM_flatprop; +} + +static int endelm(void *userdata, int state, + const char *nspace, const char *name) +{ + ne_propfind_handler *hdl = userdata; + struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207); + int n; + + if (hdl->depth > 0) { + /* nested. */ + if (hdl->value->used < MAX_FLATPROP_LEN) + ne_buffer_concat(hdl->value, "</", name, ">", NULL); + hdl->depth--; + } else { + /* end of the current property value */ + n = pstat->numprops - 1; + pstat->props[n].value = ne_buffer_finish(hdl->value); + hdl->value = ne_buffer_create(); + } + return 0; +} + +static void end_propstat(void *userdata, void *pstat_v, + const ne_status *status, + const char *description) +{ + struct propstat *pstat = pstat_v; + + /* Nothing to do if no status was given. */ + if (!status) return; + + /* If we get a non-2xx response back here, we wipe the value for + * each of the properties in this propstat, so the caller knows to + * look at the status instead. It's annoying, since for each prop + * we will have done an unnecessary strdup("") above, but there is + * no easy way round that given the fact that we don't know + * whether we've got an error or not till after we get the + * property element. + * + * Interestingly IIS breaks the 2518 DTD and puts the status + * element first in the propstat. This is useful since then we + * *do* know whether each subsequent empty prop element means, but + * we can't rely on that here. */ + if (status->klass != 2) { + int n; + + for (n = 0; n < pstat->numprops; n++) { + ne_free(pstat->props[n].value); + pstat->props[n].value = NULL; + } + } + + /* copy the status structure, and dup the reason phrase. */ + pstat->status = *status; + pstat->status.reason_phrase = ne_strdup(status->reason_phrase); +} + +/* Frees up a results set */ +static void free_propset(ne_prop_result_set *set) +{ + int n; + + for (n = 0; n < set->numpstats; n++) { + int m; + struct propstat *p = &set->pstats[n]; + + for (m = 0; m < p->numprops; m++) { + NE_FREE(p->props[m].nspace); + ne_free(p->props[m].name); + NE_FREE(p->props[m].lang); + NE_FREE(p->props[m].value); + } + + if (p->status.reason_phrase) + ne_free(p->status.reason_phrase); + if (p->props) + ne_free(p->props); + } + + if (set->pstats) + ne_free(set->pstats); + ne_free(set->href); + ne_free(set); +} + +static void end_response(void *userdata, void *resource, + const ne_status *status, + const char *description) +{ + ne_propfind_handler *handler = userdata; + ne_prop_result_set *set = resource; + + /* Pass back the results for this resource. */ + if (handler->callback && set->numpstats > 0) + handler->callback(handler->userdata, set->href, set); + + /* Clean up the propset tree we've just built. */ + free_propset(set); + handler->current = NULL; +} + +ne_propfind_handler * +ne_propfind_create(ne_session *sess, const char *uri, int depth) +{ + ne_propfind_handler *ret = ne_calloc(sizeof(ne_propfind_handler)); + + ret->parser = ne_xml_create(); + ret->parser207 = ne_207_create(ret->parser, ret); + ret->sess = sess; + ret->body = ne_buffer_create(); + ret->request = ne_request_create(sess, "PROPFIND", uri); + ret->value = ne_buffer_create(); + + ne_add_depth_header(ret->request, depth); + + ne_207_set_response_handlers(ret->parser207, + start_response, end_response); + + ne_207_set_propstat_handlers(ret->parser207, start_propstat, + end_propstat); + + /* The start of the request body is fixed: */ + ne_buffer_concat(ret->body, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL + "<propfind xmlns=\"DAV:\">", NULL); + + return ret; +} + +/* Destroy a propfind handler */ +void ne_propfind_destroy(ne_propfind_handler *handler) +{ + ne_buffer_destroy(handler->value); + if (handler->current) + free_propset(handler->current); + ne_207_destroy(handler->parser207); + ne_xml_destroy(handler->parser); + ne_buffer_destroy(handler->body); + ne_request_destroy(handler->request); + ne_free(handler); +} + +int ne_simple_propfind(ne_session *sess, const char *href, int depth, + const ne_propname *props, + ne_props_result results, void *userdata) +{ + ne_propfind_handler *hdl; + int ret; + + hdl = ne_propfind_create(sess, href, depth); + if (props != NULL) { + ret = ne_propfind_named(hdl, props, results, userdata); + } else { + ret = ne_propfind_allprop(hdl, results, userdata); + } + + ne_propfind_destroy(hdl); + + return ret; +} + +int ne_propnames(ne_session *sess, const char *href, int depth, + ne_props_result results, void *userdata) +{ + ne_propfind_handler *hdl; + int ret; + + hdl = ne_propfind_create(sess, href, depth); + + ne_buffer_zappend(hdl->body, "<propname/></propfind>"); + + ret = propfind(hdl, results, userdata); + + ne_propfind_destroy(hdl); + + return ret; +} + +void ne_propfind_set_private(ne_propfind_handler *hdl, + ne_props_create_complex creator, + void *userdata) +{ + hdl->private_creator = creator; + hdl->private_userdata = userdata; +} diff --git a/src/ne_props.h b/src/ne_props.h new file mode 100644 index 0000000..b86ac76 --- /dev/null +++ b/src/ne_props.h @@ -0,0 +1,244 @@ +/* + WebDAV Properties manipulation + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_PROPS_H +#define NE_PROPS_H + +#include "ne_request.h" +#include "ne_207.h" + +BEGIN_NEON_DECLS + +/* There are two interfaces for fetching properties. The first is + * 'ne_simple_propfind', which is relatively simple, and easy to use, + * but only lets you fetch FLAT properties, i.e. properties which are + * just a string of bytes. The complex interface is 'ne_propfind_*', + * which is complicated, and hard to use, but lets you parse + * structured properties, i.e. properties which have XML content. */ + +/* The 'ne_simple_propfind' interface. *** + * + * ne_simple_propfind allows you to fetch a set of properties for a + * single resource, or a tree of resources. You set the operation + * going by passing these arguments: + * + * - the session which should be used. + * - the URI and the depth of the operation (0, 1, infinite) + * - the names of the properties which you want to fetch + * - a results callback, and the userdata for the callback. + * + * For each resource found, the results callback is called, passing + * you two things along with the userdata you passed in originally: + * + * - the URI of the resource (const char *href) + * - the properties results set (const ne_prop_result_set *results) + * */ + +/* The name of a WebDAV property. 'nspace' may be NULL. */ +typedef struct { + const char *nspace, *name; +} ne_propname; + +typedef struct ne_prop_result_set_s ne_prop_result_set; + +/* Get the value of a given property. Will return NULL if there was an + * error fetching this property on this resource. Call + * ne_propset_result to get the response-status if so. */ +const char *ne_propset_value(const ne_prop_result_set *set, + const ne_propname *propname); + +/* Returns the status structure for fetching the given property on + * this resource. This function will return NULL if the server did not + * return the property (which is a server error). */ +const ne_status *ne_propset_status(const ne_prop_result_set *set, + const ne_propname *propname); + +/* Returns the private pointer for the given propset. */ +void *ne_propset_private(const ne_prop_result_set *set); + +/* Return language string of property (may be NULL). */ +const char *ne_propset_lang(const ne_prop_result_set *set, + const ne_propname *pname); + +/* ne_propset_iterate iterates over a properties result set, + * calling the callback for each property in the set. userdata is + * passed as the first argument to the callback. value may be NULL, + * indicating an error occurred fetching this property: look at + * status for the error in that case. + * + * If the iterator returns non-zero, ne_propset_iterate will return + * immediately with that value. + */ +typedef int (*ne_propset_iterator)(void *userdata, + const ne_propname *pname, + const char *value, + const ne_status *status); + +/* Iterate over all the properties in 'set', calling 'iterator' + * for each, passing 'userdata' as the first argument to callback. + * + * Returns: + * whatever value iterator returns. + */ +int ne_propset_iterate(const ne_prop_result_set *set, + ne_propset_iterator iterator, void *userdata); + +/* Callback for handling the results of fetching properties for a + * single resource (named by 'href'). The results are stored in the + * result set 'results': use ne_propset_* to examine this object. */ +typedef void (*ne_props_result)(void *userdata, const char *href, + const ne_prop_result_set *results); + +/* Fetch properties for a resource (if depth == NE_DEPTH_ZERO), + * or a tree of resources (if depth == NE_DEPTH_ONE or _INFINITE). + * + * Names of the properties required must be given in 'props', + * or if props is NULL, *all* properties are fetched. + * + * 'results' is called for each resource in the response, userdata is + * passed as the first argument to the callback. It is important to + * note that the callback is called as the response is read off the + * socket, so don't do anything silly in it (e.g. sleep(100), or call + * any functions which use this session). + * + * Note that if 'depth' is NE_DEPTH_INFINITY, some servers may refuse + * the request. + * + * Returns NE_*. */ +int ne_simple_propfind(ne_session *sess, const char *path, int depth, + const ne_propname *props, + ne_props_result results, void *userdata); + +/* The properties of a resource can be manipulated using ne_proppatch. + * A single proppatch request may include any number of individual + * "set" and "remove" operations, and is defined to have + * "all-or-nothing" semantics, so either all the operations succeed, + * or none do. */ + +/* A proppatch operation may either set a property to have a new + * value, in which case 'type' must be ne_propset, and 'value' must be + * non-NULL; or it can remove a property; in which case 'type' must be + * ne_propremove, and 'value' is ignored. In both cases, 'name' must + * be set to the name of the property to alter. */ +typedef struct { + const ne_propname *name; + enum { + ne_propset, + ne_propremove + } type; + const char *value; +} ne_proppatch_operation; + +/* Execute a set of property operations 'ops' on 'path'. 'ops' is an + * array terminated by an operation with a NULL 'name' field. Returns + * NE_*. */ +int ne_proppatch(ne_session *sess, const char *path, + const ne_proppatch_operation *ops); + +/* Retrieve property names for the resources at 'path'. 'results' + * callback is called for each resource. Use 'ne_propset_iterate' on + * the passed results object to retrieve the list of property names. + * */ +int ne_propnames(ne_session *sess, const char *path, int depth, + ne_props_result results, void *userdata); + +/* The complex, you-do-all-the-work, property fetch interface: + */ + +struct ne_propfind_handler_s; +typedef struct ne_propfind_handler_s ne_propfind_handler; + +/* Retrieve the 'private' pointer for the current propset for the + * given handler, as returned by the ne_props_create_complex callback + * installed using 'ne_propfind_set_private'. If this callback was + * not registered, this function will return NULL. */ +void *ne_propfind_current_private(ne_propfind_handler *handler); + +/* Create a PROPFIND handler, for the given resource or set of + * resources. + * + * Depth must be one of NE_DEPTH_*. */ +ne_propfind_handler * +ne_propfind_create(ne_session *sess, const char *path, int depth); + +/* Return the XML parser for the given handler (only need if you want + * to handle complex properties). */ +ne_xml_parser *ne_propfind_get_parser(ne_propfind_handler *handler); + +/* This interface reserves the state integer range 'x' where 0 < x + * and x < NE_PROPS_STATE_TOP. */ +#define NE_PROPS_STATE_TOP (NE_207_STATE_TOP + 100) + +/* Return the request object for the given handler. You MUST NOT use + * ne_set_request_body_* on this request object. (this call is only + * needed if for instance, you want to add extra headers to the + * PROPFIND request). The result of using the request pointer after + * ne_propfind_destroy(handler) has been called is undefined. */ +ne_request *ne_propfind_get_request(ne_propfind_handler *handler); + +/* A "complex property" has a value which is structured XML. To handle + * complex properties, you must set up and register an XML handler + * which will understand the elements which make up such properties. + * The handler must be registered with the parser returned by + * 'ne_propfind_get_parser'. + * + * To store the parsed value of the property, a 'private' structure is + * allocated in each propset (i.e. one per resource). When parsing the + * property value elements, for each new resource encountered in the + * response, the 'creator' callback is called to retrieve a 'private' + * structure for this resource. + * + * Whilst in XML element callbacks you will have registered to handle + * complex properties, you can use the 'ne_propfind_current_private' + * call to retrieve the pointer to this private structure. + * + * To retrieve this 'private' structure from the propset in the + * results callback, simply call 'ne_propset_private'. + * */ +typedef void *(*ne_props_create_complex)(void *userdata, + const char *href); + +void ne_propfind_set_private(ne_propfind_handler *handler, + ne_props_create_complex creator, + void *userdata); + +/* Fetch all properties. + * + * Returns NE_*. */ +int ne_propfind_allprop(ne_propfind_handler *handler, + ne_props_result result, void *userdata); + +/* Fetch all properties with names listed in array 'names', which is + * terminated by a property with a NULL name field. For each resource + * encountered, the result callback will be invoked, passing in + * 'userdata' as the first argument. + * + * Returns NE_*. */ +int ne_propfind_named(ne_propfind_handler *handler, + const ne_propname *names, + ne_props_result result, void *userdata); + +/* Destroy a propfind handler after use. */ +void ne_propfind_destroy(ne_propfind_handler *handler); + +END_NEON_DECLS + +#endif /* NE_PROPS_H */ diff --git a/src/ne_redirect.c b/src/ne_redirect.c new file mode 100644 index 0000000..1985793 --- /dev/null +++ b/src/ne_redirect.c @@ -0,0 +1,142 @@ +/* + HTTP-redirect support + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "ne_session.h" +#include "ne_request.h" +#include "ne_alloc.h" +#include "ne_uri.h" +#include "ne_redirect.h" +#include "ne_i18n.h" +#include "ne_string.h" + +#define REDIRECT_ID "http://www.webdav.org/neon/hooks/http-redirect" + +struct redirect { + char *location; + char *requri; + int valid; /* non-zero if .uri contains a redirect */ + ne_uri uri; + ne_session *sess; +}; + +static void +create(ne_request *req, void *session, const char *method, const char *uri) +{ + struct redirect *red = session; + NE_FREE(red->location); + NE_FREE(red->requri); + red->requri = ne_strdup(uri); + ne_add_response_header_handler(req, "Location", ne_duplicate_header, + &red->location); +} + +#define REDIR(n) ((n) == 301 || (n) == 302 || (n) == 303 || \ + (n) == 307) + +static int post_send(ne_request *req, void *private, const ne_status *status) +{ + struct redirect *red = private; + + /* Don't do anything for non-redirect status or no Location header. */ + if (!REDIR(status->code) || red->location == NULL) + return NE_OK; + + if (strstr(red->location, "://") == NULL && red->location[0] != '/') { + /* FIXME: remove this relative URI resolution hack. */ + ne_buffer *path = ne_buffer_create(); + char *pnt; + + ne_buffer_zappend(path, red->requri); + pnt = strrchr(path->data, '/'); + + if (pnt && *(pnt+1) != '\0') { + /* Chop off last path segment. */ + *(pnt+1) = '\0'; + ne_buffer_altered(path); + } + ne_buffer_zappend(path, red->location); + ne_free(red->location); + red->location = ne_buffer_finish(path); + } + + /* free last uri. */ + ne_uri_free(&red->uri); + + /* Parse the Location header */ + if (ne_uri_parse(red->location, &red->uri) || red->uri.path == NULL) { + red->valid = 0; + ne_set_error(red->sess, _("Could not parse redirect location.")); + return NE_ERROR; + } + + /* got a valid redirect. */ + red->valid = 1; + + if (!red->uri.host) { + /* Not an absoluteURI: breaks 2616 but everybody does it. */ + ne_fill_server_uri(red->sess, &red->uri); + } + + return NE_REDIRECT; +} + +static void free_redirect(void *cookie) +{ + struct redirect *red = cookie; + NE_FREE(red->location); + ne_uri_free(&red->uri); + if (red->requri) + ne_free(red->requri); + ne_free(red); +} + +void ne_redirect_register(ne_session *sess) +{ + struct redirect *red = ne_calloc(sizeof *red); + + red->sess = sess; + + ne_hook_create_request(sess, create, red); + ne_hook_post_send(sess, post_send, red); + ne_hook_destroy_session(sess, free_redirect, red); + + ne_set_session_private(sess, REDIRECT_ID, red); +} + +const ne_uri *ne_redirect_location(ne_session *sess) +{ + struct redirect *red = ne_get_session_private(sess, REDIRECT_ID); + + if (red && red->valid) + return &red->uri; + else + return NULL; +} + diff --git a/src/ne_redirect.h b/src/ne_redirect.h new file mode 100644 index 0000000..d0dd7b5 --- /dev/null +++ b/src/ne_redirect.h @@ -0,0 +1,41 @@ +/* + HTTP-redirect support + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_REDIRECT_H +#define NE_REDIRECT_H + +#include "ne_request.h" + +BEGIN_NEON_DECLS + +/* Register redirect handling: if a redirection response is given, the + * request will fail with the NE_REDIRECT code, and the destinsation + * of the redirect can be retrieved using ne_redirect_location(). */ +void ne_redirect_register(ne_session *sess); + +/* Returns location of last redirect. Will return NULL if no redirect + * has been encountered for given session, or the last redirect + * encountered could not be parsed. */ +const ne_uri *ne_redirect_location(ne_session *sess); + +END_NEON_DECLS + +#endif /* NE_REDIRECT_H */ diff --git a/src/ne_request.c b/src/ne_request.c new file mode 100644 index 0000000..0d39214 --- /dev/null +++ b/src/ne_request.c @@ -0,0 +1,1389 @@ +/* + HTTP request/response handling + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* This is the HTTP client request/response implementation. + * The goal of this code is to be modular and simple. + */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef __EMX__ +#include <sys/select.h> +#endif + +#ifdef HAVE_LIMITS_H +#include <limits.h> /* for UINT_MAX etc */ +#endif + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_i18n.h" + +#include "ne_alloc.h" +#include "ne_request.h" +#include "ne_string.h" /* for ne_buffer */ +#include "ne_utils.h" +#include "ne_socket.h" +#include "ne_uri.h" + +#include "ne_private.h" + +#define HTTP_EXPECT_TIMEOUT 15 +/* 100-continue only used if size > HTTP_EXPECT_MINSIZ */ +#define HTTP_EXPECT_MINSIZE 1024 + +/* with thanks to Jim Blandy; this macro simplified loads of code. */ +#define HTTP_ERR(x) do { int _ret = (x); if (_ret != NE_OK) return _ret; } while (0) + +#define SOCK_ERR(req, op, msg) do { ssize_t sret = (op); \ +if (sret < 0) return aborted(req, msg, sret); } while (0) + +/* This is called with each of the headers in the response */ +struct header_handler { + char *name; + ne_header_handler handler; + void *userdata; + struct header_handler *next; +}; + +/* TODO: could unify these all into a generic callback list */ + +struct body_reader { + ne_block_reader handler; + ne_accept_response accept_response; + unsigned int use:1; + void *userdata; + struct body_reader *next; +}; + +struct ne_request_s { + char *method, *uri; /* method and Request-URI */ + + ne_buffer *headers; /* request headers */ + + /* Request body. */ + ne_provide_body body_cb; + void *body_ud; + + /* Comes from either an fd or a buffer. */ + union { + int fd; + struct { + const char *buffer, *pnt; + size_t left; + } buf; + } body; + + size_t body_size, body_progress; + + /* temporary store for response lines. */ + char respbuf[BUFSIZ]; + + /**** Response ***/ + + /* The transfer encoding types */ + struct ne_response { + int length; /* Response entity-body content-length */ + size_t left; /* Bytes left to read */ + size_t chunk_left; /* Bytes of chunk left to read */ + size_t total; /* total bytes read so far. */ + /* how the message length is detemined: */ + enum { + R_TILLEOF = 0, /* read till eof */ + R_NO_BODY, /* implicitly no body (HEAD, 204, 304) */ + R_CHUNKED, /* using chunked transfer-encoding */ + R_CLENGTH /* using given content-length */ + } mode; + } resp; + + /* List of callbacks which are passed response headers */ + struct header_handler *header_catchers; + + struct hook *private; + + /* We store response header handlers in a hash table. The hash is + * derived from the header name in lower case. */ + + /* FIXME: this comment is WAY out of date, and no longer in any + * way true. */ + + /* 53 is magic, of course. For a standard ne_get() (with + * redirects), 9 header handlers are defined. Two of these are + * for Content-Length (which is a bug, and should be fixed + * really). Ignoring that hash clash, the 8 *different* handlers + * all hash uniquely into the hash table of size 53. */ +#define HH_HASHSIZE 53 + + struct header_handler *header_handlers[HH_HASHSIZE]; + /* List of callbacks which are passed response body blocks */ + struct body_reader *body_readers; + + /*** Miscellaneous ***/ + unsigned int method_is_head:1; + unsigned int use_expect100:1; + unsigned int can_persist:1; + + ne_session *session; + ne_status status; +}; + +static int open_connection(ne_request *req); + +/* The iterative step used to produce the hash value. This is DJB's + * magic "*33" hash function. Ralf Engelschall has done some amazing + * statistical analysis to show that *33 really is a good hash + * function: check the new-httpd list archives, or his 'str' library + * source code, for the details. + * + * TODO: due to limited range of characters used in header names, + * could maybe get a better hash function to use? */ + +#define HH_ITERATE(hash, ch) (((hash)*33 + (ch)) % HH_HASHSIZE); + +/* Returns hash value for header 'name', converting it to lower-case + * in-place. */ +static inline unsigned int hash_and_lower(char *name) +{ + char *pnt; + unsigned int hash = 0; + + for (pnt = name; *pnt != '\0'; pnt++) { + *pnt = tolower(*pnt); + hash = HH_ITERATE(hash,*pnt); + } + + return hash; +} + +/* Abort a request due to an non-recoverable HTTP protocol error, + * whilst doing 'doing'. 'code', if non-zero, is the socket error + * code, NE_SOCK_*, or if zero, is ignored. */ +static int aborted(ne_request *req, const char *doing, ssize_t code) +{ + ne_session *sess = req->session; + int ret = NE_ERROR; + + NE_DEBUG(NE_DBG_HTTP, "Aborted request (%" NE_FMT_SSIZE_T "): %s\n", + code, doing); + + switch(code) { + case NE_SOCK_CLOSED: + if (sess->use_proxy) { + ne_set_error(sess, _("%s: connection was closed by proxy server."), + doing); + } else { + ne_set_error(sess, _("%s: connection was closed by server."), + doing); + } + break; + case NE_SOCK_TIMEOUT: + ne_set_error(sess, _("%s: connection timed out."), doing); + ret = NE_TIMEOUT; + break; + case NE_SOCK_ERROR: + case NE_SOCK_RESET: + case NE_SOCK_TRUNC: + ne_set_error(sess, "%s: %s", doing, ne_sock_error(sess->socket)); + break; + case 0: + ne_set_error(sess, "%s", doing); + break; + } + + ne_close_connection(sess); + return ret; +} + +static void notify_status(ne_session *sess, ne_conn_status status, + const char *info) +{ + if (sess->notify_cb) { + sess->notify_cb(sess->notify_ud, status, info); + } +} + +void ne_duplicate_header(void *userdata, const char *value) +{ + char **location = userdata; + *location = ne_strdup(value); +} + +void ne_handle_numeric_header(void *userdata, const char *value) +{ + int *location = userdata; + *location = atoi(value); +} + +static void *get_private(const struct hook *hk, const char *id) +{ + for (; hk != NULL; hk = hk->next) + if (strcmp(hk->id, id) == 0) + return hk->userdata; + return NULL; +} + +void *ne_get_request_private(ne_request *req, const char *id) +{ + return get_private(req->private, id); +} + +void *ne_get_session_private(ne_session *sess, const char *id) +{ + return get_private(sess->private, id); +} + +typedef void (*void_fn)(void); + +#define ADD_HOOK(hooks, fn, ud) add_hook(&(hooks), NULL, (void_fn)(fn), (ud)) + +static void add_hook(struct hook **hooks, const char *id, void_fn fn, void *ud) +{ + struct hook *hk = ne_malloc(sizeof (struct hook)), *pos; + + if (*hooks != NULL) { + for (pos = *hooks; pos->next != NULL; pos = pos->next) + /* nullop */; + pos->next = hk; + } else { + *hooks = hk; + } + + hk->id = id; + hk->fn = fn; + hk->userdata = ud; + hk->next = NULL; +} + +void ne_hook_create_request(ne_session *sess, + ne_create_request_fn fn, void *userdata) +{ + ADD_HOOK(sess->create_req_hooks, fn, userdata); +} + +void ne_hook_pre_send(ne_session *sess, ne_pre_send_fn fn, void *userdata) +{ + ADD_HOOK(sess->pre_send_hooks, fn, userdata); +} + +void ne_hook_post_send(ne_session *sess, ne_post_send_fn fn, void *userdata) +{ + ADD_HOOK(sess->post_send_hooks, fn, userdata); +} + +void ne_hook_destroy_request(ne_session *sess, + ne_destroy_req_fn fn, void *userdata) +{ + ADD_HOOK(sess->destroy_req_hooks, fn, userdata); +} + +void ne_hook_destroy_session(ne_session *sess, + ne_destroy_sess_fn fn, void *userdata) +{ + ADD_HOOK(sess->destroy_sess_hooks, fn, userdata); +} + +void ne_set_session_private(ne_session *sess, const char *id, void *userdata) +{ + add_hook(&sess->private, id, NULL, userdata); +} + +void ne_set_request_private(ne_request *req, const char *id, void *userdata) +{ + add_hook(&req->private, id, NULL, userdata); +} + +static ssize_t body_string_send(void *userdata, char *buffer, size_t count) +{ + ne_request *req = userdata; + + if (count == 0) { + req->body.buf.left = req->body_size; + req->body.buf.pnt = req->body.buf.buffer; + } else { + /* if body_left == 0 we fall through and return 0. */ + if (req->body.buf.left < count) + count = req->body.buf.left; + + memcpy(buffer, req->body.buf.pnt, count); + req->body.buf.pnt += count; + req->body.buf.left -= count; + } + + return count; +} + +static ssize_t body_fd_send(void *userdata, char *buffer, size_t count) +{ + ne_request *req = userdata; + + if (count) { + return read(req->body.fd, buffer, count); + } else { + /* rewind since we may have to send it again */ + return lseek(req->body.fd, SEEK_SET, 0); + } +} + +/* Pulls the request body from the source and pushes it to the given + * callback. Returns 0 on success, or NE_* code */ +int ne_pull_request_body(ne_request *req, ne_push_fn fn, void *ud) +{ + int ret = 0; + char buffer[BUFSIZ]; + ssize_t bytes; + + /* tell the source to start again from the beginning. */ + (void) req->body_cb(req->body_ud, NULL, 0); + + /* TODO: should this attempt to pull exactly the number of bytes + * they specified were in the body? Currently it just pulls until + * they return zero. That makes it possible to extend this to do + * chunked request bodies (i.e. indefinitely long, no C-L), so + * this is probably a better long-term interface. */ + while ((bytes = req->body_cb(req->body_ud, buffer, sizeof buffer)) > 0) { + ret = fn(ud, buffer, bytes); + if (ret < 0) + break; + NE_DEBUG(NE_DBG_HTTPBODY, + "Body block (%" NE_FMT_SSIZE_T " bytes):\n[%.*s]\n", + bytes, (int)bytes, buffer); + } + + if (bytes < 0) { + ne_set_error(req->session, _("Error reading request body.")); + ret = NE_ERROR; + } + + return ret; +} + +static int send_with_progress(void *userdata, const char *data, size_t n) +{ + ne_request *req = userdata; + int ret; + + ret = ne_sock_fullwrite(req->session->socket, data, n); + if (ret == 0) { + req->body_progress += n; + req->session->progress_cb(req->session->progress_ud, + req->body_progress, req->body_size); + } + + return ret; +} + +/* Sends the request body down the socket. + * Returns 0 on success, or NE_* code */ +static int send_request_body(ne_request *req) +{ + int ret; + NE_DEBUG(NE_DBG_HTTP, "Sending request body...\n"); + if (req->session->progress_cb) { + /* with progress callbacks. */ + req->body_progress = 0; + ret = ne_pull_request_body(req, send_with_progress, req); + } else { + /* without progress callbacks. */ + ret = ne_pull_request_body(req, (ne_push_fn)ne_sock_fullwrite, + req->session->socket); + } + + NE_DEBUG(NE_DBG_HTTP, "Request body sent: %s.\n", ret?"failed":"okay"); + return ret; +} + +/* Lob the User-Agent, connection and host headers in to the request + * headers */ +static void add_fixed_headers(ne_request *req) +{ + if (req->session->user_agent) { + ne_buffer_concat(req->headers, + "User-Agent: ", req->session->user_agent, EOL, NULL); + } + /* Send Connection: Keep-Alive for pre-1.1 origin servers, so we + * might get a persistent connection. 2068 sec 19.7.1 says we MUST + * NOT do this for proxies, though. So we don't. Note that on the + * first request on any session, we don't know whether the server + * is 1.1 compliant, so we presume that it is not. */ + if (!req->session->is_http11 && !req->session->use_proxy) { + ne_buffer_zappend(req->headers, "Keep-Alive: " EOL); + ne_buffer_zappend(req->headers, "Connection: TE, Keep-Alive" EOL); + } else { + ne_buffer_zappend(req->headers, "Connection: TE" EOL); + } + /* We send TE: trailers since we understand trailers in the chunked + * response. */ + ne_buffer_zappend(req->headers, "TE: trailers" EOL); + +} + +int ne_accept_always(void *userdata, ne_request *req, const ne_status *st) +{ + return 1; +} + +int ne_accept_2xx(void *userdata, ne_request *req, const ne_status *st) +{ + return (st->klass == 2); +} + +/* Handler for the "Transfer-Encoding" response header: treat *any* + * such header as implying a chunked response, per the "Protocol + * Compliance" statement in the manual. */ +static void te_hdr_handler(void *userdata, const char *value) +{ + struct ne_response *resp = userdata; + + resp->mode = R_CHUNKED; +} + +/* Handler for the "Connection" response header */ +static void connection_hdr_handler(void *userdata, const char *value) +{ + ne_request *req = userdata; + if (strcasecmp(value, "close") == 0) { + req->can_persist = 0; + } else if (strcasecmp(value, "Keep-Alive") == 0) { + req->can_persist = 1; + } +} + +static void clength_hdr_handler(void *userdata, const char *value) +{ + struct ne_response *resp = userdata; + size_t len = strtoul(value, NULL, 10); + if (len != ULONG_MAX && resp->mode == R_TILLEOF) { + resp->mode = R_CLENGTH; + resp->length = len; + } +} + +ne_request *ne_request_create(ne_session *sess, + const char *method, const char *path) +{ + ne_request *req = ne_calloc(sizeof *req); + + NE_DEBUG(NE_DBG_HTTP, "Creating request...\n"); + + req->session = sess; + req->headers = ne_buffer_create(); + + /* Add in the fixed headers */ + add_fixed_headers(req); + + /* Set the standard stuff */ + req->method = ne_strdup(method); + req->method_is_head = (strcmp(method, "HEAD") == 0); + + /* Add in handlers for all the standard HTTP headers. */ + ne_add_response_header_handler(req, "Content-Length", + clength_hdr_handler, &req->resp); + ne_add_response_header_handler(req, "Transfer-Encoding", + te_hdr_handler, &req->resp); + ne_add_response_header_handler(req, "Connection", + connection_hdr_handler, req); + + /* Only use an absoluteURI here when absolutely necessary: some + * servers can't parse them. */ + if (req->session->use_proxy && !req->session->use_ssl && path[0] == '/') + req->uri = ne_concat(req->session->scheme, "://", + req->session->server.hostport, path, NULL); + else + req->uri = ne_strdup(path); + + { + struct hook *hk; + + NE_DEBUG(NE_DBG_HTTP, "Running request create hooks.\n"); + + for (hk = sess->create_req_hooks; hk != NULL; hk = hk->next) { + ne_create_request_fn fn = (ne_create_request_fn)hk->fn; + fn(req, hk->userdata, method, req->uri); + } + } + + NE_DEBUG(NE_DBG_HTTP, "Request created.\n"); + + return req; +} + +static void set_body_size(ne_request *req, size_t size) +{ + req->body_size = size; + ne_print_request_header(req, "Content-Length", "%" NE_FMT_SIZE_T, size); +} + +void ne_set_request_body_buffer(ne_request *req, const char *buffer, + size_t size) +{ + req->body.buf.buffer = buffer; + req->body_cb = body_string_send; + req->body_ud = req; + set_body_size(req, size); +} + +void ne_set_request_body_provider(ne_request *req, size_t bodysize, + ne_provide_body provider, void *ud) +{ + req->body_cb = provider; + req->body_ud = ud; + set_body_size(req, bodysize); +} + +int ne_set_request_body_fd(ne_request *req, int fd) +{ + struct stat bodyst; + + /* Get file length */ + if (fstat(fd, &bodyst) < 0) { + char err[200]; + ne_strerror(errno, err, sizeof err); + ne_set_error(req->session, _("Could not determine file length: %s"), + err); + NE_DEBUG(NE_DBG_HTTP, "Stat failed: %s\n", err); + return -1; + } + req->body.fd = fd; + req->body_cb = body_fd_send; + req->body_ud = req; + set_body_size(req, bodyst.st_size); + return 0; +} + +void ne_add_request_header(ne_request *req, const char *name, + const char *value) +{ + ne_buffer_concat(req->headers, name, ": ", value, EOL, NULL); +} + +void ne_print_request_header(ne_request *req, const char *name, + const char *format, ...) +{ + va_list params; + char buf[BUFSIZ]; + + va_start(params, format); + ne_vsnprintf(buf, sizeof buf, format, params); + va_end(params); + + ne_buffer_concat(req->headers, name, ": ", buf, EOL, NULL); +} + +void +ne_add_response_header_handler(ne_request *req, const char *name, + ne_header_handler hdl, void *userdata) +{ + struct header_handler *new = ne_calloc(sizeof *new); + unsigned int hash; + new->name = ne_strdup(name); + new->handler = hdl; + new->userdata = userdata; + hash = hash_and_lower(new->name); + new->next = req->header_handlers[hash]; + req->header_handlers[hash] = new; +} + +void ne_add_response_header_catcher(ne_request *req, + ne_header_handler hdl, void *userdata) +{ + struct header_handler *new = ne_calloc(sizeof *new); + new->handler = hdl; + new->userdata = userdata; + new->next = req->header_catchers; + req->header_catchers = new; +} + +void ne_add_response_body_reader(ne_request *req, ne_accept_response acpt, + ne_block_reader rdr, void *userdata) +{ + struct body_reader *new = ne_malloc(sizeof *new); + new->accept_response = acpt; + new->handler = rdr; + new->userdata = userdata; + new->next = req->body_readers; + req->body_readers = new; +} + +void ne_request_destroy(ne_request *req) +{ + struct body_reader *rdr, *next_rdr; + struct header_handler *hdlr, *next_hdlr; + struct hook *hk, *next_hk; + int n; + + ne_free(req->uri); + ne_free(req->method); + + for (rdr = req->body_readers; rdr != NULL; rdr = next_rdr) { + next_rdr = rdr->next; + ne_free(rdr); + } + + for (hdlr = req->header_catchers; hdlr != NULL; hdlr = next_hdlr) { + next_hdlr = hdlr->next; + ne_free(hdlr); + } + + for (n = 0; n < HH_HASHSIZE; n++) { + for (hdlr = req->header_handlers[n]; hdlr != NULL; + hdlr = next_hdlr) { + next_hdlr = hdlr->next; + ne_free(hdlr->name); + ne_free(hdlr); + } + } + + ne_buffer_destroy(req->headers); + + NE_DEBUG(NE_DBG_HTTP, "Running destroy hooks.\n"); + for (hk = req->session->destroy_req_hooks; hk; hk = hk->next) { + ne_destroy_req_fn fn = (ne_destroy_req_fn)hk->fn; + fn(req, hk->userdata); + } + + for (hk = req->private; hk; hk = next_hk) { + next_hk = hk->next; + ne_free(hk); + } + + if (req->status.reason_phrase) + ne_free(req->status.reason_phrase); + + NE_DEBUG(NE_DBG_HTTP, "Request ends.\n"); + ne_free(req); +} + + +/* Reads a block of the response into buffer, which is of size buflen. + * Returns number of bytes read, 0 on end-of-response, or NE_* on error. + * TODO?: only make one actual read() call in here... + */ +static int read_response_block(ne_request *req, struct ne_response *resp, + char *buffer, size_t *buflen) +{ + size_t willread; + ssize_t readlen; + ne_socket *sock = req->session->socket; + switch (resp->mode) { + case R_CHUNKED: + /* We are doing a chunked transfer-encoding. + * It goes: `SIZE CRLF CHUNK CRLF SIZE CRLF CHUNK CRLF ...' + * ended by a `CHUNK CRLF 0 CRLF', a 0-sized chunk. + * The slight complication is that we have to cope with + * partial reads of chunks. + * For this reason, resp.chunk_left contains the number of + * bytes left to read in the current chunk. + */ + if (resp->chunk_left == 0) { + unsigned long int chunk_len; + char *ptr; + /* We are at the start of a new chunk. */ + NE_DEBUG(NE_DBG_HTTP, "New chunk.\n"); + SOCK_ERR(req, ne_sock_readline(sock, buffer, *buflen), + _("Could not read chunk size")); + NE_DEBUG(NE_DBG_HTTP, "[Chunk Size] < %s", buffer); + chunk_len = strtoul(buffer, &ptr, 16); + /* limit chunk size to <= UINT_MAX, so it will probably + * fit in a size_t. */ + if (ptr == buffer || + chunk_len == ULONG_MAX || chunk_len > UINT_MAX) { + return aborted(req, _("Could not parse chunk size"), 0); + } + NE_DEBUG(NE_DBG_HTTP, "Got chunk size: %lu\n", chunk_len); + if (chunk_len == 0) { + /* Zero-size chunk == end of response. */ + NE_DEBUG(NE_DBG_HTTP, "Zero-size chunk.\n"); + *buflen = 0; + return NE_OK; + } + resp->chunk_left = chunk_len; + } + willread = resp->chunk_left; + break; + case R_CLENGTH: + willread = resp->left; + break; + case R_TILLEOF: + willread = *buflen; + break; + case R_NO_BODY: + default: + willread = 0; + break; + } + if (willread > *buflen) willread = *buflen; + else if (willread == 0) { + *buflen = 0; + return 0; + } + NE_DEBUG(NE_DBG_HTTP, + "Reading %" NE_FMT_SIZE_T " bytes of response body.\n", willread); + readlen = ne_sock_read(sock, buffer, willread); + + /* EOF is only valid when response body is delimited by it. For + * interop with SSL servers which perform unclean shutdown, ignore + * a truncation if no response body has yet been read. */ + if (resp->mode == R_TILLEOF && + (readlen == NE_SOCK_CLOSED || + (readlen == NE_SOCK_TRUNC && resp->total == 0))) { + NE_DEBUG(NE_DBG_HTTP, "Got EOF.\n"); + req->can_persist = 0; + readlen = 0; + } else if (readlen < 0) { + return aborted(req, _("Could not read response body"), readlen); + } else { + NE_DEBUG(NE_DBG_HTTP, "Got %" NE_FMT_SSIZE_T " bytes.\n", readlen); + } + /* safe to cast: readlen guaranteed to be >= 0 above */ + *buflen = (size_t)readlen; + NE_DEBUG(NE_DBG_HTTPBODY, + "Read block (%" NE_FMT_SSIZE_T " bytes):\n[%.*s]\n", + readlen, (int)readlen, buffer); + if (resp->mode == R_CHUNKED) { + resp->chunk_left -= readlen; + if (resp->chunk_left == 0) { + char crlfbuf[2]; + /* If we've read a whole chunk, read a CRLF */ + readlen = ne_sock_fullread(sock, crlfbuf, 2); + if (readlen < 0) + return aborted(req, _("Could not read chunk delimiter"), + readlen); + else if (crlfbuf[0] != '\r' || crlfbuf[1] != '\n') + return aborted(req, _("Chunk delimiter was invalid"), 0); + } + } else if (resp->mode == R_CLENGTH) { + resp->left -= readlen; + } + return NE_OK; +} + +ssize_t ne_read_response_block(ne_request *req, char *buffer, size_t buflen) +{ + struct body_reader *rdr; + size_t readlen = buflen; + + if (read_response_block(req, &req->resp, buffer, &readlen)) + return -1; + + req->resp.total += readlen; + + if (req->session->progress_cb) { + req->session->progress_cb(req->session->progress_ud, req->resp.total, + (req->resp.mode==R_CLENGTH)?req->resp.length:-1); + } + + /* TODO: call the readers when this fails too. */ + for (rdr = req->body_readers; rdr!=NULL; rdr=rdr->next) { + if (rdr->use) rdr->handler(rdr->userdata, buffer, readlen); + } + + return readlen; +} + +/* Build the request string, returning the buffer. */ +static ne_buffer *build_request(ne_request *req) +{ + struct hook *hk; + ne_buffer *buf = ne_buffer_create(); + + /* Add Request-Line and Host header: */ + ne_buffer_concat(buf, req->method, " ", req->uri, " HTTP/1.1" EOL, + "Host: ", req->session->server.hostport, EOL, NULL); + + /* Add custom headers: */ + ne_buffer_append(buf, req->headers->data, ne_buffer_size(req->headers)); + +#define E100 "Expect: 100-continue" EOL + if (req->use_expect100) + ne_buffer_append(buf, E100, strlen(E100)); + + NE_DEBUG(NE_DBG_HTTP, "Running pre_send hooks\n"); + for (hk = req->session->pre_send_hooks; hk!=NULL; hk = hk->next) { + ne_pre_send_fn fn = (ne_pre_send_fn)hk->fn; + fn(req, hk->userdata, buf); + } + + ne_buffer_append(buf, "\r\n", 2); + return buf; +} + +#ifdef NE_DEBUGGING +#define DEBUG_DUMP_REQUEST(x) dump_request(x) + +static void dump_request(const char *request) +{ + if ((NE_DBG_HTTPPLAIN&ne_debug_mask) == NE_DBG_HTTPPLAIN) { + /* Display everything mode */ + NE_DEBUG(NE_DBG_HTTP, "Sending request headers:\n%s", request); + } else { + /* Blank out the Authorization paramaters */ + char *reqdebug = ne_strdup(request), *pnt = reqdebug; + while ((pnt = strstr(pnt, "Authorization: ")) != NULL) { + for (pnt += 15; *pnt != '\r' && *pnt != '\0'; pnt++) { + *pnt = 'x'; + } + } + NE_DEBUG(NE_DBG_HTTP, "Sending request headers:\n%s", reqdebug); + ne_free(reqdebug); + } +} + +#else +#define DEBUG_DUMP_REQUEST(x) +#endif /* DEBUGGING */ + +/* remove trailing EOL from 'buf', where strlen(buf) == *len. *len is + * adjusted in accordance with any changes made to the string to + * remain equal to strlen(buf). */ +static inline void strip_eol(char *buf, ssize_t *len) +{ + char *pnt = &buf[*len-1]; + while (pnt >= buf && (*pnt == '\r' || *pnt == '\n')) { + *pnt-- = '\0'; + (*len)--; + } +} + +/* For persistent connection handling: if the first socket operation + * on an already-open connection fails with an EOF error, then presume + * a persistent connection timeout has occurred, and the request + * should be retried. The 'retry' flag is zero once a socket + * operation has succeeded on the connection. RETRY_RET() crafts the + * appropriate return value given a 'retry' flag, the socket error + * 'code', and the return value 'acode' from the aborted() function. */ +#define RETRY_RET(retry, code, acode) \ +((((code) == NE_SOCK_CLOSED || (code) == NE_SOCK_RESET || \ + (code) == NE_SOCK_TRUNC) && retry) ? NE_RETRY : (acode)) + +/* Read and parse response status-line into 'status'. 'retry' is non-zero + * if an NE_RETRY should be returned if an EOF is received. */ +static int read_status_line(ne_request *req, ne_status *status, int retry) +{ + char *buffer = req->respbuf; + ssize_t ret; + + ret = ne_sock_readline(req->session->socket, buffer, sizeof req->respbuf); + if (ret <= 0) { + int aret = aborted(req, _("Could not read status line"), ret); + return RETRY_RET(retry, ret, aret); + } + + NE_DEBUG(NE_DBG_HTTP, "[status-line] < %s", buffer); + strip_eol(buffer, &ret); + + if (status->reason_phrase) ne_free(status->reason_phrase); + memset(status, 0, sizeof *status); + + if (ne_parse_statusline(buffer, status)) + return aborted(req, _("Could not parse response status line."), 0); + + return 0; +} + +/* Discard a set of message headers. */ +static int discard_headers(ne_request *req) +{ + do { + SOCK_ERR(req, ne_sock_readline(req->session->socket, req->respbuf, + sizeof req->respbuf), + _("Could not read interim response headers")); + NE_DEBUG(NE_DBG_HTTP, "[discard] < %s", req->respbuf); + } while (strcmp(req->respbuf, EOL) != 0); + return NE_OK; +} + +/* Send the request, and read the response Status-Line. Returns: + * NE_RETRY connection closed by server; persistent connection + * timeout + * NE_OK success + * NE_* error + * On NE_RETRY and NE_* responses, the connection will have been + * closed already. + */ +static int send_request(ne_request *req, const ne_buffer *request) +{ + ne_session *sess = req->session; + ssize_t ret = NE_OK; + int sentbody = 0, retry; + ne_status *status = &req->status; + + /* Send the Request-Line and headers */ + NE_DEBUG(NE_DBG_HTTP, "Sending request-line and headers:\n"); + /* Open the connection if necessary */ + HTTP_ERR(open_connection(req)); + + /* Allow retry if a persistent connection has been used. */ + retry = sess->persisted; + + ret = ne_sock_fullwrite(req->session->socket, request->data, + ne_buffer_size(request)); + if (ret < 0) { + int aret = aborted(req, _("Could not send request"), ret); + return RETRY_RET(retry, ret, aret); + } + + /* FIXME: probably due to Nagle, the write above may or may not + * have been delayed, so retry is left at 1 here. */ + + if (!req->use_expect100 && req->body_size > 0) { + /* Send request body, if not using 100-continue. */ + ret = send_request_body(req); + if (ret < 0) { + int aret = aborted(req, _("Could not send request body"), ret); + return RETRY_RET(sess, ret, aret); + } + } + + NE_DEBUG(NE_DBG_HTTP, "Request sent; retry is %d\n", retry); + + /* Loop eating interim 1xx responses (RFC2616 says these MAY be + * sent by the server, even if 100-continue is not used). */ + while ((ret = read_status_line(req, status, retry)) == NE_OK + && status->klass == 1) { + NE_DEBUG(NE_DBG_HTTP, "Interim %d response.\n", status->code); + retry = 0; + /* Discard headers with the interim response. */ + if ((ret = discard_headers(req)) != NE_OK) break; + + if (req->use_expect100 && (status->code == 100) && !sentbody) { + /* Send the body after receiving the first 100 Continue */ + if ((ret = send_request_body(req)) != NE_OK) break; + sentbody = 1; + } + } + + return ret; +} + +/* Read a message header from sock into buf, which has size 'buflen'. + * + * Returns: + * NE_RETRY: Success, read a header into buf. + * NE_OK: End of headers reached. + * NE_ERROR: Error (session error is set). + */ +static int read_message_header(ne_request *req, char *buf, size_t buflen) +{ + ssize_t n; + ne_socket *sock = req->session->socket; + + n = ne_sock_readline(sock, buf, buflen); + if (n <= 0) + return aborted(req, _("Error reading response headers"), n); + NE_DEBUG(NE_DBG_HTTP, "[hdr] %s", buf); + + strip_eol(buf, &n); + + if (n == 0) { + NE_DEBUG(NE_DBG_HTTP, "End of headers.\n"); + return NE_OK; + } + + buf += n; + buflen -= n; + + while (buflen > 0) { + char ch; + + /* Collect any extra lines into buffer */ + SOCK_ERR(req, ne_sock_peek(sock, &ch, 1), + _("Error reading response headers")); + + if (ch != ' ' && ch != '\t') { + /* No continuation of this header: stop reading. */ + return NE_RETRY; + } + + /* Otherwise, read the next line onto the end of 'buf'. */ + n = ne_sock_readline(sock, buf, buflen); + if (n <= 0) { + return aborted(req, _("Error reading response headers"), n); + } + + NE_DEBUG(NE_DBG_HTTP, "[cont] %s", buf); + + strip_eol(buf, &n); + + /* assert(buf[0] == ch), which implies len(buf) > 0. + * Otherwise the TCP stack is lying, but we'll be paranoid. + * This might be a \t, so replace it with a space to be + * friendly to applications (2616 says we MAY do this). */ + if (n) buf[0] = ' '; + + /* ready for the next header. */ + buf += n; + buflen -= n; + } + + ne_set_error(req->session, _("Response header too long")); + return NE_ERROR; +} + +/* Apache's default is 100, seems reasonable. */ +#define MAX_HEADER_FIELDS 100 + +/* Read response headers. Returns NE_* code, sets session error. */ +static int read_response_headers(ne_request *req) +{ + char hdr[8192]; /* max header length */ + int ret, count = 0; + + while ((ret = read_message_header(req, hdr, sizeof hdr)) == NE_RETRY + && ++count < MAX_HEADER_FIELDS) { + struct header_handler *hdl; + char *pnt; + unsigned int hash = 0; + + for (hdl = req->header_catchers; hdl != NULL; hdl = hdl->next) + hdl->handler(hdl->userdata, hdr); + + /* Strip any trailing whitespace */ + pnt = hdr + strlen(hdr) - 1; + while (pnt > hdr && (*pnt == ' ' || *pnt == '\t')) + *pnt-- = '\0'; + + /* Convert the header name to lower case and hash it. */ + for (pnt = hdr; (*pnt != '\0' && *pnt != ':' && + *pnt != ' ' && *pnt != '\t'); pnt++) { + *pnt = tolower(*pnt); + hash = HH_ITERATE(hash,*pnt); + } + + /* Skip over any whitespace before the colon. */ + while (*pnt == ' ' || *pnt == '\t') + *pnt++ = '\0'; + + /* ignore header lines which lack a ':'. */ + if (*pnt != ':') + continue; + + /* NUL-terminate at the colon (when no whitespace before) */ + *pnt++ = '\0'; + + /* Skip any whitespace after the colon... */ + while (*pnt == ' ' || *pnt == '\t') + pnt++; + + /* pnt now points to the header value. */ + NE_DEBUG(NE_DBG_HTTP, "Header Name: [%s], Value: [%s]\n", hdr, pnt); + + /* Iterate through the header handlers */ + for (hdl = req->header_handlers[hash]; hdl != NULL; hdl = hdl->next) { + if (strcmp(hdr, hdl->name) == 0) + hdl->handler(hdl->userdata, pnt); + } + } + + if (count == MAX_HEADER_FIELDS) + ret = aborted( + req, _("Response exceeded maximum number of header fields."), 0); + + return ret; +} + +static int lookup_host(ne_session *sess, struct host_info *info) +{ + NE_DEBUG(NE_DBG_HTTP, "Doing DNS lookup on %s...\n", info->hostname); + if (sess->notify_cb) + sess->notify_cb(sess->notify_ud, ne_conn_namelookup, info->hostname); + info->address = ne_addr_resolve(info->hostname, 0); + if (ne_addr_result(info->address)) { + char buf[256]; + ne_set_error(sess, _("Could not resolve hostname `%s': %s"), + info->hostname, + ne_addr_error(info->address, buf, sizeof buf)); + ne_addr_destroy(info->address); + info->address = NULL; + return NE_LOOKUP; + } else { + return NE_OK; + } +} + +int ne_begin_request(ne_request *req) +{ + struct body_reader *rdr; + struct host_info *host; + ne_buffer *data; + const ne_status *const st = &req->status; + int ret; + + /* Resolve hostname if necessary. */ + host = req->session->use_proxy?&req->session->proxy:&req->session->server; + if (host->address == NULL) + HTTP_ERR(lookup_host(req->session, host)); + + req->resp.mode = R_TILLEOF; + + /* FIXME: Determine whether to use the Expect: 100-continue header. */ + req->use_expect100 = (req->session->expect100_works > -1) && + (req->body_size > HTTP_EXPECT_MINSIZE) && req->session->is_http11; + + /* Build the request string, and send it */ + data = build_request(req); + DEBUG_DUMP_REQUEST(data->data); + ret = send_request(req, data); + /* Retry this once after a persistent connection timeout. */ + if (ret == NE_RETRY && !req->session->no_persist) { + NE_DEBUG(NE_DBG_HTTP, "Persistent connection timed out, retrying.\n"); + ret = send_request(req, data); + } + ne_buffer_destroy(data); + if (ret != NE_OK) return ret; + + /* Determine whether server claims HTTP/1.1 compliance. */ + req->session->is_http11 = (st->major_version == 1 && + st->minor_version > 0) || st->major_version > 1; + + /* Persistent connections supported implicitly in HTTP/1.1 */ + if (req->session->is_http11) req->can_persist = 1; + + ne_set_error(req->session, "%d %s", st->code, st->reason_phrase); + + /* Read the headers */ + HTTP_ERR(read_response_headers(req)); + +#ifdef NEON_SSL + /* Special case for CONNECT handling: the response has no body, + * and the connection can persist. */ + if (req->session->in_connect && st->klass == 2) { + req->resp.mode = R_NO_BODY; + req->can_persist = 1; + } +#endif + + /* HEAD requests and 204, 304 responses have no response body, + * regardless of what headers are present. */ + if (req->method_is_head || st->code==204 || st->code==304) + req->resp.mode = R_NO_BODY; + + /* Prepare for reading the response entity-body. Call each of the + * body readers and ask them whether they want to accept this + * response or not. */ + for (rdr = req->body_readers; rdr != NULL; rdr=rdr->next) { + rdr->use = rdr->accept_response(rdr->userdata, req, st); + } + + req->resp.left = req->resp.length; + req->resp.chunk_left = 0; + + return NE_OK; +} + +int ne_end_request(ne_request *req) +{ + struct hook *hk; + int ret = NE_OK; + + /* Read headers in chunked trailers */ + if (req->resp.mode == R_CHUNKED) + HTTP_ERR(read_response_headers(req)); + + NE_DEBUG(NE_DBG_HTTP, "Running post_send hooks\n"); + for (hk = req->session->post_send_hooks; + ret == NE_OK && hk != NULL; hk = hk->next) { + ne_post_send_fn fn = (ne_post_send_fn)hk->fn; + ret = fn(req, hk->userdata, &req->status); + } + + /* Close the connection if persistent connections are disabled or + * not supported by the server. */ + if (req->session->no_persist || !req->can_persist) + ne_close_connection(req->session); + else + req->session->persisted = 1; + + return ret; +} + +int ne_request_dispatch(ne_request *req) +{ + int ret; + + /* Loop sending the request: + * Retry whilst authentication fails and we supply it. */ + + do { + ssize_t len; + + HTTP_ERR(ne_begin_request(req)); + + do { + len = ne_read_response_block(req, req->respbuf, + sizeof req->respbuf); + } while (len > 0); + + if (len < 0) { + return NE_ERROR; + } + + ret = ne_end_request(req); + + } while (ret == NE_RETRY); + + NE_DEBUG(NE_DBG_HTTP | NE_DBG_FLUSH, + "Request ends, status %d class %dxx, error line:\n%s\n", + req->status.code, req->status.klass, req->session->error); + + return ret; +} + +const ne_status *ne_get_status(const ne_request *req) +{ + return &req->status; +} + +ne_session *ne_get_session(const ne_request *req) +{ + return req->session; +} + +#ifdef NEON_SSL +/* Create a CONNECT tunnel through the proxy server. + * Returns HTTP_* */ +static int proxy_tunnel(ne_session *sess) +{ + /* Hack up an HTTP CONNECT request... */ + ne_request *req; + int ret = NE_OK; + char ruri[200]; + + /* Can't use server.hostport here; Request-URI must include `:port' */ + ne_snprintf(ruri, sizeof ruri, "%s:%u", sess->server.hostname, + sess->server.port); + req = ne_request_create(sess, "CONNECT", ruri); + + sess->in_connect = 1; + ret = ne_request_dispatch(req); + sess->in_connect = 0; + + sess->persisted = 0; /* don't treat this is a persistent connection. */ + + if (ret != NE_OK || !sess->connected || req->status.klass != 2) { + ne_set_error + (sess, _("Could not create SSL connection through proxy server")); + ret = NE_ERROR; + } + + ne_request_destroy(req); + return ret; +} +#endif + +/* Make new TCP connection to server at 'host' of type 'name'. Note + * that once a connection to a particular network address has + * succeeded, that address will be used first for the next attempt to + * connect. */ +/* TODO: an alternate implementation could always cycle through the + * addresses: this could ease server load, but could hurt SSL session + * caching for SSL sessions, which would increase server load. */ +static int do_connect(ne_request *req, struct host_info *host, const char *err) +{ + ne_session *const sess = req->session; + int ret; + + if ((sess->socket = ne_sock_create()) == NULL) { + ne_set_error(sess, _("Could not create socket")); + return NE_ERROR; + } + + if (host->current == NULL) + host->current = ne_addr_first(host->address); + + do { + notify_status(sess, ne_conn_connecting, host->hostport); +#ifdef NE_DEBUGGING + if (ne_debug_mask & NE_DBG_HTTP) { + char buf[150]; + NE_DEBUG(NE_DBG_HTTP, "Connecting to %s\n", + ne_iaddr_print(host->current, buf, sizeof buf)); + } +#endif + ret = ne_sock_connect(sess->socket, host->current, host->port); + } while (ret && /* try the next address... */ + (host->current = ne_addr_next(host->address)) != NULL); + + if (ret) { + aborted(req, err, ret); + return NE_CONNECT; + } + + notify_status(sess, ne_conn_connected, sess->proxy.hostport); + + if (sess->rdtimeout) + ne_sock_read_timeout(sess->socket, sess->rdtimeout); + + sess->connected = 1; + /* clear persistent connection flag. */ + sess->persisted = 0; + return NE_OK; +} + +static int open_connection(ne_request *req) +{ + ne_session *sess = req->session; + int ret; + + if (sess->connected) return NE_OK; + + if (!sess->use_proxy) + ret = do_connect(req, &sess->server, _("Could not connect to server")); + else + ret = do_connect(req, &sess->proxy, + _("Could not connect to proxy server")); + + if (ret != NE_OK) return ret; + +#ifdef NEON_SSL + /* Negotiate SSL layer if required. */ + if (sess->use_ssl && !sess->in_connect) { + /* CONNECT tunnel */ + if (req->session->use_proxy) + ret = proxy_tunnel(sess); + + if (ret == NE_OK) + ret = ne_negotiate_ssl(req); + + /* This is probably only really needed for ne_negotiate_ssl + * failures as proxy_tunnel will fail via aborted(). */ + if (ret != NE_OK) + ne_close_connection(sess); + } +#endif + + return ret; +} diff --git a/src/ne_request.h b/src/ne_request.h new file mode 100644 index 0000000..f626402 --- /dev/null +++ b/src/ne_request.h @@ -0,0 +1,275 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_REQUEST_H +#define NE_REQUEST_H + +#include "ne_utils.h" /* For ne_status */ +#include "ne_string.h" /* For sbuffer */ +#include "ne_session.h" + +BEGIN_NEON_DECLS + +#define NE_OK (0) /* Success */ +#define NE_ERROR (1) /* Generic error; use ne_get_error(session) for message */ +#define NE_LOOKUP (2) /* Server or proxy hostname lookup failed */ +#define NE_AUTH (3) /* User authentication failed on server */ +#define NE_PROXYAUTH (4) /* User authentication failed on proxy */ +#define NE_CONNECT (5) /* Could not connect to server */ +#define NE_TIMEOUT (6) /* Connection timed out */ +#define NE_FAILED (7) /* The precondition failed */ +#define NE_RETRY (8) /* Retry request (ne_end_request ONLY) */ +#define NE_REDIRECT (9) /* See ne_redirect.h */ + +/* FIXME: remove this */ +#define EOL "\r\n" + +/* Opaque object representing a single HTTP request. */ +typedef struct ne_request_s ne_request; + +/***** Request Handling *****/ + +/* Create a request in session 'sess', with given method and path. + * 'path' must conform to the 'abs_path' grammar in RFC2396, with an + * optional "? query" part, and MUST be URI-escaped by the caller. */ +ne_request *ne_request_create(ne_session *sess, + const char *method, const char *path); + +/* 'buffer' will be sent as the request body with given request. */ +void ne_set_request_body_buffer(ne_request *req, const char *buffer, + size_t size); + +/* Send the contents of a file as the request body; 'fd' must be a + * file descriptor of an open, seekable file. Current file offset of + * fd is not retained (and ignored: the file is read from the the + * first byte). Returns: + * 0 on okay. + * non-zero if could not determine length of file. */ +int ne_set_request_body_fd(ne_request *req, int fd); + +/* "Pull"-based request body provider: a callback which is invoked to + * provide blocks of request body on demand. + * + * Before each time the body is provided, the callback will be called + * once with buflen == 0. The body may have to be provided >1 time + * per request (for authentication retries etc.). + * + * The callback must return: + * <0 : error, abort request. + * 0 : ignore 'buffer' contents, end of body. + * 0 < x <= buflen : buffer contains x bytes of body data. */ +typedef ssize_t (*ne_provide_body)(void *userdata, + char *buffer, size_t buflen); + +/* Install a callback which is invoked as needed to provide request + * body blocks. Total request body is 'size' bytes: the callback MUST + * ensure it returns in total exactly 'size' bytes each time the + * request body is provided. */ +void ne_set_request_body_provider(ne_request *req, size_t size, + ne_provide_body provider, void *userdata); + +/* Handling response bodies... you provide TWO callbacks: + * + * 1) 'acceptance' callback: determines whether you want to handle the + * response body given the response-status information, e.g., if you + * only want 2xx responses, say so here. + * + * 2) 'reader' callback: passed blocks of the response-body as they + * arrive, if the acceptance callback returned non-zero. */ + +/* 'acceptance' callback type. Return non-zero to accept the response, + * else zero to ignore it. */ +typedef int (*ne_accept_response)( + void *userdata, ne_request *req, const ne_status *st); + +/* An 'acceptance' callback which only accepts 2xx-class responses. + * Ignores userdata. */ +int ne_accept_2xx(void *userdata, ne_request *req, const ne_status *st); + +/* An acceptance callback which accepts all responses. Ignores + * userdata. */ +int ne_accept_always(void *userdata, ne_request *req, const ne_status *st); + +/* Callback for reading a block of data. */ +typedef void (*ne_block_reader)(void *userdata, const char *buf, size_t len); + +/* Add a response reader for the given request, with the given + * acceptance function. userdata is passed as the first argument to + * the acceptance + reader callbacks. + * + * The acceptance callback is called once each time the request is + * sent: it may be sent >1 time because of authentication retries etc. + * For each time the acceptance callback is called, if it returns + * non-zero, blocks of the response body will be passed to the reader + * callback as the response is read. After all the response body has + * been read, the callback will be called with a 'len' argument of + * zero. */ +void ne_add_response_body_reader(ne_request *req, ne_accept_response accpt, + ne_block_reader reader, void *userdata); + +/* Handle response headers. Each handler is associated with a specific + * header field (indicated by name). The handler is then passed the + * value of this header field. */ + +/* The header handler callback type */ +typedef void (*ne_header_handler)(void *userdata, const char *value); + +/* Adds a response header handler for the given request. userdata is passed + * as the first argument to the header handler, and the 'value' is the + * header field value (i.e. doesn't include the "Header-Name: " part"). + */ +void ne_add_response_header_handler(ne_request *req, const char *name, + ne_header_handler hdl, void *userdata); + +/* Add handler which is passed ALL header values regardless of name */ +void ne_add_response_header_catcher(ne_request *req, + ne_header_handler hdl, void *userdata); + +/* Stock header handlers: + * 'duplicate': *(char **)userdata = strdup(value) + * 'numeric': *(int *)userdata = atoi(value) + * e.g. + * int mynum; + * ne_add_response_header_handler(myreq, "Content-Length", + * ne_handle_numeric_handler, &mynum); + * ... arranges mynum to be set to the value of the Content-Length header. + */ +void ne_duplicate_header(void *userdata, const char *value); +void ne_handle_numeric_header(void *userdata, const char *value); + +/* Adds a header to the request with given name and value. */ +void ne_add_request_header(ne_request *req, const char *name, + const char *value); +/* Adds a header to the request with given name, using printf-like + * format arguments for the value. */ +void ne_print_request_header(ne_request *req, const char *name, + const char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 3, 4))) +#endif /* __GNUC__ */ +; + +/* ne_request_dispatch: Sends the given request, and reads the + * response. Response-Status information can be retrieve with + * ne_get_status(req). + * + * NE_OK if request sent + response read okay. + * NE_AUTH user not authorised on server + * NE_PROXYAUTH user not authorised on proxy server + * NE_CONNECT could not connect to server/proxy server + * NE_TIMEOUT connection timed out mid-request + * NE_ERROR for other errors, and ne_get_error() should + * return a meaningful error string + */ +int ne_request_dispatch(ne_request *req); + +/* Returns a pointer to the response status information for the + * given request. */ +const ne_status *ne_get_status(const ne_request *req) +/* Declare this with attribute const, since we often call it >1 times + * with the same argument, and it will return the same thing each + * time. This lets the compiler optimize away any subsequent calls + * (theoretically). */ +#ifdef __GNUC__ + __attribute__ ((const)) +#endif /* __GNUC__ */ + ; + +/* Returns pointer to session associated with request. */ +ne_session *ne_get_session(const ne_request *req); + +/* Destroy memory associated with request pointer */ +void ne_request_destroy(ne_request *req); + +/* "Caller-pulls" request interface. This is an ALTERNATIVE interface + * to ne_request_dispatch: either use that, or do all this yourself: + * + * caller must call: + * 1. ne_begin_request (fail if returns non-NE_OK) + * 2. while(ne_read_response_block(...) > 0) ... loop ...; + * (fail if ne_read_response_block returns <0) + * 3. ne_end_request + * + * ne_end_request and ne_begin_request both return an NE_* code; if + * ne_end_request returns NE_RETRY, you must restart the loop from (1) + * above. */ +int ne_begin_request(ne_request *req); +int ne_end_request(ne_request *req); + +/* Read a block of the response. buffer must be at least 128 bytes. + * 'buflen' must be length of buffer. + * + * Returns: + * <0 - error, stop reading. + * 0 - end of response + * >0 - number of bytes read into buffer. + */ +ssize_t ne_read_response_block(ne_request *req, char *buffer, size_t buflen); + +/**** Request hooks handling *****/ + +typedef void (*ne_free_hooks)(void *cookie); + +/* Hook called when a create is created; passed the request method, + * and the string used as the Request-URI (which may be an abs_path, + * or an absoluteURI, depending on whether an HTTP proxy is in + * use). */ +typedef void (*ne_create_request_fn)(ne_request *req, void *userdata, + const char *method, const char *requri); +void ne_hook_create_request(ne_session *sess, + ne_create_request_fn fn, void *userdata); + +/* Hook called before the request is sent. 'header' is the raw HTTP + * header before the trailing CRLF is added: add in more here. */ +typedef void (*ne_pre_send_fn)(ne_request *req, void *userdata, + ne_buffer *header); +void ne_hook_pre_send(ne_session *sess, ne_pre_send_fn fn, void *userdata); + +/* Hook called after the request is sent. May return: + * NE_OK everything is okay + * NE_RETRY try sending the request again. + * anything else signifies an error, and the request is failed. The return + * code is passed back the _dispatch caller, so the session error must + * also be set appropriately (ne_set_error). + */ +typedef int (*ne_post_send_fn)(ne_request *req, void *userdata, + const ne_status *status); +void ne_hook_post_send(ne_session *sess, ne_post_send_fn fn, void *userdata); + +/* Hook called when the function is destroyed. */ +typedef void (*ne_destroy_req_fn)(ne_request *req, void *userdata); +void ne_hook_destroy_request(ne_session *sess, + ne_destroy_req_fn fn, void *userdata); + +typedef void (*ne_destroy_sess_fn)(void *userdata); +/* Hook called when the session is destroyed. */ +void ne_hook_destroy_session(ne_session *sess, + ne_destroy_sess_fn fn, void *userdata); + +/* Store an opaque context for the request, 'priv' is returned by a + * call to ne_request_get_private with the same ID. */ +void ne_set_request_private(ne_request *req, const char *id, void *priv); +void *ne_get_request_private(ne_request *req, const char *id); + +END_NEON_DECLS + +#endif /* NE_REQUEST_H */ + diff --git a/src/ne_session.c b/src/ne_session.c new file mode 100644 index 0000000..fe71079 --- /dev/null +++ b/src/ne_session.c @@ -0,0 +1,272 @@ +/* + HTTP session handling + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + Portions are: + Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +#include "ne_session.h" +#include "ne_alloc.h" +#include "ne_utils.h" +#include "ne_i18n.h" +#include "ne_string.h" + +#include "ne_private.h" + +/* Destroy a a list of hooks. */ +static void destroy_hooks(struct hook *hooks) +{ + struct hook *nexthk; + + while (hooks) { + nexthk = hooks->next; + ne_free(hooks); + hooks = nexthk; + } +} + +void ne_session_destroy(ne_session *sess) +{ + struct hook *hk; + + NE_DEBUG(NE_DBG_HTTP, "ne_session_destroy called.\n"); + + /* Run the destroy hooks. */ + for (hk = sess->destroy_sess_hooks; hk != NULL; hk = hk->next) { + ne_destroy_sess_fn fn = (ne_destroy_sess_fn)hk->fn; + fn(hk->userdata); + } + + destroy_hooks(sess->create_req_hooks); + destroy_hooks(sess->pre_send_hooks); + destroy_hooks(sess->post_send_hooks); + destroy_hooks(sess->destroy_req_hooks); + destroy_hooks(sess->destroy_sess_hooks); + destroy_hooks(sess->private); + + NE_FREE(sess->server.hostname); + NE_FREE(sess->server.hostport); + if (sess->server.address) ne_addr_destroy(sess->server.address); + if (sess->proxy.address) ne_addr_destroy(sess->proxy.address); + NE_FREE(sess->proxy.hostname); + NE_FREE(sess->scheme); + NE_FREE(sess->user_agent); + + if (sess->connected) { + ne_close_connection(sess); + } + +#ifdef NEON_SSL + if (sess->ssl_context) + ne_ssl_context_destroy(sess->ssl_context); + + if (sess->server_cert) + ne_ssl_cert_free(sess->server_cert); + + if (sess->client_cert) + ne_ssl_clicert_free(sess->client_cert); +#endif + + ne_free(sess); +} + +int ne_version_pre_http11(ne_session *s) +{ + return !s->is_http11; +} + +/* Stores the "hostname[:port]" segment */ +static void set_hostport(struct host_info *host, unsigned int defaultport) +{ + size_t len = strlen(host->hostname); + host->hostport = ne_malloc(len + 10); + strcpy(host->hostport, host->hostname); + if (host->port != defaultport) + ne_snprintf(host->hostport + len, 9, ":%u", host->port); +} + +/* Stores the hostname/port in *info, setting up the "hostport" + * segment correctly. */ +static void +set_hostinfo(struct host_info *info, const char *hostname, unsigned int port) +{ + NE_FREE(info->hostport); + NE_FREE(info->hostname); + info->hostname = ne_strdup(hostname); + info->port = port; +} + +ne_session *ne_session_create(const char *scheme, + const char *hostname, unsigned int port) +{ + ne_session *sess = ne_calloc(sizeof *sess); + + NE_DEBUG(NE_DBG_HTTP, "HTTP session to %s://%s:%d begins.\n", + scheme, hostname, port); + + strcpy(sess->error, "Unknown error."); + + /* use SSL if scheme is https */ + sess->use_ssl = !strcmp(scheme, "https"); + + /* set the hostname/port */ + set_hostinfo(&sess->server, hostname, port); + set_hostport(&sess->server, sess->use_ssl?443:80); + +#ifdef NEON_SSL + if (sess->use_ssl) { + sess->ssl_context = ne_ssl_context_create(); + } +#endif + + sess->scheme = ne_strdup(scheme); + + /* Default expect-100 to OFF. */ + sess->expect100_works = -1; + return sess; +} + +void ne_session_proxy(ne_session *sess, const char *hostname, + unsigned int port) +{ + sess->use_proxy = 1; + set_hostinfo(&sess->proxy, hostname, port); +} + +void ne_set_error(ne_session *sess, const char *format, ...) +{ + va_list params; + + va_start(params, format); + ne_vsnprintf(sess->error, sizeof sess->error, format, params); + va_end(params); +} + + +void ne_set_progress(ne_session *sess, + ne_progress progress, void *userdata) +{ + sess->progress_cb = progress; + sess->progress_ud = userdata; +} + +void ne_set_status(ne_session *sess, + ne_notify_status status, void *userdata) +{ + sess->notify_cb = status; + sess->notify_ud = userdata; +} + +void ne_set_expect100(ne_session *sess, int use_expect100) +{ + if (use_expect100) { + sess->expect100_works = 1; + } else { + sess->expect100_works = -1; + } +} + +void ne_set_persist(ne_session *sess, int persist) +{ + sess->no_persist = !persist; +} + +void ne_set_read_timeout(ne_session *sess, int timeout) +{ + sess->rdtimeout = timeout; +} + +#define AGENT " neon/" NEON_VERSION + +void ne_set_useragent(ne_session *sess, const char *token) +{ + if (sess->user_agent) ne_free(sess->user_agent); + sess->user_agent = ne_malloc(sizeof AGENT + strlen(token)); + strcat(strcpy(sess->user_agent, token), AGENT); +} + +const char *ne_get_server_hostport(ne_session *sess) +{ + return sess->server.hostport; +} + +const char *ne_get_scheme(ne_session *sess) +{ + return sess->scheme; +} + +void ne_fill_server_uri(ne_session *sess, ne_uri *uri) +{ + uri->host = ne_strdup(sess->server.hostname); + uri->port = sess->server.port; + uri->scheme = ne_strdup(sess->scheme); +} + +const char *ne_get_error(ne_session *sess) +{ + return ne_strclean(sess->error); +} + +void ne_close_connection(ne_session *sess) +{ + if (sess->connected) { + NE_DEBUG(NE_DBG_SOCKET, "Closing connection.\n"); + ne_sock_close(sess->socket); + sess->socket = NULL; + NE_DEBUG(NE_DBG_SOCKET, "Connection closed.\n"); + } else { + NE_DEBUG(NE_DBG_SOCKET, "(Not closing closed connection!).\n"); + } + sess->connected = 0; +} + +void ne_ssl_set_verify(ne_session *sess, ne_ssl_verify_fn fn, void *userdata) +{ + sess->ssl_verify_fn = fn; + sess->ssl_verify_ud = userdata; +} + +void ne_ssl_provide_clicert(ne_session *sess, + ne_ssl_provide_fn fn, void *userdata) +{ + sess->ssl_provide_fn = fn; + sess->ssl_provide_ud = userdata; +} + +void ne_ssl_trust_cert(ne_session *sess, const ne_ssl_certificate *cert) +{ +#ifdef NEON_SSL + ne_ssl_ctx_trustcert(sess->ssl_context, cert); +#endif +} + + + diff --git a/src/ne_session.h b/src/ne_session.h new file mode 100644 index 0000000..027e69e --- /dev/null +++ b/src/ne_session.h @@ -0,0 +1,195 @@ +/* + HTTP session handling + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_SESSION_H +#define NE_SESSION_H 1 + +#include <sys/types.h> + +#include "ne_ssl.h" +#include "ne_uri.h" /* for ne_uri */ +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +typedef struct ne_session_s ne_session; + +/* Create a session to the given server, using the given scheme. If + * "https" is passed as the scheme, SSL will be used to connect to the + * server. */ +ne_session *ne_session_create(const char *scheme, + const char *hostname, unsigned int port); + +/* Finish an HTTP session */ +void ne_session_destroy(ne_session *sess); + +/* Prematurely force the connection to be closed for the given + * session. */ +void ne_close_connection(ne_session *sess); + +/* Set the proxy server to be used for the session. */ +void ne_session_proxy(ne_session *sess, + const char *hostname, unsigned int port); + +/* Set protocol options for session: + * expect100: Defaults to OFF + * persist: Defaults to ON + * + * expect100: When set, send the "Expect: 100-continue" request header + * with requests with bodies. + * + * persist: When set, use a persistent connection. (Generally, + * you don't want to turn this off.) + * */ +void ne_set_expect100(ne_session *sess, int use_expect100); +void ne_set_persist(ne_session *sess, int persist); + +/* Progress callback. */ +typedef void (*ne_progress)(void *userdata, off_t progress, off_t total); + +/* Set a progress callback for the session. */ +void ne_set_progress(ne_session *sess, + ne_progress progress, void *userdata); + +/* Store an opaque context for the session, 'priv' is returned by a + * call to ne_session_get_private with the same ID. */ +void ne_set_session_private(ne_session *sess, const char *id, void *priv); +void *ne_get_session_private(ne_session *sess, const char *id); + +typedef enum { + ne_conn_namelookup, /* lookup up hostname (info = hostname) */ + ne_conn_connecting, /* connecting to host (info = hostname) */ + ne_conn_connected, /* connected to host (info = hostname) */ + ne_conn_secure /* connection now secure (info = crypto level) */ +} ne_conn_status; + +typedef void (*ne_notify_status)(void *userdata, + ne_conn_status status, + const char *info); + + +/* Set a status notification callback for the session, to report + * connection status. */ +void ne_set_status(ne_session *sess, + ne_notify_status status, void *userdata); + +/* Certificate verification failures. + * The certificate is not yet valid: */ +#define NE_SSL_NOTYETVALID (0x01) +/* The certificate has expired: */ +#define NE_SSL_EXPIRED (0x02) +/* The hostname for which the certificate was issued does not + * match the hostname of the server; this could mean that the + * connection is being intercepted: */ +#define NE_SSL_IDMISMATCH (0x04) +/* The certificate authority which signed the server certificate is + * not trusted: there is no indicatation the server is who they claim + * to be: */ +#define NE_SSL_UNTRUSTED (0x08) + +/* The bitmask of known failure bits: if (failures & ~NE_SSL_FAILMASK) + * is non-zero, an unrecognized failure is given, and the verification + * should be failed. */ +#define NE_SSL_FAILMASK (0x0f) + +/* A callback which is used when server certificate verification is + * needed. The reasons for verification failure are given in the + * 'failures' parameter, which is a binary OR of one or more of the + * above NE_SSL_* values. failures is guaranteed to be non-zero. The + * callback must return zero to accept the certificate: a non-zero + * return value will fail the SSL negotiation. */ +typedef int (*ne_ssl_verify_fn)(void *userdata, int failures, + const ne_ssl_certificate *cert); + +/* Install a callback to handle server certificate verification. This + * is required when the CA certificate is not known for the server + * certificate, or the server cert has other verification problems. */ +void ne_ssl_set_verify(ne_session *sess, ne_ssl_verify_fn fn, void *userdata); + +/* Use the given client certificate for the session. The client cert + * MUST be in the decrypted state, otherwise behaviour is undefined. */ +void ne_ssl_set_clicert(ne_session *sess, const ne_ssl_client_cert *clicert); + +/* Indicate that the certificate 'cert' is trusted; 'cert' is + * duplicated internally and may be destroyed at will. */ +void ne_ssl_trust_cert(ne_session *sess, const ne_ssl_certificate *cert); + +/* If the SSL library provided a default set of CA certificates, trust + * this set of CAs. */ +void ne_ssl_trust_default_ca(ne_session *sess); + +/* Callback used to load a client certificate on demand. If dncount + * is > 0, the 'dnames' array dnames[0] through dnames[dncount-1] + * gives the list of CA names which the server indicated were + * acceptable. The callback should load an appropriate client + * certificate and then pass it to 'ne_ssl_set_clicert'. */ +typedef void (*ne_ssl_provide_fn)(void *userdata, ne_session *sess, + const ne_ssl_dname *const *dnames, + int dncount); + +/* Register a function to be called when the server requests a client + * certificate. */ +void ne_ssl_provide_clicert(ne_session *sess, + ne_ssl_provide_fn fn, void *userdata); + +/* Set the timeout (in seconds) used when reading from a socket. The + * timeout value must be greater than zero. */ +void ne_set_read_timeout(ne_session *sess, int timeout); + +/* Sets the user-agent string. neon/VERSION will be appended, to make + * the full header "User-Agent: product neon/VERSION". + * If this function is not called, the User-Agent header is not sent. + * The product string must follow the RFC2616 format, i.e. + * product = token ["/" product-version] + * product-version = token + * where token is any alpha-numeric-y string [a-zA-Z0-9]* */ +void ne_set_useragent(ne_session *sess, const char *product); + +/* Returns non-zero if next-hop server does not claim compliance to + * HTTP/1.1 or later. */ +int ne_version_pre_http11(ne_session *sess); + +/* Returns the 'hostport' URI segment for the end-server, e.g. + * "my.server.com:8080". */ +const char *ne_get_server_hostport(ne_session *sess); + +/* Returns the URL scheme being used for the current session, omitting + * the trailing ':'; e.g. "http" or "https". */ +const char *ne_get_scheme(ne_session *sess); + +/* Sets the host, scheme, and port fields (and no others) of the given + * URI structure; host and scheme are malloc-allocated. */ +void ne_fill_server_uri(ne_session *sess, ne_uri *uri); + +/* Set the error string for the session; takes printf-like format + * string. */ +void ne_set_error(ne_session *sess, const char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif /* __GNUC__ */ +; + +/* Retrieve the error string for the session */ +const char *ne_get_error(ne_session *sess); + +END_NEON_DECLS + +#endif /* NE_SESSION_H */ diff --git a/src/ne_socket.c b/src/ne_socket.c new file mode 100644 index 0000000..f67e233 --- /dev/null +++ b/src/ne_socket.c @@ -0,0 +1,1040 @@ +/* + socket handling routines + Copyright (C) 1998-2003, Joe Orton <joe@manyfish.co.uk>, + Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA +*/ + +/* + portions were originally under GPL in Mutt, http://www.mutt.org/ + Relicensed under LGPL for neon, http://www.webdav.org/neon/ +*/ + +#include "config.h" + +#ifdef __hpux +/* pick up hstrerror */ +#define _XOPEN_SOURCE_EXTENDED 1 +/* don't use the broken getaddrinfo shipped in HP-UX 11.11 */ +#ifdef USE_GETADDRINFO +#undef USE_GETADDRINFO +#endif +#endif + +#include <sys/types.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/stat.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef WIN32 +#include <winsock2.h> +#include <stddef.h> +#endif + +#if defined(NEON_SSL) && defined(HAVE_LIMITS_H) +#include <limits.h> /* for INT_MAX */ +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_SOCKS_H +#include <socks.h> +#endif + +#ifdef NEON_SSL +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/pkcs12.h> /* for PKCS12_PBE_add */ +#include <openssl/rand.h> + +#include "ne_privssl.h" +#endif + +#include "ne_i18n.h" +#include "ne_utils.h" +#include "ne_string.h" + +#define NE_INET_ADDR_DEFINED +/* A slightly ugly hack: change the ne_inet_addr definition to be the + * real address type used. The API only exposes ne_inet_addr as a + * pointer to an opaque object, so this should be well-defined + * behaviour. It avoids the hassle of a real wrapper ne_inet_addr + * structure, or losing type-safety by using void *. */ +#ifdef USE_GETADDRINFO +typedef struct addrinfo ne_inet_addr; + +/* To avoid doing AAAA queries unless absolutely necessary, either use + * AI_ADDRCONFIG where available, or a run-time check for working IPv6 + * support; the latter is only known to work on Linux. */ +#if defined(AI_ADDRCONFIG) && defined(EAI_BADFLAGS) +#define USE_ADDRCONFIG +#elif defined(__linux__) +#define USE_CHECK_IPV6 +#endif + +#else +typedef struct in_addr ne_inet_addr; +#endif + +#include "ne_socket.h" +#include "ne_alloc.h" + +#if defined(__BEOS__) && !defined(BONE_VERSION) +/* pre-BONE */ +#define ne_write(a,b,c) send(a,b,c,0) +#define ne_read(a,b,c) recv(a,b,c,0) +#define ne_close(s) closesocket(s) +#define ne_errno errno +#elif defined(WIN32) +#define ne_write(a,b,c) send(a,b,c,0) +#define ne_read(a,b,c) recv(a,b,c,0) +#define ne_close(s) closesocket(s) +#define ne_errno WSAGetLastError() +#else /* really Unix! */ +#define ne_write(a,b,c) write(a,b,c) +#define ne_read(a,b,c) read(a,b,c) +#define ne_close(s) close(s) +#define ne_errno errno +#endif + +#ifdef WIN32 +#define NE_ISRESET(e) ((e) == WSAECONNABORTED || (e) == WSAETIMEDOUT || \ + (e) == WSAECONNRESET || (e) == WSAENETRESET) +#define NE_ISCLOSED(e) ((e) == WSAESHUTDOWN || (e) == WSAENOTCONN) +#define NE_ISINTR(e) (0) +#else /* Unix */ +#define NE_ISRESET(e) ((e) == ECONNRESET) +#define NE_ISCLOSED(e) ((e) == EPIPE) +#define NE_ISINTR(e) ((e) == EINTR) +#endif + +/* Socket read timeout */ +#define SOCKET_READ_TIMEOUT 120 + +/* Critical I/O functions on a socket: useful abstraction for easily + * handling SSL I/O alongside raw socket I/O. */ +struct iofns { + /* Read up to 'len' bytes into 'buf' from socket. Return <0 on + * error or EOF, or >0; number of bytes read. */ + ssize_t (*read)(ne_socket *s, char *buf, size_t len); + /* Write exactly 'len' bytes from 'buf' to socket. Return zero on + * success, <0 on error. */ + ssize_t (*write)(ne_socket *s, const char *buf, size_t len); + /* Wait up to 'n' seconds for socket to become readable. Returns + * 0 when readable, otherwise NE_SOCK_TIMEOUT or NE_SOCK_ERROR. */ + int (*readable)(ne_socket *s, int n); +}; + +struct ne_socket_s { + int fd; + char error[200]; + void *progress_ud; + int rdtimeout; /* read timeout. */ + const struct iofns *ops; +#ifdef NEON_SSL + ne_ssl_socket ssl; +#endif + /* The read buffer: ->buffer stores byte which have been read; as + * these are consumed and passed back to the caller, bufpos + * advances through ->buffer. ->bufavail gives the number of + * bytes which remain to be consumed in ->buffer (from ->bufpos), + * and is hence always <= RDBUFSIZ. */ +#define RDBUFSIZ 4096 + char buffer[RDBUFSIZ]; + char *bufpos; + size_t bufavail; +}; + +/* ne_sock_addr represents an Internet address. */ +struct ne_sock_addr_s { +#ifdef USE_GETADDRINFO + struct addrinfo *result, *cursor; +#else + struct in_addr *addrs; + size_t cursor, count; +#endif + int errnum; +}; + +/* set_error: set socket error string to 'str'. */ +#define set_error(s, str) ne_strnzcpy((s)->error, (str), sizeof (s)->error) + +/* set_strerror: set socket error to system error string for 'errnum' */ +#ifdef WIN32 +/* Print system error message to given buffer. */ +static void print_error(int errnum, char *buffer, size_t buflen) +{ + if (FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, (DWORD) errnum, 0, + buffer, buflen, NULL) == 0) + ne_snprintf(buffer, buflen, "Socket error %d", errnum); +} +#define set_strerror(s, e) print_error((e), (s)->error, sizeof (s)->error) +#else /* not WIN32 */ +#define set_strerror(s, e) ne_strerror((e), (s)->error, sizeof (s)->error) +#endif + +#ifdef NEON_SSL +static int prng_seeded = 0; + +/* Initialize SSL library; returns non-zero on failure. */ +static int init_ssl(void) +{ + SSL_load_error_strings(); + SSL_library_init(); + PKCS12_PBE_add(); /* ### not sure why this is needed. */ + + /* Check whether the PRNG has already been seeded. */ + if (RAND_status() == 1) + return 0; + +#ifdef EGD_PATH + NE_DEBUG(NE_DBG_SOCKET, "Seeding PRNG from " EGD_PATH "...\n"); + if (RAND_egd(EGD_PATH) != -1) + return 0; +#elif defined(ENABLE_EGD) + { + static const char *paths[] = { "/var/run/egd-pool", "/dev/egd-pool", + "/etc/egd-pool", "/etc/entropy" }; + size_t n; + for (n = 0; n < sizeof(paths) / sizeof(char *); n++) { + NE_DEBUG(NE_DBG_SOCKET, "Seeding PRNG from %s...\n", paths[n]); + if (RAND_egd(paths[n]) != -1) + return 0; + } + } +#endif /* EGD_PATH */ + + NE_DEBUG(NE_DBG_SOCKET, "No entropy source found; could not seed PRNG.\n"); + return -1; +} +#endif /* NEON_SSL */ + +#ifdef USE_CHECK_IPV6 +static int ipv6_disabled = 0; + +/* On Linux kernels, IPv6 is typically built as a loadable module, and + * socket(AF_INET6, ...) will fail if this module is not loaded, so + * the slow AAAA lookups can be avoided for this common case. */ +static void init_ipv6(void) +{ + int fd = socket(AF_INET6, SOCK_STREAM, 0); + + if (fd < 0) + ipv6_disabled = 1; + else + close(fd); +} +#else +#define ipv6_disabled (0) +#endif + +static int init_result = 0; + +int ne_sock_init(void) +{ +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; +#endif + + if (init_result > 0) + return 0; + else if (init_result < 0) + return -1; + +#ifdef WIN32 + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + init_result = -1; + return -1; + } + +#endif + +#ifdef NEON_SOCKS + SOCKSinit("neon"); +#endif + +#if defined(HAVE_SIGNAL) && defined(SIGPIPE) + (void) signal(SIGPIPE, SIG_IGN); +#endif + +#ifdef USE_CHECK_IPV6 + init_ipv6(); +#endif + +#ifdef NEON_SSL + if (init_ssl()) { + NE_DEBUG(NE_DBG_SOCKET, "SSL initialization failed; lacking PRNG?\n"); + init_result = -1; + return -1; + } + prng_seeded = 1; +#endif + + init_result = 1; + return 0; +} + +void ne_sock_exit(void) +{ +#ifdef WIN32 + WSACleanup(); +#endif + init_result = 0; +} + +int ne_sock_block(ne_socket *sock, int n) +{ + if (sock->bufavail) + return 0; + return sock->ops->readable(sock, n); +} + +/* Cast address object AD to type 'sockaddr_TY' */ +#define SACAST(ty, ad) ((struct sockaddr_##ty *)(ad)) + +#define SOCK_ERR(x) do { ssize_t _sock_err = (x); \ +if (_sock_err < 0) return _sock_err; } while(0) + +ssize_t ne_sock_read(ne_socket *sock, char *buffer, size_t buflen) +{ + ssize_t bytes; + +#if 0 + NE_DEBUG(NE_DBG_SOCKET, "buf: at %d, %d avail [%s]\n", + sock->bufpos - sock->buffer, sock->bufavail, sock->bufpos); +#endif + + if (sock->bufavail > 0) { + /* Deliver buffered data. */ + if (buflen > sock->bufavail) + buflen = sock->bufavail; + memcpy(buffer, sock->bufpos, buflen); + sock->bufpos += buflen; + sock->bufavail -= buflen; + return buflen; + } else if (buflen >= sizeof sock->buffer) { + /* No need for read buffer. */ + return sock->ops->read(sock, buffer, buflen); + } else { + /* Fill read buffer. */ + bytes = sock->ops->read(sock, sock->buffer, sizeof sock->buffer); + if (bytes <= 0) + return bytes; + + if (buflen > (size_t)bytes) + buflen = bytes; + memcpy(buffer, sock->buffer, buflen); + sock->bufpos = sock->buffer + buflen; + sock->bufavail = bytes - buflen; + return buflen; + } +} + +ssize_t ne_sock_peek(ne_socket *sock, char *buffer, size_t buflen) +{ + ssize_t bytes; + + if (sock->bufavail) { + /* just return buffered data. */ + bytes = sock->bufavail; + } else { + /* fill the buffer. */ + bytes = sock->ops->read(sock, sock->buffer, sizeof sock->buffer); + if (bytes <= 0) + return bytes; + + sock->bufpos = sock->buffer; + sock->bufavail = bytes; + } + + if (buflen > (size_t)bytes) + buflen = bytes; + + memcpy(buffer, sock->bufpos, buflen); + + return buflen; +} + +/* Await data on raw fd in socket. */ +static int readable_raw(ne_socket *sock, int secs) +{ + int fdno = sock->fd, ret; + fd_set rdfds; + struct timeval timeout, *tvp = (secs >= 0 ? &timeout : NULL); + + /* Init the fd set */ + FD_ZERO(&rdfds); + do { + FD_SET(fdno, &rdfds); + if (tvp) { + tvp->tv_sec = secs; + tvp->tv_usec = 0; + } + ret = select(fdno + 1, &rdfds, NULL, NULL, tvp); + } while (ret < 0 && NE_ISINTR(ne_errno)); + if (ret < 0) { + set_strerror(sock, ne_errno); + return NE_SOCK_ERROR; + } + return (ret == 0) ? NE_SOCK_TIMEOUT : 0; +} + +static ssize_t read_raw(ne_socket *sock, char *buffer, size_t len) +{ + ssize_t ret; + + ret = readable_raw(sock, sock->rdtimeout); + if (ret) return ret; + + do { + ret = ne_read(sock->fd, buffer, len); + } while (ret == -1 && NE_ISINTR(ne_errno)); + + if (ret == 0) { + set_error(sock, _("Connection closed")); + ret = NE_SOCK_CLOSED; + } else if (ret < 0) { + int errnum = ne_errno; + ret = NE_ISRESET(errnum) ? NE_SOCK_RESET : NE_SOCK_ERROR; + set_strerror(sock, errnum); + } + + return ret; +} + +#define MAP_ERR(e) (NE_ISCLOSED(e) ? NE_SOCK_CLOSED : \ + (NE_ISRESET(e) ? NE_SOCK_RESET : NE_SOCK_ERROR)) + +static ssize_t write_raw(ne_socket *sock, const char *data, size_t length) +{ + ssize_t wrote; + + do { + wrote = ne_write(sock->fd, data, length); + if (wrote > 0) { + data += wrote; + length -= wrote; + } + } while ((wrote > 0 || NE_ISINTR(ne_errno)) && length > 0); + + if (wrote < 0) { + int errnum = ne_errno; + set_strerror(sock, errnum); + return MAP_ERR(errnum); + } + + return 0; +} + +static const struct iofns iofns_raw = { read_raw, write_raw, readable_raw }; + +#ifdef NEON_SSL +/* OpenSSL I/O function implementations. */ +static int readable_ossl(ne_socket *sock, int secs) +{ + /* If there is buffered SSL data, then don't block on the socket. + * FIXME: make sure that SSL_read *really* won't block if + * SSL_pending returns non-zero. Possibly need to do + * SSL_read(ssl, buf, SSL_pending(ssl)) */ + + if (SSL_pending(sock->ssl.ssl)) + return 0; + + return readable_raw(sock, secs); +} + +/* SSL error handling, according to SSL_get_error(3). */ +static int error_ossl(ne_socket *sock, int sret) +{ + int err = SSL_get_error(sock->ssl.ssl, sret), ret = NE_SOCK_ERROR; + + switch (err) { + case SSL_ERROR_ZERO_RETURN: + ret = NE_SOCK_CLOSED; + set_error(sock, _("Connection closed")); + break; + case SSL_ERROR_SYSCALL: + err = ERR_get_error(); + if (err == 0) { + if (sret == 0) { + /* EOF without close_notify, possible truncation */ + set_error(sock, _("Secure connection truncated")); + ret = NE_SOCK_TRUNC; + } else { + /* Other socket error. */ + err = ne_errno; + set_strerror(sock, err); + ret = MAP_ERR(err); + } + } else { + ne_snprintf(sock->error, sizeof sock->error, + _("SSL error: %s"), ERR_reason_error_string(err)); + } + break; + default: + ne_snprintf(sock->error, sizeof sock->error, _("SSL error: %s"), + ERR_reason_error_string(ERR_get_error())); + break; + } + return ret; +} + +/* Work around OpenSSL's use of 'int' rather than 'size_t', to prevent + * accidentally passing a negative number, etc. */ +#define CAST2INT(n) (((n) > INT_MAX) ? INT_MAX : (n)) + +static ssize_t read_ossl(ne_socket *sock, char *buffer, size_t len) +{ + int ret; + + ret = readable_ossl(sock, sock->rdtimeout); + if (ret) return ret; + + ret = SSL_read(sock->ssl.ssl, buffer, CAST2INT(len)); + if (ret <= 0) + ret = error_ossl(sock, ret); + + return ret; +} + +static ssize_t write_ossl(ne_socket *sock, const char *data, size_t len) +{ + int ret, ilen = CAST2INT(len); + ret = SSL_write(sock->ssl.ssl, data, ilen); + /* ssl.h says SSL_MODE_ENABLE_PARTIAL_WRITE must be enabled to + * have SSL_write return < length... so, SSL_write should never + * return < length. */ + if (ret != ilen) + return error_ossl(sock, ret); + return 0; +} + +static const struct iofns iofns_ossl = { + read_ossl, + write_ossl, + readable_ossl +}; + +#endif /* NEON_SSL */ + +int ne_sock_fullwrite(ne_socket *sock, const char *data, size_t len) +{ + return sock->ops->write(sock, data, len); +} + +ssize_t ne_sock_readline(ne_socket *sock, char *buf, size_t buflen) +{ + char *lf; + size_t len; + + if ((lf = memchr(sock->bufpos, '\n', sock->bufavail)) == NULL + && sock->bufavail < RDBUFSIZ) { + /* The buffered data does not contain a complete line: move it + * to the beginning of the buffer. */ + if (sock->bufavail) + memmove(sock->buffer, sock->bufpos, sock->bufavail); + sock->bufpos = sock->buffer; + + /* Loop filling the buffer whilst no newline is found in the data + * buffered so far, and there is still buffer space available */ + do { + /* Read more data onto end of buffer. */ + ssize_t ret = sock->ops->read(sock, sock->buffer + sock->bufavail, + RDBUFSIZ - sock->bufavail); + if (ret < 0) return ret; + sock->bufavail += ret; + } while ((lf = memchr(sock->buffer, '\n', sock->bufavail)) == NULL + && sock->bufavail < RDBUFSIZ); + } + + if (lf) + len = lf - sock->bufpos + 1; + else + len = buflen; /* fall into "line too long" error... */ + + if ((len + 1) > buflen) { + set_error(sock, _("Line too long")); + return NE_SOCK_ERROR; + } + + memcpy(buf, sock->bufpos, len); + buf[len] = '\0'; + /* consume the line from buffer: */ + sock->bufavail -= len; + sock->bufpos += len; + return len; +} + +ssize_t ne_sock_fullread(ne_socket *sock, char *buffer, size_t buflen) +{ + ssize_t len; + + while (buflen > 0) { + len = ne_sock_read(sock, buffer, buflen); + if (len < 0) return len; + buflen -= len; + buffer += len; + } + + return 0; +} + +#ifndef INADDR_NONE +#define INADDR_NONE ((unsigned long) -1) +#endif + +#if !defined(USE_GETADDRINFO) && !defined(HAVE_DECL_H_ERRNO) && !defined(WIN32) +/* Ancient versions of netdb.h don't export h_errno. */ +extern int h_errno; +#endif + +/* This implemementation does not attempt to support IPv6 using + * gethostbyname2 et al. */ +ne_sock_addr *ne_addr_resolve(const char *hostname, int flags) +{ + ne_sock_addr *addr = ne_calloc(sizeof *addr); +#ifdef USE_GETADDRINFO + struct addrinfo hints = {0}; + char *pnt; + hints.ai_socktype = SOCK_STREAM; + if (hostname[0] == '[' && ((pnt = strchr(hostname, ']')) != NULL)) { + char *hn = ne_strdup(hostname + 1); + hn[pnt - hostname - 1] = '\0'; +#ifdef AI_NUMERICHOST /* added in the RFC2553 API */ + hints.ai_flags = AI_NUMERICHOST; +#endif + hints.ai_family = AF_INET6; + addr->errnum = getaddrinfo(hn, NULL, &hints, &addr->result); + ne_free(hn); + } else { +#ifdef USE_ADDRCONFIG /* added in the RFC3493 API */ + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_family = AF_UNSPEC; + addr->errnum = getaddrinfo(hostname, NULL, &hints, &addr->result); + if (addr->errnum != EAI_BADFLAGS) + return addr; + /* Retry without AI_ADDRCONFIG if this libc doesn't grok it */ + hints.ai_flags = 0; +#else + hints.ai_family = ipv6_disabled ? AF_INET : AF_UNSPEC; +#endif + addr->errnum = getaddrinfo(hostname, NULL, &hints, &addr->result); + } +#else /* Use gethostbyname() */ + unsigned long laddr; + struct hostent *hp; + + laddr = inet_addr(hostname); + if (laddr == INADDR_NONE) { + hp = gethostbyname(hostname); + if (hp == NULL) { +#ifdef WIN32 + addr->errnum = WSAGetLastError(); +#else + addr->errnum = h_errno; +#endif + } else if (hp->h_length != sizeof(struct in_addr)) { + /* fail gracefully if somebody set RES_USE_INET6 */ + addr->errnum = NO_RECOVERY; + } else { + size_t n; + /* count addresses */ + for (n = 0; hp->h_addr_list[n] != NULL; n++) + /* noop */; + + addr->count = n; + addr->addrs = ne_malloc(n * sizeof *addr->addrs); + + for (n = 0; n < addr->count; n++) + memcpy(&addr->addrs[n], hp->h_addr_list[n], hp->h_length); + } + } else { + addr->addrs = ne_malloc(sizeof *addr->addrs); + addr->count = 1; + memcpy(addr->addrs, &laddr, sizeof *addr->addrs); + } +#endif + return addr; +} + +int ne_addr_result(const ne_sock_addr *addr) +{ + return addr->errnum; +} + +const ne_inet_addr *ne_addr_first(ne_sock_addr *addr) +{ +#ifdef USE_GETADDRINFO + addr->cursor = addr->result->ai_next; + return addr->result; +#else + addr->cursor = 0; + return &addr->addrs[0]; +#endif +} + +const ne_inet_addr *ne_addr_next(ne_sock_addr *addr) +{ +#ifdef USE_GETADDRINFO + struct addrinfo *ret = addr->cursor; + if (addr->cursor) addr->cursor = addr->cursor->ai_next; +#else + struct in_addr *ret; + if (++addr->cursor < addr->count) + ret = &addr->addrs[addr->cursor]; + else + ret = NULL; +#endif + return ret; +} + +char *ne_addr_error(const ne_sock_addr *addr, char *buf, size_t bufsiz) +{ +#ifdef WIN32 + print_error(addr->errnum, buf, bufsiz); +#else + const char *err; +#ifdef USE_GETADDRINFO + /* override horrible generic "Name or service not known" error. */ + if (addr->errnum == EAI_NONAME) + err = _("Host not found"); + else + err = gai_strerror(addr->errnum); +#elif defined(HAVE_HSTRERROR) + err = hstrerror(addr->errnum); +#else + err = _("Host not found"); +#endif + ne_strnzcpy(buf, err, bufsiz); +#endif /* WIN32 */ + return buf; +} + +char *ne_iaddr_print(const ne_inet_addr *ia, char *buf, size_t bufsiz) +{ +#ifdef USE_GETADDRINFO /* implies inet_ntop */ + const char *ret; +#ifdef AF_INET6 + if (ia->ai_family == AF_INET6) { + struct sockaddr_in6 *in6 = SACAST(in6, ia->ai_addr); + ret = inet_ntop(AF_INET6, &in6->sin6_addr, buf, bufsiz); + } else +#endif + if (ia->ai_family == AF_INET) { + struct sockaddr_in *in = SACAST(in, ia->ai_addr); + ret = inet_ntop(AF_INET, &in->sin_addr, buf, bufsiz); + } else + ret = NULL; + if (ret == NULL) + ne_strnzcpy(buf, "[IP address]", bufsiz); +#else + ne_strnzcpy(buf, inet_ntoa(*ia), bufsiz); +#endif + return buf; +} + +void ne_addr_destroy(ne_sock_addr *addr) +{ +#ifdef USE_GETADDRINFO + if (addr->result) + freeaddrinfo(addr->result); +#else + if (addr->addrs) + ne_free(addr->addrs); +#endif + ne_free(addr); +} + +/* Connect socket 'fd' to address 'addr' on given 'port': */ +static int raw_connect(int fd, const ne_inet_addr *addr, unsigned int port) +{ +#ifdef USE_GETADDRINFO +#ifdef AF_INET6 + /* fill in the _family field for AIX 4.3, which forgets to do so. */ + if (addr->ai_family == AF_INET6) { + struct sockaddr_in6 in6; + memcpy(&in6, addr->ai_addr, sizeof in6); + in6.sin6_port = port; + in6.sin6_family = AF_INET6; + return connect(fd, (struct sockaddr *)&in6, sizeof in6); + } else +#endif + if (addr->ai_family == AF_INET) { + struct sockaddr_in in; + memcpy(&in, addr->ai_addr, sizeof in); + in.sin_port = port; + in.sin_family = AF_INET; + return connect(fd, (struct sockaddr *)&in, sizeof in); + } else { + errno = EINVAL; + return -1; + } +#else + struct sockaddr_in sa = {0}; + sa.sin_family = AF_INET; + sa.sin_port = port; + sa.sin_addr = *addr; + return connect(fd, (struct sockaddr *)&sa, sizeof sa); +#endif +} + +ne_socket *ne_sock_create(void) +{ + ne_socket *sock = ne_calloc(sizeof *sock); + sock->rdtimeout = SOCKET_READ_TIMEOUT; + sock->bufpos = sock->buffer; + sock->ops = &iofns_raw; + sock->fd = -1; + return sock; +} + +int ne_sock_connect(ne_socket *sock, + const ne_inet_addr *addr, unsigned int port) +{ + int fd; + +#ifdef USE_GETADDRINFO + /* use SOCK_STREAM rather than ai_socktype: some getaddrinfo + * implementations do not set ai_socktype, e.g. RHL6.2. */ + fd = socket(addr->ai_family, SOCK_STREAM, addr->ai_protocol); +#else + fd = socket(AF_INET, SOCK_STREAM, 0); +#endif + if (fd < 0) { + set_strerror(sock, ne_errno); + return -1; + } + + if (raw_connect(fd, addr, ntohs(port))) { + set_strerror(sock, ne_errno); + ne_close(fd); + return -1; + } + + sock->fd = fd; + return 0; +} + +ne_inet_addr *ne_iaddr_make(ne_iaddr_type type, const unsigned char *raw) +{ + ne_inet_addr *ia; +#if !defined(AF_INET6) || !defined(USE_GETADDRINFO) + /* fail if IPv6 address is given if IPv6 is not supported. */ + if (type == ne_iaddr_ipv6) + return NULL; +#endif + ia = ne_calloc(sizeof *ia); +#ifdef USE_GETADDRINFO + /* ai_protocol and ai_socktype aren't used by raw_connect so + * ignore them here. (for now) */ + if (type == ne_iaddr_ipv4) { + struct sockaddr_in *in4 = ne_calloc(sizeof *in4); + ia->ai_family = AF_INET; + ia->ai_addr = (struct sockaddr *)in4; + ia->ai_addrlen = sizeof *in4; + in4->sin_family = AF_INET; + memcpy(&in4->sin_addr.s_addr, raw, sizeof in4->sin_addr.s_addr); + } +#ifdef AF_INET6 + else { + struct sockaddr_in6 *in6 = ne_calloc(sizeof *in6); + ia->ai_family = AF_INET6; + ia->ai_addr = (struct sockaddr *)in6; + ia->ai_addrlen = sizeof *in6; + in6->sin6_family = AF_INET6; + memcpy(&in6->sin6_addr, raw, sizeof in6->sin6_addr.s6_addr); + } +#endif +#else /* !USE_GETADDRINFO */ + memcpy(&ia->s_addr, raw, sizeof ia->s_addr); +#endif + return ia; +} + +int ne_iaddr_cmp(const ne_inet_addr *i1, const ne_inet_addr *i2) +{ +#ifdef USE_GETADDRINFO + if (i1->ai_family != i2->ai_family) + return i2->ai_family - i1->ai_family; + if (i1->ai_family == AF_INET) { + struct sockaddr_in *in1 = SACAST(in, i1->ai_addr), + *in2 = SACAST(in, i2->ai_addr); + return memcmp(&in1->sin_addr.s_addr, &in2->sin_addr.s_addr, + sizeof in1->sin_addr.s_addr); + } else if (i1->ai_family == AF_INET6) { + struct sockaddr_in6 *in1 = SACAST(in6, i1->ai_addr), + *in2 = SACAST(in6, i2->ai_addr); + return memcmp(in1->sin6_addr.s6_addr, in2->sin6_addr.s6_addr, + sizeof in1->sin6_addr.s6_addr); + } else + return -1; +#else + return memcmp(&i1->s_addr, &i2->s_addr, sizeof i1->s_addr); +#endif +} + +void ne_iaddr_free(ne_inet_addr *addr) +{ +#ifdef USE_GETADDRINFO + ne_free(addr->ai_addr); +#endif + ne_free(addr); +} + +int ne_sock_accept(ne_socket *sock, int listener) +{ + int fd = accept(listener, NULL, NULL); + + if (fd < 0) + return -1; + + sock->fd = fd; + return 0; +} + +int ne_sock_fd(const ne_socket *sock) +{ + return sock->fd; +} + +void ne_sock_read_timeout(ne_socket *sock, int timeout) +{ + sock->rdtimeout = timeout; +} + +#ifdef NEON_SSL + +void ne_sock_switch_ssl(ne_socket *sock, void *ssl) +{ + sock->ssl.ssl = ssl; + sock->ops = &iofns_ossl; +} + +int ne_sock_connect_ssl(ne_socket *sock, ne_ssl_context *ctx) +{ + SSL *ssl; + int ret; + + if (!prng_seeded && RAND_status() != 1) { + set_error(sock, _("SSL disabled due to lack of entropy")); + return NE_SOCK_ERROR; + } + + sock->ssl.ssl = ssl = SSL_new(ctx->ctx); + if (!ssl) { + set_error(sock, _("Could not create SSL structure")); + return NE_SOCK_ERROR; + } + + SSL_set_app_data(ssl, ctx); + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + SSL_set_fd(ssl, sock->fd); + sock->ops = &iofns_ossl; + + if (ctx->sess) + SSL_set_session(ssl, ctx->sess); + + ret = SSL_connect(ssl); + if (ret != 1) { + error_ossl(sock, ret); + SSL_free(ssl); + sock->ssl.ssl = NULL; + return NE_SOCK_ERROR; + } + + return 0; +} + +ne_ssl_socket *ne_sock_sslsock(ne_socket *sock) +{ + return &sock->ssl; +} + +#endif + +const char *ne_sock_error(const ne_socket *sock) +{ + return sock->error; +} + +/* Closes given ne_socket */ +int ne_sock_close(ne_socket *sock) +{ + int ret; +#ifdef NEON_SSL + if (sock->ssl.ssl) { + SSL_shutdown(sock->ssl.ssl); + SSL_free(sock->ssl.ssl); + } +#endif + if (sock->fd < 0) + ret = 0; + else + ret = ne_close(sock->fd); + ne_free(sock); + return ret; +} + +/* Returns HOST byte order port of given name */ +int ne_service_lookup(const char *name) +{ + struct servent *ent; + ent = getservbyname(name, "tcp"); + if (ent) + return ntohs(ent->s_port); + return 0; +} diff --git a/src/ne_socket.h b/src/ne_socket.h new file mode 100644 index 0000000..58f45ff --- /dev/null +++ b/src/ne_socket.h @@ -0,0 +1,197 @@ +/* + socket handling interface + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_SOCKET_H +#define NE_SOCKET_H + +#include <sys/types.h> + +#include "ne_defs.h" +#include "ne_ssl.h" /* for ne_ssl_context */ + +BEGIN_NEON_DECLS + +/* define ssize_t for Win32 */ +#if defined(WIN32) && !defined(ssize_t) +#define ssize_t int +#endif + +#define NE_SOCK_ERROR (-1) +/* Read/Write timed out */ +#define NE_SOCK_TIMEOUT (-2) +/* Socket was closed */ +#define NE_SOCK_CLOSED (-3) +/* Connection was reset (e.g. server crashed) */ +#define NE_SOCK_RESET (-4) +/* Secure connection was subject to possible truncation attack. */ +#define NE_SOCK_TRUNC (-5) + +/* ne_socket represents a TCP socket. */ +typedef struct ne_socket_s ne_socket; + +/* ne_sock_addr represents an address object. */ +typedef struct ne_sock_addr_s ne_sock_addr; + +#ifndef NE_INET_ADDR_DEFINED +typedef struct ne_inet_addr_s ne_inet_addr; +#endif + +/* While neon itself doesn't require per-process global + * initialization, some platforms do, and so does the OpenSSL + * library. */ +int ne_sock_init(void); + +/* Shutdown any underlying libraries. */ +void ne_sock_exit(void); + +/* Resolve the given hostname. 'flags' are currently ignored. Hex + * string IPv6 addresses (e.g. `::1') may be enclosed in brackets + * (e.g. `[::1]'). */ +ne_sock_addr *ne_addr_resolve(const char *hostname, int flags); + +/* Returns zero if name resolution was successful, non-zero on + * error. */ +int ne_addr_result(const ne_sock_addr *addr); + +/* Returns the first network address associated with the 'addr' + * object. Undefined behaviour if ne_addr_result returns non-zero for + * 'addr'; otherwise, never returns NULL. */ +const ne_inet_addr *ne_addr_first(ne_sock_addr *addr); + +/* Returns the next network address associated with the 'addr' object, + * or NULL if there are no more. */ +const ne_inet_addr *ne_addr_next(ne_sock_addr *addr); + +/* NB: the pointers returned by ne_addr_first and ne_addr_next are + * valid until ne_addr_destroy is called for the corresponding + * ne_sock_addr object. They must not be passed to ne_iaddr_free. */ + +/* If name resolution fails, copies the error string into 'buffer', + * which is of size 'bufsiz'. 'buffer' is returned. */ +char *ne_addr_error(const ne_sock_addr *addr, char *buffer, size_t bufsiz); + +/* Destroys an address object created by ne_addr_resolve. */ +void ne_addr_destroy(ne_sock_addr *addr); + +/* Network address type; IPv4 or IPv6 */ +typedef enum { + ne_iaddr_ipv4 = 0, + ne_iaddr_ipv6 +} ne_iaddr_type; + +/* Create a network address from raw byte representation (in network + * byte order) of given type. 'raw' must be four bytes for an IPv4 + * address, 16 bytes for an IPv6 address. May return NULL if address + * type is not supported. */ +ne_inet_addr *ne_iaddr_make(ne_iaddr_type type, const unsigned char *raw); + +/* Compare two network addresses i1 and i2; return non-zero if they + * are not equal. */ +int ne_iaddr_cmp(const ne_inet_addr *i1, const ne_inet_addr *i2); + +/* Prints the string representation of network address 'ia' into the + * 'buffer', which is of size 'bufsiz'. Returns 'buffer'. */ +char *ne_iaddr_print(const ne_inet_addr *ia, char *buffer, size_t bufsiz); + +/* Free a network address created using ne_iaddr_make. */ +void ne_iaddr_free(ne_inet_addr *addr); + +/* Create a TCP socket; returns NULL on error. */ +ne_socket *ne_sock_create(void); + +/* Connect the socket to server at address 'addr' on port 'port'. + * Returns non-zero if a connection could not be established. */ +int ne_sock_connect(ne_socket *sock, const ne_inet_addr *addr, + unsigned int port); + +/* ne_sock_read reads up to 'count' bytes into 'buffer'. + * Returns: + * NE_SOCK_* on error, + * >0 length of data read into buffer. + */ +ssize_t ne_sock_read(ne_socket *sock, char *buffer, size_t count); + +/* ne_sock_peek reads up to 'count' bytes into 'buffer', but the data + * will still be returned on a subsequent call to ne_sock_read or + * ne_sock_peek. + * Returns: + * NE_SOCK_* on error, + * >0 length of data read into buffer. + */ +ssize_t ne_sock_peek(ne_socket *sock, char *buffer, size_t count); + +/* Block for up to 'n' seconds until data becomes available for reading + * on the socket. Returns: + * NE_SOCK_* on error, + * NE_SOCK_TIMEOUT if no data arrives in 'n' seconds. + * 0 if data arrived on the socket. + */ +int ne_sock_block(ne_socket *sock, int n); + +/* Writes 'count' bytes of 'data' to the socket. + * Returns 0 on success, NE_SOCK_* on error. */ +int ne_sock_fullwrite(ne_socket *sock, const char *data, size_t count); + +/* Reads an LF-terminated line into 'buffer', and NUL-terminate it. + * At most 'len' bytes are read (including the NUL terminator). + * Returns: + * NE_SOCK_* on error, + * >0 number of bytes read (including NUL terminator) + */ +ssize_t ne_sock_readline(ne_socket *sock, char *buffer, size_t len); + +/* Read exactly 'len' bytes into buffer; returns 0 on success, SOCK_* + * on error. */ +ssize_t ne_sock_fullread(ne_socket *sock, char *buffer, size_t len); + +/* Accept a connection on listening socket 'fd'. */ +int ne_sock_accept(ne_socket *sock, int fd); + +/* Returns the file descriptor used for socket 'sock'. */ +int ne_sock_fd(const ne_socket *sock); + +/* Close the socket, and destroy the socket object. Returns non-zero + * on error. */ +int ne_sock_close(ne_socket *sock); + +/* Return current error string for socket. */ +const char *ne_sock_error(const ne_socket *sock); + +/* Set read timeout for socket. */ +void ne_sock_read_timeout(ne_socket *sock, int timeout); + +/* Returns the standard TCP port for the given service, or zero if + * none is known. */ +int ne_service_lookup(const char *name); + +/* Enable SSL with an already-negotiated SSL socket. */ +void ne_sock_switch_ssl(ne_socket *sock, void *ssl); + +/* Perform an SSL negotiation on 'sock', using given context. */ +int ne_sock_connect_ssl(ne_socket *sock, ne_ssl_context *ctx); + +/* Return SSL socket object in use for 'sock'. */ +typedef struct ne_ssl_socket_s ne_ssl_socket; +ne_ssl_socket *ne_sock_sslsock(ne_socket *sock); + +END_NEON_DECLS + +#endif /* NE_SOCKET_H */ diff --git a/src/ne_ssl.h b/src/ne_ssl.h new file mode 100644 index 0000000..959ff7b --- /dev/null +++ b/src/ne_ssl.h @@ -0,0 +1,150 @@ +/* + SSL/TLS abstraction layer for neon + Copyright (C) 2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* ne_ssl.h defines an interface for loading and accessing the + * properties of SSL certificates. */ + +#ifndef NE_SSL_H +#define NE_SSL_H 1 + +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +/* A "distinguished name"; a unique name for some entity. */ +typedef struct ne_ssl_dname_s ne_ssl_dname; + +/* Returns a single-line string representation of a distinguished + * name, intended to be human-readable (e.g. "Acme Ltd., Norfolk, + * GB"). Return value is malloc-allocated and must be free'd by the + * caller. */ +char *ne_ssl_readable_dname(const ne_ssl_dname *dn); + +/* Returns zero if 'dn1' and 'dn2' refer to same name, or non-zero if + * they are different. */ +int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2); + +/* An SSL certificate. */ +typedef struct ne_ssl_certificate_s ne_ssl_certificate; + +/* Read a certificate from a file in PEM format; returns NULL if the + * certificate could not be parsed. */ +ne_ssl_certificate *ne_ssl_cert_read(const char *filename); + +/* Write a certificate to a file in PEM format; returns non-zero if + * the certificate could not be written. */ +int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename); + +/* Export a certificate to a base64-encoded, NUL-terminated string. + * The returned string is malloc-allocated and must be free()d by the + * caller. */ +char *ne_ssl_cert_export(const ne_ssl_certificate *cert); + +/* Import a certificate from a base64-encoded string as returned by + * ne_ssl_cert_export(). Returns a certificate object or NULL if + * 'data' was not valid. */ +ne_ssl_certificate *ne_ssl_cert_import(const char *data); + +/* Returns the identity of the certificate, or NULL if none is given. + * For a server certificate this will be the hostname of the server to + * whom the cert was issued. */ +const char *ne_ssl_cert_identity(const ne_ssl_certificate *cert); + +/* Return the certificate of the entity which signed certificate + * 'cert'. Returns NULL if 'cert' is self-signed or the issuer + * certificate is not available. */ +const ne_ssl_certificate *ne_ssl_cert_signedby(const ne_ssl_certificate *cert); + +/* Returns the distinguished name of the certificate issuer. */ +const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert); + +/* Returns the distinguished name of the certificate subject. */ +const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert); + +#define NE_SSL_DIGESTLEN (60) + +/* Calculate the digest ("fingerprint") and format it as a + * NUL-terminated hex string in 'digest', of the form "aa:bb:...:ff". + * Returns zero on success or non-zero if there was an internal error + * whilst calculating the digest. 'digest' must be at least + * NE_SSL_DIGESTLEN bytes in length. */ +int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest); + +#define NE_SSL_VDATELEN (30) + +/* Copy the validity dates into buffers 'from' and 'until' as + * NUL-terminated human-readable strings. The buffers must be at + * least NE_SSL_VDATELEN bytes in length. */ +void ne_ssl_cert_validity(const ne_ssl_certificate *cert, + char *from, char *until); + +/* Returns zero if 'c1' and 'c2' refer to the same certificate, or + * non-zero otherwise. */ +int ne_ssl_cert_cmp(const ne_ssl_certificate *c1, + const ne_ssl_certificate *c2); + +/* Deallocate memory associated with certificate. */ +void ne_ssl_cert_free(ne_ssl_certificate *cert); + +/* A client certificate (and private key). */ +typedef struct ne_ssl_client_cert_s ne_ssl_client_cert; + +/* Read a client certificate and private key from a PKCS12 file; + * returns NULL if the file could not be parsed. If the client cert + * is encrypted, it must be decrypted before use. */ +ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename); + +/* Returns the "friendly name" given for the client cert, or NULL if + * none given. (this can be called before the client cert has been + * decrypted). */ +const char *ne_ssl_clicert_name(ne_ssl_client_cert *ccert); + +/* Returns non-zero if client cert is encrypted. */ +int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *ccert); + +/* Decrypt the encrypted client cert using given password. Returns + * non-zero on failure, in which case, the function can be called + * again with a different password. For a ccert on which _encrypted() + * returns 0, calling _decrypt results in undefined behaviour. */ +int ne_ssl_clicert_decrypt(ne_ssl_client_cert *ccert, const char *password); + +/* Return the actual certificate part of the client certificate (never + * returns NULL). */ +const ne_ssl_certificate *ne_ssl_clicert_owner(const ne_ssl_client_cert *ccert); + +/* Deallocate memory associated with a client certificate. */ +void ne_ssl_clicert_free(ne_ssl_client_cert *ccert); + +/* An SSL context; only necessary when interfacing with ne_socket.h. */ +typedef struct ne_ssl_context_s ne_ssl_context; + +/* Create an SSL context. */ +ne_ssl_context *ne_ssl_context_create(void); + +/* Trust the given certificate 'cert' in context 'ctx'. */ +void ne_ssl_ctx_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert); + +/* Destroy an SSL context. */ +void ne_ssl_context_destroy(ne_ssl_context *ctx); + +END_NEON_DECLS + +#endif diff --git a/src/ne_string.c b/src/ne_string.c new file mode 100644 index 0000000..3cddad9 --- /dev/null +++ b/src/ne_string.c @@ -0,0 +1,519 @@ +/* + String utility functions + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <ctype.h> /* for isprint() etc in ne_strclean() */ + +#include "ne_alloc.h" +#include "ne_string.h" + +char *ne_token(char **str, char separator) +{ + char *ret = *str, *pnt = strchr(*str, separator); + + if (pnt) { + *pnt = '\0'; + *str = pnt + 1; + } else { + /* no separator found: return end of string. */ + *str = NULL; + } + + return ret; +} + +char *ne_qtoken(char **str, char separator, const char *quotes) +{ + char *pnt, *ret = NULL; + + for (pnt = *str; *pnt != '\0'; pnt++) { + char *quot = strchr(quotes, *pnt); + + if (quot) { + char *qclose = strchr(pnt+1, *quot); + + if (!qclose) { + /* no closing quote: invalid string. */ + return NULL; + } + + pnt = qclose; + } else if (*pnt == separator) { + /* found end of token. */ + *pnt = '\0'; + ret = *str; + *str = pnt + 1; + return ret; + } + } + + /* no separator found: return end of string. */ + ret = *str; + *str = NULL; + return ret; +} + +char *ne_shave(char *str, const char *whitespace) +{ + char *pnt, *ret = str; + + while (*ret != '\0' && strchr(whitespace, *ret) != NULL) { + ret++; + } + + /* pnt points at the NUL terminator. */ + pnt = &ret[strlen(ret)]; + + while (pnt > ret && strchr(whitespace, *(pnt-1)) != NULL) { + pnt--; + } + + *pnt = '\0'; + return ret; +} + +/* TODO: deprecate all these and use ne_token() instead. */ + +char **split_string(const char *str, const char separator, + const char *quotes, const char *whitespace) +{ + return split_string_c(str, separator, quotes, whitespace, NULL); +} + +char **split_string_c(const char *str, const char separator, + const char *quotes, const char *whitespace, + int *give_count) +{ + char **comps; + const char *pnt, *quot = NULL, + *start, *end; /* The start of the current component */ + int count, /* The number of components */ + iswhite, /* is it whitespace */ + issep, /* is it the separator */ + curr, /* current component index */ + length, /* length of component */ + leading_wspace; /* in leading whitespace still? */ + + /* Inefficient, but easier - first off, count the number of + * components we have. */ + count = 1; + for (pnt = str; *pnt!='\0'; pnt++) { + if (quotes != NULL) { + quot = strchr(quotes, *pnt); + } + if (quot != NULL) { + /* We found a quote, so skip till the next quote */ + for (pnt++; (*pnt!=*quot) && (*pnt!='\0'); pnt++) + /* nullop */; + } else if (*pnt == separator) { + count++; + } + } + + if (give_count) { + /* Write the count */ + *give_count = count; + } + + /* Now, have got the number of components. + * Allocate the comps array. +1 for the NULL */ + comps = ne_malloc(sizeof(char *) * (count + 1)); + + comps[count] = NULL; + + quot = end = start = NULL; + curr = 0; + leading_wspace = 1; + + /* Now fill in the array */ + for (pnt = str; *pnt != '\0'; pnt++) { + /* What is the current character - quote, whitespace, separator? */ + if (quotes != NULL) { + quot = strchr(quotes, *pnt); + } + iswhite = (whitespace!=NULL) && + (strchr(whitespace, *pnt) != NULL); + issep = (*pnt == separator); + /* What to do? */ + if (leading_wspace) { + if (quot!=NULL) { + /* Quoted bit */ + start = pnt; + length = 1; + leading_wspace = 0; + } else if (issep) { + /* Zero-length component */ + comps[curr++] = ne_strdup(""); + } else if (!iswhite) { + start = end = pnt; + length = 1; + leading_wspace = 0; + } + } else { + if (quot!=NULL) { + /* Quoted bit */ + length++; + } else if (issep) { + /* End of component - enter it into the array */ + length = (end - start) + 1; + comps[curr] = ne_malloc(length+1); + memcpy(comps[curr], start, length); + comps[curr][length] = '\0'; + curr++; + leading_wspace = 1; + } else if (!iswhite) { + /* Not whitespace - update end marker */ + end = pnt; + } + } + if (quot != NULL) { + /* Skip to closing quote */ + for (pnt++; *pnt!=*quot && *pnt != '\0'; ++pnt) + /* nullop */; + /* Last non-wspace char is closing quote */ + end = pnt; + } + } + /* Handle final component */ + if (leading_wspace) { + comps[curr] = ne_strdup(""); + } else { + /* End of component - enter it into the array */ + length = (end - start) + 1; + comps[curr] = ne_malloc(length+1); + memcpy(comps[curr], start, length); + comps[curr][length] = '\0'; + } + return comps; +} + +char **pair_string(const char *str, const char compsep, const char kvsep, + const char *quotes, const char *whitespace) +{ + char **comps, **pairs, *split; + int count = 0, n, length; + comps = split_string_c(str, compsep, quotes, whitespace, &count); + /* Allocate space for 2* as many components as split_string returned, + * +2 for the NULLS. */ + pairs = ne_malloc((2*count+2) * sizeof(char *)); + if (pairs == NULL) { + return NULL; + } + for (n = 0; n < count; n++) { + /* Find the split */ + split = strchr(comps[n], kvsep); + if (split == NULL) { + /* No seperator found */ + length = strlen(comps[n]); + } else { + length = split-comps[n]; + } + /* Enter the key into the array */ + pairs[2*n] = comps[n]; + /* Null-terminate the key */ + pairs[2*n][length] = '\0'; + pairs[2*n+1] = split?(split + 1):NULL; + } + ne_free(comps); + pairs[2*count] = pairs[2*count+1] = NULL; + return pairs; +} + +void split_string_free(char **components) +{ + char **pnt = components; + while (*pnt != NULL) { + ne_free(*pnt); + pnt++; + } + ne_free(components); +} + +void pair_string_free(char **pairs) +{ + int n; + for (n = 0; pairs[n] != NULL; n+=2) { + ne_free(pairs[n]); + } + ne_free(pairs); +} + +void ne_buffer_clear(ne_buffer *buf) +{ + memset(buf->data, 0, buf->length); + buf->used = 1; +} + +/* Grows for given size, returns 0 on success, -1 on error. */ +void ne_buffer_grow(ne_buffer *buf, size_t newsize) +{ +#define NE_BUFFER_GROWTH 512 + if (newsize > buf->length) { + /* If it's not big enough already... */ + buf->length = ((newsize / NE_BUFFER_GROWTH) + 1) * NE_BUFFER_GROWTH; + + /* Reallocate bigger buffer */ + buf->data = ne_realloc(buf->data, buf->length); + } +} + +static size_t count_concat(va_list *ap) +{ + size_t total = 0; + char *next; + + while ((next = va_arg(*ap, char *)) != NULL) + total += strlen(next); + + return total; +} + +static void do_concat(char *str, va_list *ap) +{ + char *next; + + while ((next = va_arg(*ap, char *)) != NULL) { +#ifdef HAVE_STPCPY + str = stpcpy(str, next); +#else + size_t len = strlen(next); + memcpy(str, next, len); + str += len; +#endif + } +} + +void ne_buffer_concat(ne_buffer *buf, ...) +{ + va_list ap; + ssize_t total; + + va_start(ap, buf); + total = buf->used + count_concat(&ap); + va_end(ap); + + /* Grow the buffer */ + ne_buffer_grow(buf, total); + + va_start(ap, buf); + do_concat(buf->data + buf->used - 1, &ap); + va_end(ap); + + buf->used = total; + buf->data[total - 1] = '\0'; +} + +char *ne_concat(const char *str, ...) +{ + va_list ap; + size_t total, slen = strlen(str); + char *ret; + + va_start(ap, str); + total = slen + count_concat(&ap); + va_end(ap); + + ret = memcpy(ne_malloc(total + 1), str, slen); + + va_start(ap, str); + do_concat(ret + slen, &ap); + va_end(ap); + + ret[total] = '\0'; + return ret; +} + +/* Append zero-terminated string... returns 0 on success or -1 on + * realloc failure. */ +void ne_buffer_zappend(ne_buffer *buf, const char *str) +{ + ne_buffer_append(buf, str, strlen(str)); +} + +void ne_buffer_append(ne_buffer *buf, const char *data, size_t len) +{ + ne_buffer_grow(buf, buf->used + len); + memcpy(buf->data + buf->used - 1, data, len); + buf->used += len; + buf->data[buf->used - 1] = '\0'; +} + +ne_buffer *ne_buffer_create(void) +{ + return ne_buffer_ncreate(512); +} + +ne_buffer *ne_buffer_ncreate(size_t s) +{ + ne_buffer *buf = ne_malloc(sizeof(*buf)); + buf->data = ne_malloc(s); + buf->data[0] = '\0'; + buf->length = s; + buf->used = 1; + return buf; +} + +void ne_buffer_destroy(ne_buffer *buf) +{ + ne_free(buf->data); + ne_free(buf); +} + +char *ne_buffer_finish(ne_buffer *buf) +{ + char *ret = buf->data; + ne_free(buf); + return ret; +} + +void ne_buffer_altered(ne_buffer *buf) +{ + buf->used = strlen(buf->data) + 1; +} + +static const char *b64_alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/="; + +char *ne_base64(const unsigned char *text, size_t inlen) +{ + /* The tricky thing about this is doing the padding at the end, + * doing the bit manipulation requires a bit of concentration only */ + char *buffer, *point; + size_t outlen; + + /* Use 'buffer' to store the output. Work out how big it should be... + * This must be a multiple of 4 bytes */ + + outlen = (inlen*4)/3; + if ((inlen % 3) > 0) /* got to pad */ + outlen += 4 - (inlen % 3); + + buffer = ne_malloc(outlen + 1); /* +1 for the \0 */ + + /* now do the main stage of conversion, 3 bytes at a time, + * leave the trailing bytes (if there are any) for later */ + + for (point=buffer; inlen>=3; inlen-=3, text+=3) { + *(point++) = b64_alphabet[ (*text)>>2 ]; + *(point++) = b64_alphabet[ ((*text)<<4 & 0x30) | (*(text+1))>>4 ]; + *(point++) = b64_alphabet[ ((*(text+1))<<2 & 0x3c) | (*(text+2))>>6 ]; + *(point++) = b64_alphabet[ (*(text+2)) & 0x3f ]; + } + + /* Now deal with the trailing bytes */ + if (inlen > 0) { + /* We always have one trailing byte */ + *(point++) = b64_alphabet[ (*text)>>2 ]; + *(point++) = b64_alphabet[ (((*text)<<4 & 0x30) | + (inlen==2?(*(text+1))>>4:0)) ]; + *(point++) = (inlen==1?'=':b64_alphabet[ (*(text+1))<<2 & 0x3c ]); + *(point++) = '='; + } + + /* Null-terminate */ + *point = '\0'; + + return buffer; +} + +/* VALID_B64: fail if 'ch' is not a valid base64 character */ +#define VALID_B64(ch) (((ch) >= 'A' && (ch) <= 'Z') || \ + ((ch) >= 'a' && (ch) <= 'z') || \ + ((ch) >= '0' && (ch) <= '9') || \ + (ch) == '/' || (ch) == '+' || (ch) == '=') + +/* DECODE_B64: decodes a valid base64 character. */ +#define DECODE_B64(ch) ((ch) >= 'a' ? ((ch) + 26 - 'a') : \ + ((ch) >= 'A' ? ((ch) - 'A') : \ + ((ch) >= '0' ? ((ch) + 52 - '0') : \ + ((ch) == '+' ? 62 : 63)))) + +size_t ne_unbase64(const char *data, unsigned char **out) +{ + size_t inlen = strlen(data); + unsigned char *outp; + const unsigned char *in; + + if (inlen == 0 || (inlen % 4) != 0) return 0; + + outp = *out = ne_malloc(inlen * 3 / 4); + + for (in = (const unsigned char *)data; *in; in += 4) { + unsigned int tmp; + if (!VALID_B64(in[0]) || !VALID_B64(in[1]) || !VALID_B64(in[2]) || + !VALID_B64(in[3]) || in[0] == '=' || in[1] == '=' || + (in[2] == '=' && in[3] != '=')) { + ne_free(*out); + return 0; + } + tmp = (DECODE_B64(in[0]) & 0x3f) << 18 | + (DECODE_B64(in[1]) & 0x3f) << 12; + *outp++ = (tmp >> 16) & 0xff; + if (in[2] != '=') { + tmp |= (DECODE_B64(in[2]) & 0x3f) << 6; + *outp++ = (tmp >> 8) & 0xff; + if (in[3] != '=') { + tmp |= DECODE_B64(in[3]) & 0x3f; + *outp++ = tmp & 0xff; + } + } + } + + return outp - *out; +} + +char *ne_strclean(char *str) +{ + char *pnt; + for (pnt = str; *pnt; pnt++) + if (iscntrl(*pnt) || !isprint(*pnt)) *pnt = ' '; + return str; +} + +char *ne_strerror(int errnum, char *buf, size_t buflen) +{ +#ifdef HAVE_STRERROR_R +#ifdef STRERROR_R_CHAR_P + /* glibc-style strerror_r which may-or-may-not use provided buffer. */ + char *ret = strerror_r(errnum, buf, buflen); + if (ret != buf) + ne_strnzcpy(buf, ret, buflen); +#else /* POSIX-style strerror_r: */ + strerror_r(errnum, buf, buflen); +#endif +#else /* no strerror_r: */ + ne_strnzcpy(buf, strerror(errnum), buflen); +#endif + return buf; +} diff --git a/src/ne_string.h b/src/ne_string.h new file mode 100644 index 0000000..81ede09 --- /dev/null +++ b/src/ne_string.h @@ -0,0 +1,137 @@ +/* + String utility functions + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_STRING_H +#define NE_STRING_H + +#include "ne_defs.h" +#include "ne_alloc.h" + +#include <stdarg.h> + +BEGIN_NEON_DECLS + +/* ne_token and ne_qtoken return the next token in *str between *str + * and separator character 'sep' or the NUL terminator. ne_qtoken + * skips over any parts quoted using a pair of any one of the + * characters given in 'quotes'. After returning, *str will point to + * the next character after the separator, or NULL if no more + * separator characters were found. + * + * ne_qtoken may return NULL if unterminated quotes are found. */ +char *ne_token(char **str, char sep); +char *ne_qtoken(char **str, char sep, const char *quotes); + +/* Return portion of 'str' with any characters in 'whitespace' shaved + * off the beginning and end. Modifies str. */ +char *ne_shave(char *str, const char *whitespace); + +/* Cleanse 'str' of non-printable characters. 'str' is modified + * in-place, and returned. */ +char *ne_strclean(char *str); + +/* A base64 encoder: converts 'len' bytes of 'text' to base64. + * Returns malloc-allocated buffer; caller must free(). */ +char *ne_base64(const unsigned char *text, size_t len); + +/* Base64 decoder; decodes NUL-terminated base64-encoded string + * 'data', places malloc-allocated raw data in '*out', returns length, + * or zero on decode error (in which case *out is undefined). */ +size_t ne_unbase64(const char *data, unsigned char **out); + +/*** OBSOLETE INTERFACES ***/ +char **split_string(const char *str, const char seperator, + const char *quotes, const char *whitespace); +char **split_string_c(const char *str, const char seperator, + const char *quotes, const char *whitespace, int *count); +char **pair_string(const char *str, const char compsep, const char kvsep, + const char *quotes, const char *whitespace); +void split_string_free(char **components); +void pair_string_free(char **pairs); +/*** END OF OBSOLETE INTERFACES */ + +/* String buffer handling. (Strings are zero-terminated still). A + * string buffer ne_buffer * which grows dynamically with the + * string. */ + +typedef struct { + char *data; /* contents: null-terminated string. */ + size_t used; /* used bytes in buffer */ + size_t length; /* length of buffer */ +} ne_buffer; + +/* Returns size of data in buffer, equiv to strlen(ne_buffer_data(buf)) */ +#define ne_buffer_size(buf) ((buf)->used - 1) + +/* Concatenate all given strings onto the end of the buffer. The + * strings must all be NUL-terminated, and MUST be followed by a NULL + * argument marking the end of the list. */ +void ne_buffer_concat(ne_buffer *buf, ...); + +/* Create a new ne_buffer. */ +ne_buffer *ne_buffer_create(void); + +/* Create a new ne_buffer of given minimum size. */ +ne_buffer *ne_buffer_ncreate(size_t size); + +/* Destroys (deallocates) a buffer */ +void ne_buffer_destroy(ne_buffer *buf); + +/* Append a NUL-terminated string 'str' to buf. */ +void ne_buffer_zappend(ne_buffer *buf, const char *str); + +/* Append 'len' bytes of 'data' to buf. 'data' does not need to be + * NUL-terminated. The resultant string will have a NUL-terminator, + * either way. */ +void ne_buffer_append(ne_buffer *buf, const char *data, size_t len); + +/* Empties the contents of buf; makes the buffer zero-length. */ +void ne_buffer_clear(ne_buffer *buf); + +/* Grows the ne_buffer to a minimum size. */ +void ne_buffer_grow(ne_buffer *buf, size_t size); + +void ne_buffer_altered(ne_buffer *buf); + +/* Destroys a buffer, WITHOUT freeing the data, and returns the + * data. */ +char *ne_buffer_finish(ne_buffer *buf); + +/* Thread-safe strerror() wrapper; place system error for errno value + * 'errnum' in 'buffer', which is of length 'buflen'. Returns + * 'buffer'. */ +char *ne_strerror(int errnum, char *buffer, size_t buflen); + +/* ne_strnzcpy copies at most 'n'-1 bytes of 'src' to 'dest', and + * ensures that 'dest' is subsequently NUL-terminated. */ +#define ne_strnzcpy(dest, src, n) do { \ +strncpy(dest, src, n-1); dest[n-1] = '\0'; } while (0) + +/* Return malloc-allocated concatenation of all NUL-terminated string + * arguments, up to a terminating NULL. */ +char *ne_concat(const char *str, ...); + +#define NE_ASC2HEX(x) (((x) <= '9') ? ((x) - '0') : (tolower((x)) + 10 - 'a')) +#define NE_HEX2ASC(x) ((char) ((x) > 9 ? ((x) - 10 + 'a') : ((x) + '0'))) + +END_NEON_DECLS + +#endif /* NE_STRING_H */ diff --git a/src/ne_stubssl.c b/src/ne_stubssl.c new file mode 100644 index 0000000..ff71e1e --- /dev/null +++ b/src/ne_stubssl.c @@ -0,0 +1,135 @@ +/* + Stubs for SSL support when no SSL library has been configured + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#include <stdlib.h> /* for NULL */ + +#include "ne_ssl.h" +#include "ne_session.h" + +char *ne_ssl_readable_dname(const ne_ssl_dname *dn) +{ + return NULL; +} + +ne_ssl_certificate *ne_ssl_cert_read(const char *filename) +{ + return NULL; +} + +int ne_ssl_cert_cmp(const ne_ssl_certificate *c1, const ne_ssl_certificate *c2) +{ + return 1; +} + +const ne_ssl_certificate *ne_ssl_cert_signedby(const ne_ssl_certificate *cert) +{ + return NULL; +} + +const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert) +{ + return NULL; +} + +const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert) +{ + return NULL; +} + +void ne_ssl_cert_free(ne_ssl_certificate *cert) {} + +ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename) +{ + return NULL; +} + +const ne_ssl_certificate *ne_ssl_clicert_owner(const ne_ssl_client_cert *ccert) +{ + return NULL; +} + +int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *ccert) +{ + return -1; +} + +int ne_ssl_clicert_decrypt(ne_ssl_client_cert *ccert, const char *password) +{ + return -1; +} + +void ne_ssl_clicert_free(ne_ssl_client_cert *ccert) {} + +void ne_ssl_trust_default_ca(ne_session *sess) {} + +ne_ssl_context *ne_ssl_context_create(void) +{ + return NULL; +} + +void ne_ssl_ctx_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert) +{} + +void ne_ssl_context_destroy(ne_ssl_context *ctx) {} + +int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char digest[60]) +{ + return -1; +} + +void ne_ssl_cert_validity(const ne_ssl_certificate *cert, char *from, char *until) +{} + +const char *ne_ssl_cert_identity(const ne_ssl_certificate *cert) +{ + return NULL; +} + + +const char *ne_ssl_clicert_name(ne_ssl_client_cert *ccert) +{ + return NULL; +} + +int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2) +{ + return -1; +} + +int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename) +{ + return -1; +} + +char *ne_ssl_cert_export(const ne_ssl_certificate *cert) +{ + return NULL; +} + +ne_ssl_certificate *ne_ssl_cert_import(const char *data) +{ + return NULL; +} + +void ne_ssl_set_clicert(ne_session *sess, const ne_ssl_client_cert *cc) +{} diff --git a/src/ne_uri.c b/src/ne_uri.c new file mode 100644 index 0000000..51107d1 --- /dev/null +++ b/src/ne_uri.c @@ -0,0 +1,334 @@ +/* + HTTP URI handling + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <ctype.h> + +#include "ne_utils.h" /* for 'min' */ +#include "ne_string.h" /* for ne_buffer */ +#include "ne_alloc.h" +#include "ne_uri.h" + +char *ne_path_parent(const char *uri) +{ + size_t len = strlen(uri); + const char *pnt = uri + len - 1; + /* skip trailing slash (parent of "/foo/" is "/") */ + if (pnt >= uri && *pnt == '/') + pnt--; + /* find previous slash */ + while (pnt > uri && *pnt != '/') + pnt--; + if (pnt < uri || (pnt == uri && *pnt != '/')) + return NULL; + return ne_strndup(uri, pnt - uri + 1); +} + +int ne_path_has_trailing_slash(const char *uri) +{ + size_t len = strlen(uri); + return ((len > 0) && + (uri[len-1] == '/')); +} + +unsigned int ne_uri_defaultport(const char *scheme) +{ + /* RFC2616/3.2.3 says use case-insensitive comparisons here. */ + if (strcasecmp(scheme, "http") == 0) + return 80; + else if (strcasecmp(scheme, "https") == 0) + return 443; + else + return 0; +} + +/* TODO: Also, maybe stop malloc'ing here, take a "char *" uri, modify + * it in-place, and have fields point inside passed uri. More work + * for the caller then though. */ +/* TODO: not a proper URI parser */ +int ne_uri_parse(const char *uri, ne_uri *parsed) +{ + const char *pnt, *slash, *colon, *atsign, *openbk; + + parsed->port = 0; + parsed->host = NULL; + parsed->path = NULL; + parsed->scheme = NULL; + parsed->authinfo = NULL; + + if (uri[0] == '\0') { + return -1; + } + + pnt = strstr(uri, "://"); + if (pnt) { + parsed->scheme = ne_strndup(uri, pnt - uri); + pnt += 3; /* start of hostport segment */ + } else { + pnt = uri; + } + + atsign = strchr(pnt, '@'); + slash = strchr(pnt, '/'); + openbk = strchr(pnt, '['); + + /* Check for an authinfo segment in the hostport segment. */ + if (atsign != NULL && (slash == NULL || atsign < slash)) { + parsed->authinfo = ne_strndup(pnt, atsign - pnt); + pnt = atsign + 1; + } + + if (openbk && (!slash || openbk < slash)) { + const char *closebk = strchr(openbk, ']'); + if (closebk == NULL) + return -1; + colon = strchr(closebk + 1, ':'); + } else { + colon = strchr(pnt, ':'); + } + + if (slash == NULL) { + parsed->path = ne_strdup("/"); + if (colon == NULL) { + parsed->host = ne_strdup(pnt); + } else { + parsed->port = atoi(colon+1); + parsed->host = ne_strndup(pnt, colon - pnt); + } + } else { + if (colon == NULL || colon > slash) { + /* No port segment */ + if (slash != uri) { + parsed->host = ne_strndup(pnt, slash - pnt); + } else { + /* No hostname segment. */ + } + } else { + /* Port segment */ + parsed->port = atoi(colon + 1); + parsed->host = ne_strndup(pnt, colon - pnt); + } + parsed->path = ne_strdup(slash); + } + + return 0; +} + +void ne_uri_free(ne_uri *u) +{ + if (u->host) ne_free(u->host); + if (u->path) ne_free(u->path); + if (u->scheme) ne_free(u->scheme); + if (u->authinfo) ne_free(u->authinfo); + memset(u, 0, sizeof *u); +} + +char *ne_path_unescape(const char *uri) +{ + const char *pnt; + char *ret, *retpos, buf[5] = { "0x00\0" }; + retpos = ret = ne_malloc(strlen(uri) + 1); + for (pnt = uri; *pnt != '\0'; pnt++) { + if (*pnt == '%') { + if (!isxdigit((unsigned char) pnt[1]) || + !isxdigit((unsigned char) pnt[2])) { + /* Invalid URI */ + ne_free(ret); + return NULL; + } + buf[2] = *++pnt; buf[3] = *++pnt; /* bit faster than memcpy */ + *retpos++ = (char)strtol(buf, NULL, 16); + } else { + *retpos++ = *pnt; + } + } + *retpos = '\0'; + return ret; +} + +/* RFC2396 spake: + * "Data must be escaped if it does not have a representation + * using an unreserved character". + */ + +/* Lookup table: character classes from 2396. (This is overkill) */ + +#define SP 0 /* space = <US-ASCII coded character 20 hexadecimal> */ +#define CO 0 /* control = <US-ASCII coded characters 00-1F and 7F hexadecimal> */ +#define DE 0 /* delims = "<" | ">" | "#" | "%" | <"> */ +#define UW 0 /* unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`" */ +#define MA 1 /* mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" */ +#define AN 2 /* alphanum = alpha | digit */ +#define RE 2 /* reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," */ + +static const char uri_chars[128] = { +/* +2 +4 +6 +8 +10 +12 +14 */ +/* 0 */ CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, +/* 16 */ CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, +/* 32 */ SP, MA, DE, DE, RE, DE, RE, MA, MA, MA, MA, RE, RE, MA, MA, RE, +/* 48 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, RE, RE, DE, RE, DE, RE, +/* 64 */ RE, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, +/* 80 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, UW, UW, UW, UW, MA, +/* 96 */ UW, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, +/* 112 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, UW, UW, UW, MA, CO +}; + +#define ESCAPE(ch) (((const signed char)(ch) < 0 || \ + uri_chars[(unsigned int)(ch)] == 0)) + +#undef SP +#undef CO +#undef DE +#undef UW +#undef MA +#undef AN +#undef RE + +char *ne_path_escape(const char *abs_path) +{ + const char *pnt; + char *ret, *retpos; + int count = 0; + for (pnt = abs_path; *pnt != '\0'; pnt++) { + if (ESCAPE(*pnt)) { + count++; + } + } + if (count == 0) { + return ne_strdup(abs_path); + } + /* An escaped character is "%xx", i.e., two MORE + * characters than the original string */ + retpos = ret = ne_malloc(strlen(abs_path) + 2*count + 1); + for (pnt = abs_path; *pnt != '\0'; pnt++) { + if (ESCAPE(*pnt)) { + /* Escape it - %<hex><hex> */ + sprintf(retpos, "%%%02x", (unsigned char) *pnt); + retpos += 3; + } else { + /* It's cool */ + *retpos++ = *pnt; + } + } + *retpos = '\0'; + return ret; +} + +#undef ESCAPE + +#define CASECMP(field) do { \ +n = strcasecmp(u1->field, u2->field); if (n) return n; } while(0) + +#define CMP(field) do { \ +n = strcmp(u1->field, u2->field); if (n) return n; } while(0) + +/* As specified by RFC 2616, section 3.2.3. */ +int ne_uri_cmp(const ne_uri *u1, const ne_uri *u2) +{ + int n; + + if (u1->path[0] == '\0' && strcmp(u2->path, "/") == 0) + return 0; + if (u2->path[0] == '\0' && strcmp(u1->path, "/") == 0) + return 0; + + CMP(path); + CASECMP(host); + CASECMP(scheme); + if (u1->port > u2->port) + return 1; + else if (u1->port < u2->port) + return -1; + return 0; +} + +#undef CMP +#undef CASECMP + +/* TODO: implement properly */ +int ne_path_compare(const char *a, const char *b) +{ + int ret = strcasecmp(a, b); + if (ret) { + /* This logic says: "If the lengths of the two URIs differ by + * exactly one, and the LONGER of the two URIs has a trailing + * slash and the SHORTER one DOESN'T, then..." */ + int traila = ne_path_has_trailing_slash(a), + trailb = ne_path_has_trailing_slash(b), + lena = strlen(a), lenb = strlen(b); + if (traila != trailb && abs(lena - lenb) == 1 && + ((traila && lena > lenb) || (trailb && lenb > lena))) { + /* Compare them, ignoring the trailing slash on the longer + * URI */ + if (strncasecmp(a, b, min(lena, lenb)) == 0) + ret = 0; + } + } + return ret; +} + +char *ne_uri_unparse(const ne_uri *uri) +{ + ne_buffer *buf = ne_buffer_create(); + + ne_buffer_concat(buf, uri->scheme, "://", uri->host, NULL); + + if (uri->port > 0 && ne_uri_defaultport(uri->scheme) != uri->port) { + char str[20]; + ne_snprintf(str, 20, ":%d", uri->port); + ne_buffer_zappend(buf, str); + } + + ne_buffer_zappend(buf, uri->path); + + return ne_buffer_finish(buf); +} + +/* Give it a path segment, it returns non-zero if child is + * a child of parent. */ +int ne_path_childof(const char *parent, const char *child) +{ + char *root = ne_strdup(child); + int ret; + if (strlen(parent) >= strlen(child)) { + ret = 0; + } else { + /* root is the first of child, equal to length of parent */ + root[strlen(parent)] = '\0'; + ret = (ne_path_compare(parent, root) == 0); + } + ne_free(root); + return ret; +} diff --git a/src/ne_uri.h b/src/ne_uri.h new file mode 100644 index 0000000..75cd58a --- /dev/null +++ b/src/ne_uri.h @@ -0,0 +1,85 @@ +/* + HTTP URI handling + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_URI_H +#define NE_URI_H + +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +/* Un-escapes a path. Returns malloc-allocated path on success, or + * NULL on an invalid %<HEX><HEX> sequence. */ +char *ne_path_unescape(const char *uri); + +/* Escapes the a path segment: returns malloc-allocated string on + * success, or NULL on malloc failure. */ +char *ne_path_escape(const char *abs_path); + +/* Returns malloc-allocated parent of path, or NULL if path has no + * parent (such as "/"). */ +char *ne_path_parent(const char *path); + +/* Returns strcmp-like value giving comparison between p1 and p2, + * ignoring trailing-slashes. */ +int ne_path_compare(const char *p1, const char *p2); + +/* Returns non-zero if child is a child of parent */ +int ne_path_childof(const char *parent, const char *child); + +/* Returns non-zero if path has a trailing slash character */ +int ne_path_has_trailing_slash(const char *path); + +/* Return the default port for the given scheme, or 0 if none is + * known. */ +unsigned int ne_uri_defaultport(const char *scheme); + +typedef struct { + char *scheme; + char *host; + unsigned int port; + char *path; + char *authinfo; +} ne_uri; + +/* Parse absoluteURI 'uri' and place parsed segments in *parsed. + * Returns zero on success, non-zero on parse error. Fields of *parsed + * are malloc'ed, structure should be free'd with uri_free on + * successful return. Any unspecified URI fields are set to NULL or 0 + * appropriately in *parsed. */ +int ne_uri_parse(const char *uri, ne_uri *parsed); + +/* Turns a URI structure back into a string. String is + * malloc-allocated, and must be free'd by the caller. */ +char *ne_uri_unparse(const ne_uri *uri); + +/* Compares URIs u1 and u2, returns non-zero if they are found to be + * non-equal. The sign of the return value is <0 if 'u1' is less than + * 'u2', or >0 if 'u2' is greater than 'u1'. */ +int ne_uri_cmp(const ne_uri *u1, const ne_uri *u2); + +/* Free URI object. */ +void ne_uri_free(ne_uri *parsed); + +END_NEON_DECLS + +#endif /* NE_URI_H */ + diff --git a/src/ne_utils.c b/src/ne_utils.c new file mode 100644 index 0000000..84f7470 --- /dev/null +++ b/src/ne_utils.c @@ -0,0 +1,185 @@ +/* + HTTP utility functions + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <stdio.h> +#include <ctype.h> /* isdigit() for ne_parse_statusline */ + +#include "ne_utils.h" +#include "ne_string.h" /* for ne_strdup */ +#include "ne_dates.h" + +#ifdef NEON_SSL +#include <openssl/opensslv.h> +#endif + +/* libxml2: pick up the version string. */ +#if defined(HAVE_LIBXML) +#include <libxml/xmlversion.h> +#elif defined(HAVE_EXPAT) && !defined(HAVE_XMLPARSE_H) +#include <expat.h> +#endif + +#ifdef NEON_ZLIB +#include <zlib.h> +#endif + +int ne_debug_mask = 0; +FILE *ne_debug_stream = NULL; + +void ne_debug_init(FILE *stream, int mask) +{ + ne_debug_stream = stream; + ne_debug_mask = mask; +#if defined(HAVE_SETVBUF) && defined(_IONBF) + /* If possible, turn off buffering on the debug log. this is very + * helpful if debugging segfaults. */ + if (stream) setvbuf(stream, NULL, _IONBF, 0); +#endif +} + +void ne_debug(int ch, const char *template, ...) +{ + va_list params; + if ((ch & ne_debug_mask) == 0) return; + fflush(stdout); + va_start(params, template); + vfprintf(ne_debug_stream, template, params); + va_end(params); + if ((ch & NE_DBG_FLUSH) == NE_DBG_FLUSH) + fflush(ne_debug_stream); +} + +#define NE_STRINGIFY(x) # x +#define NE_EXPAT_VER(x,y,z) NE_STRINGIFY(x) "." NE_STRINGIFY(y) "." NE_STRINGIFY(z) + +static const char *version_string = "neon " NEON_VERSION ": " +#ifdef NEON_IS_LIBRARY + "Library build" +#else + "Bundled build" +#endif +#ifdef HAVE_EXPAT + ", Expat" +/* expat >=1.95.2 exported the version */ +#ifdef XML_MAJOR_VERSION +" " NE_EXPAT_VER(XML_MAJOR_VERSION, XML_MINOR_VERSION, XML_MICRO_VERSION) +#endif +#else /* !HAVE_EXPAT */ +#ifdef HAVE_LIBXML + ", libxml " LIBXML_DOTTED_VERSION +#endif /* HAVE_LIBXML */ +#endif /* !HAVE_EXPAT */ +#if defined(NEON_ZLIB) && defined(ZLIB_VERSION) + ", zlib " ZLIB_VERSION +#endif /* NEON_ZLIB && ... */ +#ifdef NEON_SOCKS + ", SOCKSv5" +#endif +#ifdef NEON_SSL +#ifdef OPENSSL_VERSION_TEXT + ", " OPENSSL_VERSION_TEXT +#else + "OpenSSL (unknown version)" +#endif /* OPENSSL_VERSION_TEXT */ +#endif + "." +; + +const char *ne_version_string(void) +{ + return version_string; +} + +int ne_version_match(int major, int minor) +{ + return (NEON_VERSION_MAJOR != major) || (NEON_VERSION_MINOR < minor); +} + +int ne_supports_ssl(void) +{ +#ifdef NEON_SSL + return 1; +#else + return 0; +#endif +} + +int ne_parse_statusline(const char *status_line, ne_status *st) +{ + const char *part; + int major, minor, status_code, klass; + + /* Check they're speaking the right language */ + status_line = strstr(status_line, "HTTP/"); + if (status_line == NULL) { + return -1; + } + + /* And find out which dialect of this peculiar language + * they can talk... */ + major = 0; + minor = 0; + /* Note, we're good children, and accept leading zero's on the + * version numbers */ + for (part = status_line + 5; *part != '\0' && isdigit(*part); part++) { + major = major*10 + (*part-'0'); + } + if (*part != '.') { + return -1; + } + for (part++ ; *part != '\0' && isdigit(*part); part++) { + minor = minor*10 + (*part-'0'); + } + if (*part != ' ') { + return -1; + } + /* Skip any spaces */ + for (; *part == ' ' ; part++) /* noop */; + /* Now for the Status-Code. part now points at the first Y in + * "HTTP/x.x YYY". We want value of YYY... could use atoi, but + * probably quicker this way. */ + if (!isdigit(part[0]) || !isdigit(part[1]) || !isdigit(part[2]) || + (part[3] != '\0' && part[3] != ' ')) { + return -1; + } + status_code = 100*(part[0]-'0') + 10*(part[1]-'0') + (part[2]-'0'); + klass = part[0]-'0'; + /* Skip whitespace between status-code and reason-phrase */ + for (part+=3; *part == ' ' || *part == '\t'; part++) /* noop */; + + /* part now may be pointing to \0 if reason phrase is blank */ + + /* Fill in the results */ + st->major_version = major; + st->minor_version = minor; + st->reason_phrase = ne_strclean(ne_strdup(part)); + st->code = status_code; + st->klass = klass; + return 0; +} diff --git a/src/ne_utils.h b/src/ne_utils.h new file mode 100644 index 0000000..28b17c5 --- /dev/null +++ b/src/ne_utils.h @@ -0,0 +1,127 @@ +/* + HTTP utility functions + Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_UTILS_H +#define NE_UTILS_H + +#include <sys/types.h> + +#include <stdarg.h> +#include <stdio.h> + +#include "ne_defs.h" + +#ifdef NEON_TRIO +/* no HAVE_TRIO_H check so this works from outside neon build tree. */ +#include <trio.h> +#endif + +BEGIN_NEON_DECLS + +/* Returns a human-readable version string like: + * "neon 0.2.0: Library build, OpenSSL support" + */ +const char *ne_version_string(void); + +/* Returns non-zero if library version is not of major version + * 'major', or if minor version is not greater than or equal to + * 'minor'. */ +int ne_version_match(int major, int minor); + +/* Returns non-zero if neon has support for SSL. */ +int ne_supports_ssl(void); + +/* Use replacement snprintf's if trio is being used. */ +#ifdef NEON_TRIO +#define ne_snprintf trio_snprintf +#define ne_vsnprintf trio_vsnprintf +#else +#define ne_snprintf snprintf +#define ne_vsnprintf vsnprintf +#endif + +#ifndef WIN32 +#undef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif + +/* CONSIDER: mutt has a nicer way of way of doing debugging output... maybe + * switch to like that. */ + +#ifndef NE_DEBUGGING +#define NE_DEBUG if (0) ne_debug +#else /* DEBUGGING */ +#define NE_DEBUG ne_debug +#endif /* DEBUGGING */ + +#define NE_DBG_SOCKET (1<<0) +#define NE_DBG_HTTP (1<<1) +#define NE_DBG_XML (1<<2) +#define NE_DBG_HTTPAUTH (1<<3) +#define NE_DBG_HTTPPLAIN (1<<4) +#define NE_DBG_LOCKS (1<<5) +#define NE_DBG_XMLPARSE (1<<6) +#define NE_DBG_HTTPBODY (1<<7) +#define NE_DBG_SSL (1<<8) +#define NE_DBG_FLUSH (1<<30) + +/* Send debugging output to 'stream', for all of the given debug + * channels. To disable debugging, pass 'stream' as NULL and 'mask' + * as 0. */ +void ne_debug_init(FILE *stream, int mask); + +/* The current debug mask and stream set by the last call to + * ne_debug_init. */ +extern int ne_debug_mask; +extern FILE *ne_debug_stream; + +/* Produce debug output if any of channels 'ch' is enabled for + * debugging. */ +void ne_debug(int ch, const char *, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif /* __GNUC__ */ +; + +/* Storing an HTTP status result */ +typedef struct { + int major_version; + int minor_version; + int code; /* Status-Code value */ + int klass; /* Class of Status-Code (1-5) */ + char *reason_phrase; +} ne_status; + +/* NB: couldn't use 'class' in ne_status because it would clash with + * the C++ reserved word. */ + +/* Parser for strings which follow the Status-Line grammar from + * RFC2616. s->reason_phrase is malloc-allocated if non-NULL, and + * must be free'd by the caller. + * Returns: + * 0 on success, *s will be filled in. + * -1 on parse error. + */ +int ne_parse_statusline(const char *status_line, ne_status *s); + +END_NEON_DECLS + +#endif /* NE_UTILS_H */ diff --git a/src/ne_xml.c b/src/ne_xml.c new file mode 100644 index 0000000..d55d59c --- /dev/null +++ b/src/ne_xml.c @@ -0,0 +1,605 @@ +/* + Higher Level Interface to XML Parsers. + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +#include "ne_i18n.h" + +#include "ne_alloc.h" +#include "ne_xml.h" +#include "ne_utils.h" +#include "ne_string.h" + +#if defined(HAVE_EXPAT) +/* expat support: */ +#ifdef HAVE_XMLPARSE_H +#include "xmlparse.h" +#else +#include <expat.h> +#endif +typedef XML_Char ne_xml_char; +#elif defined(HAVE_LIBXML) +/* libxml2 support: */ +#include <libxml/xmlversion.h> +#include <libxml/parser.h> +typedef xmlChar ne_xml_char; + +#else /* not HAVE_LIBXML */ +# error need an XML parser +#endif /* not HAVE_EXPAT */ + +/* Approx. one screen of text: */ +#define ERR_SIZE (2048) + +struct handler { + ne_xml_startelm_cb *startelm_cb; /* start-element callback */ + ne_xml_endelm_cb *endelm_cb; /* end-element callback */ + ne_xml_cdata_cb *cdata_cb; /* character-data callback. */ + void *userdata; /* userdata for the above. */ + struct handler *next; /* next handler in stack. */ +}; + +#ifdef HAVE_LIBXML +static void sax_error(void *ctx, const char *msg, ...); +#endif + +struct element { + const ne_xml_char *nspace; + ne_xml_char *name; + + int state; /* opaque state integer */ + + /* Namespaces declared in this element */ + ne_xml_char *default_ns; /* A default namespace */ + struct namespace *nspaces; /* List of other namespace scopes */ + + struct handler *handler; /* Handler for this element */ + + struct element *parent; /* parent element, or NULL */ +}; + +/* We pass around a ne_xml_parser as the userdata in the parsing + * library. This maintains the current state of the parse and various + * other bits and bobs. Within the parse, we store the current branch + * of the tree, i.e., the current element and all its parents, up to + * the root, but nothing other than that. */ +struct ne_xml_parser_s { + struct element *root; /* the root of the document */ + struct element *current; /* current element in the branch */ + struct handler *top_handlers; /* always points at the + * handler on top of the stack. */ + int valid; /* non-zero whilst parse should continue */ + int prune; /* if non-zero, depth within a dead branch */ + +#ifdef HAVE_EXPAT + XML_Parser parser; + char *encoding; +#else + xmlParserCtxtPtr parser; +#endif + char error[ERR_SIZE]; +}; + +/* The callback handlers */ +static void start_element(void *userdata, const ne_xml_char *name, const ne_xml_char **atts); +static void end_element(void *userdata, const ne_xml_char *name); +static void char_data(void *userdata, const ne_xml_char *cdata, int len); +static const char *resolve_nspace(const struct element *elm, + const char *prefix, size_t pfxlen); + +/* Linked list of namespace scopes */ +struct namespace { + ne_xml_char *name; + ne_xml_char *uri; + struct namespace *next; +}; + +#ifdef HAVE_LIBXML + +/* Could be const as far as we care, but libxml doesn't want that */ +static xmlSAXHandler sax_handler = { + NULL, /* internalSubset */ + NULL, /* isStandalone */ + NULL, /* hasInternalSubset */ + NULL, /* hasExternalSubset */ + NULL, /* resolveEntity */ + NULL, /* getEntity */ + NULL, /* entityDecl */ + NULL, /* notationDecl */ + NULL, /* attributeDecl */ + NULL, /* elementDecl */ + NULL, /* unparsedEntityDecl */ + NULL, /* setDocumentLocator */ + NULL, /* startDocument */ + NULL, /* endDocument */ + start_element, /* startElement */ + end_element, /* endElement */ + NULL, /* reference */ + char_data, /* characters */ + NULL, /* ignorableWhitespace */ + NULL, /* processingInstruction */ + NULL, /* comment */ + NULL, /* xmlParserWarning */ + sax_error, /* xmlParserError */ + sax_error, /* fatal error (never called by libxml2?) */ + NULL, /* getParameterEntity */ + char_data /* cdataBlock */ +}; + +/* empty attributes array to mimic expat behaviour */ +static const char *empty_atts[] = {NULL, NULL}; + +/* macro for determining the attributes array to pass */ +#define PASS_ATTS(atts) (atts ? (const char **)(atts) : empty_atts) + +#else + +#define PASS_ATTS(atts) ((const char **)(atts)) + +/* XML declaration callback for expat. */ +static void decl_handler(void *userdata, + const XML_Char *version, const XML_Char *encoding, + int standalone) +{ + ne_xml_parser *p = userdata; + if (encoding) p->encoding = ne_strdup(encoding); +} + +#endif /* HAVE_LIBXML */ + +int ne_xml_currentline(ne_xml_parser *p) +{ +#ifdef HAVE_EXPAT + return XML_GetCurrentLineNumber(p->parser); +#else + return p->parser->input->line; +#endif +} + +const char *ne_xml_doc_encoding(const ne_xml_parser *p) +{ +#ifdef HAVE_LIBXML + return p->parser->encoding; +#else + return p->encoding; +#endif +} + +/* Extract the namespace prefix declarations from 'atts'. */ +static int declare_nspaces(ne_xml_parser *p, struct element *elm, + const ne_xml_char **atts) +{ + int n; + + for (n = 0; atts && atts[n]; n += 2) { + if (strcasecmp(atts[n], "xmlns") == 0) { + /* New default namespace */ + elm->default_ns = ne_strdup(atts[n+1]); + } else if (strncasecmp(atts[n], "xmlns:", 6) == 0) { + struct namespace *ns; + + if (atts[n][6] == '\0' || atts[n+1][0] == '\0') { + ne_snprintf(p->error, ERR_SIZE, + ("XML parse error at line %d: invalid namespace " + "declaration"), ne_xml_currentline(p)); + return -1; + } + + /* New namespace scope */ + ns = ne_calloc(sizeof(*ns)); + ns->next = elm->nspaces; + elm->nspaces = ns; + ns->name = ne_strdup(atts[n]+6); /* skip the xmlns= */ + ns->uri = ne_strdup(atts[n+1]); + } + } + + return 0; +} + +/* Expand an XML qualified name, which may include a namespace prefix + * as well as the local part. */ +static int expand_qname(ne_xml_parser *p, struct element *elm, + const ne_xml_char *qname) +{ + const ne_xml_char *pfx; + + pfx = strchr(qname, ':'); + if (pfx == NULL) { + struct element *e = elm; + + /* Find default namespace; guaranteed to terminate as the root + * element always has default_ns="". */ + while (e->default_ns == NULL) + e = e->parent; + + elm->name = ne_strdup(qname); + elm->nspace = e->default_ns; + } else { + const char *uri = resolve_nspace(elm, qname, pfx-qname); + + if (uri) { + /* The name is everything after the ':' */ + if (pfx[1] == '\0') { + ne_snprintf(p->error, ERR_SIZE, + ("XML parse error at line %d: element name missing" + "after namespace prefix"), ne_xml_currentline(p)); + return -1; + } + elm->name = ne_strdup(pfx+1); + elm->nspace = uri; + } else { + ne_snprintf(p->error, ERR_SIZE, + ("XML parse error at line %d: undeclared namespace"), + ne_xml_currentline(p)); + return -1; + } + } + return 0; +} + +/* Called with the start of a new element. */ +static void start_element(void *userdata, const ne_xml_char *name, + const ne_xml_char **atts) +{ + ne_xml_parser *p = userdata; + struct element *elm; + struct handler *hand; + int state = NE_XML_DECLINE; + + if (!p->valid) return; + + if (p->prune) { + p->prune++; + return; + } + + /* Create a new element */ + elm = ne_calloc(sizeof *elm); + elm->parent = p->current; + p->current = elm; + + if (declare_nspaces(p, elm, atts) || expand_qname(p, elm, name)) { + p->valid = 0; + return; + } + + /* Find a handler which will accept this element (or abort the parse) */ + for (hand = elm->parent->handler; hand && state == NE_XML_DECLINE; + hand = hand->next) { + elm->handler = hand; + state = hand->startelm_cb(hand->userdata, elm->parent->state, + elm->nspace, elm->name, PASS_ATTS(atts)); + } + + NE_DEBUG(NE_DBG_XMLPARSE, "XML: start-element (%d, {%s, %s}) => %d\n", + elm->parent->state, elm->nspace, elm->name, state); + + if (state > 0) + elm->state = state; + else if (state == NE_XML_DECLINE) + /* prune this branch. */ + p->prune++; + else /* state == NE_XML_ABORT */ + p->valid = 0; +} + +/* Destroys an element structure. */ +static void destroy_element(struct element *elm) +{ + struct namespace *this_ns, *next_ns; + ne_free(elm->name); + /* Free the namespaces */ + this_ns = elm->nspaces; + while (this_ns != NULL) { + next_ns = this_ns->next; + ne_free(this_ns->name); + ne_free(this_ns->uri); + ne_free(this_ns); + this_ns = next_ns; + }; + if (elm->default_ns) + ne_free(elm->default_ns); + ne_free(elm); +} + +/* cdata SAX callback */ +static void char_data(void *userdata, const ne_xml_char *data, int len) +{ + ne_xml_parser *p = userdata; + struct element *elm = p->current; + + if (!p->valid || p->prune) return; + + if (elm->handler->cdata_cb && + elm->handler->cdata_cb(elm->handler->userdata, elm->state, data, len)) { + NE_DEBUG(NE_DBG_XML, "Cdata callback failed.\n"); + p->valid = 0; + } +} + +/* Called with the end of an element */ +static void end_element(void *userdata, const ne_xml_char *name) +{ + ne_xml_parser *p = userdata; + struct element *elm = p->current; + + if (!p->valid) return; + + if (p->prune) { + if (p->prune-- > 1) return; + } else if (elm->handler->endelm_cb && + elm->handler->endelm_cb(elm->handler->userdata, elm->state, + elm->nspace, elm->name)) { + NE_DEBUG(NE_DBG_XML, "XML: end-element for %d failed.\n", elm->state); + p->valid = 0; + } + + NE_DEBUG(NE_DBG_XMLPARSE, "XML: end-element (%d, {%s, %s})\n", + elm->state, elm->nspace, elm->name); + + /* move back up the tree */ + p->current = elm->parent; + p->prune = 0; + + destroy_element(elm); +} + +/* Find a namespace definition for 'prefix' in given element, where + * length of prefix is 'pfxlen'. Returns the URI or NULL. */ +static const char *resolve_nspace(const struct element *elm, + const char *prefix, size_t pfxlen) +{ + const struct element *s; + + /* Search up the tree. */ + for (s = elm; s != NULL; s = s->parent) { + const struct namespace *ns; + /* Iterate over defined spaces on this node. */ + for (ns = s->nspaces; ns != NULL; ns = ns->next) { + if (strlen(ns->name) == pfxlen && + memcmp(ns->name, prefix, pfxlen) == 0) + return ns->uri; + } + } + + return NULL; +} + +ne_xml_parser *ne_xml_create(void) +{ + ne_xml_parser *p = ne_calloc(sizeof *p); + /* Initialize other stuff */ + p->valid = 1; + /* Placeholder for the root element */ + p->current = p->root = ne_calloc(sizeof *p->root); + p->root->default_ns = ""; + p->root->state = 0; +#ifdef HAVE_EXPAT + p->parser = XML_ParserCreate(NULL); + if (p->parser == NULL) { + abort(); + } + XML_SetElementHandler(p->parser, start_element, end_element); + XML_SetCharacterDataHandler(p->parser, char_data); + XML_SetUserData(p->parser, (void *) p); + XML_SetXmlDeclHandler(p->parser, decl_handler); +#else + p->parser = xmlCreatePushParserCtxt(&sax_handler, + (void *)p, NULL, 0, NULL); + if (p->parser == NULL) { + abort(); + } + p->parser->replaceEntities = 1; +#endif + return p; +} + +void ne_xml_push_handler(ne_xml_parser *p, + ne_xml_startelm_cb *startelm_cb, + ne_xml_cdata_cb *cdata_cb, + ne_xml_endelm_cb *endelm_cb, + void *userdata) +{ + struct handler *hand = ne_calloc(sizeof(struct handler)); + + hand->startelm_cb = startelm_cb; + hand->cdata_cb = cdata_cb; + hand->endelm_cb = endelm_cb; + hand->userdata = userdata; + + /* If this is the first handler registered, update the + * base pointer too. */ + if (p->top_handlers == NULL) { + p->root->handler = hand; + p->top_handlers = hand; + } else { + p->top_handlers->next = hand; + p->top_handlers = hand; + } +} + +void ne_xml_parse_v(void *userdata, const char *block, size_t len) +{ + ne_xml_parser *p = userdata; + /* FIXME: The two XML parsers break all our nice abstraction by + * choosing different char *'s. The swine. This cast will come + * back and bite us someday, no doubt. */ + ne_xml_parse(p, block, len); +} + +/* Parse the given block of input of length len */ +void ne_xml_parse(ne_xml_parser *p, const char *block, size_t len) +{ + int ret, flag; + /* duck out if it's broken */ + if (!p->valid) { + NE_DEBUG(NE_DBG_XML, "Not parsing %" NE_FMT_SIZE_T " bytes.\n", + len); + return; + } + if (len == 0) { + flag = -1; + block = ""; + NE_DEBUG(NE_DBG_XML, "Got 0-length buffer, end of document.\n"); + } else { + NE_DEBUG(NE_DBG_XML, "Parsing %" NE_FMT_SIZE_T " length buffer.\n", + len); + flag = 0; + } + /* Note, don't write a parser error if !p->valid, since an error + * will already have been written in that case. */ +#ifdef HAVE_EXPAT + ret = XML_Parse(p->parser, block, len, flag); + NE_DEBUG(NE_DBG_XMLPARSE, "XML_Parse returned %d\n", ret); + if (ret == 0 && p->valid) { + ne_snprintf(p->error, ERR_SIZE, + "XML parse error at line %d: %s", + XML_GetCurrentLineNumber(p->parser), + XML_ErrorString(XML_GetErrorCode(p->parser))); + p->valid = 0; + } +#else + ret = xmlParseChunk(p->parser, block, len, flag); + NE_DEBUG(NE_DBG_XMLPARSE, "xmlParseChunk returned %d\n", ret); + /* Parse errors are normally caught by the sax_error() callback, + * which clears p->valid. */ + if (p->parser->errNo && p->valid) { + ne_snprintf(p->error, ERR_SIZE, "XML parse error at line %d.", + ne_xml_currentline(p)); + p->valid = 0; + } +#endif +} + +int ne_xml_valid(ne_xml_parser *p) +{ + return p->valid; +} + +void ne_xml_destroy(ne_xml_parser *p) +{ + struct element *elm, *parent; + struct handler *hand, *next; + + /* Free up the handlers on the stack: the root element has the + * pointer to the base of the handler stack. */ + for (hand = p->root->handler; hand!=NULL; hand=next) { + next = hand->next; + ne_free(hand); + } + + /* Clean up remaining elements */ + for (elm = p->current; elm != p->root; elm = parent) { + parent = elm->parent; + destroy_element(elm); + } + + /* free root element */ + ne_free(p->root); + +#ifdef HAVE_EXPAT + XML_ParserFree(p->parser); + if (p->encoding) ne_free(p->encoding); +#else + xmlFreeParserCtxt(p->parser); +#endif + + ne_free(p); +} + +void ne_xml_set_error(ne_xml_parser *p, const char *msg) +{ + ne_snprintf(p->error, ERR_SIZE, msg); +} + +#ifdef HAVE_LIBXML +static void sax_error(void *ctx, const char *msg, ...) +{ + ne_xml_parser *p = ctx; + va_list ap; + char buf[1024]; + + va_start(ap, msg); + ne_vsnprintf(buf, 1024, msg, ap); + va_end(ap); + + ne_snprintf(p->error, ERR_SIZE, + _("XML parse error at line %d: %s."), + p->parser->input->line, buf); + + p->valid = 0; +} +#endif + +const char *ne_xml_get_error(ne_xml_parser *p) +{ + return p->error; +} + +const char * +ne_xml_get_attr(ne_xml_parser *p, const char **attrs, + const char *nspace, const char *name) +{ + int n; + + for (n = 0; attrs[n] != NULL; n += 2) { + char *pnt = strchr(attrs[n], ':'); + + if (!nspace && !pnt && strcmp(attrs[n], name) == 0) { + return attrs[n+1]; + } else if (nspace && pnt) { + /* If a namespace is given, and the local part matches, + * then resolve the namespace and compare that too. */ + if (strcmp(pnt + 1, name) == 0) { + const char *uri = resolve_nspace(p->current, + attrs[n], pnt - attrs[n]); + if (uri && strcmp(uri, nspace) == 0) + return attrs[n+1]; + } + } + } + + return NULL; +} + +int ne_xml_mapid(const struct ne_xml_idmap map[], size_t maplen, + const char *nspace, const char *name) +{ + size_t n; + + for (n = 0; n < maplen; n++) + if (strcmp(name, map[n].name) == 0 && + strcmp(nspace, map[n].nspace) == 0) + return map[n].id; + + return 0; +} diff --git a/src/ne_xml.h b/src/ne_xml.h new file mode 100644 index 0000000..f767652 --- /dev/null +++ b/src/ne_xml.h @@ -0,0 +1,140 @@ +/* + neon XML parser interface + Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_XML_H +#define NE_XML_H + +#include <sys/types.h> /* for size_t */ + +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +/* The neon XML interface filters a streamed XML tree through a stack + * of SAX "handlers". A handler is made up of three callbacks + * (start-element, char-data, end-element). Each start-element event + * is passed to each handler in the stack in turn until one until one + * accepts the element. This handler then receives subsequent + * char-data and end-element events. + * + * For each new start-element, the search up the handler stack begins + * with the handler for the parent element (for the root element, at + * the base of the stack). + * + * For each accepted element, a "state" integer is stored, which is + * passed to the corresponding char-data and end-element callbacks for + * the element. This integer is also passed to the start-element + * callback of child elements so they can determine context. + * + * If no handler in the stack accepts a particular element, it (and + * its children, if any) is ignored. */ + +#define NE_XML_DECLINE (0) +#define NE_XML_ABORT (-1) + +/* The startelm callback may return: + * <0 => abort the parse (NE_XML_ABORT) + * 0 => decline this element (NE_XML_DECLINE) + * >0 => accept this element; value is state for this element. + * The 'parent' integer is the state returned by the handler of the + * parent element. */ +typedef int ne_xml_startelm_cb(void *userdata, int parent, + const char *nspace, const char *name, + const char **atts); + +/* state for the root element */ +#define NE_XML_STATEROOT (0) + +/* Character data callback; may return non-zero to abort the parse. */ +typedef int ne_xml_cdata_cb(void *userdata, int state, + const char *cdata, size_t len); +/* End element callback; may return non-zero to abort the parse. */ +typedef int ne_xml_endelm_cb(void *userdata, int state, + const char *nspace, const char *name); + +typedef struct ne_xml_parser_s ne_xml_parser; + +/* Create an XML parser. */ +ne_xml_parser *ne_xml_create(void); + +/* Push a new handler on the stack of parser 'p'. 'cdata' and/or + * 'endelm' may be NULL; startelm must be non-NULL. */ +void ne_xml_push_handler(ne_xml_parser *p, + ne_xml_startelm_cb *startelm, + ne_xml_cdata_cb *cdata, + ne_xml_endelm_cb *endelm, + void *userdata); + +/* Returns non-zero if the parse was valid, zero if it failed (e.g., + * any of the callbacks failed, the XML was not well-formed, etc). + * Use ne_xml_get_error to retrieve the error message if it failed. */ +int ne_xml_valid(ne_xml_parser *p); + +/* Destroy the parser object. */ +void ne_xml_destroy(ne_xml_parser *p); + +/* Parse the given block of input of length len. Block does not need + * to be NUL-terminated. */ +void ne_xml_parse(ne_xml_parser *p, const char *block, size_t len); + +/* As above, casting (ne_xml_parser *)userdata internally. + * (This function can be passed to ne_add_response_body_reader) */ +void ne_xml_parse_v(void *userdata, const char *block, size_t len); + +/* Return current parse line for errors */ +int ne_xml_currentline(ne_xml_parser *p); + +/* Set error message for parser */ +void ne_xml_set_error(ne_xml_parser *p, const char *msg); + +const char *ne_xml_get_error(ne_xml_parser *p); + +/* From a start_element callback which was passed 'attrs' using given + * parser, return attribute of given name and namespace. If nspace is + * NULL, no namespace resolution is performed. */ +const char *ne_xml_get_attr(ne_xml_parser *parser, + const char **attrs, const char *nspace, + const char *name); + +/* Return the encoding of the document being parsed. May return NULL + * if no encoding is defined or if the XML declaration has not been + * parsed. */ +const char *ne_xml_doc_encoding(const ne_xml_parser *p); + +/* A utility interface for mapping {nspace, name} onto an int. */ +struct ne_xml_idmap { + const char *nspace, *name; + int id; +}; + +/* Return the size of an idmap array */ +#define NE_XML_MAPLEN(map) (sizeof(map) / sizeof(struct ne_xml_idmap)) + +/* Return the 'id' corresponding to {nspace, name}, or zero. */ +int ne_xml_mapid(const struct ne_xml_idmap map[], size_t maplen, + const char *nspace, const char *name); + +/* media type, appropriate for adding to a Content-Type header */ +#define NE_XML_MEDIA_TYPE "application/xml" + +END_NEON_DECLS + +#endif /* NE_XML_H */ diff --git a/test/.cvsignore b/test/.cvsignore new file mode 100644 index 0000000..fb288d0 --- /dev/null +++ b/test/.cvsignore @@ -0,0 +1,41 @@ +tests +*-tests +Makefile +request +*.log* +server +regress +compress +*.gz +*.tmp +acl +auth +lock +basic +ssl +xml +stubs +ca +ca-stamp +ssigned.pem +wildcard.* +*.cert +*.csr +fqdn.pem +wrongcn.pem +socket +redirect +session +*.out +core* +props +socket-ssl +resolve +*.bb +*.da +*.bbg +ca*.pem +chain.pem +*.p12 +client.* +output.pem diff --git a/test/COPYING b/test/COPYING new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/test/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/test/ChangeLog b/test/ChangeLog new file mode 100644 index 0000000..a32791c --- /dev/null +++ b/test/ChangeLog @@ -0,0 +1,1329 @@ +Sat Jun 21 12:59:49 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (versions): Fix and enable test. + +Wed Jun 18 20:09:59 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (is_alive): Adapt for new socket API. + + * socket.c (do_connect, addr_connect): Likewise. + +Tue May 20 20:14:03 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (cert_fingerprint): Fix for VPATH builds. + +Sat May 10 17:13:05 2003 Joe Orton <joe@manyfish.co.uk> + + * xml.c (matches): Add regression test for prefix matching bug + fixed in 0.18.0. + +Sat Apr 26 19:22:29 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (any_te_header): New function. + +Wed Apr 23 18:24:19 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Test ne_ssl_cert_import, ne_ssl_cert_export, + ne_ssl_cert_write stubs. + +Wed Apr 23 14:05:38 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (read_write): New function. + +Wed Apr 23 00:34:44 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (cache_cert, verify_cache): New functions. + +Wed Apr 23 00:14:14 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (any_ssl_request): Free the cert after passing it to + ne_ssl_trust_cert. + +Tue Apr 22 23:24:33 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (unbase64): Improve coverage. + +Tue Apr 22 20:25:15 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (import_export, flatten_pem, cert_compare): New functions. + +Tue Apr 22 18:32:43 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (b64_check, unbase64): New functions. + (base64): Use b64_check. + +Tue Apr 22 15:54:04 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (base64): Test decoding binary data which + contains bytes with the high bit set. + +Tue Apr 22 14:18:03 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (base64): Moved here... + + * util-tests.c (base64): ...from here. + +Tue Apr 22 13:17:48 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (just_serve_string, fail_not_ssl): New functions. + +Tue Apr 22 13:09:13 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Test ne_ssl_cert_validity stub. + +Tue Apr 22 11:35:10 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (versions): Run test as XFAIL. + +Tue Apr 22 11:33:43 2003 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (version_string): New function. + +Tue Apr 22 09:23:27 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (check_validity, cert_validity): New functions. + +Mon Apr 21 19:45:39 2003 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (digest_md5): Replace ne_md5_buffer. + (md5): Use digest_md5; test 500-byte string. + +Mon Apr 21 18:38:02 2003 Joe Orton <joe@manyfish.co.uk> + + * xml.c (fail_parse): Call ne_xml_parse with length=0 finish + parse. + +Mon Apr 21 17:18:45 2003 Joe Orton <joe@manyfish.co.uk> + + * props.c: Add tests for ne_207.h interface and ne_simple_propfind + from ne_props.h. + + * xml.c: Add tests for new XML interface. + + * Makefile.in: Run props tests before lock since the latter is + implemented using the former. + +Mon Apr 7 22:27:18 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Test for ne_ssl_cert_identity stub. + +Mon Apr 7 22:17:56 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (cert_fingerprint): Renamed from fingerprint. + (check_identity, cert_identities): New functions. + +Sun Apr 6 20:18:30 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Adjust for new clicert API. + +Sun Apr 6 20:12:48 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (dname_compare): Renamed from comparisons. + (dname_readable): New function. + + * makekeys.sh: Create justmail.cert. + +Sun Apr 6 20:00:18 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (keypw_prompt): Removed function. + (init, load_client_cert, client_cert_provided): Adapt for new + clicert API. + (ccert_unencrypted): New function. + +Fri Apr 4 22:34:12 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_request_with_error): Refactored from + fail_request; check for a particular error string. + (fail_request): Use fail_request_with_error. + (invalid_response_gives_error): New function. + (fail_long_header): Use it. + (fail_corrupt_chunks): New function. + +Sat Mar 29 14:39:20 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (comparisons): New function. + + * stubs.c (stub_ssl): Test ne_ssl_dname_cmp. + +Sat Mar 29 13:58:37 2003 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Generate noclient.p12. + + * ssl.c (load_client_cert): Test ne_ssl_clicert_name. + + * stubs.c (stub_ssl): Check for ne_ssl_clicert_name stub. + +Sat Mar 29 13:31:35 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (load_client_cert): Test ne_ssl_clicert_owner. + +Fri Mar 28 22:13:55 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fingerprint): New function. + + * stubs.c (stub_ssl): Check for ne_ssl_cert_digest stub. + +Wed Mar 26 22:52:15 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fail_missing_CN): New function. + + * makekeys.sh: Generate missingcn.cert. + + * openssl.conf: Allow commonName to be omitted from CSR. + +Wed Mar 26 22:41:48 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (load_server_certs): Renamed from load_ca; test loading + non-existent file. + +Wed Mar 26 20:38:08 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Updated for new SSL interface. + +Tue Mar 25 20:32:07 2003 Joe Orton <joe@manyfish.co.uk> + + Update tests for changes to SSL interface: + + * socket.c (init_ssl): Use ne_ssl_context_create, + ne_ssl_cert_read, ne_ssl_ctx_trustcert. + (begin): Use ne_sock_connect_ssl. + + * ssl.c (serve_ssl_chained, trust_default_ca, load_client_cert, + check_dname, check_cert_dnames, check_cert, check_chain, + parse_chain, cc_check_dnames, cc_provided_dnames): New functions. + (serve_ccert): Always trust SERVER_CERT; optionally call + SSL_CTX_set_client_CA_list. + (any_ssl_request, load_ca, fail_truncated_eof): Use + ne_ssl_cert_read and ne_ssl_trust_cert. + (keypw_prompt): Fail if userdata is NULL. + (fail_load_ccerts, load_pkcs12_ccert, load_pem_ccert, check_DNs): + Removed functions. + (parse_cert): Use check_cert. + (client_cert_provided, client_cert_pkcs12): Rewritten for new API. + + * makekeys.sh: Generate calist.pem, unclient.p12. + +Wed Mar 12 22:36:27 2003 Joe Orton <joe@manyfish.co.uk> + + * redirect.c (simple): Fold in tests for 30[237] redirects for + better coverage. + (no_redirect): Another test for _location returning NULL. + +Wed Mar 12 22:29:45 2003 Joe Orton <joe@manyfish.co.uk> + + * redirect.c (process_redir): Factored out from check_redir. + (no_redirect): New function. + +Sun Mar 9 17:46:37 2003 Joe Orton <joe@manyfish.co.uk> + + * lock.c (fail_discover): New function. + +Sat Mar 1 10:53:58 2003 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (authinfo): Removed. + (escapes): Test nothing-to-escape and invalid URI cases. + (compares): Gain 100% branch coverage in ne_path_compare. + (default_port): Test unknown scheme case. + (parse): Test authinfo here, and some edge cases. + (unparse): Fill in port if default. + +Sat Mar 1 09:20:42 2003 Joe Orton <joe@manyfish.co.uk> + + * socket.c (multi_init): New function. + +Sat Mar 1 08:04:09 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (cleaner): New function. + +Wed Feb 26 22:13:14 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_eof_chunk, fail_eof_badclen): New tests. + +Wed Feb 26 21:54:39 2003 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (support): New function. + (bad_sl, accept_sl): More status-lines. + +Tue Feb 25 21:06:18 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Fail if response contains + "Proxy-Authorization" header. + (apt_post_send, apt_creds, auth_proxy_tunnel): New functions. + +Thu Nov 28 21:25:01 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (te_over_clength2): New test. + +Sun Nov 17 18:59:04 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (addr_make_v4, addr_make_v6, addr_compare): New + functions. + +Fri Oct 11 00:49:01 2002 Joe Orton <joe@manyfish.co.uk> + + * props.c (regress): Moved from regress.c:propfind_segv; add + regression test for ne_props.c segfault fixed in rev 1.83. + + * regress.c: Removed. + +Tue Oct 8 20:06:55 2002 Joe Orton <joe@manyfish.co.uk> + + * xml.c (matches): Add tests that entities in attribute values are + dereferenced by the XML parser. + +Fri Oct 4 17:10:19 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (no_body_bad_clength, no_body_empty_clength): New + tests. + (expect_no_body): Use better paths in the requests. + +Tue Sep 24 21:27:33 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_long_header, versions, hook_create_req): New + functions. + +Tue Sep 17 21:08:17 2002 Joe Orton <joe@manyfish.co.uk> + + * openssl.conf (neonca): Make 'countryName' optional in CA policy. + (reqDN.CNfirst): New section. + + * makekeys.sh: Generate 'cnfirst.cert', which has commonName as + first attribute in subject DN. + + * ssl.c (commonName_first): New function. + +Tue Sep 10 21:11:18 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_double_lookup): New function. + +Sun Aug 25 23:16:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Add 'unclean' argument. + (all callers changed). + (serve_response_unclean, empty_truncated_eof, fail_truncated_eof): + New functions. + +Sun Aug 25 19:16:00 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (resolve_numeric): Test ne_addr_print too. + +Sun Aug 25 13:39:37 2002 Joe Orton <joe@manyfish.co.uk> + + * resolve.c: New file. + +Sun Aug 25 11:25:12 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (is_alive): Update for new ne_addr_* interface. + +Sun Aug 25 08:31:16 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (serve_truncate, ssl_truncate): New functions. + +Sun Aug 25 08:28:17 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (do_connect): New function; use new + ne_sock_connect/ne_addr interface. + (begin) [SOCKET_SSL, !SOCKET_SSL]: Use do_connect. + (resolve_numeric): Adjust for new ne_addr interface. + (resolve_ipv6): Disable test. + +Sat Aug 24 08:50:06 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_statusline): New function. + +Fri Aug 23 22:52:38 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (init): FAILHARD if initialization fails. + +Wed Aug 21 13:29:58 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (null_uri): Removed test. + (parse): More tests including IPv6 address tests; use ONCMP macro. + (failparse): New function. + (unparse): Add URI with IPv6 address. + +Wed Aug 21 13:28:37 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (resolve_ipv6): New function. + +Mon Aug 19 16:59:46 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (resolve): Adapt for new ne_addr_resolve interface. + (resolve_numeric): New test. + + * request.c (is_alive): Use new ne_addr_resolve interface. + +Mon Aug 19 16:57:53 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (begin): Fix handling of connect failure. + (TO_BEGIN): Handle errors from to_begin properly. + +Sun Aug 18 23:37:34 2002 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (str_errors): Check return value and behaviour + when error string is truncated, an + +Sun Aug 18 23:31:51 2002 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (str_errors): Moved to... + + * string-tests.c (str_errors): here. + +Sun Aug 18 23:11:28 2002 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (strnzcpy): New function. + +Sun Aug 18 08:18:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (caseless_match): New function. + + * makekeys.sh: Create caseless.cert. + +Sun Aug 18 08:12:32 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (notdns_altname): New function. + + * makekeys.sh: Create altname4.cert. + + * openssl.conf (altExt4): New section. + +Sun Aug 18 07:42:30 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (multi_commonName): New function. + + * openssl.conf (req): Use distinguished_name section as + specificied by $REQDN. + (reqDN.doubleCN): New section. + + * makekeys.sh: Set $REQDN; create twocn.cert. + +Sun Aug 18 00:47:19 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (accept_signed_cert): New function, factored out from + simple. + (simple): Use accept_signed_cert. + (subject_altname, two_subject_altname, two_subject_altname2): + New function. + + * openssl.conf: Add extension sections altExt, altExt2, altExt3. + + * makekeys.sh: Generate altname.cert, altname2.cert, + altname3.cert. + +Sat Aug 17 18:41:42 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh (csr_fields): New function; generate output for + `openssl req'. + +Sat Aug 17 18:27:36 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Add CA and REQ variables to simplify OpenSSL + invocation. Pass -config to req rather than relying on installed + default configuration. + + * openssl.conf: Add `req' and `reqDN' sections to allow use with + `openssl req' command. Add CA basic constraint extention to + certificates used. + +Sat Aug 10 10:42:57 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Use openssl binary as ${OPENSSL}. + + * Makefile.in: Pick up OPENSSL from configure, and pass it through + to makekeys.sh. + +Sat Aug 10 10:18:15 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (begin): Don't use run-time initialization. + + * request.c (s_progress): Fix warnings on FreeBSD. + +Mon Aug 5 21:08:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (ccert_provider, client_cert_provided): New functions. + (fail_load_ccerts): Enable function. + +Sun Aug 4 22:32:43 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (serve_abort, retry_after_abort): New functions. + +Sun Aug 4 13:28:47 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (continued_header): New function. + +Sun Aug 4 12:54:52 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c [SOCKET_SSL] (ssl_closure): New function; use instead + of read_reset, write_reset for SOCKET_SSL build. + +Sun Aug 4 12:27:34 2002 Joe Orton <joe@manyfish.co.uk> + + Build socket.c twice, once for testing over SSL connections: + + * Makefile.in (socket-ssl.o, socket-ssl): New targets. + (SSL_TESTS): Include socket-ssl target. + + * socket.c [SOCKET_SSL] (init_ssl, wrap_serve): New functions. + [SOCKET_SSL] (begin): Alternate implementation. + +Sat Aug 3 22:20:59 2002 Joe Orton <joe@manyfish.co.uk> + + * session.c (privates): New function. + +Sat Aug 3 22:20:14 2002 Joe Orton <joe@manyfish.co.uk> + + * auth.c (fail_auth_cb, tunnel_regress): New function. + +Sat Aug 3 22:12:48 2002 Joe Orton <joe@manyfish.co.uk> + + * auth.c (forget_regress): New function. + +Sun Jul 28 12:24:02 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (lock_timeout, submit_test, lock_shared): Use ne_concat, + not CONCAT? macros. + + * ssl.c (init, fail_expired, fail_notvalid): Likewise. + +Thu Jul 25 00:04:47 2002 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (buf_concat, buf_concat2, buf_concat3): Renamed + from concat, concat1, concat3). + (concat): New function. + +Sun Jul 14 11:42:03 2002 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (versioning): New function. + +Thu Jul 11 17:24:29 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (no_headers): New function. + +Wed Jul 10 22:58:01 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (any_2xx_request_body): New function. + +Wed Jul 10 22:44:12 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (ptimeout_eof, ptimeout_eof2, close_not_retried, + serve_close2): New functions. + (abort_respbody): Rejoin child earlier for reliable results. + +Sun Jul 7 12:17:11 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (expect_eof): Better error reporting. + (good_close): Split from finish(). + (finish): Use good_close. + (expect_write_closed, write_reset, read_reset): Add tests that + an ECONNRESET is treated as a SOCK_CLOSED failure. + +Sun Jul 7 08:38:12 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (serve_response): Use discard_body(). + +Sun Jul 7 08:28:56 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (serve_expect, full_write, small_writes, large_writes, + echo_server, echo_expect, echo_lines): New functions. + +Sat Jul 6 13:11:33 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (serve_eof, fail_early_eof, fail_eof_continued, + fail_eof_headers): New functions. + +Sat Jul 6 08:58:17 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (serve_100_once, expect_100_once): New functions. + +Fri Jul 5 21:43:58 2002 Joe Orton <joe@manyfish.co.uk> + + * auth.c (username): Use the correct spelling of Aladdin. + (auth_hdr): Simplify debug messages. + (auth_serve): Fail if no Authorization header is given. + (basic): Check for response status. + +Fri Jul 5 21:41:02 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (any_2xx_request): New function. + +Sun Jun 30 17:10:59 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_noserver): Factor out from host_not_found. + (fail_lookup): Equivalent to old host_not_found. + (fail_connect, abort_respbody): New function. + +Sun Jun 30 14:32:32 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_chunksize): New function. + +Sun Jun 30 10:39:17 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (test_persist): Factor out from persist; take + response and response body as arguments. + (persist_http11): New function, equivalent to old persist. + (persist_chunked, persist_http10): New functions. + +Sun Jun 30 10:25:07 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (serve_response): Factor out from single_serve_string, + many_serve_string. + (single_serve_string, many_serve_string): Use serve_response. + +Sun Jun 30 09:13:55 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (expect_response, persist, persist_timeout, + multi_header): Rely on the fact that the test framework + will reap the server. + (expect_no_body, no_body_304, no_body_204, no_body_HEAD, + no_body_chunks): New functions. + +Tue Jun 25 23:05:42 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (trailing_header): New function. + +Sun Jun 23 23:00:03 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (no_verify): Fix sixth argument to any_ssl_request. + +Sun Jun 23 15:21:06 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (grind): New target. + + * run.sh: Respect $HARNESS. + +Sun Jun 23 15:20:38 2002 Joe Orton <joe@manyfish.co.uk> + + * props.c: New file. + +Sun Jun 23 09:37:10 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Ignore failure from `hostname -[sdf]' commands, as + appropriate tests are skipped on failure. + +Sun Jun 23 08:33:50 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (host_not_found): Use any_request(); simplify. + (proxy_no_resolve): New function. + +Sun Jun 16 11:40:19 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Succeed if connection is closed + by client after negotiation. + (serve_tunnel, fail_tunnel, proxy_tunnel): New functions. + +Mon Jun 10 21:18:03 2002 Joe Orton <joe@manyfish.co.uk> + + * redirect.c (check_redir): Await server child before returning. + +Sun Jun 9 13:05:25 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (DECL): Don't use run-time initialization. + (single_read, single_peek, small_reads, read_and_peek, line_closure, + larger_read, line_toolong): Use DECL, as last declaration. + +Sun Jun 9 13:03:36 2002 Joe Orton <joe@manyfish.co.uk> + + * compress.c (reader, do_fetch): Check that inflated data is of + expected length. + +Sun Jun 9 11:40:54 2002 Joe Orton <joe@manyfish.co.uk> + + * redirect.c (struct redir_args): Add 'path' field. + (any_request): Use path in Request-URI. + (simple, redir_303, non_absolute): Fill in path. + (relative_1, relative_2): New functions. + +Tue Jun 4 16:56:08 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (parents): Improve ne_path_parent tests. + +Mon Jun 3 18:22:31 2002 Joe Orton <joe@manyfish.co.uk> + + * cookies.c: New file. + +Sun Jun 2 10:06:42 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (dav_capabilities): New function. + +Sat Jun 1 10:39:04 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (to_begin, to_end, peek_timeout, read_timeout, + readline_timeout, fullread_timeout): New functions. + +Sat Jun 1 10:38:13 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (read_timeout): Use sleepy_server. + (hung_server): Removed. + +Sat Jun 1 10:32:45 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (sleepy_server): New function. + +Thu May 30 20:00:40 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (finish): New function, factored out from common code. + (small_reads, read_and_peek, larger_read): Use it. + (line_simple, line_closure, line_empty, line_toolong, line_mingle, + line_chunked): New functions. + +Sun May 26 14:54:52 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fill_uri, match_hostport, hostports): Moved functions + to session.c. + + * session.c: New file. + +Fri May 24 08:14:21 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (match_hostport, hostports): New functions. + +Tue May 21 21:29:25 2002 Joe Orton <joe@manyfish.co.uk> + + * redirect.c: New file. + +Sun May 19 18:25:48 2002 Joe Orton <joe@manyfish.co.uk> + + * auth.c, lock.c, regress.c, socket.c, ssl.c, utils.c, utils.h: + Update for socket API change; s/sock_/ne_sock_/, + s/SOCK_/NE_SOCK_/. + +Wed May 8 19:41:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Take response body as parameter; all + callers changed. + (serve_eof, simple_eof): New functions. + +Wed May 8 17:17:27 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c: New file. + + * sock-tests.c: Removed file. + + * Makefile.in: Updated accordingly. + +Wed May 8 11:53:35 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (host_not_found): New function. + +Wed May 1 21:41:02 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (parse): New function. + (simple, simple_ssl): Adjust for ne_uri_parse port default. + +Tue Apr 23 21:39:09 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (read_timeout): Better diagnostic for test failure + cases. + +Sun Apr 14 12:00:19 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (content_type): Updated to reflect default charset + ISO-8859-1 for text/ media types. + +Sun Apr 7 17:35:21 2002 Joe Orton <joe@manyfish.co.uk> + + * run.sh: Set MALLOC_CHECK_ so glibc's heap corruption detection + is enabled. + +Sun Apr 7 17:30:37 2002 Joe Orton <joe@manyfish.co.uk> + + * compress.c (do_fetch): Reset 'failed' flag to zero each time. + +Wed Apr 3 20:16:43 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (NO_BODY): Renamed from NOBODY (all callers changed). + (empty_header, ignore_header_ws, ignore_header_ws2): New tests. + (ignore_header_ws3): Renamed from ignore_header_spaces. + +Tue Apr 2 21:09:33 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (expect_header_value): New function. + (ignore_header_case, ignore_header_spaces, + ignore_header_tabs): New tests. + +Mon Mar 25 21:51:24 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (multi_lock_response, lock_shared): New function. + (lock_compare): Factored out from discover_results. + (discover, lock_timeout, submit_test): Adjust for lock API + changes. + +Mon Mar 25 21:36:55 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fail_ssl_request): Check failure bits against + NE_SSL_FAILMASK. + +Sun Mar 10 22:07:48 2002 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_decompress, sd_reader): New function. + +Sun Mar 10 21:39:29 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (activelock): New function, factored out from + lock_response. + (lock_response): Use activelock; adjust argument types. + (make_lock): Adjusted for lock_response arg changes. + (discard_response, serve_discovery, discover_result, discover): + New functions. + +Wed Mar 6 22:22:04 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (submit_test): Handle failures gracefully. + +Wed Mar 6 21:23:27 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (submit_test): Update to expect an absoluteURI in If: + headers. + +Wed Mar 6 21:17:37 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (unparse): New function. + +Tue Mar 5 22:59:37 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (cmp): Checks for case-insensitive comparison, and + empty path, "/" equivalence. + +Mon Mar 4 01:07:03 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (blank_response): Add test for potential segfault + in strip_eol (would fail if run under Electric Fence). + +Sun Mar 3 20:50:01 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (make_lock, store_single, store_several, got_if_header, + serve_verify_if, do_request, submit_test, if_simple, + if_under_infinite, if_infinite_over, if_child, if_covered_child): + New tests. + + (lock_timeout): Adjusted for API changes. + +Sun Mar 3 15:29:05 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (cmp_differ, cmp): New functions. + +Sun Mar 3 11:08:36 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fill_uri): New function. + +Sun Feb 17 21:31:21 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fqdn_match): Removed test. + +Sun Feb 17 20:32:16 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Create keypair for client cert. + + * ssl.c (do_ssl_response, any_ssl_request, all callers thereof): + Better error handling. + (serve_ccert, load_pem_ccert, keypw_prompt, load_pkcs12_ccert, + fail_load_ccerts, client_cert_pem, client_cert_pkcs12): New + functions. + +Sun Feb 17 11:54:19 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (do_range): Factored out from + get_range/fail_range_length. + (get_range, fail_range_length): Use do_range. + (fail_range_units, fail_range_notrange, fail_range_unsatify): New + tests. + +Sun Feb 17 11:36:00 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (get_range, fail_range_length): New functions. + +Sat Feb 16 23:29:40 2002 Joe Orton <joe@manyfish.co.uk> + + * xml.c: New file. + + * Makefile.in (DAV_TESTS): Add xml tests. + +Sat Feb 16 15:26:27 2002 Joe Orton <joe@manyfish.co.uk> + + * compress.c (do_fetch): Rename from fetch(); add 'expect_fail' + paramater. (fetch): Re-implement using do_fetch. + (fail_trailing, fail_bad_csum, fail_truncate): New functions. + + * Makefile.in (trailing.gz, truncated.gz, badcsum.gz): New helper + files. + +Thu Feb 14 19:09:42 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (everywhere): Simplify use of expect_response. + +Thu Feb 14 19:05:48 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (ignore_bad_headers): New function. + +Mon Feb 11 22:06:40 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: If the hostname command is clever enough to give + FQDN, hostname, domainname, then create wildcard.cert; cert with a + wildcard commonName. + + * ssl.c (wildcard_match): New function + +Mon Feb 11 21:55:52 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (any_ssl_request): Take session pointer, don't + initialize here. (DEFSESS): New macro. + (everywhere): Use DEFSESS rather than passing pointer-to- + session pointer. + +Mon Feb 11 20:44:44 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fqdn_match): Test for FQDN matching against non-qualified + FQDN. + (makekeys.sh): Create server cert with FQDN. + +Sun Feb 10 12:36:55 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (chunk_oversize): New function. + +Sat Feb 9 21:12:47 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (reason_phrase): New function. + +Sat Feb 9 16:50:58 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (read_timeout, hung_server): New functions. + +Thu Feb 7 22:58:31 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (cache_verify, no_verify, count_vfy): New functions. + +Thu Feb 7 19:39:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (any_ssl_request): Take server function as argument: all + callers changed. + (fail_ssl_request): Renamed from failreq; uses any_ssl_request. + +Wed Feb 6 20:43:32 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (load_ca): New function. + +Wed Feb 6 20:36:15 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (any_ssl_request): Make ca_cert and verify_fn arguments + optional. + (trustall): Removed function. + (simple): Use the CA cert; no need for a verify function. + (parse_cert): Don't give a CA cert, force use of verify function. + (failreq): Bug fix, don't trust server cert as CA. + (fail_wrongCN, fail_notvalid, fail_expired): Pass server cert + as CA cert server cert is self-signed. + +Tue Feb 5 20:33:42 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fail_untrusted_ca, fail_self_signed): New tests. + (fail_serve): New function. + (failreq, any_ssl_request): Take ca cert argument. + (check_DNs, trustall, get_failures): Adjust for new verify + callback interface. + +Sat Feb 2 14:18:11 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Factored out from serve_ssl. + (serve_ssl): Use do_ssl_response. + (serve_scache, session_cache): New functions. + +Thu Jan 31 21:09:58 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (ca-stamp): New target. + + * makekeys.sh: New helper script. + + * ssl.c (parse_cert, fail_wrongCN, fail_expired, fail_notvalid): + New tests. + (any_ssl_request, trustall, check_DNs, failreq): New auxiliaries. + +Thu Jan 31 20:42:38 2002 Joe Orton <joe@manyfish.co.uk> + + * wrongcn.pem, notvalid.pem, expired.pem, server.key: New files. + + * Makefile.in: Remove targets to generate certs. + +Wed Jan 30 21:15:33 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (wrongcn.pem): New target. + +Wed Jan 30 19:58:18 2002 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c: Updated for ne_buffer API change. + +Sat Jan 26 11:23:34 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Pick up appropriate TESTS, HELPERS from configure. + (ssltests*, davtests*): Remove crud. + + * compress.c: Presume zlib support present if built. + +Sun Jan 20 23:29:37 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c: New file. + + * Makefile.in (ssltests-no, ssltests-yes, server.pem, server.key): + New targets. + (check): Conditionally run SSL tests. + +Sun Jan 20 13:20:56 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (davtests-no, davtests-yes): Separate test programs + which require DAV support; only run if DAV is enabled. + + * Makefile.in (test): Pass SRCDIR env var through to run.sh. + + * run.sh: Pass SRCDIR as argv[1] to test programs. + + * compress.c (init): New function. Use 'newsfn' global for + filename of NEWS file. + +Sun Jan 20 13:06:40 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Fixes for VPATH build + +Mon Jan 14 01:58:39 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (content_type): Add harsher charset handling tests. + +Sun Jan 13 14:01:57 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (lock_timeout): Use make_session. + + * acl.c (test_acl): Use make_session. + + * auth.c (basic, retries): Use make_session. + +Sun Jan 13 14:01:13 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (make_session): New function. + +Sun Jan 13 14:00:34 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (content_type): Rename ctype to ct; check if charset is + unexpectedly set. + +Sun Jan 13 13:58:07 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c: New file. + + * Makefile.in: Add `basic' test suite. + +Mon Jan 7 22:05:33 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Don't pass CFLAGS to CC when linking. + +Mon Jan 7 21:46:03 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c: New file. + + * Makefile.in: Add 'lock' to TESTS, build lock. + +Mon Jan 7 21:17:21 2002 Joe Orton <joe@manyfish.co.uk> + + * skeleton.c: Add skeleton test suite. + +Tue Jan 1 21:47:09 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Use CPPFLAGS correctly. + +Sun Dec 9 14:02:50 2001 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (ONCMP): New macro. (everywhere): Use it. + (grow): Add ne_buffer_grow test. + +Sun Dec 9 13:12:27 2001 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (concat2, concat3): New ne_buffer_concat tests. + +Sat Dec 1 18:35:29 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.c (any_request): Don't set the error context. + +Sat Dec 1 12:21:48 2001 Joe Orton <joe@manyfish.co.uk> + + * auth.c (retry_failure, retry_fail_cb, retry_fail_serve): New + functions. + +Tue Nov 27 21:24:22 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (s_progress, provide_progress, send_progress): New + functions. + +Sun Nov 18 19:11:23 2001 Joe Orton <joe@manyfish.co.uk> + + * auth.c (send_response): New function. (auth_serve): Simplify + using send_response. (retry_serve, retry_cb, retries): New + functions. + +Sat Nov 17 22:32:29 2001 Joe Orton <joe@manyfish.co.uk> + + * auth.c (auth_serve, basic): Simplify, use a persistent + connection and any_request() to work with --disable-dav builds. + +Sat Nov 17 22:30:43 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.c (any_request): New function. + +Sun Oct 28 19:38:05 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Use explicit link rules. + +Fri Oct 26 20:08:33 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (persist_timeout): Test behaviour when connection + closes after between 1 and 10 requests. + +Fri Oct 26 20:04:27 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.c (many_serve_string): New function. + +Sun Oct 7 17:48:53 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.c: New file. + + * request.c (single_serve_string): Moved to utils.c. + + * Makefile.in: Link utils.o into all libtest.a. Move libtest.a + into this directory. + +Sun Oct 7 15:01:47 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (persist, persist_timeout, serve_twice, is_alive): New + functions. (closed_connection): Avoid race condition. + +Sat Oct 6 14:33:42 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (prepare_request, finish_request): Renamed from + make_request, destroy_request. (skip_interim_1xx, skip_many_1xx, + skip_1xx_hdrs): New functions. + +Wed Oct 3 00:03:33 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_request): Optionally include a request body, and + optionally presume the server runs "forever". (all callers + changed). (serve_close, closed_connection): New function. + +Sat Sep 29 14:08:16 2001 Joe Orton <joe@manyfish.co.uk> + + * compress.c (fetch): Update for new decompression API. + +Sat Sep 29 11:21:56 2001 Joe Orton <joe@manyfish.co.uk> + + * compress.c: New file. + + * Makefile.in: Build compress test, and some its helpers. Add + -lneon to LIBS, and pick up NEON_CFLAGS. + +Thu Sep 27 20:31:51 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.h: New file. + + * request.c: Moved ONREQ() into utils.h + +Mon Aug 27 00:34:56 2001 Joe Orton <joe@manyfish.co.uk> + + * regress.c: New file. + +Mon Aug 27 00:33:13 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (discard_request): Moved into common/child.c. + (make_request, destroy_request): Convenience functions. + (serve_non_http, not_http): New test. + +Sun Jun 24 22:15:46 2001 Joe Orton <joe@manyfish.co.uk> + + * test.[ch], child.[ch]: Moved into 'common' subdir. + + * Makefile.in: Updated likewise. + +Tue Jun 19 22:00:06 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (parse_dates): Test date parsers. + +Sun Jun 10 17:36:11 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (infinite_headers, unbounded_headers): New test. + +Sun Jun 10 16:38:53 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c [HAVE_PIPE]: Use a pipe between child and parent to know + when the child is ready to accept connections. Avoids boring + sleep()ing. + +Fri Jun 8 21:19:35 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (segv, main): Remove SEGV handler in favour of useful + core dumps. + +Mon Jun 4 01:15:52 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_socket): Set socket family correctly. + +Thu May 31 08:58:41 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (md5_alignment): New test for MD5 alignment issue + on Sparc. + +Thu May 31 00:40:43 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (minisleep): Just sleep for a second anyway. + +Thu May 31 00:19:16 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_socket): Casts for bind and setsockopt arguments. + +Thu May 31 00:02:21 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (send_bodies): Test callback-provided request bodies. + +Wed May 30 22:37:08 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (child_segv): New function. (in_child): Install + different SEGV handler. (segv): Sleep so the re-raised SEGV + signal gets handled and we dump core. + +Wed May 30 19:24:32 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (send_bodies): New test for sending request bodies. + +Wed May 16 21:19:49 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (expect_response): Renamed, fold together + single_request and do_get_request. (all callers changed) + +Wed May 16 20:59:19 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (construct_get, run_request): New functions. + (fold_headers, fold_many_headers, multi_header): New tests. + +Sat May 12 17:37:36 2001 Joe Orton <joe@manyfish.co.uk> + + * server.c: Renamed from http-tests.c. + +Sat May 12 17:35:05 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (minisleep): New function. (spawn_server, reap_server): + New functions. (server_child): Call in_child. + +Sat May 12 17:33:57 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Open two log files for debugging messages. + (in_child): Switch to debug using child log. + +Sat May 12 11:18:18 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Call sock_init. (segv): Re-raise SEGV signal + after printing message. + +Mon May 7 10:38:50 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (chunk_syntax_1, chunk_syntax_2, chunk_syntax_3, + chunk_syntax_4, chunk_syntax_5): Split down from chunk_syntax. + +Mon May 7 10:37:38 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (base64): Update for ne_base64() changes. Add + tests for binary data. + +Sun May 6 23:55:36 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.h (ON): Use global buffer 'on_err_buf'. Make 'name' + variable public. + +Sun May 6 23:53:06 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (single_serve_string): General version of + single_serve_*. (single_request): Pass in expected response body. + (single_get_*): Use new single_request/single_serve_string. + (chunk_syntax): Add some tests for chunk syntax. + +Sun May 6 22:29:36 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c, child.h: New files, split down from request.c. + +Sun May 6 21:53:28 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (spawn_server): Sleep for a while to let the server + get going. (do_request): Use passed parameters when creating + request. + +Sun May 6 21:34:27 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (spawn_server): Use callback to handle the server side + of connection. (single_request): New function. (single_get_eof, + single_get_clength, single_get_chunked): New functions. + (reap_server): New function. + +Sun May 6 20:02:32 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c: New file. + +Wed May 2 12:08:53 2001 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (token1, token2, nulls, empty, quoted, badquotes, + shave, combo): New tests for ne_token and ne_shave. + +Wed May 2 12:04:52 2001 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c: Updated for sbuffer -> ne_buffer changes. + +Wed May 2 01:08:45 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (check): Alias for test goal. + +Wed May 2 01:08:36 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (segv): Disable SEGV handler once handling it. + +Sun Apr 29 14:57:59 2001 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (slash): Check behaviour of passing zero-length URI. + +Sun Apr 29 13:43:59 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (clean): New target. (libtest.a): Depend on libneon + to force rebuilds when necessary. (all): Build but don't test. + +Sun Apr 29 13:41:13 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c: Add status line with leading garbage. + +Sun Apr 29 13:39:53 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (status_lines): Add some tests for invalid status + lines too. + +Sun Apr 29 13:38:31 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Use basename(argv[0]) as suite name. Fail if no + tests are in the functions vector. + +Sun Apr 29 11:06:45 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (segv): New function. (main): Add SIGSEGV handler. + +Fri Apr 27 00:00:12 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (base64): New test. + +Thu Apr 26 22:39:44 2001 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (just_hostname, just_path, null_uri): New tests. + +Thu Apr 26 22:03:58 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (md5): Test of MD5 functions. + +Mon Apr 23 23:08:02 2001 Joe Orton <joe@manyfish.co.uk> + + * http-tests.c (simple_head): Add HEAD test. + +Mon Apr 23 22:49:52 2001 Joe Orton <joe@manyfish.co.uk> + + * http-tests.c (simple_get): Check for EOF after reading response + body of HTTP/1.0 GET request. + + (null_resource): New function, test for 404 on null resource. + + diff --git a/test/Makefile.in b/test/Makefile.in new file mode 100644 index 0000000..eb0da59 --- /dev/null +++ b/test/Makefile.in @@ -0,0 +1,171 @@ +# Makefile for neon test suite. + +SHELL = @SHELL@ +CPPFLAGS = @CPPFLAGS@ -I. -I$(top_srcdir)/src -I$(top_srcdir)/test/common +CFLAGS = @CFLAGS@ @NEON_CFLAGS@ +LDFLAGS = -L. @LDFLAGS@ -L$(top_srcdir)/src -L../src/.libs +DEFS = @DEFS@ + +top_builddir = .. +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +AR = ar + +RANLIB = @RANLIB@ +LIBS = -ltest -lneon @NEON_LIBS@ +CC = @CC@ +OPENSSL = @OPENSSL@ + +HELPERS = @HELPERS@ +BASIC_TESTS = uri-tests util-tests string-tests session socket \ + request auth basic stubs redirect +ZLIB_TESTS = compress +ZLIB_HELPERS = file1.gz file2.gz trailing.gz badcsum.gz truncated.gz +DAV_TESTS = xml acl props lock +SSL_TESTS = socket-ssl ssl +SSL_HELPERS = ca-stamp +TESTS = @TESTS@ +HDRS = common/tests.h common/child.h utils.h $(top_builddir)/config.h +VALGRIND = valgrind --gdb-attach=yes --leak-check=yes + +LIBTEST = libtest.a + +# By default, compile but don't run the tests. +all: $(TESTS) + +clean: + rm -f $(TESTS) $(HELPERS) *.o common/*.o libtest.a *.log + rm -rf ca + rm -f ca-stamp client.key *.csr ssigned.pem wrongcn.pem \ + server.cert client.cert client.p12 + +check: $(TESTS) $(HELPERS) + @SRCDIR=$(srcdir) $(SHELL) $(srcdir)/run.sh $(TESTS) + +grind: $(TESTS) $(HELPERS) + @SRCDIR=$(srcdir) HARNESS="$(VALGRIND)" $(SHELL) $(srcdir)/run.sh $(TESTS) + +file1.gz: ../NEWS + gzip -c --no-name $(top_srcdir)/NEWS > $@ + +file2.gz: ../NEWS + gzip -c --name $(top_srcdir)/NEWS > $@ + +# gzip file with trailing bytes. +trailing.gz: ../NEWS + gzip -c --no-name $(top_srcdir)/NEWS > $@ + echo "hello, world" >> $@ + +truncated.gz: file1.gz + dd if=file1.gz of=$@ bs=2048 count=2 + +badcsum.gz: file1.gz + dd of=$@ if=file1.gz bs=1 count=`perl -e 'printf "%d", (stat("file1.gz"))[7] - 8;'` + echo 'broken!' >> $@ + +# Dummy target to create the CA keys etc. makekeys stderr is redirected +# since it changes for every invocation; not helpful for regression +# testing. +ca-stamp: $(srcdir)/makekeys.sh $(srcdir)/openssl.conf + rm -rf ca + OPENSSL=$(OPENSSL) \ + $(SHELL) $(srcdir)/makekeys.sh $(srcdir) 2>makekeys.out + @echo timestamp > ca-stamp + +Makefile: $(srcdir)/Makefile.in + cd .. && ./config.status test/Makefile + +LIBOBJS = common/tests.o common/child.o utils.o + +$(LIBTEST): $(LIBOBJS) $(HDRS) ../src/@NEON_TARGET@ + $(AR) cru $@ $(LIBOBJS) + $(RANLIB) $@ + +.c.o: + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +uri-tests: uri-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) $(CFLAGS) -o $@ uri-tests.o $(LIBS) + +request: request.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ request.o $(LIBS) + +string-tests: string-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ string-tests.o $(LIBS) + +socket: socket.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ socket.o $(LIBS) + +compress: compress.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ compress.o $(LIBS) + +acl: acl.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ acl.o $(LIBS) + +utils: utils.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ utils.o $(LIBS) + +util-tests: util-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ util-tests.o $(LIBS) + +auth: auth.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ auth.o $(LIBS) + +lock: lock.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ lock.o $(LIBS) + +basic: basic.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ basic.o $(LIBS) + +# Recompile socket.c with SOCKET_SSL defined +socket-ssl.o: $(srcdir)/socket.c $(HDRS) + $(CC) -DSOCKET_SSL $(CPPFLAGS) $(CFLAGS) -c $(srcdir)/socket.c -o $@ + +socket-ssl: socket-ssl.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ socket-ssl.o $(LIBS) + +ssl: ssl.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ ssl.o $(LIBS) + +xml: xml.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ xml.o $(LIBS) + +stubs: stubs.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ stubs.o $(LIBS) + +redirect: redirect.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ redirect.o $(LIBS) + +session: session.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ session.o $(LIBS) + +cookies: cookies.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ cookies.o $(LIBS) + +props: props.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ props.o $(LIBS) + +resolve: resolve.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ resolve.o $(LIBS) + +#FOO: FOO.o $(LIBTEST) +# $(CC) $(LDFLAGS) -o $@ FOO.o $(LIBS) + +auth.o: auth.c $(HDRS) +uri-tests.o: uri-tests.c $(HDRS) +util-tests.o: util-tests.c $(HDRS) +string-tests.o: string-tests.c $(HDRS) +socket.o: socket.c $(HDRS) +server.o: server.c $(HDRS) +request.o: request.c $(HDRS) +regress.o: regress.c $(HDRS) +compress.o: compress.c $(HDRS) +acl.o: acl.c $(HDRS) +utils.o: utils.c $(HDRS) +stubs.o: stubs.c $(HDRS) +props.o: props.c $(HDRS) +session.o: session.c $(HDRS) +redirect.o: redirect.c $(HDRS) +basic.o: basic.c $(HDRS) diff --git a/test/README b/test/README new file mode 100644 index 0000000..d8f6528 --- /dev/null +++ b/test/README @@ -0,0 +1,39 @@ + +Stupidly Simple Test Suite for neon +----------------------------------- + +The aim of the test suite is two-fold: + + 1. ensure compliance to the relevant RFCs in network behaviour. + + 2. ensure that the promises made by the public API are met + by the current implementation. + +The file `STATUS' makes an attempt at listing RFC requirements and how +the test suite tests whether neon meets them or not (it's not finished +yet). + +The test suite is licensed under the GPL. + +Important Note About Test Failures +---------------------------------- + +Note that a test failure either means a bug in the test or a bug in +the code itself. On platforms without pipe(), there is a race +condition in the code which forks a server process: if you get random +failures on a slow or loaded box, increase the sleep time in +common/child.c:minisleep(). + +Extra Stuff +----------- + +server-tests requires that you have a running HTTP server on localhost +port 80, and you have copied htdocs/* to server-htdocs-root/test/* + +Credits +------- + +This test suite is inspired by the Subversion project, discussion on +the subversion mailing list, and seeing chromatic's talks on XP. The +presentation is inspired by the standard Perl test suite. Imitation +is the greatest form of flattery, right? diff --git a/test/STATUS b/test/STATUS new file mode 100644 index 0000000..a5483ef --- /dev/null +++ b/test/STATUS @@ -0,0 +1,74 @@ + -*- text -*- + +This document attempts to list RFC requirements and determine whether +neon meets them, or where they do not apply, etc. + + Yes: test written, succeeds + No: test written, but currently fails + ???: no test written + ---: feature not supported + App: this is an application issue not a neon issue + + RFC2616 + ======= + +3.1: MUST treat major/minor as separate digits Yes +3.1: MUST ignore leading zeros Yes +3.1: MUST only send HTTP/1.1 when appropriate ??? + +3.2.2: MUST use abs_path of "/" in Request-URI App +3.2.3: comparisons of host names MUST be case-insensitive Yes + comparisons of scheme names MUST be ... Yes + comparison of empty abs_path equivalent to "/" No/--- + +3.3.1: MUST accept three date formats App/Yes [2] + MUST only generate RFC1123-style dates App + +3.3.1: MUST use GMT for http-dates ??? + MUST assume GMT when parsing asctime dates ??? + +3.4.1: MUST respect charset label provided Yes/App + +3.5*: content codings App + +3.6: MUST requirements for multiple transfer-codings --- [4] + +3.6.1: parsing of chunked transfer coding Yes + MUST be able to handle "chunked" transfer-coding Yes + MUST ignore unknown chunk-extension extensions Yes + +3.7: parsing of Content-Type headers Yes + +3.7: MUST NOT have LWS between type/subtype in C-T hdr App + SHOULD only send parameters to "new HTTP apps" (>1.0?) App + +3.7.1: MUST represent HTTP message in canonical form App + MUST accept CRLF/CR/LF as line-breaks in text/* media App + MUST NOT use only CR or LF in HTTP control structures ??? + MUST specify charset if not ISO-8859-1 App + +3.7.2: multipart types --- + +3.8: SHOULD have short product token Yes/App [5] + SHOULD use product-version for version identifier Yes/App + only product-version differs between versions Yes/App + +3.9: Content Negotiation ---/App + +3.10: Language Tags ---/App + +3.11: Entity Tags ---/App + + + + + +[2]: date parser is provided which handles all three formats, but no +handling of the Date header is present within neon. + +[3]: not sure if neon should be handling of this internally. + +[4]: neon only supports using just chunked Transfer-Coding or none. + +[5]: these reflect that applications may add their own product tokens + alongside neon's. diff --git a/test/acl.c b/test/acl.c new file mode 100644 index 0000000..14008b1 --- /dev/null +++ b/test/acl.c @@ -0,0 +1,115 @@ +/* + Dummy ACL tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "ne_acl.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +#ifdef NEON_NODAV + +static int skip(void) +{ + t_context("built without WebDAV support"); + return SKIP; +} + +ne_test tests[] = { T(skip), T(NULL) }; + +#else + +/**** DUMMY TESTS: just makes sure the stuff doesn't dump core. */ + +static int test_acl(const char *uri, ne_acl_entry *es, int nume) +{ + ne_session *sess; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n\r\n")); + + ON(ne_acl_set(sess, uri, es, nume)); + + CALL(await_server()); + ne_session_destroy(sess); + + return OK; +} + +static int grant_all(void) +{ + ne_acl_entry e = {0}; + + e.apply = ne_acl_all; + e.type = ne_acl_grant; + + CALL(test_acl("/foo", &e, 1)); + + return OK; +} + +static int deny_all(void) +{ + ne_acl_entry e = {0}; + + e.apply = ne_acl_all; + e.type = ne_acl_deny; + + CALL(test_acl("/foo", &e, 1)); + + return OK; +} + +static int deny_one(void) +{ + ne_acl_entry e = {0}; + + e.apply = ne_acl_href; + e.type = ne_acl_deny; + e.principal = "http://webdav.org/users/joe"; + + CALL(test_acl("/foo", &e, 1)); + + return OK; +} + +static int deny_byprop(void) +{ + ne_acl_entry e = {0}; + + e.apply = ne_acl_property; + e.type = ne_acl_deny; + e.principal = "owner"; + + CALL(test_acl("/foo", &e, 1)); + + return OK; +} + +ne_test tests[] = { + T(grant_all), + T(deny_all), + T(deny_one), + T(deny_byprop), + T(NULL) +}; + +#endif /* NEON_NODAV */ diff --git a/test/auth.c b/test/auth.c new file mode 100644 index 0000000..30fe88e --- /dev/null +++ b/test/auth.c @@ -0,0 +1,315 @@ +/* + Authentication tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_auth.h" +#include "ne_basic.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static const char username[] = "Aladdin", password[] = "open sesame"; +static int auth_failed; + +static const char www_wally[] = "WWW-Authenticate: Basic realm=WallyWorld"; + +static int auth_cb(void *userdata, const char *realm, int tries, + char *un, char *pw) +{ + strcpy(un, username); + strcpy(pw, password); + return tries; +} + +static void auth_hdr(char *value) +{ +#define B "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" + auth_failed = strcmp(value, B); + NE_DEBUG(NE_DBG_HTTP, "Got auth header: [%s]\nWanted header: [%s]\n" + "Result: %d\n", value, B, auth_failed); +#undef B +} + +/* Sends a response with given response-code. If hdr is not NULL, + * sends that header string too (appending an EOL). If eoc is + * non-zero, request must be last sent down a connection; otherwise, + * clength 0 is sent to maintain a persistent connection. */ +static int send_response(ne_socket *sock, const char *hdr, int code, int eoc) +{ + char buffer[BUFSIZ]; + + sprintf(buffer, "HTTP/1.1 %d Blah Blah" EOL, code); + + if (hdr) { + strcat(buffer, hdr); + strcat(buffer, EOL); + } + + if (eoc) { + strcat(buffer, "Connection: close" EOL EOL); + } else { + strcat(buffer, "Content-Length: 0" EOL EOL); + } + + return SEND_STRING(sock, buffer); +} + +/* Server function which sends two responses: first requires auth, + * second doesn't. */ +static int auth_serve(ne_socket *sock, void *userdata) +{ + auth_failed = 1; + + /* Register globals for discard_request. */ + got_header = auth_hdr; + want_header = "Authorization"; + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, auth_failed?500:200, 1); + + return 0; +} + +static int basic(void) +{ + ne_session *sess; + + CALL(make_session(&sess, auth_serve, NULL)); + ne_set_server_auth(sess, auth_cb, NULL); + + CALL(any_2xx_request(sess, "/norman")); + + ne_session_destroy(sess); + CALL(await_server()); + return OK; +} + +static int retry_serve(ne_socket *sock, void *ud) +{ + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + return OK; +} + +static int retry_cb(void *userdata, const char *realm, int tries, + char *un, char *pw) +{ + int *count = userdata; + + /* dummy creds; server ignores them anyway. */ + strcpy(un, "a"); + strcpy(pw, "b"); + + switch (*count) { + case 0: + case 1: + if (tries == *count) { + *count += 1; + return 0; + } else { + t_context("On request #%d, got attempt #%d", *count, tries); + *count = -1; + return 1; + } + break; + case 2: + case 3: + /* server fails a subsequent request, check that tries has + * reset to zero. */ + if (tries == 0) { + *count += 1; + return 0; + } else { + t_context("On retry after failure #%d, tries was %d", + *count, tries); + *count = -1; + return 1; + } + break; + case 4: + case 5: + if (tries > 1) { + t_context("Attempt counter reached #%d", tries); + *count = -1; + return 1; + } + return tries; + default: + t_context("Count reached %d!?", *count); + *count = -1; + } + return 1; +} + +/* Test that auth retries are working correctly. */ +static int retries(void) +{ + ne_session *sess; + int count = 0; + + CALL(make_session(&sess, retry_serve, NULL)); + + ne_set_server_auth(sess, retry_cb, &count); + + /* This request will be 401'ed twice, then succeed. */ + ONREQ(any_request(sess, "/foo")); + + /* auth_cb will have set up context. */ + CALL(count != 2); + + /* this request will be 401'ed once, then succeed. */ + ONREQ(any_request(sess, "/foo")); + + /* auth_cb will have set up context. */ + CALL(count != 3); + + /* some 20x requests. */ + ONREQ(any_request(sess, "/foo")); + ONREQ(any_request(sess, "/foo")); + + /* this request will be 401'ed once, then succeed. */ + ONREQ(any_request(sess, "/foo")); + + /* auth_cb will have set up context. */ + CALL(count != 4); + + /* First request is 401'ed by the server at both attempts. */ + ONV(any_request(sess, "/foo") != NE_AUTH, + ("auth succeeded, should have failed: %s", ne_get_error(sess))); + + count++; + + /* Second request is 401'ed first time, then will succeed if + * retried. 0.18.0 didn't reset the attempt counter though so + * this didn't work. */ + ONV(any_request(sess, "/foo") == NE_AUTH, + ("auth failed on second try, should have succeeded: %s", ne_get_error(sess))); + + ne_session_destroy(sess); + + CALL(await_server()); + + return OK; +} + +/* crashes with neon <0.22 */ +static int forget_regress(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_forget_auth(sess); + ne_session_destroy(sess); + return OK; +} + +static int fail_auth_cb(void *ud, const char *realm, int attempt, + char *un, char *pw) +{ + return 1; +} + +/* this may trigger a segfault in neon 0.21.x and earlier. */ +static int tunnel_regress(void) +{ + ne_session *sess = ne_session_create("https", "localhost", 443); + ne_session_proxy(sess, "localhost", 7777); + ne_set_server_auth(sess, fail_auth_cb, NULL); + CALL(spawn_server(7777, single_serve_string, + "HTTP/1.1 401 Auth failed.\r\n" + "WWW-Authenticate: Basic realm=asda\r\n" + "Content-Length: 0\r\n\r\n")); + any_request(sess, "/foo"); + ne_session_destroy(sess); + CALL(await_server()); + return OK; +} + +/* test digest auth 2068-style. */ + +/* test digest auth 2617-style. */ + +/* negotiation: within a single header, multiple headers. + * check digest has precedence */ + +/* test auth-int, auth-int FAILURE. chunk trailers/non-trailer */ + +/* test logout */ + +/* proxy auth, proxy AND origin */ + +ne_test tests[] = { + T(lookup_localhost), + T(basic), + T(retries), + T(forget_regress), + T(tunnel_regress), + T(NULL) +}; diff --git a/test/basic.c b/test/basic.c new file mode 100644 index 0000000..15f5c8a --- /dev/null +++ b/test/basic.c @@ -0,0 +1,228 @@ +/* + Tests for high-level HTTP interface (ne_basic.h) + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <fcntl.h> + +#include "ne_basic.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int content_type(void) +{ + int n; + static const struct { + const char *value, *type, *subtype, *charset; + } ctypes[] = { + { "foo/bar", "foo", "bar", NULL }, + { "foo/bar ", "foo", "bar", NULL }, + { "application/xml", "application", "xml", NULL }, + /* text/ subtypes default to charset ISO-8859-1. */ + { "text/lemon", "text", "lemon", "ISO-8859-1" }, +#undef TXU +#define TXU "text", "xml", "utf-8" + /* 2616 doesn't *say* that charset can be quoted, but bets are + * that some servers do it anyway. */ + { "text/xml; charset=utf-8", TXU }, + { "text/xml; charset=utf-8; foo=bar", TXU }, + { "text/xml;charset=utf-8", TXU }, + { "text/xml ;charset=utf-8", TXU }, + { "text/xml;charset=utf-8;foo=bar", TXU }, + { "text/xml; foo=bar; charset=utf-8", TXU }, + { "text/xml; foo=bar; charset=utf-8; bar=foo", TXU }, + { "text/xml; charset=\"utf-8\"", TXU }, + { "text/xml; charset='utf-8'", TXU }, + { "text/xml; foo=bar; charset=\"utf-8\"; bar=foo", TXU }, +#undef TXU + /* badly quoted charset should come out as NULL */ + { "foo/lemon; charset=\"utf-8", "foo", "lemon", NULL }, + { NULL } + }; + + for (n = 0; ctypes[n].value != NULL; n++) { + ne_content_type ct = {0}; + + ne_content_type_handler(&ct, ctypes[n].value); + + ONV(strcmp(ct.type, ctypes[n].type), + ("for `%s': type was `%s'", ctypes[n].value, ct.type)); + + ONV(strcmp(ct.subtype, ctypes[n].subtype), + ("for `%s': subtype was `%s'", ctypes[n].value, ct.subtype)); + + ONV(ctypes[n].charset && ct.charset == NULL, + ("for `%s': charset unset", ctypes[n].value)); + + ONV(ctypes[n].charset == NULL && ct.charset != NULL, + ("for `%s': unexpected charset `%s'", ctypes[n].value, + ct.charset)); + + ONV(ctypes[n].charset && ct.charset && + strcmp(ctypes[n].charset, ct.charset), + ("for `%s': charset was `%s'", ctypes[n].value, ct.charset)); + + NE_FREE(ct.value); + } + + return OK; +} + +/* Do ranged GET for range 'start' to 'end'; with 'resp' as response. + * If 'fail' is non-NULL, expect ne_get_range to fail, and fail the + * test with given message if it doesn't. */ +static int do_range(off_t start, off_t end, const char *fail, + char *resp) +{ + ne_session *sess; + ne_content_range range = {0}; + int fd, ret; + + CALL(make_session(&sess, single_serve_string, resp)); + + range.start = start; + range.end = end; + + fd = open("/dev/null", O_WRONLY); + + ret = ne_get_range(sess, "/foo", &range, fd); + + close(fd); + CALL(await_server()); + + if (fail) { +#if 0 + t_warning("error was %s", ne_get_error(sess)); +#endif + ONN(fail, ret == NE_OK); + } else { + ONREQ(ret); + } + + ne_session_destroy(sess); + return OK; +} + +static int get_range(void) +{ + return do_range(1, 10, NULL, + "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n" + "Content-Range: bytes 1-10\r\n" + "Content-Length: 10\r\n\r\nabcdefghij"); +} + +static int fail_range_length(void) +{ + return do_range(1, 10, "range response length mismatch should fail", + "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n" + "Content-Range: bytes 1-2\r\n" + "Content-Length: 2\r\n\r\nab"); +} + +static int fail_range_units(void) +{ + return do_range(1, 2, "range response units check should fail", + "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n" + "Content-Range: fish 1-2\r\n" + "Content-Length: 2\r\n\r\nab"); +} + +static int fail_range_notrange(void) +{ + return do_range(1, 2, "non-ranged response should fail", + "HTTP/1.1 200 Widgets\r\n" "Connection: close\r\n" + "Content-Range: bytes 1-2\r\n" + "Content-Length: 2\r\n\r\nab"); +} + +static int fail_range_unsatify(void) +{ + return do_range(1, 2, "unsatisfiable range should fail", + "HTTP/1.1 416 No Go\r\n" "Connection: close\r\n" + "Content-Length: 2\r\n\r\nab"); +} + +static int dav_capabilities(void) +{ + static const struct { + const char *hdrs; + unsigned int class1, class2, exec; + } caps[] = { + { "DAV: 1,2\r\n", 1, 1, 0 }, + { "DAV: 1 2\r\n", 0, 0, 0 }, + /* these aren't strictly legal DAV: headers: */ + { "DAV: 2,1\r\n", 1, 1, 0 }, + { "DAV: 1, 2 \r\n", 1, 1, 0 }, + { "DAV: 1\r\nDAV:2\r\n", 1, 1, 0 }, + { NULL, 0, 0, 0 } + }; + char resp[BUFSIZ]; + int n; + + for (n = 0; caps[n].hdrs != NULL; n++) { + ne_server_capabilities c = {0}; + ne_session *sess; + + ne_snprintf(resp, BUFSIZ, "HTTP/1.0 200 OK\r\n" + "Connection: close\r\n" + "%s" "\r\n", caps[n].hdrs); + + CALL(make_session(&sess, single_serve_string, resp)); + + ONREQ(ne_options(sess, "/foo", &c)); + + ONV(c.dav_class1 != caps[n].class1, + ("class1 was %d not %d", c.dav_class1, caps[n].class1)); + ONV(c.dav_class2 != caps[n].class2, + ("class2 was %d not %d", c.dav_class2, caps[n].class2)); + ONV(c.dav_executable != caps[n].exec, + ("class2 was %d not %d", c.dav_executable, caps[n].exec)); + + CALL(await_server()); + + ne_session_destroy(sess); + } + + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(content_type), + T(get_range), + T(fail_range_length), + T(fail_range_units), + T(fail_range_notrange), + T(fail_range_unsatify), + T(dav_capabilities), + T(NULL) +}; + diff --git a/test/common/ChangeLog b/test/common/ChangeLog new file mode 100644 index 0000000..11d9840 --- /dev/null +++ b/test/common/ChangeLog @@ -0,0 +1,222 @@ +Wed Jun 18 20:10:45 2003 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_child, spawn_server_repeat): Adapt for new + socket API. + +Sun Mar 9 17:52:11 2003 Joe Orton <joe@manyfish.co.uk> + + * test.h (T_EXPECT_FAIL): New constant. + (T_XFAIL): New test function wrapper. + + * tests.c (main): Handle expected failures. + +Sat Mar 1 21:04:35 2003 Joe Orton <joe@manyfish.co.uk> + + Extend the ne_test structure with a 'flags' field which can + optionally request leak checking at run-time. + + * tests.h (ne_test): Add 'flags' field. + (T_CHECK_LEAKS): New flag. + (T): Use T_CHECK_LEAKS flag by default. + (T_LEAKY): Like T, but with no flags set. + + * tests.c (main) [NEON_MEMLEAK]: If leak checking is requested, + if a test passes, but leaks memory, fail the test. + +Wed Feb 26 21:52:15 2003 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Test the "disable debugging" mode of + ne_debug_init, NE_DBG_FLUSH, and a ne_debug() with no output. + +Fri Aug 23 22:54:35 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Call ne_sock_init after ne_debug_init, so that + debugging messages are caught from ne_sock_init. Print a warning + message if ne_sock_init fails. + +Wed Aug 21 13:29:20 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.h (ONCMP): New macro. + +Mon Aug 19 16:53:20 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (lookup_localhost): Just use inet_addr to resolve + 127.0.0.1. + +Sun Aug 18 13:50:30 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.c (TEST_DEBUG): Add NE_DBG_SSL. + +Sat Aug 10 10:19:18 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_send): Fix declaration. + (discard_body): Use NE_FMT_SSIZE_T for print ssize_t's. + +Sat Jul 6 08:42:37 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (discard_body): New function. + +Sun Jun 30 10:26:33 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_send): New function. + (discard_request): Fail with appropriate error. + +Sun Jun 30 09:03:51 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Reap server after each test has run. + +Sun Jun 30 09:00:43 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (reap_server): Set `child' to 0 so child can't be reaped + twice. + +Sun Jun 23 12:09:09 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (serve_file): Use large buffer when sending in chunked + mode to support large chunk sizes. + +Sun Jun 23 09:35:09 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (serve_file): Use NE_FMT_OFF_T and NE_FMT_SSIZE_T. + +Thu May 30 21:57:39 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (minisleep): Export function. + +Sun May 19 18:23:19 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c, tests.c: s/sock_/ne_sock_/, s/SOCK_/NE_SOCK_/ for + socket layer API change. + + * child.h (SEND_STRING): New macro. + +Sun May 19 08:57:21 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (lookup_hostname): Conditionally use hstrerror(). + +Mon Feb 25 20:54:56 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.c (t_context): Use ne_vsnprintf. + +Mon Feb 11 21:52:23 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (lookup_hostname): New function. + (server_child, do_listen): Pass around struct in_addr argument. + (spawn_server_addr): Renamed from spawn_server, take bind_local + flag to use localhost or "real" hostname to bind to. + (spawn_server): New function, use spawn_server + +Mon Feb 11 20:51:27 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (minisleep) [HAVE_USLEEP]: Use nice short usleep() + rather than boring long sleep(). + +Sat Feb 2 14:15:25 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (spawn_server_repeat): Exit child process (with failure) + if the server callback fails. + +Fri Jan 4 22:06:17 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.h: Add SKIPREST result value. + + * tests.c (TEST_DEBUG): Add NE_DBG_LOCKS. (main): Support + SKIPREST. + +Tue Jan 1 20:36:58 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.h: Make test_suite symbol have external linkage. + +Sat Nov 10 22:28:55 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c, test.h: Export name of test suite. + +Sun Nov 4 13:56:42 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (discard_request): Support retrieving arbitrary header + values from request via want_request, got_request globals. + +Wed Oct 24 21:41:39 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.h (ONV): New macro. (ON, ONN): Redefine in terms of ONV. + +Wed Oct 24 20:44:59 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main, t_warning, segv): Use colours when stdout is a + terminal device. + +Wed Oct 24 20:36:38 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (t_context, t_warning): Renamed from i_am, warning. + (t_context): Take printf varargs. + + * tests.h (ONN, ON): Update, simplify. + + * child.c: Update. + +Tue Oct 23 22:15:17 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Vertically align results after warnings. + +Tue Oct 2 23:36:44 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (do_listen): Refactored from server_socket, only does + bind/listen. (spawn_server): Moved awaken/accept calls here. + (spawn_server_repeat, dead_server): New functions. + +Sun Sep 30 10:14:35 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.h: Use a function/name structure for tests, add 'T' macro + for easily writing initializers. Rename 'name' global variable to + 'test_context' to avoid parameter name collisions. + + * child.c (spawn_server): Update accordingly. + + * tests.c (i_am): Update accordingly. (main): Update; prettify + output using new 'name' from test structure. Cope better when all + tests in a suite are skipped. + +Sat Sep 29 11:04:40 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (serve_file): If 'chunks' is set in argument object, + then deliver the file as a series of chunks. + +Thu Sep 27 20:28:45 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (serve_file): New function. + +Thu Sep 27 20:28:41 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (discard_request): Reset clength. + +Mon Aug 27 00:31:09 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (test_num): Expose test counter. (segv): Handle + segfault nicely. + +Mon Aug 27 00:30:20 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (discard_request): New function, from request.c in + neon/test. + +Wed Aug 8 22:09:21 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c, test.h: Only define test_argc/argv once. + +Mon Jul 16 16:30:28 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (warning): Take printf-style arguments list. + +Mon Jul 16 16:16:08 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Cope with skipped tests properly. + +Mon Jul 16 16:00:59 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (warning): New function. (main): Cope with warnings. + +Sun Jul 8 16:09:33 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Export argc/argv as test_argc, test_argv. + + diff --git a/test/common/README b/test/common/README new file mode 100644 index 0000000..a910a23 --- /dev/null +++ b/test/common/README @@ -0,0 +1,4 @@ + +Simple test framework for neon; licensed under the GNU GPL. + +Copyright (C) 2001-2002 Joe Orton diff --git a/test/common/child.c b/test/common/child.c new file mode 100644 index 0000000..84f580e --- /dev/null +++ b/test/common/child.c @@ -0,0 +1,454 @@ +/* + Framework for testing with a server process + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <sys/wait.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <sys/stat.h> + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <fcntl.h> +#include <netdb.h> +#include <signal.h> + +#include "ne_socket.h" +#include "ne_utils.h" +#include "ne_string.h" + +#include "tests.h" +#include "child.h" + +static pid_t child = 0; + +int clength; + +static struct in_addr lh_addr = {0}, hn_addr = {0}; + +const char *want_header = NULL; +got_header_fn got_header = NULL; +char *local_hostname = NULL; + +/* If we have pipe(), then use a pipe between the parent and child to + * know when the child is ready to accept incoming connections. + * Otherwise use boring sleep()s trying to avoid the race condition + * between listen() and connect() in the two processes. */ +#ifdef HAVE_PIPE +#define USE_PIPE 1 +#endif + +int lookup_localhost(void) +{ + /* this will break if a system is set up so that `localhost' does + * not resolve to 127.0.0.1, but... */ + lh_addr.s_addr = inet_addr("127.0.0.1"); + return OK; +} + +int lookup_hostname(void) +{ + char buf[BUFSIZ]; + struct hostent *ent; + + local_hostname = NULL; + ONV(gethostname(buf, BUFSIZ) < 0, + ("gethostname failed: %s", strerror(errno))); + + ent = gethostbyname(buf); +#ifdef HAVE_HSTRERROR + ONV(ent == NULL, + ("could not resolve `%s': %s", buf, hstrerror(h_errno))); +#else + ONV(ent == NULL, ("could not resolve `%s'", buf)); +#endif + + local_hostname = ne_strdup(ent->h_name); + + return OK; +} + +static int do_listen(struct in_addr addr, int port) +{ + int ls = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in saddr = {0}; + int val = 1; + + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(int)); + + saddr.sin_addr = addr; + saddr.sin_port = htons(port); + saddr.sin_family = AF_INET; + + if (bind(ls, (struct sockaddr *)&saddr, sizeof(saddr))) { + printf("bind failed: %s\n", strerror(errno)); + return -1; + } + if (listen(ls, 5)) { + printf("listen failed: %s\n", strerror(errno)); + return -1; + } + + return ls; +} + +void minisleep(void) +{ +#ifdef HAVE_USLEEP + usleep(500); +#else + sleep(1); +#endif +} + +/* This runs as the child process. */ +static int server_child(int readyfd, struct in_addr addr, int port, + server_fn callback, void *userdata) +{ + ne_socket *s = ne_sock_create(); + int ret, listener; + + in_child(); + + listener = do_listen(addr, port); + if (listener < 0) + return FAIL; + +#ifdef USE_PIPE + /* Tell the parent we're ready for the request. */ + write(readyfd, "a", 1); +#endif + + ONN("accept failed", ne_sock_accept(s, listener)); + + ret = callback(s, userdata); + + ne_sock_close(s); + + return ret; +} + +int spawn_server(int port, server_fn fn, void *ud) +{ + return spawn_server_addr(1, port, fn, ud); +} + +int spawn_server_addr(int bind_local, int port, server_fn fn, void *ud) +{ + int fds[2]; + struct in_addr addr; + + addr = bind_local?lh_addr:hn_addr; + +#ifdef USE_PIPE + if (pipe(fds)) { + perror("spawn_server: pipe"); + return FAIL; + } +#else + /* avoid using uninitialized variable. */ + fds[0] = fds[1] = 0; +#endif + + child = fork(); + + ONN("fork server", child == -1); + + if (child == 0) { + /* this is the child. */ + int ret; + + ret = server_child(fds[1], addr, port, fn, ud); + +#ifdef USE_PIPE + close(fds[0]); + close(fds[1]); +#endif + + /* print the error out otherwise it gets lost. */ + if (ret) { + printf("server child failed: %s\n", test_context); + } + + /* and quit the child. */ + exit(ret); + } else { + char ch; + +#ifdef USE_PIPE + if (read(fds[0], &ch, 1) < 0) + perror("parent read"); + + close(fds[0]); + close(fds[1]); +#else + minisleep(); +#endif + + return OK; + } +} + +int spawn_server_repeat(int port, server_fn fn, void *userdata, int n) +{ + int fds[2]; + +#ifdef USE_PIPE + if (pipe(fds)) { + perror("spawn_server: pipe"); + return FAIL; + } +#else + /* avoid using uninitialized variable. */ + fds[0] = fds[1] = 0; +#endif + + child = fork(); + + if (child == 0) { + /* this is the child. */ + int listener, count = 0; + + in_child(); + + listener = do_listen(lh_addr, port); + +#ifdef USE_PIPE + write(fds[1], "Z", 1); +#endif + + close(fds[1]); + close(fds[0]); + + /* Loop serving requests. */ + while (++count < n) { + ne_socket *sock = ne_sock_create(); + int ret; + + NE_DEBUG(NE_DBG_HTTP, "child awaiting connection #%d.\n", count); + ONN("accept failed", ne_sock_accept(sock, listener)); + ret = fn(sock, userdata); + ne_sock_close(sock); + NE_DEBUG(NE_DBG_HTTP, "child served request, %d.\n", ret); + if (ret) { + printf("server child failed: %s\n", test_context); + exit(-1); + } + /* don't send back notification to parent more than + * once. */ + } + + NE_DEBUG(NE_DBG_HTTP, "child aborted.\n"); + close(listener); + + exit(-1); + + } else { + char ch; + /* this is the parent. wait for the child to get ready */ +#ifdef USE_PIPE + if (read(fds[0], &ch, 1) < 0) + perror("parent read"); + + close(fds[0]); + close(fds[1]); +#else + minisleep(); +#endif + } + + return OK; +} + +int dead_server(void) +{ + int status; + + if (waitpid(child, &status, WNOHANG)) { + /* child quit already! */ + return FAIL; + } + + NE_DEBUG(NE_DBG_HTTP, "child has not quit.\n"); + + return OK; +} + + +int await_server(void) +{ + int status; + + (void) wait(&status); + + /* so that we aren't reaped by mistake. */ + child = 0; + + ONN("error from server process", WEXITSTATUS(status)); + + return OK; +} + +int reap_server(void) +{ + int status; + + if (child != 0) { + (void) kill(child, SIGTERM); + minisleep(); + (void) wait(&status); + child = 0; + } + + return OK; +} + +ssize_t server_send(ne_socket *sock, const char *str, size_t len) +{ + NE_DEBUG(NE_DBG_HTTP, "Sending: %.*s\n", (int)len, str); + return ne_sock_fullwrite(sock, str, len); +} + +int discard_request(ne_socket *sock) +{ + char buffer[1024]; + size_t offset = want_header?strlen(want_header):0; + + clength = 0; + + NE_DEBUG(NE_DBG_HTTP, "Discarding request...\n"); + do { + ONV(ne_sock_readline(sock, buffer, 1024) < 0, + ("error reading line: %s", ne_sock_error(sock))); + NE_DEBUG(NE_DBG_HTTP, "[req] %s", buffer); + if (strncasecmp(buffer, "content-length:", 15) == 0) { + clength = atoi(buffer + 16); + } + if (got_header != NULL && want_header != NULL && + strncasecmp(buffer, want_header, offset) == 0 && + buffer[offset] == ':') { + char *value = buffer + offset + 1; + if (*value == ' ') value++; + got_header(ne_shave(value, "\r\n")); + } + } while (strcmp(buffer, "\r\n") != 0); + + return OK; +} + +int discard_body(ne_socket *sock) +{ + while (clength > 0) { + char buf[BUFSIZ]; + size_t bytes = clength; + ssize_t ret; + if (bytes > sizeof(buf)) bytes = sizeof(buf); + NE_DEBUG(NE_DBG_HTTP, "Discarding %" NE_FMT_SIZE_T " bytes.\n", + bytes); + ret = ne_sock_read(sock, buf, bytes); + ONV(ret < 0, ("socket read failed (%" NE_FMT_SSIZE_T "): %s", + ret, ne_sock_error(sock))); + clength -= ret; + NE_DEBUG(NE_DBG_HTTP, "Got %" NE_FMT_SSIZE_T " bytes.\n", ret); + } + NE_DEBUG(NE_DBG_HTTP, "Discard successful.\n"); + return OK; +} + +int serve_file(ne_socket *sock, void *ud) +{ + char buffer[BUFSIZ]; + struct stat st; + struct serve_file_args *args = ud; + ssize_t ret; + int fd; + + CALL(discard_request(sock)); + + ne_sock_fullread(sock, buffer, clength); + + fd = open(args->fname, O_RDONLY); + if (fd < 0) { + SEND_STRING(sock, + "HTTP/1.0 404 File Not Found\r\n" + "Content-Length: 0\r\n\r\n"); + return 0; + } + + ONN("fstat fd", fstat(fd, &st)); + + SEND_STRING(sock, "HTTP/1.0 200 OK\r\n"); + if (args->chunks) { + sprintf(buffer, "Transfer-Encoding: chunked\r\n"); + } else { + sprintf(buffer, "Content-Length: %" NE_FMT_OFF_T "\r\n", + st.st_size); + } + + if (args->headers) { + strcat(buffer, args->headers); + } + + strcat(buffer, "\r\n"); + + SEND_STRING(sock, buffer); + + NE_DEBUG(NE_DBG_HTTP, "Serving %s (%" NE_FMT_OFF_T " bytes).\n", + args->fname, st.st_size); + + if (args->chunks) { + char buf[1024]; + + while ((ret = read(fd, &buf, args->chunks)) > 0) { + /* this is a small integer, cast it explicitly to avoid + * warnings with printing an ssize_t. */ + sprintf(buffer, "%x\r\n", (unsigned int)ret); + SEND_STRING(sock, buffer); + ONN("writing body", ne_sock_fullwrite(sock, buf, ret)); + SEND_STRING(sock, "\r\n"); + } + + SEND_STRING(sock, "0\r\n\r\n"); + + } else { + while ((ret = read(fd, buffer, BUFSIZ)) > 0) { + ONN("writing body", ne_sock_fullwrite(sock, buffer, ret)); + } + } + + ONN("error reading from file", ret < 0); + + (void) close(fd); + + return OK; +} diff --git a/test/common/child.h b/test/common/child.h new file mode 100644 index 0000000..9c506b9 --- /dev/null +++ b/test/common/child.h @@ -0,0 +1,113 @@ +/* + Framework for testing with a server process + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef CHILD_H +#define CHILD_H 1 + +#include "config.h" + +#ifdef HAVE_STRING_H +#include <string.h> /* for strlen() */ +#endif + +#include "ne_socket.h" + +/* Test which does DNS lookup on "localhost": this must be the first + * named test. */ +int lookup_localhost(void); + +/* Test which looks up real local hostname. */ +int lookup_hostname(void); + +/* set to local hostname if lookup_hostname succeeds. */ +extern char *local_hostname; + +/* Callback for spawn_server. */ +typedef int (*server_fn)(ne_socket *sock, void *userdata); + +/* Spawns server child process: + * - forks child process. + * - child process listens on localhost at given port. + * - when you connect to it, 'fn' is run... + * fn is passed the client/server socket as first argument, + * and userdata as second. + * - the socket is closed when 'fn' returns, so don't close in in 'fn'. + */ +int spawn_server(int port, server_fn fn, void *userdata); + +/* Like spawn_server; if bind_local is non-zero, binds server to + * localhost, otherwise, binds server to real local hostname. (must + * have called lookup_localhost or lookup_hostname as approprate + * beforehand). */ +int spawn_server_addr(int bind_local, int port, server_fn fn, void *userdata); + +/* Like spawn server except will continue accepting connections and + * processing requests, up to 'n' times. If 'n' is reached, then the + * child process exits with a failure status. */ +int spawn_server_repeat(int port, server_fn fn, void *userdata, int n); + +/* Blocks until child process exits, and gives return code of 'fn'. */ +int await_server(void); + +/* Kills child process. */ +int reap_server(void); + +/* Returns non-zero if server process has already died. */ +int dead_server(void); + +/* If discard_request comes across a header called 'want_header', it + * will call got_header passing the header field value. */ +extern const char *want_header; +typedef void (*got_header_fn)(char *value); +extern got_header_fn got_header; + +/* Send string to child; ne_sock_fullwrite with debugging. */ +ssize_t server_send(ne_socket *sock, const char *data, size_t len); + +/* Utility macro: send given string down socket. */ +#define SEND_STRING(sock, str) server_send((sock), (str), strlen((str))) + +/* Utility function: discard request. Sets context on error. */ +int discard_request(ne_socket *sock); + +/* Utility function: discard request body. Sets context on error. */ +int discard_body(ne_socket *sock); + +struct serve_file_args { + const char *fname; + const char *headers; + int chunks; +}; + +/* Utility function: callback for spawn_server: pass pointer to + * serve_file_args as userdata, and args->fname is served as a 200 + * request. If args->headers is non-NULL, it must be a set of + * CRLF-terminated lines which is added in to the response headers. + * If args->chunks is non-zero, the file is delivered using chunks of + * that size. */ +int serve_file(ne_socket *sock, void *ud); + +/* set to value of C-L header by discard_request. */ +extern int clength; + +/* Sleep for a short time. */ +void minisleep(void); + +#endif /* CHILD_H */ diff --git a/test/common/run.sh b/test/common/run.sh new file mode 100755 index 0000000..01ed5a9 --- /dev/null +++ b/test/common/run.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +rm -f debug.log +rm -f child.log + +# for shared builds. +LD_LIBRARY_PATH=../src/.libs:$LD_LIBRARY_PATH +export LD_LIBRARY_PATH + +for f in $*; do + if ./$f; then + : + else + echo FAILURE + exit 1 + fi +done + +exit 0 diff --git a/test/common/tests.c b/test/common/tests.c new file mode 100644 index 0000000..2ff6b5f --- /dev/null +++ b/test/common/tests.c @@ -0,0 +1,318 @@ +/* + Stupidly simple test framework + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <sys/signal.h> + +#include <stdio.h> +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +#include "ne_utils.h" +#include "ne_socket.h" + +#include "tests.h" +#include "child.h" + +char test_context[BUFSIZ]; +int have_context = 0; + +static FILE *child_debug, *debug; + +char **test_argv; +int test_argc; + +const char *test_suite; +int test_num; + +/* statistics for all tests so far */ +static int passes = 0, fails = 0, skipped = 0, warnings = 0; + +/* per-test globals: */ +static int warned, aborted = 0; +static const char *test_name; /* current test name */ + +static int use_colour = 0; + +/* resource for ANSI escape codes: + * http://www.isthe.com/chongo/tech/comp/ansi_escapes.html */ +#define COL(x) do { if (use_colour) printf("\033[" x "m"); } while (0) + +#define NOCOL COL("00") + +void t_context(const char *context, ...) +{ + va_list ap; + va_start(ap, context); + ne_vsnprintf(test_context, BUFSIZ, context, ap); + va_end(ap); + have_context = 1; +} + +void t_warning(const char *str, ...) +{ + va_list ap; + COL("43;01"); printf("WARNING:"); NOCOL; + putchar(' '); + va_start(ap, str); + vprintf(str, ap); + va_end(ap); + warnings++; + warned++; + putchar('\n'); +} + +#define TEST_DEBUG \ +(NE_DBG_HTTP | NE_DBG_SOCKET | NE_DBG_HTTPBODY | NE_DBG_HTTPAUTH | \ + NE_DBG_LOCKS | NE_DBG_XMLPARSE | NE_DBG_XML | NE_DBG_SSL) + +#define W(m) write(0, m, strlen(m)) + +static void child_segv(int signo) +{ + signal(SIGSEGV, SIG_DFL); + W("(possible segfault in child, not dumping core)\n"); + exit(-1); +} + +static void segv(int signo) +{ + signal(SIGSEGV, SIG_DFL); + if (use_colour) { + W("\033[41;37;01m"); + } + W("FAILED - segmentation fault."); + if (use_colour) { + W("\033[00m"); + } + W("\n"); + fflush(debug); + reap_server(); + kill(getpid(), SIGSEGV); +} + +void in_child(void) +{ + ne_debug_init(child_debug, TEST_DEBUG); + NE_DEBUG(TEST_DEBUG, "**** Child forked for test %s ****\n", test_name); + signal(SIGSEGV, child_segv); +} + +int main(int argc, char *argv[]) +{ + int n; + static const char dots[] = "......................"; + + /* get basename(argv[0]) */ + test_suite = strrchr(argv[0], '/'); + if (test_suite == NULL) { + test_suite = argv[0]; + } else { + test_suite++; + } + +#if defined(HAVE_ISATTY) && defined(STDOUT_FILENO) + if (isatty(STDOUT_FILENO)) { + use_colour = 1; + } +#endif + + test_argc = argc; + test_argv = argv; + + debug = fopen("debug.log", "a"); + if (debug == NULL) { + fprintf(stderr, "%s: Could not open debug.log: %s\n", test_suite, + strerror(errno)); + return -1; + } + child_debug = fopen("child.log", "a"); + if (child_debug == NULL) { + fprintf(stderr, "%s: Could not open child.log: %s\n", test_suite, + strerror(errno)); + fclose(debug); + return -1; + } + + if (tests[0].fn == NULL) { + printf("-> no tests found in `%s'\n", test_suite); + return -1; + } + + /* install special SEGV handler. */ + signal(SIGSEGV, segv); + + /* test the "no-debugging" mode of ne_debug. */ + ne_debug_init(NULL, 0); + NE_DEBUG(TEST_DEBUG, "This message should go to /dev/null"); + + /* enable debugging for real. */ + ne_debug_init(debug, TEST_DEBUG); + NE_DEBUG(TEST_DEBUG | NE_DBG_FLUSH, "Version string: %s\n", + ne_version_string()); + + /* another silly test. */ + NE_DEBUG(0, "This message should also go to /dev/null"); + + if (ne_sock_init()) { + COL("43;01"); printf("WARNING:"); NOCOL; + printf(" Socket library initalization failed.\n"); + } + + printf("-> running `%s':\n", test_suite); + + for (n = 0; !aborted && tests[n].fn != NULL; n++) { + int result; +#ifdef NEON_MEMLEAK + size_t allocated = ne_alloc_used; +#endif + + test_name = tests[n].name; + printf("%2d. %s%.*s ", n, test_name, + (int) (strlen(dots) - strlen(test_name)), dots); + have_context = 0; + test_num = n; + warned = 0; + fflush(stdout); + NE_DEBUG(TEST_DEBUG, "******* Running test %d: %s ********\n", + n, test_name); + + /* run the test. */ + result = tests[n].fn(); + +#ifdef NEON_MEMLEAK + /* issue warnings for memory leaks, if requested */ + if ((tests[n].flags & T_CHECK_LEAKS) && result == OK && + ne_alloc_used > allocated) { + t_context("memory leak of %" NE_FMT_SIZE_T " bytes", + ne_alloc_used - allocated); + ne_alloc_dump(debug); + result = FAIL; + } +#endif + + if (tests[n].flags & T_EXPECT_FAIL) { + if (result == OK) { + t_context("test passed but expected failure"); + result = FAIL; + } else if (result == FAIL) + result = OK; + } + + /* align the result column if we've had warnings. */ + if (warned) { + printf(" %s ", dots); + } + + switch (result) { + case OK: + COL("32"); printf("pass"); NOCOL; + if (warned) { + printf(" (with %d warning%s)", warned, (warned > 1)?"s":""); + } + putchar('\n'); + passes++; + break; + case FAILHARD: + aborted = 1; + /* fall-through */ + case FAIL: + COL("41;37;01"); printf("FAIL"); NOCOL; + if (have_context) { + printf(" (%s)", test_context); + } + putchar('\n'); + fails++; + break; + case SKIPREST: + aborted = 1; + /* fall-through */ + case SKIP: + COL("44"); printf("SKIPPED"); NOCOL; + if (have_context) { + printf(" (%s)", test_context); + } + putchar('\n'); + skipped++; + break; + default: + COL("41;37;01"); printf("OOPS"); NOCOL; + printf(" unexpected test result `%d'\n", result); + break; + } + + reap_server(); + } + + /* discount skipped tests */ + if (skipped) { + printf("-> %d %s.\n", skipped, + skipped==1?"test was skipped":"tests were skipped"); + n -= skipped; + if (passes + fails != n) { + printf("-> ARGH! Number of test results does not match " + "number of tests.\n" + "-> ARGH! Test Results are INRELIABLE.\n"); + } + } + /* print the summary. */ + if (skipped && n == 0) { + printf("<- all tests skipped for `%s'.\n", test_suite); + } else { + printf("<- summary for `%s': " + "of %d tests run: %d passed, %d failed. %.1f%%\n", + test_suite, n, passes, fails, 100*(float)passes/n); + if (warnings) { + printf("-> %d warning%s issued.\n", warnings, + warnings==1?" was":"s were"); + } + } + + if (fclose(debug)) { + fprintf(stderr, "Error closing debug.log: %s\n", strerror(errno)); + fails = 1; + } + + if (fclose(child_debug)) { + fprintf(stderr, "Error closing child.log: %s\n", strerror(errno)); + fails = 1; + } + + ne_sock_exit(); + + return fails; +} + diff --git a/test/common/tests.h b/test/common/tests.h new file mode 100644 index 0000000..72e8275 --- /dev/null +++ b/test/common/tests.h @@ -0,0 +1,125 @@ +/* + Stupidly simple test framework + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef TESTS_H +#define TESTS_H 1 + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <stdio.h> + +#define OK 0 +#define FAIL 1 +#define FAILHARD 2 /* fail and skip all succeeding tests in this suite. */ +#define SKIP 3 /* test was skipped because precondition was not met */ +#define SKIPREST 4 /* skipped, and skip all succeeding tests in suite */ + +/* A test function. Must return any of OK, FAIL, FAILHARD, SKIP, or + * SKIPREST. May call t_warning() any number of times. If not + * returning OK, optionally call t_context to provide an error + * message. */ +typedef int (*test_func)(void); + +typedef struct { + test_func fn; /* the function to test. */ + const char *name; /* the name of the test. */ + int flags; +} ne_test; + +/* possible values for flags: */ +#define T_CHECK_LEAKS (1) /* check for memory leaks */ +#define T_EXPECT_FAIL (2) /* expect failure */ + +/* array of tests to run: must be defined by each test suite. */ +extern ne_test tests[]; + +/* define a test function which has the same name as the function, + * and does check for memory leaks. */ +#define T(fn) { fn, #fn, T_CHECK_LEAKS } +/* define a test function which is expected to return FAIL. */ +#define T_XFAIL(fn) { fn, #fn, T_EXPECT_FAIL | T_CHECK_LEAKS } +/* define a test function which isn't checked for memory leaks. */ +#define T_LEAKY(fn) { fn, #fn, 0 } + +/* current test number */ +extern int test_num; + +/* name of test suite */ +extern const char *test_suite; + +/* Provide result context message. */ +void t_context(const char *ctx, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif /* __GNUC__ */ + ; + +extern char test_context[]; + +/* the command-line arguments passed in to the test suite: */ +extern char **test_argv; +extern int test_argc; + +/* child process should call this. */ +void in_child(void); + +/* issue a warning. */ +void t_warning(const char *str, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif /* __GNUC__ */ +; + +/* Macros for easily writing is-not-zero comparison tests; the ON* + * macros fail the function if a comparison is not zero. + * + * ONV(x,vs) takes a comparison X, and a printf varargs list for + * the failure message. + * e.g. ONV(strcmp(bar, "foo"), ("bar was %s not 'foo'", bar)) + * + * ON(x) takes a comparison X, and uses the line number for the failure + * message. e.g. ONV(strcmp(bar, "foo")) + * + * ONN(n, x) takes a comparison X, and a flat string failure message. + * e.g. ONN("foo was wrong", strcmp(bar, "foo")) */ + +#define ONV(x,vs) do { if ((x)) { t_context vs; return FAIL; } } while (0) +#define ON(x) ONV((x), ("line %d", __LINE__ )) +#define ONN(n,x) ONV((x), (n)) + +/* ONCMP(exp, act, name): 'exp' is the expected string, 'act' is the + * actual string for some field 'name'. Succeeds if strcmp(exp,act) + * == 0 or both are NULL. */ +#define ONCMP(exp, act, ctx, name) do { \ +ONV(exp && !act, ("%s: " name " was NULL, expected non-NULL", ctx)); \ +ONV(!exp && act, ("%s: " name " was non-NULL, expected NULL", ctx)); \ +ONV(exp && strcmp(exp, act), ("%s: " name " was %s not %s", ctx, exp, act)); \ +} while (0) + +/* return immediately with result of test 'x' if it fails. */ +#define CALL(x) do { int t_ret = (x); if (t_ret != OK) return t_ret; } while (0) + +/* PRECOND: skip current test if condition 'x' is not true. */ +#define PRECOND(x) do { if (!(x)) { return SKIP; } } while (0) + +#endif /* TESTS_H */ diff --git a/test/compress.c b/test/compress.c new file mode 100644 index 0000000..720ebe0 --- /dev/null +++ b/test/compress.c @@ -0,0 +1,216 @@ +/* + tests for compressed response handling. + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <fcntl.h> + +#include "ne_compress.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int failed; + +static char *newsfn = "../NEWS"; + +struct body { + const char *str; + size_t len; +}; + +static int init(void) +{ + if (test_argc > 1) { + newsfn = ne_concat(test_argv[1], "/../NEWS", NULL); + } + return lookup_localhost(); +} + +static void reader(void *ud, const char *block, size_t len) +{ + struct body *b = ud; + + if (failed || len > b->len || memcmp(b->str, block, len) != 0) { + failed = 1; + } else { + b->str += len; + b->len -= len; + } +} + +static int file2buf(int fd, ne_buffer *buf) +{ + char buffer[BUFSIZ]; + ssize_t n; + + while ((n = read(fd, buffer, BUFSIZ)) > 0) { + ne_buffer_append(buf, buffer, n); + } + + return 0; +} + +static int do_fetch(const char *realfn, const char *gzipfn, + int chunked, int expect_fail) +{ + ne_session *sess; + ne_request *req; + int fd; + ne_buffer *buf = ne_buffer_create(); + struct serve_file_args sfargs; + ne_decompress *dc; + struct body body; + + fd = open(realfn, O_RDONLY); + ONN("failed to open file", fd < 0); + file2buf(fd, buf); + (void) close(fd); + + body.str = buf->data; + body.len = buf->used - 1; + + failed = 0; + + if (gzipfn) { + sfargs.fname = gzipfn; + sfargs.headers = "Content-Encoding: gzip\r\n"; + } else { + sfargs.fname = realfn; + sfargs.headers = NULL; + } + sfargs.chunks = chunked; + + CALL(make_session(&sess, serve_file, &sfargs)); + + req = ne_request_create(sess, "GET", "/"); + dc = ne_decompress_reader(req, ne_accept_2xx, reader, &body); + + ONREQ(ne_request_dispatch(req)); + + ONN("file not served", ne_get_status(req)->code != 200); + + ONN("decompress succeeded", expect_fail && !ne_decompress_destroy(dc)); + ONV(!expect_fail && ne_decompress_destroy(dc), + ("decompress failed: %s", ne_get_error(sess))); + + ne_request_destroy(req); + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + CALL(await_server()); + + ONN("inflated response compare", failed); + if (!expect_fail) + ONN("inflated response truncated", body.len != 0); + + return OK; +} + +static int fetch(const char *realfn, const char *gzipfn, int chunked) +{ + return do_fetch(realfn, gzipfn, chunked, 0); +} + +/* Test the no-compression case. */ +static int not_compressed(void) +{ + return fetch(newsfn, NULL, 0); +} + +static int simple(void) +{ + return fetch(newsfn, "file1.gz", 0); +} + +/* file1.gz has an embedded filename. */ +static int withname(void) +{ + return fetch(newsfn, "file2.gz", 0); +} + +/* deliver various different sizes of chunks: tests the various + * decoding cases. */ +static int chunked_1b_wn(void) +{ + return fetch(newsfn, "file2.gz", 1); +} + +static int chunked_1b(void) +{ + return fetch(newsfn, "file1.gz", 1); +} + +static int chunked_12b(void) +{ + return fetch(newsfn, "file2.gz", 12); +} + +static int chunked_20b(void) +{ + return fetch(newsfn, "file2.gz", 20); +} + +static int chunked_10b(void) +{ + return fetch(newsfn, "file1.gz", 10); +} + +static int chunked_10b_wn(void) +{ + return fetch(newsfn, "file2.gz", 10); +} + +static int fail_trailing(void) +{ + return do_fetch(newsfn, "trailing.gz", 0, 1); +} + +static int fail_truncate(void) +{ + return do_fetch(newsfn, "truncated.gz", 0, 1); +} + +static int fail_bad_csum(void) +{ + return do_fetch(newsfn, "badcsum.gz", 0, 1); +} + +ne_test tests[] = { + T_LEAKY(init), + T(not_compressed), + T(simple), + T(withname), + T(fail_trailing), + T(fail_bad_csum), + T(fail_truncate), + T(chunked_1b), + T(chunked_1b_wn), + T(chunked_12b), + T(chunked_20b), + T(chunked_10b), + T(chunked_10b_wn), + T(NULL) +}; diff --git a/test/cookies.c b/test/cookies.c new file mode 100644 index 0000000..706ca7e --- /dev/null +++ b/test/cookies.c @@ -0,0 +1,140 @@ +/* + Test for cookies interface (ne_cookies.h) + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" +#include "ne_cookies.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int serve_cookie(ne_socket *sock, void *ud) +{ + const char *hdr = ud; + char buf[BUFSIZ]; + + CALL(discard_request(sock)); + + ne_snprintf(buf, BUFSIZ, "HTTP/1.0 200 Okey Dokey\r\n" + "Connection: close\r\n" "%s\r\n\r\n", hdr); + + SEND_STRING(sock, buf); + + return OK; +} + +static int fetch_cookie(const char *hdr, const char *path, + ne_cookie_cache *jar) +{ + ne_session *sess; + + CALL(make_session(&sess, serve_cookie, (void *)hdr)); + + ne_cookie_register(sess, jar); + + CALL(any_request(sess, path)); + + ne_session_destroy(sess); + CALL(await_server()); + + return OK; +} + +static int parsing(void) +{ + static const struct { + const char *hdr, *name, *value; + } cookies[] = { + { "Set-Cookie: alpha=bar", "alpha", "bar" }, + { "Set-Cookie2: alpha=bar", "alpha", "bar" }, + { "Set-Cookie: beta = bar", "beta", "bar" }, + { "Set-Cookie: delta = bar; norman=fish", "delta", "bar" }, + { NULL, NULL, NULL } + }; + int n; + + for (n = 0; cookies[n].hdr != NULL; n++) { + ne_cookie_cache jar = {0}; + ne_cookie *ck; + + CALL(fetch_cookie(cookies[n].hdr, "/foo", &jar)); + + ck = jar.cookies; + ONV(ck == NULL, ("%d: cookie jar was empty!", n)); + + ONV(strcmp(ck->name, cookies[n].name) || + strcmp(ck->value, cookies[n].value), + ("%d: was [%s]=[%s]!", n, ck->name, ck->value)); + } + + return OK; +} + +static int rejects(void) +{ + static const struct { + const char *hdr, *path; + } resps[] = { + /* names prefixed with $ are illegal */ + { "Set-Cookie2: $foo=bar, Version=1", "/foo" }, +#define FOOBAR "Set-Cookie2: foo=bar, Version=1" + /* Path is not prefix of Request-URI */ + { FOOBAR ", Path=/bar", "/foo" }, + /* Domain must have embedded dots. */ + { FOOBAR ", Domain=fish", "/foo" }, + /* Domain must domain-match request-host */ + { FOOBAR ", Domain=other.host.com", "/foo" }, + /* Port not named in Port list */ + { FOOBAR ", Port=\"12\"", "/foo" }, + { NULL, NULL } + }; + int n; + + for (n = 0; resps[n].hdr != NULL; n++) { + ne_cookie_cache jar = {0}; + + CALL(fetch_cookie(resps[n].hdr, resps[n].path, &jar)); + + ONV(jar.cookies != NULL, + ("cookie was returned for `%s'", resps[n].hdr)); + } + + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(parsing), + T(rejects), + T(NULL) +}; + diff --git a/test/expired.pem b/test/expired.pem new file mode 100644 index 0000000..0062cc8 --- /dev/null +++ b/test/expired.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDODCCAuKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBoTELMAkGA1UEBhMCR0Ix +FzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlkZ2UxGjAY +BgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFBIERlcHQx +EjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPbmVvbkB3ZWJkYXYu +b3JnMB4XDTAyMDEyMTIwMzkwNFoXDTAyMDEzMTIwMzkwNFowgaExCzAJBgNVBAYT +AkdCMRcwFQYDVQQIEw5DYW1icmlkZ2VzaGlyZTESMBAGA1UEBxMJQ2FtYnJpZGdl +MRowGAYDVQQKExFOZW9uIEhhY2tlcnMgTHRkLjEVMBMGA1UECxMMTmVvbiBRQSBE +ZXB0MRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgkqhkiG9w0BCQEWD25lb25Ad2Vi +ZGF2Lm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDzRU5sZ8+CWQPvPkqJw9Kl +oEgT2FqzZR9RT/qbJuRBmRphiRr0g7JOh5Mr7LXaKShedFLhGidutyKKwIZJnRht +AgMBAAGjggEBMIH+MB0GA1UdDgQWBBRFA3ktzHSuD9uB6mJOWoElmOtknzCBzgYD +VR0jBIHGMIHDgBRFA3ktzHSuD9uB6mJOWoElmOtkn6GBp6SBpDCBoTELMAkGA1UE +BhMCR0IxFzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlk +Z2UxGjAYBgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFB +IERlcHQxEjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPbmVvbkB3 +ZWJkYXYub3JnggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADQQBDSFbe +9EjP+IyZ4vhJSk66gLSN8CnafoGm5JHpNOHy5gWLh7j0a/dxWRd4gpoBYBB6Y9rO +YV6Eq3njdj0gu+NN +-----END CERTIFICATE----- diff --git a/test/htdocs/plain b/test/htdocs/plain new file mode 100644 index 0000000..bce1946 --- /dev/null +++ b/test/htdocs/plain @@ -0,0 +1 @@ +Test file. diff --git a/test/lock.c b/test/lock.c new file mode 100644 index 0000000..5fb80d4 --- /dev/null +++ b/test/lock.c @@ -0,0 +1,540 @@ +/* + lock tests + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_locks.h" +#include "ne_socket.h" +#include "ne_basic.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +/* returns an activelock XML element. */ +static char *activelock(enum ne_lock_scope scope, + int depth, + const char *owner, + long timeout, + const char *token_href) +{ + static char buf[BUFSIZ]; + + ne_snprintf(buf, BUFSIZ, + "<D:activelock>\n" + "<D:locktype><D:write/></D:locktype>\n" + "<D:lockscope><D:%s/></D:lockscope>\n" + "<D:depth>%d</D:depth>\n" + "<D:owner>%s</D:owner>\n" + "<D:timeout>Second-%ld</D:timeout>\n" + "<D:locktoken><D:href>%s</D:href></D:locktoken>\n" + "</D:activelock>", + scope==ne_lockscope_exclusive?"exclusive":"shared", + depth, owner, timeout, token_href); + + return buf; +} + +/* return body of LOCK response for given lock. */ +static char *lock_response(enum ne_lock_scope scope, + int depth, + const char *owner, + long timeout, + const char *token_href) +{ + static char buf[BUFSIZ]; + sprintf(buf, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<D:prop xmlns:D=\"DAV:\">" + "<D:lockdiscovery>%s</D:lockdiscovery></D:prop>\n", + activelock(scope, depth, owner, timeout, token_href)); + return buf; +} + +/* return body of LOCK response where response gives multiple + * activelocks (i.e. shared locks). */ +static char *multi_lock_response(struct ne_lock **locks) +{ + ne_buffer *buf = ne_buffer_create(); + int n; + + ne_buffer_zappend(buf, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<D:prop xmlns:D=\"DAV:\">" + "<D:lockdiscovery>"); + + for (n = 0; locks[n] != NULL; n++) { + char *lk = activelock(locks[n]->scope, locks[n]->depth, + locks[n]->owner, locks[n]->timeout, + locks[n]->token); + ne_buffer_zappend(buf, lk); + } + + ne_buffer_zappend(buf, "</D:lockdiscovery></D:prop>"); + return ne_buffer_finish(buf); +} + +static char *discover_response(const char *href, const struct ne_lock *lk) +{ + static char buf[BUFSIZ]; + ne_snprintf(buf, BUFSIZ, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<D:multistatus xmlns:D='DAV:'>\n" + "<D:response><D:href>%s</D:href><D:propstat>\n" + "<D:prop><D:lockdiscovery>%s</D:lockdiscovery></D:prop>\n" + "<D:status>HTTP/1.1 200 OK</D:status></D:propstat>\n" + "</D:response></D:multistatus>\n", + href, activelock(lk->scope, lk->depth, lk->owner, + 7200, lk->token)); + return buf; +} + +static struct ne_lock *make_lock(const char *path, const char *token, + enum ne_lock_scope scope, int depth) +{ + struct ne_lock *lock = ne_calloc(sizeof *lock); + + lock->token = ne_strdup(token); + lock->scope = scope; + lock->depth = depth; + lock->uri.host = ne_strdup("localhost"); + lock->uri.scheme = ne_strdup("http"); + lock->uri.path = ne_strdup(path); + lock->uri.port = 7777; + + return lock; +} + +/* Tests for lock store handling. */ +static int store_single(void) +{ + ne_lock_store *store = ne_lockstore_create(); + struct ne_lock *lk = make_lock("/foo", "blah", ne_lockscope_exclusive, 0); + struct ne_lock *lk2; + + ONN("create failed", store == NULL); + + ONN("new lock store not empty", ne_lockstore_first(store) != NULL); + + ne_lockstore_add(store, lk); + + ONN("lock not found in store", ne_lockstore_first(store) != lk); + + ONN(">1 locks in store?", ne_lockstore_next(store) != NULL); + + lk2 = ne_lockstore_findbyuri(store, &lk->uri); + + ONN("lock not found by URI", lk2 == NULL); + ONN("other lock found by URI", lk2 != lk); + + ne_lockstore_remove(store, lk); + + ONN("store not empty after removing lock", + ne_lockstore_first(store) != NULL); + + ONN("lock still found after removing lock", + ne_lockstore_findbyuri(store, &lk->uri) != NULL); + + ne_lockstore_destroy(store); + ne_lock_destroy(lk); + + return OK; +} + +static int store_several(void) +{ + ne_lock_store *store = ne_lockstore_create(); + struct ne_lock *lk = make_lock("/foo", "blah", ne_lockscope_exclusive, 0); + struct ne_lock *lk2 = make_lock("/bar", "blee", ne_lockscope_exclusive, 0); + struct ne_lock *lf, *lf2; + + ONN("create failed", store == NULL); + + ne_lockstore_add(store, lk); + ne_lockstore_add(store, lk2); + + lf = ne_lockstore_first(store); + ONN("lock store empty", lf == NULL); + lf2 = ne_lockstore_next(store); + ONN("lock store >2 locks", ne_lockstore_next(store) != NULL); + + /* guarantee that _first, _next returned either of the + * combinations: (lf, lf2) or (lf2, lf) */ + ONN("found wrong locks", ((lf != lk && lf != lk2) || + (lf2 != lk && lf2 != lk2) || + (lf == lf2))); + + ONN("first find failed", + ne_lockstore_findbyuri(store, &lk->uri) != lk); + ONN("second find failed", + ne_lockstore_findbyuri(store, &lk2->uri) != lk2); + + ne_lockstore_remove(store, lk); + ne_lock_destroy(lk); + + ONN("remove left stray lock?", ne_lockstore_first(store) != lk2); + + ONN("remove left >1 lock?", ne_lockstore_next(store) != NULL); + + ne_lockstore_remove(store, lk2); + ne_lock_destroy(lk2); + + ONN("store not empty after removing all locks", + ne_lockstore_first(store) != NULL); + + ne_lockstore_destroy(store); + + return OK; +} + +/* regression test for <= 0.18.2, where timeout field was not parsed correctly. */ +static int lock_timeout(void) +{ + ne_session *sess; + char *resp, *rbody = lock_response(ne_lockscope_exclusive, 0, "me", + 6500, "opaquelocktoken:foo"); + struct ne_lock *lock = ne_lock_create(); + + resp = ne_concat("HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n" + "Lock-Token: <opaquelocktoken:foo>" EOL + "Connection: close\r\n\r\n", rbody, NULL); + + CALL(make_session(&sess, single_serve_string, resp)); + ne_free(resp); + + ne_fill_server_uri(sess, &lock->uri); + lock->uri.path = ne_strdup("/foo"); + lock->timeout = 5; + + ONREQ(ne_lock(sess, lock)); + + ONN("lock timeout ignored in response", + lock->timeout != 6500); + + ne_session_destroy(sess); + ne_lock_destroy(lock); + + CALL(await_server()); + + return OK; +} + +static int verify_if; +static const char *verify_if_expect; + +static void got_if_header(char *value) +{ + verify_if = !strcmp(verify_if_expect, value); + NE_DEBUG(NE_DBG_HTTP, "Verified If header, %d: got [%s] expected [%s]\n", + verify_if, value, verify_if_expect); +} + +/* Server callback which checks that an If: header is recevied. */ +static int serve_verify_if(ne_socket *sock, void *userdata) +{ + /* tell us about If headers in the request. */ + want_header = "If"; + got_header = got_if_header; + verify_if_expect = userdata; + + verify_if = 0; + + CALL(discard_request(sock)); + + if (verify_if) { + ON(SEND_STRING(sock, "HTTP/1.1 200 OK" EOL)); + } else { + ON(SEND_STRING(sock, "HTTP/1.1 403 Wrong If Header" EOL)); + } + + ON(SEND_STRING(sock, "Connection: close" EOL EOL)); + + return OK; +} + +/* Make a request which will require a lock. */ +static int do_request(ne_session *sess, const char *path, int depth, + int modparent) +{ + ne_request *req = ne_request_create(sess, "RANDOM", path); + + if (depth > 0) { + ne_add_depth_header(req, depth); + } + + if (depth != -1) + ne_lock_using_resource(req, path, depth); + if (modparent) + ne_lock_using_parent(req, path); + + ONREQ(ne_request_dispatch(req)); + + ONV(ne_get_status(req)->code != 200, + ("request failed: %s", ne_get_error(sess))); + + ne_request_destroy(req); + + return OK; +} + +/* Tests If: header submission, for a lock of depth 'lockdepth' at + * 'lockpath', with a request to 'reqpath' which Depth header of + * 'reqdepth'. If modparent is non-zero; the request is flagged to + * modify the parent resource too. */ +static int submit_test(const char *lockpath, int lockdepth, + const char *reqpath, int reqdepth, + int modparent) +{ + ne_lock_store *store = ne_lockstore_create(); + ne_session *sess; + struct ne_lock *lk = ne_lock_create(); + char *expect_if; + int ret; + + expect_if = ne_concat("<http://localhost:7777", lockpath, + "> (<somelocktoken>)", NULL); + CALL(make_session(&sess, serve_verify_if, expect_if)); + ne_free(expect_if); + + ne_fill_server_uri(sess, &lk->uri); + lk->uri.path = ne_strdup(lockpath); + lk->token = ne_strdup("somelocktoken"); + lk->depth = lockdepth; + + /* register the lock store, and add our lock for "/foo" to it. */ + ne_lockstore_register(store, sess); + ne_lockstore_add(store, lk); + + ret = do_request(sess, reqpath, reqdepth, modparent); + CALL(await_server()); + + ne_lockstore_destroy(store); + ne_session_destroy(sess); + + return ret; +} + +static int if_simple(void) +{ + return submit_test("/foo", 0, "/foo", 0, 0); +} + +static int if_under_infinite(void) +{ + return submit_test("/foo", NE_DEPTH_INFINITE, "/foo/bar", 0, 0); +} + +static int if_infinite_over(void) +{ + return submit_test("/foo/bar", 0, "/foo/", NE_DEPTH_INFINITE, 0); +} + +static int if_child(void) +{ + return submit_test("/foo/", 0, "/foo/bar", 0, 1); +} + +/* this is a special test, where the PARENT resource of "/foo/bar" is + * modified, but NOT "/foo/bar" itself. An UNLOCK request on a + * lock-null resource can do this; see ne_unlock() for the comment. + * Regression test for neon <= 0.19.3, which didn't handle this + * correctly. */ +static int if_covered_child(void) +{ + return submit_test("/", NE_DEPTH_INFINITE, "/foo/bar", -1, 1); +} + +static int serve_discovery(ne_socket *sock, void *userdata) +{ + char buf[BUFSIZ], *resp = userdata; + + ON(discard_request(sock)); + ONN("no PROPFIND body", clength == 0); + ON(ne_sock_read(sock, buf, clength) < 0); + ON(SEND_STRING(sock, "HTTP/1.0 207 OK" EOL + "Connection: close" EOL EOL)); + ON(SEND_STRING(sock, resp)); + return OK; +} + +struct result_args { + struct ne_lock *lock; + int result; +}; + +static int lock_compare(const char *ctx, + const struct ne_lock *a, const struct ne_lock *b) +{ + ONV(!a->uri.host || !a->uri.scheme || !a->uri.path, + ("URI structure incomplete in %s", ctx)); + ONV(ne_uri_cmp(&a->uri, &b->uri) != 0, + ("URI comparison failed for %s: %s not %s", ctx, + ne_uri_unparse(&a->uri), ne_uri_unparse(&b->uri))); + ONV(a->depth != b->depth, + ("%s depth was %d not %d", ctx, a->depth, b->depth)); + ONV(a->scope != b->scope, + ("%s scope was %d not %d", ctx, a->scope, b->scope)); + ONV(a->type != b->type, + ("%s type was %d not %d", ctx, a->type, b->type)); + return OK; +} + +static void discover_result(void *userdata, const struct ne_lock *lk, + const char *path, const ne_status *st) +{ + struct result_args *args = userdata; + args->result = lock_compare("discovered lock", lk, args->lock); +} + +static int discover(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + char *response; + int ret; + struct result_args args; + + args.lock = ne_lock_create(); + + args.lock->owner = ne_strdup("someowner"); + args.lock->token = ne_strdup("sometoken"); + + /* default */ + args.result = FAIL; + t_context("results callback never invoked"); + + ne_fill_server_uri(sess, &args.lock->uri); + args.lock->uri.path = ne_strdup("/lockme"); + + response = discover_response("/lockme", args.lock); + CALL(spawn_server(7777, serve_discovery, response)); + + ret = ne_lock_discover(sess, "/lockme", discover_result, &args); + CALL(await_server()); + ONREQ(ret); + + ne_lock_destroy(args.lock); + ne_session_destroy(sess); + + return args.result; +} + +/* Check that the token for the response header */ +static int lock_shared(void) +{ + ne_session *sess; + char *resp, *rbody; + struct ne_lock *lock, *resplocks[3]; + +#define FILLK(l, s) do { \ +(l)->token = strdup("opaquelocktoken:" s); \ +(l)->owner = strdup("owner " s); \ +(l)->uri.path = strdup("/" s); (l)->uri.host = strdup("localhost"); \ +(l)->uri.scheme = strdup("http"); (l)->uri.port = 7777; } while (0) + + resplocks[0] = ne_lock_create(); + resplocks[1] = ne_lock_create(); + resplocks[2] = NULL; + FILLK(resplocks[0], "alpha"); + FILLK(resplocks[1], "beta"); + resplocks[0]->timeout = 100; + resplocks[1]->timeout = 200; + + rbody = multi_lock_response(resplocks); + + resp = ne_concat("HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n" + "Lock-Token: <opaquelocktoken:beta>" EOL + "Connection: close\r\n\r\n", rbody, NULL); + ne_free(rbody); + + CALL(make_session(&sess, single_serve_string, resp)); + ne_free(resp); + + lock = ne_lock_create(); + ne_fill_server_uri(sess, &lock->uri); + lock->uri.path = ne_strdup("/beta"); + + ONREQ(ne_lock(sess, lock)); + + CALL(await_server()); + + CALL(lock_compare("returned lock", resplocks[1], lock)); + + ne_session_destroy(sess); + ne_lock_destroy(lock); + ne_lock_destroy(resplocks[0]); + ne_lock_destroy(resplocks[1]); + + return OK; +} + +static void dummy_discover(void *userdata, const struct ne_lock *lock, + const char *uri, const ne_status *status) +{ +} + +/* This failed with neon 0.23.x and earlier when memory leak detection + * is enabled. */ +static int fail_discover(void) +{ + ne_session *sess; + int ret; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.0 207 OK\r\n" "Connection: close\r\n" "\r\n" + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<D:multistatus xmlns:D='DAV:'>\n" + "<D:response><D:href>/foo/bar</D:href><D:propstat>\n" + "</parse this, my friend>\n")); + + ret = ne_lock_discover(sess, "/foo", dummy_discover, NULL); + CALL(await_server()); + + ONN("discovery okay for response with invalid XML!?", ret != NE_ERROR); + + ne_session_destroy(sess); + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(store_single), + T(store_several), + T(if_simple), + T(if_under_infinite), + T(if_infinite_over), + T(if_child), + T(if_covered_child), + T(lock_timeout), + T(lock_shared), + T(discover), + T(fail_discover), + T(NULL) +}; + diff --git a/test/makekeys.sh b/test/makekeys.sh new file mode 100755 index 0000000..59a1e51 --- /dev/null +++ b/test/makekeys.sh @@ -0,0 +1,151 @@ +#!/bin/sh +# Helper script to create CA and server certificates. + +srcdir=${1-.} + +OPENSSL=${OPENSSL-openssl} +CONF=${srcdir}/openssl.conf +REQ="${OPENSSL} req -config ${CONF}" +CA="${OPENSSL} ca -config ${CONF} -batch" +# MKCERT makes a self-signed cert +MKCERT="${REQ} -x509 -new -days 900" + +REQDN=reqDN +export REQDN + +set -ex + +mkdir ca +touch ca/index.txt +echo 01 > ca/serial + +${OPENSSL} genrsa -rand ${srcdir}/../configure > ca/key.pem +${OPENSSL} genrsa -rand ${srcdir}/../configure > client.key + +${MKCERT} -key ca/key.pem -out ca/cert.pem <<EOF +US +California +Oakland +Neosign +Random Dept +nowhere.example.com +neon@webdav.org +EOF + +# Function to generate appropriate output for `openssl req'. +csr_fields() { +CN=${2-"localhost"} +OU=${1-"Neon QA Dept"} +Org=${3-"Neon Hackers Ltd"} +Locality=${4-"Cambridge"} +State=${5-"Cambridgeshire"} +cat <<EOF +GB +${State} +${Locality} +${Org} +${OU} +${CN} +neon@webdav.org +. +. +EOF +} + +csr_fields | ${REQ} -new -key ${srcdir}/server.key -out server.csr + +csr_fields "Upper Case Dept" lOcALhost | \ +${REQ} -new -key ${srcdir}/server.key -out caseless.csr + +csr_fields "Use AltName Dept" nowhere.example.com | \ +${REQ} -new -key ${srcdir}/server.key -out altname.csr + +csr_fields "Two AltName Dept" nowhere.example.com | \ +${REQ} -new -key ${srcdir}/server.key -out altname2.csr + +csr_fields "Third AltName Dept" nowhere.example.com | \ +${REQ} -new -key ${srcdir}/server.key -out altname3.csr + +csr_fields "Fourth AltName Dept" localhost | \ +${REQ} -new -key ${srcdir}/server.key -out altname4.csr + +csr_fields "Self-Signed" | \ +${MKCERT} -key ${srcdir}/server.key -out ssigned.pem + +csr_fields "Bad Hostname Department" nohost.example.com | \ +${MKCERT} -key ${srcdir}/server.key -out wrongcn.pem + +### produce a set of CA certs + +csr_fields "First Random CA" "first.example.com" "CAs Ltd." Lincoln Lincolnshire | \ +${MKCERT} -key ${srcdir}/server.key -out ca1.pem + +csr_fields "Second Random CA" "second.example.com" "CAs Ltd." Falmouth Cornwall | \ +${MKCERT} -key ${srcdir}/server.key -out ca2.pem + +csr_fields "Third Random CA" "third.example.com" "CAs Ltd." Ipswich Suffolk | \ +${MKCERT} -key ${srcdir}/server.key -out ca3.pem + +csr_fields "Fourth Random CA" "fourth.example.com" "CAs Ltd." Norwich Norfolk | \ +${MKCERT} -key ${srcdir}/server.key -out ca4.pem + +cat ca[1234].pem > calist.pem + +# Only works with a Linuxy hostname command: continue without it, +# as appropriate tests are skipped if these fail. +hostname=`hostname -s 2>/dev/null` || true +domain=`hostname -d 2>/dev/null` || true +fqdn=`hostname -f 2>/dev/null` || true +if [ "x${hostname}.${domain}" = "x${fqdn}" ]; then + csr_fields "Wildcard Cert Dept" "*.${domain}" | \ + ${REQ} -new -key ${srcdir}/server.key -out wildcard.csr + ${CA} -days 900 -in wildcard.csr -out wildcard.cert +fi + +csr_fields "Neon Client Cert" ignored.example.com | \ +${REQ} -new -key client.key -out client.csr + +### requests using special DN. + +REQDN=reqDN.doubleCN +csr_fields "Double CN Dept" "nohost.example.com +localhost" | ${REQ} -new -key ${srcdir}/server.key -out twocn.csr + +REQDN=reqDN.CNfirst +echo localhost | ${REQ} -new -key ${srcdir}/server.key -out cnfirst.csr + +REQDN=reqDN.missingCN +echo GB | ${REQ} -new -key ${srcdir}/server.key -out missingcn.csr + +REQDN=reqDN.justEmail +echo blah@example.com | ${REQ} -new -key ${srcdir}/server.key -out justmail.csr + +### don't put ${REQ} invocations after here + +for f in server client twocn caseless cnfirst missingcn justmail; do + ${CA} -days 900 -in ${f}.csr -out ${f}.cert +done + +${CA} -extensions altExt -days 900 -in altname.csr -out altname.cert +${CA} -extensions altExt2 -days 900 -in altname2.csr -out altname2.cert +${CA} -extensions altExt3 -days 900 -in altname3.csr -out altname3.cert +${CA} -extensions altExt4 -days 900 -in altname4.csr -out altname4.cert + +# generate a PKCS12 cert from the client cert: -passOUT because it's the +# passphrase on the OUTPUT cert, confusing... +echo foobar | ${OPENSSL} pkcs12 -export -passout stdin \ + -name "Just A Neon Client Cert" \ + -in client.cert -inkey client.key -out client.p12 + +# generate a PKCS12 cert with no password +echo | ${OPENSSL} pkcs12 -export -passout stdin \ + -name "An Unencrypted Neon Client Cert" \ + -in client.cert -inkey client.key -out unclient.p12 + +# generate a PKCS12 cert with no friendly name +echo | ${OPENSSL} pkcs12 -export -passout stdin \ + -in client.cert -inkey client.key -out noclient.p12 + +### a file containing a complete chain + +cat ca/cert.pem server.cert > chain.pem diff --git a/test/notvalid.pem b/test/notvalid.pem new file mode 100644 index 0000000..42008b3 --- /dev/null +++ b/test/notvalid.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDODCCAuKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBoTELMAkGA1UEBhMCR0Ix +FzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlkZ2UxGjAY +BgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFBIERlcHQx +EjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPbmVvbkB3ZWJkYXYu +b3JnMB4XDTIzMTIyNzIwNDAyOVoXDTIzMTIyODIwNDAyOVowgaExCzAJBgNVBAYT +AkdCMRcwFQYDVQQIEw5DYW1icmlkZ2VzaGlyZTESMBAGA1UEBxMJQ2FtYnJpZGdl +MRowGAYDVQQKExFOZW9uIEhhY2tlcnMgTHRkLjEVMBMGA1UECxMMTmVvbiBRQSBE +ZXB0MRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgkqhkiG9w0BCQEWD25lb25Ad2Vi +ZGF2Lm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDzRU5sZ8+CWQPvPkqJw9Kl +oEgT2FqzZR9RT/qbJuRBmRphiRr0g7JOh5Mr7LXaKShedFLhGidutyKKwIZJnRht +AgMBAAGjggEBMIH+MB0GA1UdDgQWBBRFA3ktzHSuD9uB6mJOWoElmOtknzCBzgYD +VR0jBIHGMIHDgBRFA3ktzHSuD9uB6mJOWoElmOtkn6GBp6SBpDCBoTELMAkGA1UE +BhMCR0IxFzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlk +Z2UxGjAYBgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFB +IERlcHQxEjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPbmVvbkB3 +ZWJkYXYub3JnggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADQQA80TYV +2F4QLveuldmxGoIOq5hHGxCR6aVsdtm4PGY49R5/ObCAgdWw/JV/Tc448JAz5QvU +ahr1x9kA4Vo5NZ4q +-----END CERTIFICATE----- diff --git a/test/openssl.conf b/test/openssl.conf new file mode 100644 index 0000000..9180b49 --- /dev/null +++ b/test/openssl.conf @@ -0,0 +1,77 @@ +[ca] +default_ca = neonca + +[neonca] +dir = ./ca +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/cert.pem +serial = $dir/serial +private_key = $dir/key.pem +policy = policy_any +default_md = md5 +x509_extensions = issuedExt + +[policy_any] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = optional +emailAddress = optional + +[req] +distinguished_name = $ENV::REQDN +x509_extensions = caExt + +[caExt] +basicConstraints = CA:true + +[issuedExt] +basicConstraints = CA:false + +# subjectAltName extension sections +[altExt] +subjectAltName = DNS:localhost + +# 2+3: AltNames with multiple entries to test the matching logic +[altExt2] +subjectAltName = DNS:nohost.example.com, DNS:localhost + +[altExt3] +subjectAltName = DNS:localhost, DNS:nohost.example.com + +# an AltName with no DNS entries; should use commonName instead for +# identity check +[altExt4] +subjectAltName = email:neon@webdav.org + +[reqDN] +countryName = Country Name +stateOrProvinceName = State or Province Name +localityName = Locality Name +organizationName = Organization Name +organizationalUnitName = Organizational Unit Name +commonName = Common Name (eg, your name or your server\'s hostname) +emailAddress = Email Address + +# a DN which gives two commonName attributes. +[reqDN.doubleCN] +countryName = Country Name +stateOrProvinceName = State or Province Name +localityName = Locality Name +organizationName = Organization Name +organizationalUnitName = Organizational Unit Name +0.commonName = Common Name +1.commonName = Common Name +emailAddress = Email Address + +[reqDN.CNfirst] +commonName = Common Name + +[reqDN.missingCN] +countryName = CountryName + +[reqDN.justEmail] +emailAddress = CountryName diff --git a/test/props.c b/test/props.c new file mode 100644 index 0000000..61d3c22 --- /dev/null +++ b/test/props.c @@ -0,0 +1,508 @@ +/* + Tests for property handling + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_props.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static const ne_propname p_alpha = {"DAV:", "alpha"}, + p_beta = {"http://webdav.org/random/namespace", "beta"}, + p_delta = {NULL, "delta"}; + +/* Tests little except that ne_proppatch() doesn't segfault. */ +static int patch_simple(void) +{ + ne_session *sess; + ne_proppatch_operation ops[] = { + { &p_alpha, ne_propset, "fish" }, + { &p_beta, ne_propremove, NULL }, + { NULL, ne_propset, NULL } + }; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.1 200 Goferit\r\n" + "Connection: close\r\n\r\n")); + ONREQ(ne_proppatch(sess, "/fish", ops)); + ne_session_destroy(sess); + return await_server(); +} + +#define RESP207 "HTTP/1.0 207 Stuff\r\n" "Server: foo\r\n\r\n" + +static void dummy_results(void *ud, const char *href, + const ne_prop_result_set *rset) +{ + NE_DEBUG(NE_DBG_HTTP, "dummy_results.\n"); +} + +/* Regression tests for propfind bodies which caused segfaults. */ +static int regress(void) +{ + static const char *bodies[] = { + RESP207 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<multistatus xmlns=\"DAV:\">" + "<response><propstat><prop><href>" + "</href></prop></propstat></response>" + "</multistatus>", + + /* segfaults with neon <= 0.23.5 */ + RESP207 "<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">" + "<D:response><D:href>/foo/</D:href>" + "<D:propstat/>" + "<D:status>HTTP/1.1 404 Not Found</D:status>" + "</D:multistatus>", + NULL, + }; + ne_session *sess; + int n; + + for (n = 0; bodies[n] != NULL; n++) { + CALL(make_session(&sess, single_serve_string, (void *)bodies[n])); + ne_simple_propfind(sess, "/", 0, NULL, dummy_results, NULL); + ne_session_destroy(sess); + CALL(await_server()); + } + + return OK; +} + +static int pstat_count; + +/* tos_*: set of 207 callbacks which serialize the data back into a + * text stream, which can be easily checked for correctness. */ +static void *tos_startresp(void *buf, const char *href) +{ + ne_buffer_concat(buf, "start-resp[", href, "];", NULL); + pstat_count = 0; + return ne_strdup(href); +} + +static void tos_status_descr(ne_buffer *buf, const ne_status *status, + const char *description) +{ + if (status) { + char s[50]; + ne_snprintf(s, sizeof s, "-status={%d %s}", status->code, + status->reason_phrase); + ne_buffer_zappend(buf, s); + } + if (description) + ne_buffer_concat(buf, "-descr={", description, "}", NULL); +} + +static void tos_endresp(void *buf, void *response, + const ne_status *status, const char *description) +{ + char *href = response; + ne_buffer_concat(buf, "end-resp[", href, "]", NULL); + ne_free(href); + tos_status_descr(buf, status, description); + ne_buffer_zappend(buf, ";"); +} + +static void *tos_startpstat(void *buf, void *resphref) +{ + char num[20], *href; + sprintf(num, "-%d", ++pstat_count); + href = ne_concat(resphref, num, NULL); + ne_buffer_concat(buf, "start-pstat[", href, "];", NULL); + return href; +} + +static void tos_endpstat(void *buf, void *href, + const ne_status *status, const char *description) +{ + ne_buffer_concat(buf, "end-pstat[", href, "]", NULL); + tos_status_descr(buf, status, description); + ne_buffer_zappend(buf, ";"); + ne_free(href); +} + +struct propctx { + ne_207_parser *p207; + ne_buffer *buf; +}; + +#define STATE_myprop (NE_PROPS_STATE_TOP) + +static int tos_startprop(void *userdata, int parent, + const char *nspace, const char *name, + const char **atts) +{ + if (parent == NE_207_STATE_PROP && + strcmp(nspace, "DAV:") == 0 && + (strcmp(name, "propone") == 0 || strcmp(name, "proptwo") == 0)) { + /* Handle this! */ + struct propctx *ctx = userdata; + char *resphref = ne_207_get_current_response(ctx->p207); + char *pstathref = ne_207_get_current_propstat(ctx->p207); + + ne_buffer_concat(ctx->buf, "start-prop[", resphref, ",", pstathref, + ",", name, "];", NULL); + + return STATE_myprop; + } else { + return NE_XML_DECLINE; + } +} + +static int tos_cdata(void *userdata, int state, + const char *cdata, size_t len) +{ + struct propctx *ctx = userdata; + + ne_buffer_zappend(ctx->buf, "cdata-prop["); + ne_buffer_append(ctx->buf, cdata, len); + ne_buffer_zappend(ctx->buf, "];"); + return 0; +} + +static int tos_endprop(void *userdata, int state, + const char *nspace, const char *name) +{ + struct propctx *ctx = userdata; + + ne_buffer_concat(ctx->buf, "end-prop[", name, "];", NULL); + return 0; +} + + +static int run_207_response(char *resp, const char *expected) +{ + ne_buffer *buf = ne_buffer_create(); + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_xml_parser *p = ne_xml_create(); + ne_207_parser *p207 = ne_207_create(p, buf); + ne_request *req = ne_request_create(sess, "PROPFIND", "/foo"); + struct propctx ctx; + + ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v, p); + + ne_207_set_response_handlers(p207, tos_startresp, tos_endresp); + ne_207_set_propstat_handlers(p207, tos_startpstat, tos_endpstat); + + ctx.buf = buf; + ctx.p207 = p207; + ne_xml_push_handler(p, tos_startprop, tos_cdata, tos_endprop, &ctx); + + CALL(spawn_server(7777, single_serve_string, resp)); + + ONREQ(ne_request_dispatch(req)); + + CALL(await_server()); + + ONV(!ne_xml_valid(p), + ("response body was invalid: %s", ne_xml_get_error(p))); + + ONV(strcmp(buf->data, expected), + ("comparison failed.\n" + "expected string: `%s'\n" + "got string: `%s'", expected, buf->data)); + + ne_buffer_destroy(buf); + ne_207_destroy(p207); + ne_xml_destroy(p); + ne_request_destroy(req); + ne_session_destroy(sess); + return OK; +} + +/* Macros for easily writing a 207 response body; all expand to + * a string literal. */ +#define MULTI_207(x) "HTTP/1.0 207 Foo\r\nConnection: close\r\n\r\n" \ +"<?xml version='1.0'?>\r\n" \ +"<D:multistatus xmlns:D='DAV:'>" x "</D:multistatus>" +#define RESP_207(href, x) "<D:response><D:href>" href "</D:href>" x \ +"</D:response>" +#define PSTAT_207(x) "<D:propstat>" x "</D:propstat>" +#define STAT_207(s) "<D:status>HTTP/1.1 " s "</D:status>" +#define DESCR_207(d) "<D:responsedescription>" d "</D:responsedescription>" +#define DESCR_REM "The end of the world, as we know it" + +#define PROPS_207(x) "<D:prop>" x "</D:prop>" +#define APROP_207(n, c) "<D:" n ">" c "</D:" n ">" + +/* Tests for the 207 interface: send a 207 response body, compare the + * re-serialized string returned with that expected. */ +static int two_oh_seven(void) +{ + static char *ts[][2] = { + { MULTI_207(RESP_207("/foo", "")), + "start-resp[/foo];end-resp[/foo];" }, + + /* test for response status handling */ + { MULTI_207(RESP_207("/bar", STAT_207("200 OK"))), + "start-resp[/bar];end-resp[/bar]-status={200 OK};" }, + + /* test that empty description == NULL description argument */ + { MULTI_207(RESP_207("/bar", STAT_207("200 OK") DESCR_207(""))), + "start-resp[/bar];end-resp[/bar]-status={200 OK};" }, + + /* test multiple responses */ + { MULTI_207(RESP_207("/hello/world", STAT_207("200 OK")) + RESP_207("/foo/bar", STAT_207("999 French Fries"))), + "start-resp[/hello/world];end-resp[/hello/world]-status={200 OK};" + "start-resp[/foo/bar];end-resp[/foo/bar]" + "-status={999 French Fries};" + }, + + /* test multiple propstats in mulitple responses */ + { MULTI_207(RESP_207("/al/pha", + PSTAT_207(STAT_207("321 Une")) + PSTAT_207(STAT_207("432 Deux")) + PSTAT_207(STAT_207("543 Trois"))) + RESP_207("/be/ta", + PSTAT_207(STAT_207("787 Quatre")) + PSTAT_207(STAT_207("878 Cinq")))), + "start-resp[/al/pha];" + "start-pstat[/al/pha-1];end-pstat[/al/pha-1]-status={321 Une};" + "start-pstat[/al/pha-2];end-pstat[/al/pha-2]-status={432 Deux};" + "start-pstat[/al/pha-3];end-pstat[/al/pha-3]-status={543 Trois};" + "end-resp[/al/pha];" + "start-resp[/be/ta];" + "start-pstat[/be/ta-1];end-pstat[/be/ta-1]-status={787 Quatre};" + "start-pstat[/be/ta-2];end-pstat[/be/ta-2]-status={878 Cinq};" + "end-resp[/be/ta];" + }, + + /* test that incomplete responses are completely ignored. */ + { MULTI_207("<D:response/>" + RESP_207("/", STAT_207("123 Hoorah")) + "<D:response/>" + "<D:response><D:propstat>hello</D:propstat></D:response>" + "<D:response><D:href/></D:response>" + RESP_207("/bar", STAT_207("200 OK"))), + "start-resp[/];end-resp[/]-status={123 Hoorah};" + "start-resp[/bar];end-resp[/bar]-status={200 OK};" }, + + /* tests for propstat status */ + { MULTI_207(RESP_207("/pstat", + PSTAT_207("<D:prop/>" STAT_207("666 Doomed")))), + "start-resp[/pstat];start-pstat[/pstat-1];" + "end-pstat[/pstat-1]-status={666 Doomed};end-resp[/pstat];" }, + + { MULTI_207(RESP_207("/pstat", PSTAT_207("<D:status/>"))), + "start-resp[/pstat];start-pstat[/pstat-1];" + "end-pstat[/pstat-1];end-resp[/pstat];" }, + + /* tests for responsedescription handling */ + { MULTI_207(RESP_207("/bar", STAT_207("200 OK") DESCR_207(DESCR_REM))), + "start-resp[/bar];end-resp[/bar]-status={200 OK}" + "-descr={" DESCR_REM "};" }, + + { MULTI_207(RESP_207("/bar", + PSTAT_207(STAT_207("456 Too Hungry") + DESCR_207("Not enough food available")) + STAT_207("200 OK") DESCR_207("Not " DESCR_REM))), + "start-resp[/bar];" + "start-pstat[/bar-1];end-pstat[/bar-1]-status={456 Too Hungry}" + "-descr={Not enough food available};" + "end-resp[/bar]-status={200 OK}-descr={Not " DESCR_REM "};" }, + + /* intermingle some random elements and cdata to make sure + * they are ignored. */ + { MULTI_207("<D:fish-food/>blargl" + RESP_207("/b<ping-pong/>ar", "<D:sausages/>" + PSTAT_207("<D:hello-mum/>blergl") + STAT_207("200 <pong-ping/> OK") "foop" + DESCR_207(DESCR_REM) "carroon") + "carapi"), + "start-resp[/bar];start-pstat[/bar-1];end-pstat[/bar-1];" + "end-resp[/bar]-status={200 OK}-descr={" DESCR_REM "};" }, + + /* test for properties within a 207. */ + { MULTI_207(RESP_207("/alpha", + PSTAT_207(PROPS_207( + APROP_207("propone", "hello") + APROP_207("proptwo", "foobar")) + STAT_207("200 OK")))), + "start-resp[/alpha];start-pstat[/alpha-1];" + "start-prop[/alpha,/alpha-1,propone];cdata-prop[hello];" + "end-prop[propone];" + "start-prop[/alpha,/alpha-1,proptwo];cdata-prop[foobar];" + "end-prop[proptwo];" + "end-pstat[/alpha-1]-status={200 OK};end-resp[/alpha];" } + + }; + size_t n; + + for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) + CALL(run_207_response(ts[n][0], ts[n][1])); + + return OK; +} + +/* Serialize propfind result callbacks into a string */ +static int simple_iterator(void *buf, const ne_propname *name, + const char *value, const ne_status *st) +{ + char code[20]; + ne_buffer_concat(buf, "prop:[{", name->nspace, ",", + name->name, "}=", NULL); + if (value) + ne_buffer_concat(buf, "'", value, "'", NULL); + else + ne_buffer_zappend(buf, "#novalue#"); + sprintf(code, ":{%d ", st->code); + if (st->reason_phrase) + ne_buffer_concat(buf, code, st->reason_phrase, "}];", NULL); + else + ne_buffer_concat(buf, code, "#noreason#}];", NULL); + return 0; +} + +static void simple_results(void *buf, const char *href, + const ne_prop_result_set *rset) +{ + ne_buffer_concat(buf, "results(", href, ",", NULL); + ne_propset_iterate(rset, simple_iterator, buf); + ne_buffer_zappend(buf, ")//"); +} + +static int diffcmp(const char *expected, const char *actual) +{ + size_t n; + + if (!strcmp(expected, actual)) return OK; + + for (n = 0; expected[n] && actual[n]; n++) { + if (expected[n] != actual[n]) { + t_context("difference at byte %" NE_FMT_SIZE_T ": " + "`%.6s...' not `%.6s...'", + n, actual+n, expected+n); + break; + } + } + + return FAIL; +} + + +static int run_simple_propfind(const ne_propname *props, char *resp, + int depth, const char *expected) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_buffer *buf = ne_buffer_create(); + + CALL(spawn_server(7777, single_serve_string, resp)); + + ONREQ(ne_simple_propfind(sess, "/propfind", depth, props, + simple_results, buf)); + + CALL(await_server()); + + CALL(diffcmp(expected, buf->data)); + + ne_buffer_destroy(buf); + ne_session_destroy(sess); + return OK; +} + +/* a PROPFIND response body for the {DAV:}fishbone property, using + * given property value and status. */ +#define FISHBONE_RESP(value, status) MULTI_207(RESP_207("/foop", \ + PSTAT_207(PROPS_207(APROP_207("fishbone", value)) \ + STAT_207(status)))) + +static int pfind_simple(void) +{ + static const struct { + char *resp; + const char *expected; + int depth, pset; + } ts[] = { + /* simple single property. */ + { FISHBONE_RESP("hello, world", "212 Well OK"), + "results(/foop,prop:[{DAV:,fishbone}='hello, world':{212 Well OK}];)//", + 0, 0 }, + /* property with some nested elements. */ + { FISHBONE_RESP("this is <foo/> a property <bar><lemon>fish</lemon></bar> value", + "299 Just About OK"), + "results(/foop,prop:[{DAV:,fishbone}=" + "'this is <foo></foo> a property " + "<bar><lemon>fish</lemon></bar> value':" + "{299 Just About OK}];)//", + 0, 0 }, + + /* failed to fetch a property. */ + { FISHBONE_RESP("property value is ignored", + "404 Il n'ya pas de property"), + "results(/foop,prop:[{DAV:,fishbone}=#novalue#:" + "{404 Il n'ya pas de property}];)//", + 0, 0 }, + +#if 0 + /* propstat missing status should be ignored; if a response contains no + * valid propstats, it should also be ignored. */ + { MULTI_207(RESP_207("/alpha", PSTAT_207(APROP_207("fishbone", "unseen"))) + RESP_207("/beta", PSTAT_207(APROP_207("fishbone", "hello, world") + STAT_207("200 OK")))), + "results(/beta,prop:[{DAV:,fishbone}='hello, world':{200 OK}];)//", 0, 0}, +#endif + + /* props on several resources */ + { MULTI_207(RESP_207("/alpha", + PSTAT_207(PROPS_207(APROP_207("fishbone", "strike one")) + STAT_207("234 First is OK"))) + RESP_207("/beta", + PSTAT_207(PROPS_207(APROP_207("fishbone", "strike two")) + STAT_207("256 Second is OK")))), + "results(/alpha,prop:[{DAV:,fishbone}='strike one':{234 First is OK}];)//" + "results(/beta,prop:[{DAV:,fishbone}='strike two':{256 Second is OK}];)//", + 0, 0} + }; + const ne_propname pset1[] = { + { "DAV:", "fishbone", }, + { NULL, NULL } + }; + size_t n; + + for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) { + const ne_propname *pset = pset1; + + CALL(run_simple_propfind(pset, ts[n].resp, ts[n].depth, + ts[n].expected)); + } + + + return OK; +} + +ne_test tests[] = { + T(two_oh_seven), + T(patch_simple), + T(pfind_simple), + T(regress), + T(NULL) +}; + diff --git a/test/redirect.c b/test/redirect.c new file mode 100644 index 0000000..80b23bb --- /dev/null +++ b/test/redirect.c @@ -0,0 +1,189 @@ +/* + Tests for 3xx redirect interface (ne_redirect.h) + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_redirect.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +struct redir_args { + int code; + const char *dest; + const char *path; +}; + +static int serve_redir(ne_socket *sock, void *ud) +{ + struct redir_args *args = ud; + char buf[BUFSIZ]; + + CALL(discard_request(sock)); + + ne_snprintf(buf, BUFSIZ, + "HTTP/1.0 %d Get Ye Away\r\n" + "Content-Length: 0\r\n" + "Location: %s\r\n\n", + args->code, args->dest); + + SEND_STRING(sock, buf); + + return OK; +} + +/* Run a request to 'path' and retrieve the redirect destination to + * *redir. */ +static int process_redir(ne_session *sess, const char *path, + const ne_uri **redir) +{ + ONN("did not get NE_REDIRECT", any_request(sess, path) != NE_REDIRECT); + *redir = ne_redirect_location(sess); + return OK; +} + +static int check_redir(struct redir_args *args, const char *expect) +{ + ne_session *sess; + const ne_uri *loc; + char *unp; + + CALL(make_session(&sess, serve_redir, args)); + ne_redirect_register(sess); + + CALL(process_redir(sess, args->path, &loc)); + ONN("redirect location was NULL", loc == NULL); + + unp = ne_uri_unparse(loc); + ONV(strcmp(unp, expect), ("redirected to `%s' not `%s'", unp, expect)); + ne_free(unp); + + ne_session_destroy(sess); + CALL(await_server()); + + return OK; +} + +#define DEST "http://foo.com/blah/blah/bar" +#define PATH "/redir/me" + +static int simple(void) +{ + struct redir_args args[] = { + {301, DEST, PATH}, + {302, DEST, PATH}, + {303, DEST, PATH}, + {307, DEST, PATH}, + {0, NULL, NULL} + }; + int n; + + for (n = 0; args[n].code; n++) + CALL(check_redir(&args[n], DEST)); + + return OK; +} + +/* check that a non-absoluteURI is qualified properly */ +static int non_absolute(void) +{ + struct redir_args args = {302, "/foo/bar/blah", PATH}; + return check_redir(&args, "http://localhost:7777/foo/bar/blah"); +} + +static int relative_1(void) +{ + struct redir_args args = {302, "norman", "/foo/bar"}; + return check_redir(&args, "http://localhost:7777/foo/norman"); +} + +static int relative_2(void) +{ + struct redir_args args = {302, "wishbone", "/foo/bar/"}; + return check_redir(&args, "http://localhost:7777/foo/bar/wishbone"); +} + +#if 0 +/* could implement failure on self-referential redirects, but + * realistically, the application must implement a max-redirs count + * check, so it's kind of redundant. Mozilla takes this approach. */ +static int fail_loop(void) +{ + ne_session *sess; + + CALL(make_session(&sess, serve_redir, "http://localhost:7777/foo/bar")); + + ne_redirect_register(sess); + + ONN("followed looping redirect", + any_request(sess, "/foo/bar") != NE_ERROR); + + ne_session_destroy(sess); + return OK; +} +#endif + +/* ensure that ne_redirect_location returns NULL when no redirect has + * been encountered, or redirect hooks aren't registered. */ +static int no_redirect(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + const ne_uri *loc; + + ONN("redirect non-NULL before register", ne_redirect_location(sess)); + ne_redirect_register(sess); + ONN("initial redirect non-NULL", ne_redirect_location(sess)); + + CALL(spawn_server(7777, single_serve_string, + "HTTP/1.0 200 OK\r\n\r\n\r\n")); + ONREQ(any_request(sess, "/noredir")); + CALL(await_server()); + + ONN("redirect non-NULL after non-redir req", ne_redirect_location(sess)); + + CALL(spawn_server(7777, single_serve_string, "HTTP/1.0 302 Get Ye Away\r\n" + "Location: /blah\r\n" "\r\n")); + CALL(process_redir(sess, "/foo", &loc)); + CALL(await_server()); + + ne_session_destroy(sess); + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(simple), + T(non_absolute), + T(relative_1), + T(relative_2), + T(no_redirect), + T(NULL) +}; + diff --git a/test/request.c b/test/request.c new file mode 100644 index 0000000..ed3029f --- /dev/null +++ b/test/request.c @@ -0,0 +1,1649 @@ +/* + HTTP request handling tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <time.h> /* for time() */ + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static char buffer[BUFSIZ]; + +static ne_session *def_sess; +static ne_request *def_req; + +static int prepare_request(server_fn fn, void *ud) +{ + static char uri[100]; + + def_sess = ne_session_create("http", "localhost", 7777); + + sprintf(uri, "/test%d", test_num); + + def_req = ne_request_create(def_sess, "GET", uri); + + CALL(spawn_server(7777, fn, ud)); + + return OK; +} + +static int finish_request(void) +{ + ne_request_destroy(def_req); + ne_session_destroy(def_sess); + return await_server(); +} + +#define RESP200 "HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n" +#define TE_CHUNKED "Transfer-Encoding: chunked\r\n" + +/* takes response body chunks and appends them to a buffer. */ +static void collector(void *ud, const char *data, size_t len) +{ + ne_buffer *buf = ud; + ne_buffer_append(buf, data, len); +} + +typedef ne_request *(*construct_request)(ne_session *sess, void *userdata); + +/* construct a get request, callback for run_request. */ +static ne_request *construct_get(ne_session *sess, void *userdata) +{ + ne_request *r = ne_request_create(sess, "GET", "/"); + ne_buffer *buf = userdata; + + ne_add_response_body_reader(r, ne_accept_2xx, collector, buf); + + return r; +} + +/* run a request created by callback 'cb' in session 'sess'. */ +static int run_request(ne_session *sess, int status, + construct_request cb, void *userdata) +{ + ne_request *req = cb(sess, userdata); + + ON(req == NULL); + + ONREQ(ne_request_dispatch(req)); + + ONV(ne_get_status(req)->code != status, + ("response status-code was %d not %d", + ne_get_status(req)->code, status)); + + ne_request_destroy(req); + + return OK; +} + +/* Runs a server function 'fn', expecting to get a header 'name' with value + * 'value' in the response. */ +static int expect_header_value(const char *name, const char *value, + server_fn fn, void *userdata) +{ + ne_session *sess; + ne_request *req; + char *gotval = NULL; + + CALL(make_session(&sess, fn, userdata)); + + req = ne_request_create(sess, "FOO", "/bar"); + ne_add_response_header_handler(req, name, ne_duplicate_header, &gotval); + ONREQ(ne_request_dispatch(req)); + CALL(await_server()); + + ONN("no header value set", gotval == NULL); + ONV(strcmp(gotval, value), + ("header value mis-match: got [%s] not [%s]", gotval, value)); + + ne_request_destroy(req); + ne_session_destroy(sess); + ne_free(gotval); + + return OK; +} + +/* runs a server function 'fn', expecting response body to be equal to + * 'expect' */ +static int expect_response(const char *expect, server_fn fn, void *userdata) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_buffer *buf = ne_buffer_create(); + + ON(sess == NULL || buf == NULL); + ON(spawn_server(7777, fn, userdata)); + + CALL(run_request(sess, 200, construct_get, buf)); + + ON(await_server()); + + ONN("response body match", strcmp(buf->data, expect)); + + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + return OK; +} + +#define EMPTY_RESP RESP200 "Content-Length: 0\r\n\r\n" + +/* Process a request with given method and response, expecting to get + * a zero-length response body. A second request is sent down the + * connection (to ensure that the response isn't silently eaten), so + * 'resp' must be an HTTP/1.1 response with no 'Connection: close' + * header. */ +static int expect_no_body(const char *method, const char *resp) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_request *req = ne_request_create(sess, method, "/first"); + ssize_t ret; + char *r = ne_malloc(strlen(resp) + sizeof(EMPTY_RESP)); + + strcpy(r, resp); + strcat(r, EMPTY_RESP); + ON(spawn_server(7777, single_serve_string, r)); + ne_free(r); + + ONN("failed to begin request", ne_begin_request(req)); + ret = ne_read_response_block(req, buffer, BUFSIZ); + ONV(ret != 0, ("got response block of size %" NE_FMT_SSIZE_T, ret)); + ONN("failed to end request", ne_end_request(req)); + + /* process following request; makes sure that nothing extra has + * been eaten by the first request. */ + ONV(any_request(sess, "/second"), + ("second request on connection failed: %s",ne_get_error(sess))); + + ON(await_server()); + + ne_request_destroy(req); + ne_session_destroy(sess); + return OK; +} + +static int reason_phrase(void) +{ + ne_session *sess; + + CALL(make_session(&sess, single_serve_string, RESP200 + "Connection: close\r\n\r\n")); + CALL(any_request(sess, "/foo")); + CALL(await_server()); + + ONV(strcmp(ne_get_error(sess), "200 OK"), + ("reason phrase mismatch: got `%s' not `200 OK'", + ne_get_error(sess))); + + ne_session_destroy(sess); + return OK; +} + +static int single_get_eof(void) +{ + return expect_response("a", single_serve_string, + RESP200 + "Connection: close\r\n" + "\r\n" + "a"); +} + +static int single_get_clength(void) +{ + return expect_response("a", single_serve_string, + RESP200 + "Content-Length: 1\r\n" + "\r\n" + "a" + "bbbbbbbbasdasd"); +} + +static int single_get_chunked(void) +{ + return expect_response("a", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "1\r\n" + "a\r\n" + "0\r\n" "\r\n" + "g;lkjalskdjalksjd"); +} + +static int no_body_304(void) +{ + return expect_no_body("GET", "HTTP/1.1 304 Not Mfodified\r\n" + "Content-Length: 5\r\n\r\n"); +} + +static int no_body_204(void) +{ + return expect_no_body("GET", "HTTP/1.1 204 Not Modified\r\n" + "Content-Length: 5\r\n\r\n"); +} + +static int no_body_HEAD(void) +{ + return expect_no_body("HEAD", "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n\r\n"); +} + +static int no_body_empty_clength(void) +{ + return expect_no_body("GET", "HTTP/1.1 200 OK\r\n" + "Content-Length:\r\n\r\n"); +} + +static int no_body_bad_clength(void) +{ + return expect_no_body("GET", "HTTP/1.1 200 OK\r\n" + "Content-Length: foobar\r\n\r\n"); +} + +static int no_headers(void) +{ + return expect_response("abcde", single_serve_string, + "HTTP/1.1 200 OK\r\n\r\n" + "abcde"); +} + +#define CHUNK(len, data) #len "\r\n" data "\r\n" + +#define ABCDE_CHUNKS CHUNK(1, "a") CHUNK(1, "b") \ + CHUNK(1, "c") CHUNK(1, "d") \ + CHUNK(1, "e") CHUNK(0, "") + +static int chunks(void) +{ + /* lots of little chunks. */ + return expect_response("abcde", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + ABCDE_CHUNKS); +} + +static int te_header(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 "Transfer-Encoding: CHUNKED\r\n" + "\r\n" ABCDE_CHUNKS); +} + +/* test that the presence of *any* t-e header implies a chunked + * response. */ +static int any_te_header(void) +{ + return expect_response("abcde", single_serve_string, RESP200 + "Transfer-Encoding: punked\r\n" "\r\n" + ABCDE_CHUNKS); +} + +static int chunk_numeric(void) +{ + /* leading zero's */ + return expect_response("0123456789abcdef", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "000000010\r\n" "0123456789abcdef\r\n" + "000000000\r\n" "\r\n"); +} + +static int chunk_extensions(void) +{ + /* chunk-extensions. */ + return expect_response("0123456789abcdef", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "000000010; foo=bar; norm=fish\r\n" + "0123456789abcdef\r\n" + "000000000\r\n" "\r\n"); +} + +static int chunk_trailers(void) +{ + /* trailers. */ + return expect_response("abcde", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "00000005; foo=bar; norm=fish\r\n" + "abcde\r\n" + "000000000\r\n" + "X-Hello: world\r\n" + "X-Another: header\r\n" + "\r\n"); +} + +static int chunk_oversize(void) +{ +#define BIG (20000) + char *body = ne_malloc(BIG + 1); + static const char rnd[] = "abcdefghijklm"; + int n; + ne_buffer *buf = ne_buffer_create(); + + for (n = 0; n < BIG; n++) { + body[n] = rnd[n % (sizeof(rnd) - 1)]; + } + body[n] = '\0'; +#undef BIG + + ne_buffer_concat(buf, RESP200 TE_CHUNKED "\r\n" + "4E20\r\n", body, "\r\n", + "0\r\n\r\n", NULL); + + CALL(expect_response(body, single_serve_string, buf->data)); + + ne_buffer_destroy(buf); + ne_free(body); + + return OK; +} + +static int te_over_clength(void) +{ + /* T-E dominates over C-L. */ + return expect_response("abcde", single_serve_string, + RESP200 TE_CHUNKED + "Content-Length: 300\r\n" + "\r\n" + ABCDE_CHUNKS); +} + +/* te_over_clength with the headers the other way round; check for + * ordering problems. */ +static int te_over_clength2(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 "Content-Length: 300\r\n" + TE_CHUNKED + "\r\n" + ABCDE_CHUNKS); +} + +/* obscure case which is possibly a valid request by 2616, but should + * be handled correctly in any case. neon <0.22.0 tries to + * eat the response body, which is probably incorrect. */ +static int no_body_chunks(void) +{ + return expect_no_body("HEAD", "HTTP/1.1 204 Not Modified\r\n" + TE_CHUNKED "\r\n"); +} + +static int serve_twice(ne_socket *sock, void *userdata) +{ + const char *resp = userdata; + + CALL(discard_request(sock)); + SEND_STRING(sock, resp); + + CALL(discard_request(sock)); + SEND_STRING(sock, resp); + + return OK; +} + +/* Test persistent connection handling: serve 'response' twice on a + * single TCP connection, expecting to get a response body equal to + * 'body' both times. */ +static int test_persist(const char *response, const char *body) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_buffer *buf = ne_buffer_create(); + + ON(sess == NULL || buf == NULL); + ON(spawn_server(7777, serve_twice, (char *)response)); + + CALL(run_request(sess, 200, construct_get, buf)); + + ONV(strcmp(buf->data, body), + ("response #1 mismatch: [%s] not [%s]", buf->data, body)); + + /* Run it again. */ + ne_buffer_clear(buf); + CALL(run_request(sess, 200, construct_get, buf)); + + ON(await_server()); + + ONV(strcmp(buf->data, body), + ("response #2 mismatch: [%s] not [%s]", buf->data, body)); + + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + return OK; +} + +static int persist_http11(void) +{ + return test_persist(RESP200 "Content-Length: 5\r\n\r\n" "abcde", + "abcde"); +} + +static int persist_chunked(void) +{ + return test_persist(RESP200 TE_CHUNKED "\r\n" ABCDE_CHUNKS, + "abcde"); +} + +static int persist_http10(void) +{ + return test_persist("HTTP/1.0 200 OK\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 5\r\n\r\n" "abcde", + "abcde"); +} + +/* Server function for fail_early_eof */ +static int serve_eof(ne_socket *sock, void *ud) +{ + const char *resp = ud; + + /* dummy request/response. */ + CALL(discard_request(sock)); + CALL(SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n")); + /* real request/response. */ + CALL(discard_request(sock)); + CALL(SEND_STRING(sock, resp)); + + return OK; +} + +/* Utility function: 'resp' is a truncated response; such that an EOF + * arrives early during response processing; but NOT as a valid + * premature EOF due to a persistent connection timeout. It is an + * error if the request is then retried, and the test fails. */ +static int fail_early_eof(const char *resp) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + + CALL(spawn_server_repeat(7777, serve_eof, (char *)resp, 3)); + + ONREQ(any_request(sess, "/foo")); + ONN("request retried after early EOF", + any_request(sess, "/foobar") == NE_OK); + + CALL(reap_server()); + ne_session_destroy(sess); + return OK; +} + +/* This failed with neon <0.22. */ +static int fail_eof_continued(void) +{ + return fail_early_eof("HTTP/1.1 100 OK\r\n\r\n"); +} + +static int fail_eof_headers(void) +{ + return fail_early_eof("HTTP/1.1 200 OK\r\nJimbob\r\n"); +} + +static int fail_eof_chunk(void) +{ + return fail_early_eof(RESP200 TE_CHUNKED "\r\n" "1\r\n" "a"); +} + +static int fail_eof_badclen(void) +{ + return fail_early_eof(RESP200 "Content-Length: 10\r\n\r\n" "abcde"); +} + +/* Persistent connection timeout where a FIN is sent to terminate the + * connection, which is caught by a 0 return from the read() when the + * second request reads the status-line. */ +static int ptimeout_eof(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + + CALL(spawn_server_repeat(7777, single_serve_string, + RESP200 "Content-Length: 0\r\n" "\r\n", 4)); + + CALL(any_2xx_request(sess, "/first")); + CALL(any_2xx_request(sess, "/second")); + + ONN("server died prematurely?", dead_server()); + reap_server(); + + ne_session_destroy(sess); + return OK; +} + +/* Persistent connection timeout where a FIN is sent to terminate the + * connection, but the request fails in the write() call which sends + * the body. */ +static int ptimeout_eof2(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + + CALL(spawn_server_repeat(7777, single_serve_string, + RESP200 "Content-Length: 0\r\n" "\r\n", 4)); + + CALL(any_2xx_request(sess, "/first")); + minisleep(); + CALL(any_2xx_request_body(sess, "/second")); + + ONN("server died prematurely?", dead_server()); + reap_server(); + + ne_session_destroy(sess); + return OK; +} + +/* TODO: add a ptimeout_reset too, if an RST can be reliably generated + * mid-connection. */ + +/* Emulates a persistent connection timeout on the server. This tests + * the timeout occuring after between 1 and 10 requests down the + * connection. */ +static int persist_timeout(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_buffer *buf = ne_buffer_create(); + int n; + struct many_serve_args args; + + ON(sess == NULL || buf == NULL); + + args.str = RESP200 "Content-Length: 5\r\n\r\n" "abcde"; + + for (args.count = 1; args.count < 10; args.count++) { + + ON(spawn_server(7777, many_serve_string, &args)); + + for (n = 0; n < args.count; n++) { + + ONV(run_request(sess, 200, construct_get, buf), + ("%d of %d, request failed: %s", n, args.count, + ne_get_error(sess))); + + ONV(strcmp(buf->data, "abcde"), + ("%d of %d, response body mismatch", n, args.count)); + + /* Ready for next time. */ + ne_buffer_clear(buf); + } + + ON(await_server()); + + } + + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + return OK; +} + +/* Test that an HTTP/1.0 server is not presumed to support persistent + * connections by default. */ +static int no_persist_http10(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + + CALL(spawn_server_repeat(7777, single_serve_string, + "HTTP/1.0 200 OK\r\n" + "Content-Length: 5\r\n\r\n" + "abcde" + "Hello, world - what a nice day!\r\n", + 4)); + + /* if the connection is treated as persistent, the status-line for + * the second request will be "Hello, world...", which will + * fail. */ + + ONREQ(any_request(sess, "/foobar")); + ONREQ(any_request(sess, "/foobar")); + + ONN("server died prematurely?", dead_server()); + CALL(reap_server()); + ne_session_destroy(sess); + return OK; +} + +static int ignore_bad_headers(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 + "Stupid Header\r\n" + "ReallyStupidHeader\r\n" + "Content-Length: 5\r\n" + "\r\n" + "abcde"); +} + +static int fold_headers(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 "Content-Length: \r\n 5\r\n" + "\r\n" + "abcde"); +} + +static int fold_many_headers(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 "Content-Length: \r\n \r\n \r\n \r\n 5\r\n" + "\r\n" + "abcde"); +} + +#define NO_BODY "Content-Length: 0\r\n\r\n" + +static int empty_header(void) +{ + return expect_header_value("ranDom-HEader", "", + single_serve_string, + RESP200 "RANDom-HeADEr:\r\n" + NO_BODY); +} + +static int ignore_header_case(void) +{ + return expect_header_value("ranDom-HEader", "noddy", + single_serve_string, + RESP200 "RANDom-HeADEr: noddy\r\n" + NO_BODY); +} + +static int ignore_header_ws(void) +{ + return expect_header_value("ranDom-HEader", "fishy", + single_serve_string, + RESP200 "RANDom-HeADEr: fishy\r\n" + NO_BODY); +} + +static int ignore_header_ws2(void) +{ + return expect_header_value("ranDom-HEader", "fishy", + single_serve_string, + RESP200 "RANDom-HeADEr \t : fishy\r\n" + NO_BODY); +} + +static int ignore_header_ws3(void) +{ + return expect_header_value("ranDom-HEader", "fishy", + single_serve_string, + RESP200 "RANDom-HeADEr: fishy \r\n" + NO_BODY); +} + +static int ignore_header_tabs(void) +{ + return expect_header_value("ranDom-HEader", "geezer", + single_serve_string, + RESP200 "RANDom-HeADEr: \t \tgeezer\r\n" + NO_BODY); +} + +static int trailing_header(void) +{ + return expect_header_value("gONe", "fishing", + single_serve_string, + RESP200 TE_CHUNKED + "\r\n0\r\n" + "Hello: world\r\n" + "GONE: fishing\r\n" + "\r\n"); +} + +static int continued_header(void) +{ + return expect_header_value("hello", "w o r l d", single_serve_string, + RESP200 "Hello: \n\tw\r\n\to r l\r\n\td \r\n" + NO_BODY); +} + +static void mh_header(void *ctx, const char *value) +{ + int *state = ctx; + static const char *hdrs[] = { "jim", "jab", "jar" }; + + if (*state < 0 || *state > 2) { + /* already failed. */ + return; + } + + if (strcmp(value, hdrs[*state])) + *state = -*state; + else + (*state)++; +} + +/* check headers callbacks are working correctly. */ +static int multi_header(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_request *req; + int state = 0; + + ON(sess == NULL); + ON(spawn_server(7777, single_serve_string, + RESP200 + "X-Header: jim\r\n" + "x-header: jab\r\n" + "x-Header: jar\r\n" + "Content-Length: 0\r\n\r\n")); + + req = ne_request_create(sess, "GET", "/"); + ON(req == NULL); + + ne_add_response_header_handler(req, "x-header", mh_header, &state); + + ONREQ(ne_request_dispatch(req)); + + ON(await_server()); + + ON(state != 3); + + ne_request_destroy(req); + ne_session_destroy(sess); + + return OK; +} + +struct s1xx_args { + int count; + int hdrs; +}; + +static int serve_1xx(ne_socket *sock, void *ud) +{ + struct s1xx_args *args = ud; + CALL(discard_request(sock)); + + do { + if (args->hdrs) { + SEND_STRING(sock, "HTTP/1.1 100 Continue\r\n" + "Random: header\r\n" + "Another: header\r\n\r\n"); + } else { + SEND_STRING(sock, "HTTP/1.1 100 Continue\r\n\r\n"); + } + } while (--args->count > 0); + + SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n"); + + return OK; +} + +#define sess def_sess + +static int skip_interim_1xx(void) +{ + struct s1xx_args args = {0, 0}; + ON(prepare_request(serve_1xx, &args)); + ONREQ(ne_request_dispatch(def_req)); + return finish_request(); +} + +static int skip_many_1xx(void) +{ + struct s1xx_args args = {5, 0}; + ON(prepare_request(serve_1xx, &args)); + ONREQ(ne_request_dispatch(def_req)); + return finish_request(); +} + +static int skip_1xx_hdrs(void) +{ + struct s1xx_args args = {5, 5}; + ON(prepare_request(serve_1xx, &args)); + ONREQ(ne_request_dispatch(def_req)); + return finish_request(); +} + +#undef sess + +/* server for expect_100_once: eats a dummy request, then serves a + * 100-continue request, and fails if the request body is sent + * twice. */ +static int serve_100_once(ne_socket *sock, void *ud) +{ + struct s1xx_args args = {2, 0}; + char ch; + /* dummy first request. */ + CALL(discard_request(sock)); + CALL(SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n")); + /* now the real 1xx request. */ + CALL(serve_1xx(sock, &args)); + CALL(discard_body(sock)); + ONN("body was served twice", ne_sock_read(sock, &ch, 1) == 1); + return OK; +} + +/* regression test; fails with neon <0.22, where the request body was + * served *every* time a 1xx response was received, rather than just + * once. */ +static int expect_100_once(void) +{ + ne_session *sess; + ne_request *req; + char body[BUFSIZ]; + + CALL(make_session(&sess, serve_100_once, NULL)); + ne_set_expect100(sess, 1); + + /* 100-continue is only used if the server is known to claim + * HTTP/1.1 compliance; make a dummy request on the socket first, + * to trigger that logic. */ + CALL(any_request(sess, "/foo")); + + /* now the real request. */ + req = ne_request_create(sess, "GET", "/foo"); + memset(body, 'A', sizeof(body)); + ne_set_request_body_buffer(req, body, sizeof(body)); + ONN("request failed", ne_request_dispatch(req)); + ne_request_destroy(req); + ne_session_destroy(sess); + CALL(await_server()); + return OK; +} + +struct body { + char *body; + size_t size; +}; + +static int want_body(ne_socket *sock, void *userdata) +{ + struct body *b = userdata; + char *buf = ne_malloc(b->size); + + clength = 0; + CALL(discard_request(sock)); + ONN("request has c-l header", clength == 0); + + ONN("request length", clength != (int)b->size); + + NE_DEBUG(NE_DBG_HTTP, + "reading body of %" NE_FMT_SIZE_T " bytes...\n", b->size); + + ON(ne_sock_fullread(sock, buf, b->size)); + + ON(SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n")); + + ON(memcmp(buf, b->body, b->size)); + + ne_free(buf); + return OK; +} + +static ssize_t provide_body(void *userdata, char *buf, size_t buflen) +{ + static const char *pnt; + static size_t left; + struct body *b = userdata; + + if (buflen == 0) { + pnt = b->body; + left = b->size; + } else { + if (left < buflen) buflen = left; + memcpy(buf, pnt, buflen); + left -= buflen; + } + + return buflen; +} + +static int send_bodies(void) +{ + unsigned int n, m; + + struct body bodies[] = { + { "abcde", 5 }, + { "\0\0\0\0\0\0", 6 }, + { NULL, 50000 }, + { NULL } + }; + +#define BIG 2 + /* make the body with some cruft. */ + bodies[BIG].body = ne_malloc(bodies[BIG].size); + for (n = 0; n < bodies[BIG].size; n++) { + bodies[BIG].body[n] = (char)n%80; + } + + for (m = 0; m < 2; m++) { + for (n = 0; bodies[n].body != NULL; n++) { + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_request *req; + + ON(sess == NULL); + ON(spawn_server(7777, want_body, &(bodies[n]))); + + req = ne_request_create(sess, "PUT", "/"); + ON(req == NULL); + + if (m == 0) { + ne_set_request_body_buffer(req, bodies[n].body, bodies[n].size); + } else { + ne_set_request_body_provider(req, bodies[n].size, + provide_body, &bodies[n]); + } + + ONREQ(ne_request_dispatch(req)); + + CALL(await_server()); + + ne_request_destroy(req); + ne_session_destroy(sess); + } + } + + ne_free(bodies[BIG].body); + return OK; +} + +static int serve_infinite_headers(ne_socket *sock, void *userdata) +{ + CALL(discard_request(sock)); + + SEND_STRING(sock, RESP200); + + for (;;) { + SEND_STRING(sock, "x-foo: bar\r\n"); + } + + return 0; +} + +/* Utility function: run a request using the given server fn, and the + * request should fail. If 'error' is non-NULL, it must be a substring + * of the error string. */ +static int fail_request_with_error(int with_body, server_fn fn, void *ud, + int forever, const char *error) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_request *req; + int ret; + + ON(sess == NULL); + + if (forever) { + ON(spawn_server_repeat(7777, fn, ud, 100)); + } else { + ON(spawn_server(7777, fn, ud)); + } + + req = ne_request_create(sess, "GET", "/"); + ON(req == NULL); + + if (with_body) { + static const char *body = "random stuff"; + + ne_set_request_body_buffer(req, body, strlen(body)); + } + + /* request should fail. */ + ret = ne_request_dispatch(req); + ONN("request succeeded", ret == NE_OK); + + if (!forever) { + /* reap the server, don't care what it's doing. */ + reap_server(); + } + + NE_DEBUG(NE_DBG_HTTP, "Response gave error `%s'\n", ne_get_error(sess)); + + ONV(error && strstr(ne_get_error(sess), error) == NULL, + ("failed with error `%s', no `%s'", ne_get_error(sess), error)); + + if (!forever) + ONV(any_request(sess, "/fail/to/connect") != NE_CONNECT, + ("subsequent request re-used connection?")); + + ne_request_destroy(req); + ne_session_destroy(sess); + + return OK; +} + +/* Run a random GET request which is given 'body' as the response; the + * request must fail, and 'error' must be found in the error + * string. */ +static int invalid_response_gives_error(const char *resp, const char *error) +{ + return fail_request_with_error(0, single_serve_string, (void *)resp, 0, error); +} + +/* Utility function: run a request using the given server fn, and the + * request must fail. */ +static int fail_request(int with_body, server_fn fn, void *ud, int forever) +{ + return fail_request_with_error(with_body, fn, ud, forever, NULL); +} + +static int unbounded_headers(void) +{ + return fail_request(0, serve_infinite_headers, NULL, 0); +} + +static int blank_response(void) +{ + return fail_request(0, single_serve_string, "\r\n", 0); +} + +static int serve_non_http(ne_socket *sock, void *ud) +{ + SEND_STRING(sock, "Hello Mum.\n"); + ne_sock_readline(sock, buffer, BUFSIZ); + return OK; +} + +/* Test behaviour when not speaking to an HTTP server. Regression test + * for infinite loop. */ +static int not_http(void) +{ + return fail_request(0, serve_non_http, NULL, 0); +} + +static int serve_infinite_folds(ne_socket *sock, void *ud) +{ + SEND_STRING(sock, "HTTP/1.0 200 OK\r\nFoo: bar\r\n"); + for (;;) { + SEND_STRING(sock, " hello there.\r\n"); + } + return OK; +} + +static int unbounded_folding(void) +{ + return fail_request(0, serve_infinite_folds, NULL, 0); +} + +static int serve_close(ne_socket *sock, void *ud) +{ + /* do nothing; the socket will be closed. */ + return 0; +} + +/* Returns non-zero if port is alive. */ +static int is_alive(int port) +{ + ne_sock_addr *addr; + ne_socket *sock = ne_sock_create(); + const ne_inet_addr *ia; + int connected = 0; + + addr = ne_addr_resolve("localhost", 0); + for (ia = ne_addr_first(addr); ia && !connected; ia = ne_addr_next(addr)) + connected = ne_sock_connect(sock, ia, 7777) == 0; + ne_addr_destroy(addr); + if (sock == NULL) + return 0; + else { + ne_sock_close(sock); + return 1; + } +} + +/* This is a regression test for neon 0.17.0 and earlier, which goes + * into an infinite loop if a request with a body is sent to a server + * which simply closes the connection. */ +static int closed_connection(void) +{ + int ret; + + /* This spawns a server process which will run the 'serve_close' + * response function 200 times, then die. This guarantees that the + * request eventually fails... */ + CALL(fail_request(1, serve_close, NULL, 1)); + /* if server died -> infinite loop was detected. */ + ret = !is_alive(7777); + reap_server(); + ONN("server aborted, infinite loop?", ret); + return OK; +} + +static int serve_close2(ne_socket *sock, void *userdata) +{ + int *count = userdata; + *count += 1; + if (*count == 1) + return 0; + NE_DEBUG(NE_DBG_HTTP, "Re-entered! Buggy client.\n"); + CALL(discard_request(sock)); + CALL(SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n")); + return 0; +} + +/* As closed_connection(); but check that the client doesn't retry + * after receiving the EOF on the first request down a new + * connection. */ +static int close_not_retried(void) +{ + int count = 0; + ne_session *sess = ne_session_create("http", "localhost", 7777); + CALL(spawn_server_repeat(7777, serve_close2, &count, 3)); + ONN("request was retried after EOF", any_request(sess, "/foo") == NE_OK); + reap_server(); + ne_session_destroy(sess); + return OK; +} + +static enum { + prog_error, /* error */ + prog_transfer, /* doing a transfer */ + prog_done /* finished. */ +} prog_state = prog_transfer; + +static off_t prog_last = -1, prog_total; + +/* callback for send_progress. */ +static void s_progress(void *userdata, off_t prog, off_t total) +{ + NE_DEBUG(NE_DBG_HTTP, + "progress callback: %" NE_FMT_OFF_T "/%" NE_FMT_OFF_T ".\n", + prog, total); + + switch (prog_state) { + case prog_error: + case prog_done: + return; + case prog_transfer: + if (total != prog_total) { + t_context("total unexpected: %ld not %ld", total, prog_total); + prog_state = prog_error; + } + else if (prog > total) { + t_context("first progress was invalid (%ld/%ld)", prog, total); + prog_state = prog_error; + } + else if (prog_last != -1 && prog_last > prog) { + t_context("progess went backwards: %ld to %ld", prog_last, prog); + prog_state = prog_error; + } + else if (prog_last == prog) { + t_context("no progress made! %ld to %ld", prog_last, prog); + prog_state = prog_error; + } + else if (prog == total) { + prog_state = prog_done; + } + break; + } + + prog_last = prog; +} + +static ssize_t provide_progress(void *userdata, char *buf, size_t bufsiz) +{ + int *count = userdata; + + if (*count >= 0 && buf != NULL) { + buf[0] = 'a'; + *count -= 1; + return 1; + } else { + return 0; + } +} + +static int send_progress(void) +{ + static int count = 200; + + ON(prepare_request(single_serve_string, + RESP200 "Connection: close\r\n\r\n")); + + prog_total = 200; + + ne_set_progress(def_sess, s_progress, NULL); + ne_set_request_body_provider(def_req, count, + provide_progress, &count); + +#define sess def_sess + ONREQ(ne_request_dispatch(def_req)); +#undef sess + + ON(finish_request()); + + CALL(prog_state == prog_error); + + return OK; +} + +static int read_timeout(void) +{ + ne_session *sess; + ne_request *req; + time_t start, finish; + int ret; + + CALL(make_session(&sess, sleepy_server, NULL)); + + /* timeout after one second. */ + ne_set_read_timeout(sess, 1); + + req = ne_request_create(sess, "GET", "/timeout"); + + time(&start); + ret = ne_request_dispatch(req); + time(&finish); + + reap_server(); + + ONN("request succeeded, should have timed out", ret == NE_OK); + ONV(ret != NE_TIMEOUT, + ("request failed non-timeout error: %s", ne_get_error(sess))); + ONN("timeout ignored, or very slow machine", finish - start > 3); + + ne_request_destroy(req); + ne_session_destroy(sess); + + return OK; +} + +/* expect failure code 'code', for request to given hostname and port, + * without running a server. */ +static int fail_noserver(const char *hostname, unsigned int port, int code) +{ + ne_session *sess = ne_session_create("http", hostname, port); + int ret = any_request(sess, "/foo"); + ne_session_destroy(sess); + + ONV(ret == NE_OK, + ("request to server at %s:%u succeded?!", hostname, port)); + ONV(ret != code, ("request failed with %d not %d", ret, code)); + + return OK; +} + +static int fail_lookup(void) +{ + return fail_noserver("no.such.domain", 7777, NE_LOOKUP); +} + +/* neon 0.23.0 to 0.23.3: if a nameserver lookup failed, subsequent + * requests on the session would crash. */ +static int fail_double_lookup(void) +{ + ne_session *sess = ne_session_create("http", "nohost.example.com", 80); + ONN("request did not give lookup failure", + any_request(sess, "/foo") != NE_LOOKUP); + ONN("second request did not give lookup failure", + any_request(sess, "/bar") != NE_LOOKUP); + ne_session_destroy(sess); + return OK; +} + +static int fail_connect(void) +{ + return fail_noserver("localhost", 7777, NE_CONNECT); +} + +/* Test that the origin server hostname is NOT resolved for a proxied + * request. */ +static int proxy_no_resolve(void) +{ + ne_session *sess = ne_session_create("http", "no.such.domain", 80); + int ret; + + ne_session_proxy(sess, "localhost", 7777); + CALL(spawn_server(7777, single_serve_string, + RESP200 "Content-Length: 0\r\n\r\n")); + + ret = any_request(sess, "/foo"); + ne_session_destroy(sess); + + ONN("origin server name resolved when proxy used", ret == NE_LOOKUP); + + CALL(await_server()); + + return OK; +} + +/* If the chunk size is entirely invalid, the request should be + * aborted. Fails with neon <0.22; invalid chunk sizes would be + * silently treated as 'zero'. */ +static int fail_chunksize(void) +{ + return fail_request(0, single_serve_string, + RESP200 TE_CHUNKED "\r\n" "ZZZZZ\r\n\r\n", 0); +} + +/* in neon <0.22, if an error occcurred whilst reading the response + * body, the connection would not be closed (though this test will + * succeed in neon <0.22 since it the previous test fails). */ +static int abort_respbody(void) +{ + ne_session *sess; + + CALL(make_session(&sess, single_serve_string, + RESP200 TE_CHUNKED "\r\n" + "zzz\r\n" + RESP200 "Content-Length: 0\r\n\r\n")); + + /* connection must be aborted on the first request, since it + * contains an invalid chunk size. */ + ONN("invalid chunk size was accepted?", + any_request(sess, "/foo") != NE_ERROR); + + CALL(await_server()); + + /* second request should fail since server has gone away. */ + ONN("connection was not aborted", any_request(sess, "/foo") == NE_OK); + + ne_session_destroy(sess); + return OK; +} + +static int serve_abort(ne_socket *sock, void *ud) +{ + exit(0); +} + +/* Test that after an aborted request on a peristent connection, a + * failure of the *subsequent* request is not treated as a persistent + * connection timeout and retried. */ +static int retry_after_abort(void) +{ + ne_session *sess; + + /* Serve two responses down a single persistent connection, the + * second of which is invalid and will cause the request to be + * aborted. */ + CALL(make_session(&sess, single_serve_string, + RESP200 "Content-Length: 0\r\n\r\n" + RESP200 TE_CHUNKED "\r\n" + "zzzzz\r\n")); + + CALL(any_request(sess, "/first")); + ONN("second request should fail", any_request(sess, "/second") == NE_OK); + CALL(await_server()); + + /* spawn a server, abort the server immediately. If the + * connection reset is interpreted as a p.conn timeout, a new + * connection will be attempted, which will fail with + * NE_CONNECT. */ + CALL(spawn_server(7777, serve_abort, NULL)); + ONN("third request was retried", + any_request(sess, "/third") == NE_CONNECT); + reap_server(); + + ne_session_destroy(sess); + return OK; +} + +/* Fail to parse the response status line: check the error message is + * sane. Failed during 0.23-dev briefly, and possibly with 0.22.0 + * too. */ +static int fail_statusline(void) +{ + ne_session *sess; + int ret; + + CALL(make_session(&sess, single_serve_string, "Fish.\r\n")); + + ret = any_request(sess, "/fail"); + ONV(ret != NE_ERROR, ("request failed with %d not NE_ERROR", ret)); + + /* FIXME: will break for i18n. */ + ONV(strcmp(ne_get_error(sess), "Could not parse response status line."), + ("session error was `%s'", ne_get_error(sess))); + + ne_session_destroy(sess); + return OK; +} + +#define LEN (9000) +static int fail_long_header(void) +{ + char resp[LEN + 500] = "HTTP/1.1 200 OK\r\n" + "Server: fish\r\n"; + size_t len = strlen(resp); + + /* add a long header */ + memset(resp + len, 'a', LEN); + resp[len + LEN] = '\0'; + + strcat(resp, "\r\n\r\n"); + + return invalid_response_gives_error(resp, "Line too long"); +} + +static int fail_corrupt_chunks(void) +{ + static const struct { + const char *resp, *error; + } ts[] = { + /* not CRLF */ + { RESP200 TE_CHUNKED "\r\n" "5\r\n" "abcdeFISH", + "delimiter was invalid" }, + /* short CRLF */ + { RESP200 TE_CHUNKED "\r\n" "5\r\n" "abcde\n", + "not read chunk delimiter" }, + /* CR-notLF */ + { RESP200 TE_CHUNKED "\r\n" "5\r\n" "abcde\rZZZ", + "delimiter was invalid" }, + { NULL, NULL } + }; + int n; + + for (n = 0; ts[n].resp; n++) + CALL(invalid_response_gives_error(ts[n].resp, ts[n].error)); + + return OK; +} + +static int versions(void) +{ + ne_session *sess; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n\r\n" + + "HTTP/1.0 200 OK\r\n" + "Content-Length: 0\r\n\r\n")); + + CALL(any_request(sess, "/http11")); + + ONN("did not detect HTTP/1.1 compliance", + ne_version_pre_http11(sess) != 0); + + CALL(any_request(sess, "/http10")); + + ONN("did not detect lack of HTTP/1.1 compliance", + ne_version_pre_http11(sess) == 0); + + ne_session_destroy(sess); + + return OK; +} + +struct cr_args { + const char *method, *uri; + int result; +}; + +static void hk_createreq(ne_request *req, void *userdata, + const char *method, const char *requri) +{ + struct cr_args *args = userdata; + + args->result = 1; /* presume failure */ + + if (strcmp(args->method, method)) + t_context("Hook got method %s not %s", method, args->method); + else if (strcmp(args->uri, requri)) + t_context("Hook got Req-URI %s not %s", requri, args->uri); + else + args->result = 0; +} + +static int hook_create_req(void) +{ + ne_session *sess; + struct cr_args args; + + CALL(make_session(&sess, single_serve_string, EMPTY_RESP EMPTY_RESP)); + + ne_hook_create_request(sess, hk_createreq, &args); + + args.method = "GET"; + args.uri = "/foo"; + args.result = -1; + + CALL(any_request(sess, "/foo")); + + ONN("first hook never called", args.result == -1); + if (args.result) return FAIL; + + args.uri = "http://localhost:7777/bar"; + args.result = -1; + + /* force use of absoluteURI in request-uri */ + ne_session_proxy(sess, "localhost", 7777); + + CALL(any_request(sess, "/bar")); + + ONN("second hook never called", args.result == -1); + if (args.result) return FAIL; + + ne_session_destroy(sess); + + return OK; +} + +static int serve_check_method(ne_socket *sock, void *ud) +{ + char *method = ud; + char buf[20]; + size_t methlen = strlen(method); + + if (ne_sock_read(sock, buf, methlen) != (ssize_t)methlen) + return -1; + + ONN("method corrupted", memcmp(buf, method, methlen)); + + return single_serve_string(sock, "HTTP/1.1 204 OK\r\n\r\n"); +} + + +/* Test that the method string passed to ne_request_create is + * strdup'ed. */ +static int dup_method(void) +{ + char method[] = "FOO"; + ne_session *sess; + ne_request *req; + + CALL(make_session(&sess, serve_check_method, method)); + + req = ne_request_create(sess, method, "/bar"); + + strcpy(method, "ZZZ"); + + ONREQ(ne_request_dispatch(req)); + ne_request_destroy(req); + ne_session_destroy(sess); + CALL(await_server()); + + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(single_get_clength), + T(single_get_eof), + T(single_get_chunked), + T(no_body_204), + T(no_body_304), + T(no_body_HEAD), + T(no_body_empty_clength), + T(no_body_bad_clength), + T(no_headers), + T(chunks), + T(te_header), + T(any_te_header), + T(reason_phrase), + T(chunk_numeric), + T(chunk_extensions), + T(chunk_trailers), + T(chunk_oversize), + T(te_over_clength), + T(te_over_clength2), + T(no_body_chunks), + T(persist_http11), + T(persist_chunked), + T(persist_http10), + T(persist_timeout), + T(no_persist_http10), + T(ptimeout_eof), + T(ptimeout_eof2), + T(closed_connection), + T(close_not_retried), + T(send_progress), + T(ignore_bad_headers), + T(fold_headers), + T(fold_many_headers), + T(multi_header), + T(empty_header), + T(trailing_header), + T(ignore_header_case), + T(ignore_header_ws), + T(ignore_header_ws2), + T(ignore_header_ws3), + T(ignore_header_tabs), + T(continued_header), + T(skip_interim_1xx), + T(skip_many_1xx), + T(skip_1xx_hdrs), + T(send_bodies), + T(expect_100_once), + T(unbounded_headers), + T(unbounded_folding), + T(blank_response), + T(not_http), + T(fail_eof_continued), + T(fail_eof_headers), + T(fail_eof_chunk), + T(fail_eof_badclen), + T(fail_long_header), + T(fail_corrupt_chunks), + T(read_timeout), + T(fail_lookup), + T(fail_double_lookup), + T(fail_connect), + T(proxy_no_resolve), + T(fail_chunksize), + T(abort_respbody), + T(retry_after_abort), + T(fail_statusline), + T(dup_method), + T(versions), + T(hook_create_req), + T(NULL) +}; diff --git a/test/resolve.c b/test/resolve.c new file mode 100644 index 0000000..bd0789e --- /dev/null +++ b/test/resolve.c @@ -0,0 +1,59 @@ +/* + Test program for the neon resolver interface + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <stdio.h> + +#include "ne_socket.h" + +int main(int argc, char **argv) +{ + ne_sock_addr *addr; + char buf[256]; + int ret = 0; + + if (argc < 2) { + printf("Usage: %s hostname\n", argv[0]); + return 1; + } + + if (ne_sock_init()) { + printf("%s: Failed to initialize socket library.\n", argv[0]); + return 1; + } + + addr = ne_addr_resolve(argv[1], 0); + if (ne_addr_result(addr)) { + printf("Could not resolve `%s': %s\n", argv[1], + ne_addr_error(addr, buf, sizeof buf)); + ret = 2; + } else { + const ne_inet_addr *ia; + printf("Resolved `%s' OK:", argv[1]); + for (ia = ne_addr_first(addr); ia; ia = ne_addr_next(addr)) { + printf(" <%s>", ne_iaddr_print(ia, buf, sizeof buf)); + } + putchar('\n'); + } + ne_addr_destroy(addr); + + return ret; +} diff --git a/test/run.sh b/test/run.sh new file mode 100644 index 0000000..7d72749 --- /dev/null +++ b/test/run.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +rm -f debug.log +rm -f child.log + +# for shared builds. +LD_LIBRARY_PATH=../src/.libs:$LD_LIBRARY_PATH + +# enable an safety-checking malloc in glibc which will abort() if +# heap corruption is detected. +MALLOC_CHECK_=2 + +export LD_LIBRARY_PATH MALLOC_CHECK_ + +for f in $*; do + if ${HARNESS} ./$f ${SRCDIR}; then + : + else + echo FAILURE + [ -z "$CARRYON" ] && exit 1 + fi +done + +exit 0 diff --git a/test/server.key b/test/server.key new file mode 100644 index 0000000..cdfb91b --- /dev/null +++ b/test/server.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAPNFTmxnz4JZA+8+SonD0qWgSBPYWrNlH1FP+psm5EGZGmGJGvSD +sk6HkyvstdopKF50UuEaJ263IorAhkmdGG0CAwEAAQJAJBhYdoVAqNqEVu8rKB3C +F4kcqLUlYBDVAL+ZM4QlwgWncAKk2C53BwH4PVWIIfyysleyt3bTAtqg/tgMNM06 +AQIhAP1HKbuppa+UY4rNP4Xcyj5BrCU4wVz77sg/ygW+mWIhAiEA9eKcUnnaIpig +hlWtx9qz++85/JtahA85j6T48v0hBM0CIQCa8ByUg2wq45CdSX+xiOZjfVMslfKb +yjZBY9xW9UjpYQIgdy9j5JqKANEIpnTran95VLot2mMXagHTPeySe331PlUCIQD0 +rL1AXeIR3Vd4D8dgab/FVbg4i94qBiY0731nyPJRoQ== +-----END RSA PRIVATE KEY----- diff --git a/test/session.c b/test/session.c new file mode 100644 index 0000000..6fe04bd --- /dev/null +++ b/test/session.c @@ -0,0 +1,158 @@ +/* + Tests for session handling + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_session.h" + +#include "tests.h" + +/* should be in a 'session.c'? */ +static int fill_uri(void) +{ + ne_uri uri = {0}; + ne_session *sess = ne_session_create("http", "localhost", 7777); + + ne_fill_server_uri(sess, &uri); + + ONN("hostname mis-match", strcmp(uri.host, "localhost")); + ONN("port mis-match", uri.port != 7777); + ONN("scheme mis-match", strcmp(uri.scheme, "http")); + + ne_session_destroy(sess); + ne_uri_free(&uri); + + return OK; +} + +static int match_hostport(const char *scheme, const char *hostname, int port, + const char *hostport) +{ + ne_session *sess = ne_session_create(scheme, hostname, port); + const char *hp = ne_get_server_hostport(sess); + ONV(strcmp(hp, hostport), + ("hostport incorrect for %s: `%s' not `%s'", scheme, hp, hostport)); + ne_session_destroy(sess); + return OK; +} + +static int hostports(void) +{ + static const struct { + const char *scheme, *hostname; + int port; + const char *hostport; + } hps[] = { + { "http", "host.name", 80, "host.name" }, + { "http", "host.name", 555, "host.name:555" }, + { "http", "host.name", 443, "host.name:443" }, + { "https", "host.name", 80, "host.name:80" }, + { "https", "host.name", 443, "host.name" }, + { "https", "host.name", 700, "host.name:700" }, + { NULL } + }; + int n; + + for (n = 0; hps[n].scheme; n++) { + CALL(match_hostport(hps[n].scheme, hps[n].hostname, + hps[n].port, hps[n].hostport)); + } + + return OK; +} + + +/* Check that ne_set_error is passing through to printf correctly. */ +static int errors(void) +{ + ne_session *sess = ne_session_create("http", "foo.com", 80); + +#define EXPECT "foo, hello world, 100, bar!" + + ne_set_error(sess, "foo, %s, %d, bar!", "hello world", 100); + + ONV(strcmp(ne_get_error(sess), EXPECT), + ("session error was `%s' not `%s'", + ne_get_error(sess), EXPECT)); +#undef EXPECT + + ne_session_destroy(sess); + return OK; +} + +#define ID1 "foo" +#define ID2 "bar" + +static int privates(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 80); + char *v1 = "hello", *v2 = "world"; + + ne_set_session_private(sess, ID1, v1); + ne_set_session_private(sess, ID2, v2); + +#define PRIV(msg, id, val) \ +ONN(msg, ne_get_session_private(sess, id) != val) + + PRIV("private #1 wrong", ID1, v1); + PRIV("private #2 wrong", ID2, v2); + PRIV("unknown id wrong", "no such ID", NULL); + + ne_session_destroy(sess); + return OK; +} + +/* test that ne_session_create doesn't really care what scheme you + * give it, and that ne_get_scheme() works. */ +static int get_scheme(void) +{ + static const char *schemes[] = { + "http", "https", "ftp", "ldap", "foobar", NULL + }; + int n; + + for (n = 0; schemes[n]; n++) { + ne_session *sess = ne_session_create(schemes[n], "localhost", 80); + ONV(strcmp(ne_get_scheme(sess), schemes[n]), + ("scheme was `%s' not `%s'!", ne_get_scheme(sess), schemes[n])); + ne_session_destroy(sess); + } + + return OK; +} + +ne_test tests[] = { + T(fill_uri), + T(hostports), + T(errors), + T(privates), + T(get_scheme), + T(NULL) +}; + diff --git a/test/skeleton.c b/test/skeleton.c new file mode 100644 index 0000000..9574bdb --- /dev/null +++ b/test/skeleton.c @@ -0,0 +1,51 @@ +/* + neon test suite + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int foo(void) +{ + /* This is a skeleton test suite file. */ + return OK; +} + +ne_test tests[] = { + T(foo), /* test functions here */ + + /* end of test functions. */ + T(NULL) +}; + diff --git a/test/socket.c b/test/socket.c new file mode 100644 index 0000000..26b107b --- /dev/null +++ b/test/socket.c @@ -0,0 +1,918 @@ +/* + Socket handling tests + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* This module can be compiled with -DSOCKET_SSL enabled, to run all + * the tests over an SSL connection. */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/socket.h> /* for AF_INET6 */ + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#include <time.h> /* for time() */ + +#include "ne_socket.h" +#include "ne_utils.h" +#include "ne_alloc.h" + +#include "child.h" +#include "tests.h" +#include "utils.h" + +#ifdef SOCKET_SSL +#include <openssl/err.h> +#include <openssl/ssl.h> + +#include "ne_ssl.h" + + +SSL_CTX *server_ctx; +ne_ssl_context *client_ctx; +#endif + +static ne_sock_addr *localhost; +static char buffer[BUFSIZ]; + +#if defined(AF_INET6) && defined(USE_GETADDRINFO) +#define TEST_IPV6 +#endif + +/* tests for doing init/finish multiple times. */ +static int multi_init(void) +{ + int res1 = ne_sock_init(), res2 = ne_sock_init(); + + ONV(res1 != res2, ("cached init result changed from %d to %d", + res1, res2)); + + ne_sock_exit(); + res1 = ne_sock_init(); + + ONV(res1 != res2, ("re-init after exit gave %d not %d", + res1, res2)); + + res2 = ne_sock_init(); + + ONV(res1 != res2, ("second time, cached init result changed from %d to %d", + res1, res2)); + + return OK; +} + +static ne_socket *do_connect(ne_sock_addr *addr, unsigned int port) +{ + ne_socket *sock = ne_sock_create(); + const ne_inet_addr *ia; + + if (!sock) return NULL; + + for (ia = ne_addr_first(addr); ia; ia = ne_addr_next(addr)) { + if (ne_sock_connect(sock, ia, port) == 0) + return sock; + } + + ne_sock_close(sock); + return NULL; +} + +#ifdef SOCKET_SSL + +/* FIXME: largely cut'n'pasted from ssl.c. */ +static int init_ssl(void) +{ + char *server_key; + ne_ssl_certificate *cert; + + /* take srcdir as argv[1]. */ + if (test_argc > 1) { + server_key = ne_concat(test_argv[1], "/server.key", NULL); + } else { + server_key = "server.key"; + } + + ONN("sock_init failed.\n", ne_sock_init()); + server_ctx = SSL_CTX_new(SSLv23_server_method()); + ONN("SSL_CTX_new failed", server_ctx == NULL); + ONN("failed to load private key", + !SSL_CTX_use_PrivateKey_file(server_ctx, server_key, + SSL_FILETYPE_PEM)); + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, "server.cert", + SSL_FILETYPE_PEM)); + + client_ctx = ne_ssl_context_create(); + ONN("SSL_CTX_new failed for client", client_ctx == NULL); + + cert = ne_ssl_cert_read("ca/cert.pem"); + ONN("could not load ca/cert.pem", cert == NULL); + + ne_ssl_ctx_trustcert(client_ctx, cert); + + return OK; +} +#endif + +static int resolve(void) +{ + char buf[256]; + localhost = ne_addr_resolve("localhost", 0); + ONV(ne_addr_result(localhost), + ("could not resolve `localhost': %s", + ne_addr_error(localhost, buf, sizeof buf))); + /* and again for child.c */ + return lookup_localhost(); +} + +static int serve_close(ne_socket *sock, void *ud) +{ + return 0; +} + +#ifdef SOCKET_SSL +struct serve_pair { + server_fn fn; + void *userdata; +}; + +static int wrap_serve(ne_socket *sock, void *ud) +{ + struct serve_pair *pair = ud; + int fd = ne_sock_fd(sock), ret; + SSL *ssl = SSL_new(server_ctx); + BIO *bio = BIO_new_socket(fd, BIO_NOCLOSE); + + ONN("SSL_new failed", ssl == NULL); + SSL_set_bio(ssl, bio, bio); + +#define ERROR_SSL_STRING (ERR_reason_error_string(ERR_get_error())) + + NE_DEBUG(NE_DBG_SOCKET, "Doing SSL accept:\n"); + ret = SSL_accept(ssl); + if (ret != 1) { + NE_DEBUG(NE_DBG_SOCKET, "SSL_accept failed: %s\n", ERROR_SSL_STRING); + return 1; + } + + NE_DEBUG(NE_DBG_SOCKET, "SSL accept okay.\n"); + ne_sock_switch_ssl(sock, ssl); + return pair->fn(sock, pair->userdata); +} + +static int begin(ne_socket **sock, server_fn fn, void *ud) +{ + struct serve_pair pair; + pair.fn = fn; + pair.userdata = ud; + CALL(spawn_server(7777, wrap_serve, &pair)); + *sock = do_connect(localhost, 7777); + ONN("could not connect to localhost:7777", *sock == NULL); + ONV(ne_sock_connect_ssl(*sock, client_ctx), + ("SSL negotation failed: %s", ne_sock_error(*sock))); + return OK; +} + +#else +/* non-SSL begin() function. */ +static int begin(ne_socket **sock, server_fn fn, void *ud) +{ + CALL(spawn_server(7777, fn, ud)); + *sock = do_connect(localhost, 7777); + ONN("could not connect to localhost:7777", *sock == NULL); + return OK; +} +#endif + +static int resolve_numeric(void) +{ + ne_sock_addr *addr = ne_addr_resolve("127.0.0.1", 0); + ONV(ne_addr_result(addr), + ("failed to resolve 127.0.0.1: %s", + ne_addr_error(addr, buffer, sizeof buffer))); + ONN("ne_addr_first returned NULL", ne_addr_first(addr) == NULL); + ONN("ne_iaddr_print didn't return buffer", + ne_iaddr_print(ne_addr_first(addr), buffer, sizeof buffer) != buffer); + ONV(strcmp(buffer, "127.0.0.1"), ("ntop gave `%s' not 127.0.0.1", buffer)); + ne_addr_destroy(addr); + return OK; +} + +#if 0 +static int resolve_ipv6(void) +{ + char err[256]; + ne_sock_addr *addr = ne_addr_resolve("[::1]", 0); + ONV(ne_addr_result(addr), + ("could not resolve `[::1]': %s", + ne_addr_error(addr, err, sizeof err))); + ne_addr_destroy(addr); + return OK; +} +#endif + +static const unsigned char raw_127[4] = "\x7f\0\0\01", /* 127.0.0.1 */ +raw6_nuls[16] = /* :: */ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +#ifdef TEST_IPV6 +static const unsigned char +raw6_cafe[16] = /* feed::cafe */ "\xfe\xed\0\0\0\0\0\0\0\0\0\0\0\0\xca\xfe", +raw6_babe[16] = /* cafe:babe:: */ "\xca\xfe\xba\xbe\0\0\0\0\0\0\0\0\0\0\0\0"; +#endif + +static int addr_make_v4(void) +{ + ne_inet_addr *ia; + char pr[50]; + + ia = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ONN("ne_iaddr_make returned NULL", ia == NULL); + + ne_iaddr_print(ia, pr, sizeof pr); + ONV(strcmp(pr, "127.0.0.1"), ("address was %s not 127.0.0.1", pr)); + + ne_iaddr_free(ia); + return OK; +} + +static int addr_make_v6(void) +{ +#ifdef TEST_IPV6 + struct { + const unsigned char *addr; + const char *rep; + } as[] = { + { raw6_cafe, "feed::cafe" }, + { raw6_babe, "cafe:babe::" }, + { raw6_nuls, "::" }, + { NULL, NULL } + }; + int n; + + for (n = 0; as[n].rep != NULL; n++) { + ne_inet_addr *ia = ne_iaddr_make(ne_iaddr_ipv6, as[n].addr); + char pr[128]; + + ONV(ia == NULL, ("could not make address for %d", n)); + ne_iaddr_print(ia, pr, sizeof pr); + ONV(strcmp(pr, as[n].rep), + ("address %d was '%s' not '%s'", n, pr, as[n].rep)); + + ne_iaddr_free(ia); + } + + return OK; +#else + /* should fail when lacking IPv6 support. */ + ne_inet_addr *ia = ne_iaddr_make(ne_iaddr_ipv6, raw6_nuls); + ONN("ne_iaddr_make did not return NULL", ia != NULL); +#endif + return OK; +} + +static int addr_compare(void) +{ + ne_inet_addr *ia1, *ia2; + int ret; + + ia1 = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ia2 = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ONN("addr_make returned NULL", !ia1 || !ia2); + + ret = ne_iaddr_cmp(ia1, ia2); + ONV(ret != 0, ("comparison of equal IPv4 addresses was %d", ret)); + ne_iaddr_free(ia2); + + ia2 = ne_iaddr_make(ne_iaddr_ipv4, "abcd"); + ret = ne_iaddr_cmp(ia1, ia2); + ONN("comparison of unequal IPv4 addresses was zero", ret == 0); + +#ifdef TEST_IPV6 + ne_iaddr_free(ia2); + ia2 = ne_iaddr_make(ne_iaddr_ipv6, "feed::1"); + ret = ne_iaddr_cmp(ia1, ia2); + ONN("comparison of IPv4 and IPv6 addresses was zero", ret == 0); + + ne_iaddr_free(ia1); + ia1 = ne_iaddr_make(ne_iaddr_ipv4, "feed::1"); + ret = ne_iaddr_cmp(ia1, ia2); + ONN("comparison of equal IPv6 addresses was zero", ret == 0); + +#endif + + ne_iaddr_free(ia1); + ne_iaddr_free(ia2); + return OK; +} + +static int just_connect(void) +{ + ne_socket *sock; + + CALL(begin(&sock, serve_close, NULL)); + ne_sock_close(sock); + return await_server(); +} + +/* Connect to an address crafted using ne_iaddr_make rather than from + * the resolver. */ +static int addr_connect(void) +{ + ne_socket *sock = ne_sock_create(); + ne_inet_addr *ia; + + ia = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ONN("ne_iaddr_make returned NULL", ia == NULL); + + CALL(spawn_server(7777, serve_close, NULL)); + ONN("could not connect", ne_sock_connect(sock, ia, 7777)); + ne_sock_close(sock); + CALL(await_server()); + + ne_iaddr_free(ia); + return OK; +} + +/* Exect a read() to return EOF */ +static int expect_close(ne_socket *sock) +{ + ssize_t n = ne_sock_read(sock, buffer, 1); + ONV(n > 0, ("read got %" NE_FMT_SSIZE_T "bytes not closure", n)); + ONV(n < 0 && n != NE_SOCK_CLOSED, + ("read got error not closure: `%s'", ne_sock_error(sock))); + return OK; +} + +static int good_close(ne_socket *sock) +{ + NE_DEBUG(NE_DBG_SOCKET, "Socket error was %s\n", ne_sock_error(sock)); + ONN("close failed", ne_sock_close(sock)); + return OK; +} + +/* Finish a test, closing socket and rejoining child. If eof is non-zero, + * expects to read EOF from the socket before closing. */ +static int finish(ne_socket *sock, int eof) +{ + if (eof) + CALL(expect_close(sock)); + CALL(good_close(sock)); + return await_server(); +} + +/* Exect a ne_sock_peek() to return EOF */ +static int expect_peek_close(ne_socket *sock) +{ + ssize_t n = ne_sock_read(sock, buffer, 1); + ONV(n != NE_SOCK_CLOSED, ("peek gave %" NE_FMT_SSIZE_T " not closure", n)); + return OK; +} + +/* Test that just does a connect then a close. */ +static int read_close(void) +{ + ne_socket *sock; + + CALL(begin(&sock, serve_close, NULL)); + CALL(expect_close(sock)); + ONN("close failed", ne_sock_close(sock)); + return await_server(); +} + +/* Test that just does a connect then a close (but gets the close via + * ne_sock_peek). */ +static int peek_close(void) +{ + ne_socket *sock; + + CALL(begin(&sock, serve_close, NULL)); + CALL(expect_peek_close(sock)); + ONN("close failed", ne_sock_close(sock)); + return await_server(); +} + + +struct string { + char *data; + size_t len; +}; + +static int serve_string(ne_socket *sock, void *ud) +{ + struct string *str = ud; + + NE_DEBUG(NE_DBG_SOCKET, "Serving string: [[[%.*s]]]\n", + (int)str->len, str->data); + + ONN("write failed", ne_sock_fullwrite(sock, str->data, str->len)); + + return 0; +} + +static int serve_string_slowly(ne_socket *sock, void *ud) +{ + struct string *str = ud; + size_t n; + + NE_DEBUG(NE_DBG_SOCKET, "Slowly serving string: [[[%.*s]]]\n", + (int)str->len, str->data); + + for (n = 0; n < str->len; n++) { + ONN("write failed", ne_sock_fullwrite(sock, &str->data[n], 1)); + minisleep(); + } + + return 0; +} + +/* Don't change this string. */ +#define STR "Hello, World." + +/* do a sock_peek() on sock for 'len' bytes, and expect 'str'. */ +static int peek_expect(ne_socket *sock, const char *str, size_t len) +{ + ssize_t ret = ne_sock_peek(sock, buffer, len); + ONV((ssize_t)len != ret, + ("peek got %" NE_FMT_SSIZE_T " bytes not %" NE_FMT_SIZE_T, ret, len)); + ONV(memcmp(str, buffer, len), + ("peek mismatch: `%.*s' not `%.*s'", + (int)len, buffer, (int)len, str)); + return OK; +} + +/* do a sock_read() on sock for 'len' bytes, and expect 'str'. */ +static int read_expect(ne_socket *sock, const char *str, size_t len) +{ + ssize_t ret = ne_sock_read(sock, buffer, len); + ONV((ssize_t)len != ret, + ("peek got %" NE_FMT_SSIZE_T " bytes not %" NE_FMT_SIZE_T, ret, len)); + ONV(memcmp(str, buffer, len), + ("read mismatch: `%.*s' not `%.*s'", + (int)len, buffer, (int)len, str)); + return OK; +} + +/* Declare a struct string */ +#define DECL(var,str) struct string var = { str, 0 }; var.len = strlen(str) + +/* Test a simple read. */ +static int single_read(void) +{ + ne_socket *sock; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + CALL(read_expect(sock, STR, strlen(STR))); + CALL(expect_close(sock)); + CALL(good_close(sock)); + + return await_server(); +} + +/* Test a simple peek. */ +static int single_peek(void) +{ + ne_socket *sock; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + CALL(peek_expect(sock, STR, strlen(STR))); + + return finish(sock, 0); +} + +/* Test lots of 1-byte reads. */ +static int small_reads(void) +{ + ne_socket *sock; + char *pnt; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + + /* read the string byte-by-byte. */ + for (pnt = hello.data; *pnt; pnt++) { + CALL(read_expect(sock, pnt, 1)); + } + + return finish(sock, 1); +} + +/* peek or read, expecting to get given string. */ +#define READ(str) CALL(read_expect(sock, str, strlen(str))) +#define PEEK(str) CALL(peek_expect(sock, str, strlen(str))) + +/* Stress out the read buffer handling a little. */ +static int read_and_peek(void) +{ + ne_socket *sock; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + + PEEK("Hello"); + PEEK("Hell"); + PEEK(STR); + READ("He"); + PEEK("llo, "); + READ("l"); + PEEK("lo, World."); + READ("lo, Worl"); + PEEK("d."); PEEK("d"); + READ("d."); + + return finish(sock, 1); +} + +/* Read more bytes than were written. */ +static int larger_read(void) +{ + ne_socket *sock; + ssize_t nb; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + + nb = ne_sock_read(sock, buffer, hello.len + 10); + ONV(nb != (ssize_t)hello.len, + ("read gave too many bytes (%" NE_FMT_SSIZE_T ")", nb)); + ONN("read gave wrong data", memcmp(buffer, hello.data, hello.len)); + + return finish(sock, 1); +} + +static int line_expect(ne_socket *sock, const char *line) +{ + ssize_t ret = ne_sock_readline(sock, buffer, BUFSIZ); + size_t len = strlen(line); + ONV(ret == NE_SOCK_CLOSED, ("socket closed, expecting `%s'", line)); + ONV(ret < 0, ("socket error `%s', expecting `%s'", + ne_sock_error(sock), line)); + ONV((size_t)ret != len, + ("readline got %" NE_FMT_SSIZE_T ", expecting `%s'", ret, line)); + ONV(strcmp(line, buffer), + ("readline mismatch: `%s' not `%s'", buffer, line)); + return OK; +} + +#define LINE(x) CALL(line_expect(sock, x)) + +#define STR2 "Goodbye, cruel world." + +static int line_simple(void) +{ + ne_socket *sock; + DECL(oneline, STR "\n" STR2 "\n"); + + CALL(begin(&sock, serve_string, &oneline)); + LINE(STR "\n"); + LINE(STR2 "\n"); + + return finish(sock, 1); +} + +static int line_closure(void) +{ + ne_socket *sock; + ssize_t ret; + DECL(oneline, STR "\n" "foobar"); + + CALL(begin(&sock, serve_string, &oneline)); + LINE(STR "\n"); + + ret = ne_sock_readline(sock, buffer, BUFSIZ); + ONV(ret != NE_SOCK_CLOSED, + ("readline got %" NE_FMT_SSIZE_T " not EOF", ret)); + + return finish(sock, 0); +} + +/* check that empty lines are handled correctly. */ +static int line_empty(void) +{ + ne_socket *sock; + DECL(oneline, "\n\na\n\n"); + + CALL(begin(&sock, serve_string, &oneline)); + LINE("\n"); LINE("\n"); + LINE("a\n"); LINE("\n"); + + return finish(sock, 1); +} + +static int line_toolong(void) +{ + ne_socket *sock; + ssize_t ret; + DECL(oneline, "AAAAAA\n"); + + CALL(begin(&sock, serve_string, &oneline)); + ret = ne_sock_readline(sock, buffer, 5); + ONV(ret != NE_SOCK_ERROR, + ("readline should fail on long line: %" NE_FMT_SSIZE_T, ret)); + + return finish(sock, 0); +} + +/* readline()s mingled with other operations: buffering tests. */ +static int line_mingle(void) +{ + ne_socket *sock; + DECL(oneline, "alpha\nbeta\ndelta\ngamma\n"); + + CALL(begin(&sock, serve_string, &oneline)); + + READ("a"); LINE("lpha\n"); + READ("beta"); LINE("\n"); + PEEK("d"); PEEK("delt"); + LINE("delta\n"); + READ("gam"); LINE("ma\n"); + + return finish(sock, 1); +} + +/* readline which needs multiple read() calls. */ +static int line_chunked(void) +{ + ne_socket *sock; + DECL(oneline, "this is a line\n"); + + CALL(begin(&sock, serve_string_slowly, &oneline)); + + LINE("this is a line\n"); + + return finish(sock, 1); +} + +static time_t to_start, to_finish; + +static int to_begin(ne_socket **sock) +{ + CALL(begin(sock, sleepy_server, NULL)); + ne_sock_read_timeout(*sock, 1); + to_start = time(NULL); + return OK; +} + +static int to_end(ne_socket *sock) +{ + to_finish = time(NULL); + reap_server(); /* hopefully it's hung. */ + ONN("timeout ignored, or very slow machine", to_finish - to_start > 3); + ONN("close failed", ne_sock_close(sock)); + return OK; +} + +#define TO_BEGIN ne_socket *sock; CALL(to_begin(&sock)) +#define TO_OP(x) do { int to_ret = (x); \ +ONV(to_ret != NE_SOCK_TIMEOUT, ("operation did not timeout: %d", to_ret)); \ +} while (0) +#define TO_FINISH return to_end(sock) + +static int peek_timeout(void) +{ + TO_BEGIN; + TO_OP(ne_sock_peek(sock, buffer, 1)); + TO_FINISH; +} + +static int read_timeout(void) +{ + TO_BEGIN; + TO_OP(ne_sock_read(sock, buffer, 1)); + TO_FINISH; +} + +static int readline_timeout(void) +{ + TO_BEGIN; + TO_OP(ne_sock_readline(sock, buffer, 1)); + TO_FINISH; +} + +static int fullread_timeout(void) +{ + TO_BEGIN; + TO_OP(ne_sock_fullread(sock, buffer, 1)); + TO_FINISH; +} + +static int serve_expect(ne_socket *sock, void *ud) +{ + struct string *str = ud; + ssize_t ret; + + while (str->len && + (ret = ne_sock_read(sock, buffer, sizeof(buffer))) > 0) { + NE_DEBUG(NE_DBG_SOCKET, "Got %" NE_FMT_SSIZE_T " bytes.\n", ret); + ONV(memcmp(str->data, buffer, ret), + ("unexpected data: [%.*s] not [%.*s]", + (int)ret, buffer, (int)ret, str->data)); + str->data += ret; + str->len -= ret; + NE_DEBUG(NE_DBG_SOCKET, "%" NE_FMT_SIZE_T " bytes left.\n", str->len); + } + + NE_DEBUG(NE_DBG_SOCKET, "All data read.\n"); + return OK; +} + +static int full_write(ne_socket *sock, const char *data, size_t len) +{ + int ret = ne_sock_fullwrite(sock, data, len); + ONV(ret, ("write failed (%d): %s", ret, ne_sock_error(sock))); + return OK; +} + +#define WRITEL(str) CALL(full_write(sock, str, strlen(str))); \ +minisleep() + +static int small_writes(void) +{ + ne_socket *sock; + DECL(str, "This\nIs\nSome\nText.\n"); + + CALL(begin(&sock, serve_expect, &str)); + + WRITEL("This\n"); WRITEL("Is\n"); WRITEL("Some\n"); WRITEL("Text.\n"); + + return finish(sock, 1); +} + +static int large_writes(void) +{ +#define LARGE_SIZE (123456) + struct string str; + ne_socket *sock; + ssize_t n; + + str.data = ne_malloc(LARGE_SIZE); + str.len = LARGE_SIZE; + + for (n = 0; n < LARGE_SIZE; n++) + str.data[n] = 41 + n % 130; + + CALL(begin(&sock, serve_expect, &str)); + CALL(full_write(sock, str.data, str.len)); + + ne_free(str.data); + return finish(sock, 1); +} + +/* echoes back lines. */ +static int echo_server(ne_socket *sock, void *ud) +{ + ssize_t ret; + + while ((ret = ne_sock_readline(sock, buffer, sizeof(buffer))) > 0) { + NE_DEBUG(NE_DBG_SOCKET, "Line: %s", buffer); + ONN("write failed", ne_sock_fullwrite(sock, buffer, ret)); + NE_DEBUG(NE_DBG_SOCKET, "Wrote line.\n"); + } + + ONN("readline failed", ret != NE_SOCK_CLOSED); + return 0; +} + +static int echo_expect(ne_socket *sock, const char *line) +{ + CALL(full_write(sock, line, strlen(line))); + return line_expect(sock, line); +} + +#define ECHO(line) CALL(echo_expect(sock, line)) + +static int echo_lines(void) +{ + ne_socket *sock; + CALL(begin(&sock, echo_server, NULL)); + ECHO("hello,\n"); + ECHO("\n"); + ECHO("world\n"); + return finish(sock, 0); +} + +#ifdef SOCKET_SSL +/* harder to simulate closure cases for an SSL connection, since it + * may be doing multiple read()s or write()s per ne_sock_* call. */ +static int ssl_closure(void) +{ + ne_socket *sock; + ssize_t ret; + CALL(begin(&sock, serve_close, NULL)); + CALL(full_write(sock, "a", 1)); + CALL(await_server()); + ret = ne_sock_fullwrite(sock, "a", 1); + ONV(ret != NE_SOCK_RESET && ret != NE_SOCK_CLOSED, + ("write got %" NE_FMT_SSIZE_T " not reset or closure", ret)); + return good_close(sock); +} + +static int serve_truncate(ne_socket *sock, void *userdata) +{ + exit(0); +} + +/* when an EOF is received without a clean shutdown (close_notify + message). */ +static int ssl_truncate(void) +{ + ne_socket *sock; int ret; + + CALL(begin(&sock, serve_truncate, NULL)); + ret = ne_sock_read(sock, buffer, 1); + ONV(ret != NE_SOCK_TRUNC, + ("socket got error %d not truncation: `%s'", ret, + ne_sock_error(sock))); + return finish(sock, 0); +} + +#else +/* thanks to W Richard Stevens for the precise repro case for getting + * an RST on demand. */ +static int write_reset(void) +{ + ne_socket *sock; + int ret; + CALL(begin(&sock, serve_close, NULL)); + CALL(full_write(sock, "a", 1)); + CALL(await_server()); + ret = ne_sock_fullwrite(sock, "a", 1); + ONV(ret != NE_SOCK_RESET, ("write got %d not reset", ret)); + return good_close(sock); +} + +static int read_reset(void) +{ + ne_socket *sock; + ssize_t ret; + CALL(begin(&sock, serve_close, NULL)); + CALL(full_write(sock, "a", 1)); + CALL(await_server()); + ret = ne_sock_read(sock, buffer, 1); + ONV(ret != NE_SOCK_RESET, ("read got %" NE_FMT_SSIZE_T " not reset", ret)); + return good_close(sock); +} +#endif + +ne_test tests[] = { + T(multi_init), + T_LEAKY(resolve), + T(resolve_numeric), +#ifdef SOCKET_SSL + T_LEAKY(init_ssl), +#endif + T(addr_make_v4), + T(addr_make_v6), + T(addr_compare), + T(just_connect), + T(addr_connect), + T(read_close), + T(peek_close), + T(single_read), + T(single_peek), + T(small_reads), + T(read_and_peek), + T(larger_read), + T(line_simple), + T(line_closure), + T(line_empty), + T(line_toolong), + T(line_mingle), + T(line_chunked), + T(small_writes), + T(large_writes), + T(echo_lines), +#ifdef SOCKET_SSL + T(ssl_closure), + T(ssl_truncate), +#else + T(write_reset), + T(read_reset), +#endif + T(read_timeout), + T(peek_timeout), + T(readline_timeout), + T(fullread_timeout), + T(NULL) +}; diff --git a/test/ssl.c b/test/ssl.c new file mode 100644 index 0000000..7fc94e4 --- /dev/null +++ b/test/ssl.c @@ -0,0 +1,1476 @@ +/* + neon test suite + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <sys/stat.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" +#include "ne_auth.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +#ifndef NEON_SSL +/* this file shouldn't be built if SSL is not enabled. */ +#error SSL not supported +#endif + +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/evp.h> + +#define ERROR_SSL_STRING (ERR_reason_error_string(ERR_get_error())) + +#define SERVER_CERT "server.cert" +#define CA_CERT "ca/cert.pem" + +#define SERVER_DNAME "Neon QA Dept, Neon Hackers Ltd, " \ + "Cambridge, Cambridgeshire, GB" +#define CACERT_DNAME "Random Dept, Neosign, Oakland, California, US" + +static SSL_CTX *server_ctx = NULL; + +static char *srcdir = "."; + +static ne_ssl_certificate *def_ca_cert = NULL, *def_server_cert; +static ne_ssl_client_cert *def_cli_cert; + +static int check_dname(const ne_ssl_dname *dn, const char *expected, + const char *which); + +static int s_strwrite(SSL *s, const char *buf) +{ + size_t len = strlen(buf); + + ONV(SSL_write(s, buf, len) != (int)len, + ("SSL_write failed: %s", ERROR_SSL_STRING)); + + return OK; +} + +/* Do an SSL response over socket given context; returning ssl session + * structure in *sess if sess is non-NULL. */ +static int do_ssl_response(ne_socket *sock, SSL_CTX *ctx, SSL_SESSION **sess, + const char *resp, int unclean) +{ + int fd = ne_sock_fd(sock), ret; + /* we don't want OpenSSL to close this socket for us. */ + BIO *bio = BIO_new_socket(fd, BIO_NOCLOSE); + char buf[BUFSIZ]; + SSL *ssl = SSL_new(ctx); + + ONN("SSL_new failed", ssl == NULL); + + SSL_set_bio(ssl, bio, bio); + + ONV(SSL_accept(ssl) != 1, + ("SSL_accept failed: %s", ERROR_SSL_STRING)); + + ret = SSL_read(ssl, buf, BUFSIZ - 1); + if (ret == 0) + return 0; /* connection closed by parent; give up. */ + ONV(ret < 0, ("SSL_read failed (%d): %s", ret, ERROR_SSL_STRING)); + + buf[ret] = '\0'; + + NE_DEBUG(NE_DBG_HTTP, "Request over SSL was: [%s]\n", buf); + + ONN("request over SSL contained Proxy-Authorization header", + strstr(buf, "Proxy-Authorization:") != NULL); + + CALL(s_strwrite(ssl, resp)); + + /* copy out the session if requested. */ + if (sess) { + *sess = SSL_get1_session(ssl); + } + + if (!unclean) { + /* Erk, shutdown is messy! See Eric Rescorla's article: + * http://www.linuxjournal.com/article.php?sid=4822 ; we'll just + * hide our heads in the sand here. */ + SSL_shutdown(ssl); + SSL_free(ssl); + } + + return 0; +} + +#define DEF_RESP "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n" + +/* Standard server callback to send an HTTP response; SSL negotiated + * using certificate passed as userdata. */ +static int serve_ssl(ne_socket *sock, void *ud) +{ + const char *cert = ud; + + NE_DEBUG(NE_DBG_HTTP, "using server cert %s\n", cert); + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, cert, SSL_FILETYPE_PEM)); + + CALL(do_ssl_response(sock, server_ctx, NULL, DEF_RESP, 0)); + + return OK; +} + +static int serve_response_unclean(ne_socket *sock, void *ud) +{ + const char *resp = ud; + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, + SERVER_CERT, SSL_FILETYPE_PEM)); + + CALL(do_ssl_response(sock, server_ctx, NULL, resp, 1)); + + return OK; +} + +/* Server function which requires the use of a client cert. + * 'userdata' must be the name of the file giving acceptable CA + * certificates. */ +static int serve_ccert(ne_socket *sock, void *ud) +{ + const char *calist = ud; + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, SERVER_CERT, SSL_FILETYPE_PEM)); + + /* require a client cert. */ + SSL_CTX_set_verify(server_ctx, SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + + /* load the CA used to verify the client cert. */ + ONN("failed to load CA cert", + SSL_CTX_load_verify_locations(server_ctx, CA_CERT, NULL) != 1); + + if (calist) { + /* send acceptable CA cert list to the client */ + SSL_CTX_set_client_CA_list(server_ctx, SSL_load_client_CA_file(calist)); + } + + CALL(do_ssl_response(sock, server_ctx, NULL, DEF_RESP, 0)); + + return OK; +} + +/* serve_ssl wrapper which ignores server failure and always succeeds */ +static int fail_serve(ne_socket *sock, void *ud) +{ + serve_ssl(sock, ud); + return OK; +} + +/* Wrapper for serve_ssl which registers the verify location, so that + * the CA cert will be sent along with the server cert itself in the + * certificate exchange. */ +static int serve_ssl_chained(ne_socket *sock, void *ud) +{ + SSL_CTX_load_verify_locations(server_ctx, "ca/cert.pem", NULL); + return serve_ssl(sock, ud); +} + +#define DEFSESS (ne_session_create("https", "localhost", 7777)) + +/* Run a request in the given session. */ +static int any_ssl_request(ne_session *sess, server_fn fn, void *server_ud, + char *ca_cert, + ne_ssl_verify_fn verify_fn, void *verify_ud) +{ + int ret; + + if (ca_cert) { + ne_ssl_certificate *ca = ne_ssl_cert_read(ca_cert); + ONV(ca == NULL, ("could not load CA cert `%s'", ca_cert)); + ne_ssl_trust_cert(sess, ca); + ne_ssl_cert_free(ca); + } + + CALL(spawn_server(7777, fn, server_ud)); + + if (verify_fn) + ne_ssl_set_verify(sess, verify_fn, verify_ud); + + ret = any_request(sess, "/foo"); + + CALL(await_server()); + + ONREQ(ret); + + return OK; +} + +static int init(void) +{ + char *server_key; + + /* take srcdir as argv[1]. */ + if (test_argc > 1) { + srcdir = test_argv[1]; + server_key = ne_concat(srcdir, "/server.key", NULL); + } else { + server_key = "server.key"; + } + + if (ne_sock_init()) { + t_context("could not initialize socket/SSL library."); + return FAILHARD; + } + + server_ctx = SSL_CTX_new(SSLv23_server_method()); + if (server_ctx == NULL) { + t_context("could not create SSL_CTX: %s", ERROR_SSL_STRING); + return FAILHARD; + } else if (!SSL_CTX_use_PrivateKey_file(server_ctx, server_key, + SSL_FILETYPE_PEM)) { + t_context("failed to load private key: %s", ERROR_SSL_STRING); + return FAILHARD; + } + + def_ca_cert = ne_ssl_cert_read(CA_CERT); + if (def_ca_cert == NULL) { + t_context("couldn't load CA cert %s", CA_CERT); + return FAILHARD; + } + + def_server_cert = ne_ssl_cert_read(SERVER_CERT); + if (def_server_cert == NULL) { + t_context("couldn't load server cert %s", SERVER_CERT); + return FAILHARD; + } + + /* tests for the encrypted client cert, client.p12 */ + def_cli_cert = ne_ssl_clicert_read("client.p12"); + ONN("could not load client.p12", def_cli_cert == NULL); + + ONN("client.p12 is not encrypted!?", + !ne_ssl_clicert_encrypted(def_cli_cert)); + + ONN("failed to decrypt client.p12", + ne_ssl_clicert_decrypt(def_cli_cert, "foobar")); + + return OK; +} + +/* just check the result codes of loading server certs. */ +static int load_server_certs(void) +{ + ne_ssl_certificate *cert; + + cert = ne_ssl_cert_read("Makefile"); + ONN("invalid CA cert file loaded successfully", cert != NULL); + + cert = ne_ssl_cert_read("nonesuch.pem"); + ONN("non-existent 'nonesuch.pem' loaded successfully", cert != NULL); + + cert = ne_ssl_cert_read("ssigned.pem"); + ONN("could not load ssigned.pem", cert == NULL); + ne_ssl_cert_free(cert); + + return OK; +} + +static int trust_default_ca(void) +{ + ne_session *sess = DEFSESS; + ne_ssl_trust_default_ca(sess); + ne_session_destroy(sess); + return OK; +} + +#define CC_NAME "Just A Neon Client Cert" + +/* Tests for loading client certificates */ +static int load_client_cert(void) +{ + ne_ssl_client_cert *cc; + const ne_ssl_certificate *cert; + const char *name; + + cc = ne_ssl_clicert_read("client.p12"); + ONN("could not load client.p12", cc == NULL); + ONN("client.p12 not encrypted!?", !ne_ssl_clicert_encrypted(cc)); + name = ne_ssl_clicert_name(cc); + ONN("no friendly name given", name == NULL); + ONV(strcmp(name, CC_NAME), ("friendly name was %s not %s", name, CC_NAME)); + ONN("failed to decrypt", ne_ssl_clicert_decrypt(cc, "foobar")); + ne_ssl_clicert_free(cc); + + cc = ne_ssl_clicert_read("client.p12"); + ONN("decrypted client.p12 with incorrect password!?", + ne_ssl_clicert_decrypt(cc, "barfoo") == 0); + ne_ssl_clicert_free(cc); + + /* tests for the unencrypted client cert, client2.p12 */ + cc = ne_ssl_clicert_read("unclient.p12"); + ONN("could not load unencrypted cert unclient.p12", cc == NULL); + ONN("unencrypted cert marked encrypted?", ne_ssl_clicert_encrypted(cc)); + cert = ne_ssl_clicert_owner(cc); + ONN("client cert had no certificate", cert == NULL); + CALL(check_dname(ne_ssl_cert_subject(cert), + "Neon Client Cert, Neon Hackers Ltd, " + "Cambridge, Cambridgeshire, GB", + "client cert subject")); + CALL(check_dname(ne_ssl_cert_issuer(cert), CACERT_DNAME, + "client cert issuer")); + ne_ssl_clicert_free(cc); + + /* test for ccert without a friendly name, noclient.p12 */ + cc = ne_ssl_clicert_read("noclient.p12"); + ONN("could not load noclient.p12", cc == NULL); + name = ne_ssl_clicert_name(cc); + ONV(name != NULL, ("noclient.p12 had friendly name `%s'", name)); + ne_ssl_clicert_free(cc); + + /* tests for loading bogus files. */ + cc = ne_ssl_clicert_read("Makefile"); + ONN("loaded Makefile as client cert!?", cc != NULL); + + /* test for loading nonexistent file. */ + cc = ne_ssl_clicert_read("nosuch.pem"); + ONN("loaded nonexistent file as client cert!?", cc != NULL); + + return OK; +} + +/* Test that 'cert', which is signed by CA_CERT, is accepted + * unconditionaly. */ +static int accept_signed_cert(char *cert) +{ + ne_session *sess = DEFSESS; + /* no verify callback needed. */ + CALL(any_ssl_request(sess, serve_ssl, cert, CA_CERT, NULL, NULL)); + ne_session_destroy(sess); + return OK; +} + +static int simple(void) +{ + return accept_signed_cert(SERVER_CERT); +} + +/* Serves using HTTP/1.0 get-till-EOF semantics. */ +static int serve_eof(ne_socket *sock, void *ud) +{ + const char *cert = ud; + + NE_DEBUG(NE_DBG_HTTP, "using server cert %s\n", cert); + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, cert, SSL_FILETYPE_PEM)); + + CALL(do_ssl_response(sock, server_ctx, NULL, + "HTTP/1.0 200 OK\r\n" + "Connection: close\r\n" + "\r\n" + "This is a response body, like it or not.", + 0)); + + return OK; +} + +/* Test read-til-EOF behaviour with SSL. */ +static int simple_eof(void) +{ + ne_session *sess = DEFSESS; + + CALL(any_ssl_request(sess, serve_eof, SERVER_CERT, CA_CERT, NULL, NULL)); + ne_session_destroy(sess); + return OK; +} + +static int empty_truncated_eof(void) +{ + ne_session *sess = DEFSESS; + + CALL(any_ssl_request(sess, serve_response_unclean, + "HTTP/1.0 200 OK\r\n" "\r\n", + CA_CERT, NULL, NULL)); + + ne_session_destroy(sess); + return OK; +} + +static int fail_truncated_eof(void) +{ + ne_session *sess = DEFSESS; + int ret; + + ne_ssl_trust_cert(sess, def_ca_cert); + CALL(spawn_server(7777, serve_response_unclean, + "HTTP/1.0 200 OK\r\n" "\r\n" + "This is some content\n" + "Followed by a truncation attack!\n")); + + ret = any_request(sess, "/foo"); + CALL(await_server()); + + ONV(ret != NE_ERROR, + ("request failed with %d not error: `%s'", ret, ne_get_error(sess))); + ne_session_destroy(sess); + return OK; +} + +/* Server function which just sends a string then EOF. */ +static int just_serve_string(ne_socket *sock, void *userdata) +{ + const char *str = userdata; + server_send(sock, str, strlen(str)); + return 0; +} + +/* test for the SSL negotiation failing. */ +static int fail_not_ssl(void) +{ + ne_session *sess = DEFSESS; + int ret; + + CALL(spawn_server(7777, just_serve_string, "Hello, world.\n")); + ret = any_request(sess, "/bar"); + CALL(await_server()); + + ONN("request did not fail", ret != NE_ERROR); + + ne_session_destroy(sess); + return OK; +} + +static int wildcard_ok = 0; + +static int wildcard_init(void) +{ + struct stat stbuf; + + t_context("wildcard.cert not found:\n" + "This test requires a Linux-like hostname command, see makekeys.sh"); + PRECOND(stat("wildcard.cert", &stbuf) == 0); + + PRECOND(lookup_hostname() == OK); + + wildcard_ok = 1; + return OK; +} + +static int wildcard_match(void) +{ + ne_session *sess; + + PRECOND(wildcard_ok); + + sess = ne_session_create("https", local_hostname, 7777); + + CALL(any_ssl_request(sess, serve_ssl, + "wildcard.cert", CA_CERT, NULL, NULL)); + ne_session_destroy(sess); + + return OK; +} + +/* Check that hostname comparisons are not cases-sensitive. */ +static int caseless_match(void) +{ + return accept_signed_cert("caseless.cert"); +} + +/* Test that the subjectAltName extension has precedence over the + * commonName attribute */ +static int subject_altname(void) +{ + return accept_signed_cert("altname.cert"); +} + +/* tests for multiple altNames. */ +static int two_subject_altname(void) +{ + return accept_signed_cert("altname2.cert"); +} + +static int two_subject_altname2(void) +{ + return accept_signed_cert("altname3.cert"); +} + +/* Test that a subject altname with *only* an eMail entry is + * ignored, and the commonName is used instead. */ +static int notdns_altname(void) +{ + return accept_signed_cert("altname4.cert"); +} + +/* test that the *most specific* commonName attribute is used. */ +static int multi_commonName(void) +{ + return accept_signed_cert("twocn.cert"); +} + +/* regression test for neon <= 0.23.4 where if commonName was the first + * RDN in the subject DN, it was ignored. */ +static int commonName_first(void) +{ + return accept_signed_cert("cnfirst.cert"); +} + +static int check_dname(const ne_ssl_dname *dn, const char *expected, + const char *which) +{ + char *dname; + + ONV(dn == NULL, ("certificate %s dname was NULL", which)); + + dname = ne_ssl_readable_dname(dn); + + NE_DEBUG(NE_DBG_SSL, "Got dname `%s', expecting `%s'\n", dname, expected); + + ONV(strcmp(dname, expected), + ("certificate %s dname was `%s' not `%s'", which, dname, expected)); + + ne_free(dname); + + return 0; +} + +/* Check that the readable subject issuer dnames of 'cert' match + * 'subject' and 'issuer' (if non-NULL). */ +static int check_cert_dnames(const ne_ssl_certificate *cert, + const char *subject, const char *issuer) +{ + ONN("no server certificate presented", cert == NULL); + CALL(check_dname(ne_ssl_cert_subject(cert), subject, "subject")); + return issuer ? check_dname(ne_ssl_cert_issuer(cert), issuer, "issuer") : OK; +} + +/* Verify callback which checks that the certificate presented has the + * predetermined subject and issuer DN (as per makekeys.sh). */ +static int check_cert(void *userdata, int fs, const ne_ssl_certificate *cert) +{ + int *ret = userdata; + + if (check_cert_dnames(cert, SERVER_DNAME, CACERT_DNAME) == FAIL) + *ret = -1; + else + *ret = 1; + + return 0; +} + +/* Check that certificate attributes are passed correctly. */ +static int parse_cert(void) +{ + ne_session *sess = DEFSESS; + int ret = 0; + + /* don't give a CA cert; should force the verify callback to be + * used. */ + CALL(any_ssl_request(sess, serve_ssl, SERVER_CERT, NULL, + check_cert, &ret)); + ne_session_destroy(sess); + + ONN("cert verification never called", ret == 0); + + if (ret == -1) + return FAIL; + + return OK; +} + +/* Check the certificate chain presented against known dnames. */ +static int check_chain(void *userdata, int fs, const ne_ssl_certificate *cert) +{ + int *ret = userdata; + + if (check_cert_dnames(cert, SERVER_DNAME, CACERT_DNAME) == FAIL) { + *ret = -1; + return 0; + } + + cert = ne_ssl_cert_signedby(cert); + if (cert == NULL) { + t_context("no CA cert in chain"); + *ret = -1; + return 0; + } + + if (check_cert_dnames(cert, CACERT_DNAME, CACERT_DNAME) == FAIL) { + *ret = -1; + return 0; + } + + *ret = 1; + return 0; +} + +/* Check that certificate attributes are passed correctly. */ +static int parse_chain(void) +{ + ne_session *sess = DEFSESS; + int ret = 0; + + /* don't give a CA cert; should force the verify callback to be + * used. */ + CALL(any_ssl_request(sess, serve_ssl_chained, SERVER_CERT, NULL, + check_chain, &ret)); + ne_session_destroy(sess); + + ONN("cert verification never called", ret == 0); + + if (ret == -1) + return FAIL; + + return OK; +} + + +static int count_vfy(void *userdata, int fs, const ne_ssl_certificate *c) +{ + int *count = userdata; + (*count)++; + return 0; +} + +static int no_verify(void) +{ + ne_session *sess = DEFSESS; + int count = 0; + + CALL(any_ssl_request(sess, serve_ssl, SERVER_CERT, CA_CERT, count_vfy, + &count)); + + ONN("verify callback called unnecessarily", count != 0); + + ne_session_destroy(sess); + + return OK; +} + +static int cache_verify(void) +{ + ne_session *sess = DEFSESS; + int ret, count = 0; + + /* force verify cert. */ + ret = any_ssl_request(sess, serve_ssl, SERVER_CERT, NULL, count_vfy, + &count); + + CALL(spawn_server(7777, serve_ssl, SERVER_CERT)); + ret = any_request(sess, "/foo2"); + CALL(await_server()); + + ONV(count != 1, + ("verify callback result not cached: called %d times", count)); + + ne_session_destroy(sess); + + return OK; +} + +/* Copy failures into *userdata, and fail verification. */ +static int get_failures(void *userdata, int fs, const ne_ssl_certificate *c) +{ + int *out = userdata; + *out = fs; + return -1; +} + +/* Helper function: run a request using the given self-signed server + * certificate, and expect the request to fail with the given + * verification failure flags. */ +static int fail_ssl_request(char *cert, char *cacert, + const char *msg, int failures) +{ + ne_session *sess = DEFSESS; + int gotf = 0, ret; + + ret = any_ssl_request(sess, fail_serve, cert, cacert, + get_failures, &gotf); + + ONV(gotf == 0, + ("no error in verification callback; request failed: %s", + ne_get_error(sess))); + + ONV(gotf & ~NE_SSL_FAILMASK, + ("verification flags %x outside mask %x", gotf, NE_SSL_FAILMASK)); + + /* check the failure flags were as expected. */ + ONV(failures != gotf, + ("verification flags were %d not %d", gotf, failures)); + + /* and check that the request was failed too. */ + ONN(msg, ret == NE_OK); + + ne_session_destroy(sess); + + return OK; +} + +/* Note that the certs used for fail_* are all self-signed, so the + * cert is passed as CA cert and server cert to fail_ssl_request. */ + +/* Check that a certificate with the incorrect commonName attribute is + * flagged as such. */ +static int fail_wrongCN(void) +{ + return fail_ssl_request("wrongcn.pem", "wrongcn.pem", + "certificate with incorrect CN was accepted", + NE_SSL_IDMISMATCH); +} + +/* Check that an expired certificate is flagged as such. */ +static int fail_expired(void) +{ + char *c = ne_concat(srcdir, "/expired.pem", NULL); + CALL(fail_ssl_request(c, c, "expired certificate was accepted", + NE_SSL_EXPIRED)); + ne_free(c); + return OK; +} + +static int fail_notvalid(void) +{ + char *c = ne_concat(srcdir, "/notvalid.pem", NULL); + CALL(fail_ssl_request(c, c, "not yet valid certificate was accepted", + NE_SSL_NOTYETVALID)); + ne_free(c); + return OK; +} + +/* Check that a server cert with a random issuer and self-signed cert + * fail with UNTRUSTED. */ +static int fail_untrusted_ca(void) +{ + return fail_ssl_request("server.cert", NULL, "untrusted CA.", + NE_SSL_UNTRUSTED); +} + +static int fail_self_signed(void) +{ + return fail_ssl_request("ssigned.pem", NULL, "self-signed cert", + NE_SSL_UNTRUSTED); +} + +/* Test for failure when a server cert is presented which has no + * commonName (and no alt names either). */ +static int fail_missing_CN(void) +{ + ne_session *sess = DEFSESS; + + ONN("accepted server cert with missing commonName", + any_ssl_request(sess, fail_serve, "missingcn.cert", SERVER_CERT, + NULL, NULL) == NE_OK); + + ONV(strstr(ne_get_error(sess), "missing commonName") == NULL, + ("unexpected session error `%s'", ne_get_error(sess))); + + ne_session_destroy(sess); + return OK; +} + +struct scache_args { + SSL_CTX *ctx; + char *cert; + int count; + SSL_SESSION *sess; +}; + +/* FIXME: factor out shared code with serve_ssl */ +static int serve_scache(ne_socket *sock, void *ud) +{ + struct scache_args *args = ud; + SSL_SESSION *sess; + + if (args->count == 0) { + /* enable OpenSSL's internal session cache, enabling the + * negotiation to re-use a session if both sides support it. */ + SSL_CTX_set_session_cache_mode(args->ctx, SSL_SESS_CACHE_SERVER); + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(args->ctx, + args->cert, SSL_FILETYPE_PEM)); + } + + args->count++; + + CALL(do_ssl_response(sock, args->ctx, &sess, DEF_RESP, 0)); + + /* dump session to child.log for debugging. */ + SSL_SESSION_print_fp(ne_debug_stream, sess); + + if (args->count == 1) { + /* save the session. */ + args->sess = sess; + } else { + /* could just to do this with SSL_CTX_sess_hits really, + * but this is a more thorough test. */ + ONN("cached SSL session not used", + SSL_SESSION_cmp(args->sess, sess)); + SSL_SESSION_free(args->sess); + SSL_SESSION_free(sess); + } + + return 0; +} + +/* Test that the SSL session is cached across connections. */ +static int session_cache(void) +{ + struct scache_args args; + ne_session *sess = ne_session_create("https", "localhost", 7777); + + args.ctx = server_ctx; + args.count = 0; + args.cert = SERVER_CERT; + + ne_ssl_trust_cert(sess, def_ca_cert); + + /* have spawned server listen for several connections. */ + CALL(spawn_server_repeat(7777, serve_scache, &args, 4)); + + ONREQ(any_request(sess, "/req1")); + ONREQ(any_request(sess, "/req2")); + ne_session_destroy(sess); + /* server should still be waiting for connections: if not, + * something went wrong. */ + ONN("error from child", dead_server()); + /* now get rid of it. */ + reap_server(); + + return OK; +} + +/* Callback for client_cert_provider; takes a c. cert as userdata and + * registers it. */ +static void ccert_provider(void *userdata, ne_session *sess, + const ne_ssl_dname *const *dns, int dncount) +{ + const ne_ssl_client_cert *cc = userdata; + ne_ssl_set_clicert(sess, cc); +} + +/* Test that the on-demand client cert provider callback is used. */ +static int client_cert_provided(void) +{ + ne_session *sess = DEFSESS; + ne_ssl_client_cert *cc; + + cc = ne_ssl_clicert_read("client.p12"); + ONN("could not load client.p12", cc == NULL); + ONN("could not decrypt client.p12", + ne_ssl_clicert_decrypt(cc, "foobar")); + + ne_ssl_provide_clicert(sess, ccert_provider, cc); + CALL(any_ssl_request(sess, serve_ccert, NULL, CA_CERT, + NULL, NULL)); + + ne_session_destroy(sess); + ne_ssl_clicert_free(cc); + return OK; +} + +static void cc_check_dnames(void *userdata, ne_session *sess, + const ne_ssl_dname *const *dns, int dncount) +{ + int n, *ret = userdata; + static const char *expected[4] = { + "First Random CA, CAs Ltd., Lincoln, Lincolnshire, GB", + "Second Random CA, CAs Ltd., Falmouth, Cornwall, GB", + "Third Random CA, CAs Ltd., Ipswich, Suffolk, GB", + "Fourth Random CA, CAs Ltd., Norwich, Norfolk, GB" + }; + + ne_ssl_set_clicert(sess, def_cli_cert); + + if (dncount != 4) { + t_context("dname count was %d not 4", dncount); + *ret = -1; + return; + } + + for (n = 0; n < 4; n++) { + char which[5]; + + sprintf(which, "%d", n); + + if (check_dname(dns[n], expected[n], which) == FAIL) { + *ret = -1; + return; + } + } + + *ret = 1; +} + +/* Test for the list of acceptable dnames sent to the client. */ +static int cc_provided_dnames(void) +{ + int check = 0; + ne_session *sess = DEFSESS; + + PRECOND(def_cli_cert); + + ne_ssl_provide_clicert(sess, cc_check_dnames, &check); + + CALL(any_ssl_request(sess, serve_ccert, "calist.pem", CA_CERT, NULL, NULL)); + + ne_session_destroy(sess); + + ONN("provider function not called", check == 0); + + return (check == -1) ? FAIL : OK; +} + +/* Tests use of a client certificate. */ +static int client_cert_pkcs12(void) +{ + ne_session *sess = DEFSESS; + + PRECOND(def_cli_cert); + + ne_ssl_set_clicert(sess, def_cli_cert); + CALL(any_ssl_request(sess, serve_ccert, NULL, CA_CERT, NULL, NULL)); + + ne_session_destroy(sess); + return OK; +} + + +/* Tests use of an unencrypted client certificate. */ +static int ccert_unencrypted(void) +{ + ne_session *sess = DEFSESS; + ne_ssl_client_cert *ccert; + + ccert = ne_ssl_clicert_read("unclient.p12"); + ONN("unclient.p12 was encrypted", ne_ssl_clicert_encrypted(ccert)); + + ne_ssl_set_clicert(sess, ccert); + CALL(any_ssl_request(sess, serve_ccert, NULL, CA_CERT, NULL, NULL)); + + ne_ssl_clicert_free(ccert); + ne_session_destroy(sess); + return OK; +} + +static int serve_tunnel(ne_socket *sock, void *ud) +{ + CALL(discard_request(sock)); + + SEND_STRING(sock, "HTTP/1.1 200 OK\r\nServer: Fish\r\n\r\n"); + return serve_ssl(sock, ud); +} + +/* neon versions <= 0.21.2 segfault here because ne_sock_close would + * be called twice on the socket after the server cert verification + * fails. */ +static int fail_tunnel(void) +{ + ne_session *sess = ne_session_create("https", "example.com", 443); + ne_session_proxy(sess, "localhost", 7777); + + ONN("server cert verification didn't fail", + any_ssl_request(sess, serve_tunnel, SERVER_CERT, CA_CERT, + NULL, NULL) != NE_ERROR); + + ne_session_destroy(sess); + return OK; +} + +static int proxy_tunnel(void) +{ + ne_session *sess = ne_session_create("https", "localhost", 443); + ne_session_proxy(sess, "localhost", 7777); + + /* CA cert is trusted, so no verify callback should be needed. */ + CALL(any_ssl_request(sess, serve_tunnel, SERVER_CERT, CA_CERT, + NULL, NULL)); + + ne_session_destroy(sess); + return OK; +} + +/* a tricky test which requires spawning a second server process in + * time for a new connection after a 407. */ +static int apt_post_send(ne_request *req, void *ud, const ne_status *st) +{ + if (st->code == 407) { + NE_DEBUG(NE_DBG_HTTP, "Got 407, awaiting server...\n"); + CALL(await_server()); + NE_DEBUG(NE_DBG_HTTP, "Spawning proper tunnel server...\n"); + CALL(spawn_server(7777, serve_tunnel, SERVER_CERT)); + NE_DEBUG(NE_DBG_HTTP, "Spawned.\n"); + } + return OK; +} + +static int apt_creds(void *userdata, const char *realm, int attempt, + char *username, char *password) +{ + strcpy(username, "foo"); + strcpy(password, "bar"); + return 0; +} + +/* Test for using SSL over a CONNECT tunnel via a proxy server which + * requires authentication. Broke briefly between 0.23.x and + * 0.24.0. */ +static int auth_proxy_tunnel(void) +{ + ne_session *sess = ne_session_create("https", "localhost", 443); + int ret; + + ne_session_proxy(sess, "localhost", 7777); + ne_hook_post_send(sess, apt_post_send, NULL); + ne_set_proxy_auth(sess, apt_creds, NULL); + + CALL(spawn_server(7777, single_serve_string, + "HTTP/1.0 407 I WANT MORE BISCUITS\r\n" + "Proxy-Authenticate: Basic realm=\"bigbluesea\"\r\n" + "Connection: close\r\n" "\r\n")); + + /* trust the CA */ + ne_ssl_trust_cert(sess, def_ca_cert); + /* run the dreaded request. */ + ret = any_request(sess, "/foobar"); + CALL(await_server()); + ONREQ(ret); + + ne_session_destroy(sess); + return 0; +} + +/* Compare against known digest of notvalid.pem. Via: + * $ openssl x509 -fingerprint -sha1 -noout -in notvalid.pem */ +#define THE_DIGEST "cf:5c:95:93:76:c6:3c:01:8b:62:" \ + "b1:6f:f7:7f:42:32:ac:e6:69:1b" + +static int cert_fingerprint(void) +{ + char *fn = ne_concat(srcdir, "/notvalid.pem", NULL); + ne_ssl_certificate *cert = ne_ssl_cert_read(fn); + char digest[60]; + + ne_free(fn); + + ONN("could not load notvalid.pem", cert == NULL); + + ONN("failed to digest", ne_ssl_cert_digest(cert, digest)); + ne_ssl_cert_free(cert); + + ONV(strcmp(digest, THE_DIGEST), + ("digest was %s not %s", digest, THE_DIGEST)); + + return OK; +} + +/* verify that identity of certificate in filename 'fname' is 'identity' */ +static int check_identity(const char *fname, const char *identity) +{ + ne_ssl_certificate *cert = ne_ssl_cert_read(fname); + const char *id; + + ONV(cert == NULL, ("could not read cert `%s'", fname)); + + id = ne_ssl_cert_identity(cert); + + if (identity) { + ONV(id == NULL, ("certificate `%s' had no identity", fname)); + ONV(strcmp(id, identity), + ("certificate `%s' had identity `%s' not `%s'", fname, + id, identity)); + } else { + ONV(id != NULL, ("certificate `%s' had identity `%s' (expected none)", + fname, id)); + } + + ne_ssl_cert_free(cert); + return OK; +} + +/* check certificate identities. */ +static int cert_identities(void) +{ + static const struct { + const char *fname, *identity; + } certs[] = { + { "twocn.cert", "localhost" }, + { "altname.cert", "localhost" }, + { "altname2.cert", "nohost.example.com" }, + { "altname4.cert", "localhost" }, + { "ca4.pem", "fourth.example.com" }, + { NULL, NULL } + }; + int n; + + for (n = 0; certs[n].fname != NULL; n++) + CALL(check_identity(certs[n].fname, certs[n].identity)); + + return OK; +} + +static int check_validity(const char *fname, + const char *from, const char *until) +{ + char actfrom[NE_SSL_VDATELEN], actuntil[NE_SSL_VDATELEN]; + ne_ssl_certificate *cert; + + cert = ne_ssl_cert_read(fname); + ONV(cert == NULL, ("could not load cert `%s'", fname)); + + /* cover all calling combos for nice coverage analysis */ + ne_ssl_cert_validity(cert, NULL, NULL); + ne_ssl_cert_validity(cert, actfrom, NULL); + ne_ssl_cert_validity(cert, NULL, actuntil); + ne_ssl_cert_validity(cert, actfrom, actuntil); + + ONV(strcmp(actfrom, from), + ("%s: start time was `%s' not `%s'", fname, actfrom, from)); + + ONV(strcmp(actuntil, until), + ("%s: end time was `%s' not `%s'", fname, actuntil, until)); + + ne_ssl_cert_free(cert); + return OK; +} + +/* ceritificate validity times. */ +static int cert_validity(void) +{ + char *cert = ne_concat(srcdir, "/expired.pem", NULL); + CALL(check_validity(cert, "Jan 21 20:39:04 2002 GMT", "Jan 31 20:39:04 2002 GMT")); + ne_free(cert); + cert = ne_concat(srcdir, "/notvalid.pem", NULL); + CALL(check_validity(cert, "Dec 27 20:40:29 2023 GMT", "Dec 28 20:40:29 2023 GMT")); + ne_free(cert); + return OK; +} + +/* dname comparisons. */ +static int dname_compare(void) +{ + ne_ssl_certificate *ssigned; + const ne_ssl_dname *dn1, *dn2; + + dn1 = ne_ssl_cert_subject(def_server_cert); + dn2 = ne_ssl_cert_subject(def_server_cert); + ONN("identical subject names not equal", ne_ssl_dname_cmp(dn1, dn2) != 0); + + dn2 = ne_ssl_cert_issuer(def_server_cert); + ONN("issuer and subject names equal for signed cert", + ne_ssl_dname_cmp(dn1, dn2) == 0); + + dn1 = ne_ssl_cert_subject(def_ca_cert); + ONN("issuer of signed cert not equal to subject of CA cert", + ne_ssl_dname_cmp(dn1, dn2) != 0); + + ssigned = ne_ssl_cert_read("ssigned.pem"); + ONN("could not load ssigned.pem", ssigned == NULL); + + dn1 = ne_ssl_cert_subject(ssigned); + dn2 = ne_ssl_cert_issuer(ssigned); + ONN("issuer and subject names not equal for self-signed cert", + ne_ssl_dname_cmp(dn1, dn2)); + ne_ssl_cert_free(ssigned); + + return OK; +} + +/* tests for ne_ssl_readable_dname */ +static int dname_readable(void) +{ + ne_ssl_certificate *cert; + + cert = ne_ssl_cert_read("justmail.cert"); + ONN("could not load justmail.cert", cert == NULL); + + CALL(check_cert_dnames(cert, "blah@example.com", NULL)); + ne_ssl_cert_free(cert); + + return OK; +} + +/* test cert comparisons */ +static int cert_compare(void) +{ + ne_ssl_certificate *c1, *c2; + + c1 = ne_ssl_cert_read("server.cert"); + c2 = ne_ssl_cert_read("server.cert"); + ONN("identical certs don't compare equal", ne_ssl_cert_cmp(c1, c2) != 0); + ONN("identical certs don't compare equal", ne_ssl_cert_cmp(c2, c1) != 0); + ne_ssl_cert_free(c2); + + c2 = ne_ssl_cert_read("ssigned.pem"); + ONN("different certs don't compare different", + ne_ssl_cert_cmp(c1, c2) == 0); + ONN("different certs don't compare different", + ne_ssl_cert_cmp(c2, c1) == 0); + ne_ssl_cert_free(c2); + ne_ssl_cert_free(c1); + + return OK; +} + +/* Extract raw base64 string from a PEM file */ +static int flatten_pem(const char *fname, char **out) +{ + FILE *fp = fopen(fname, "r"); + char buf[80]; + size_t outlen = 0; + int ignore = 1; + + ONV(fp == NULL, ("could not open %s", fname)); + + *out = NULL; + + while (fgets(buf, sizeof buf, fp) != NULL) { + size_t len = strlen(buf) - 1; + + if (len < 1) continue; + + /* look for the wrapper lines. */ + if (strncmp(buf, "-----", 5) == 0) { + ignore = !ignore; + continue; + } + + /* ignore until the first wrapper line */ + if (ignore) continue; + + *out = realloc(*out, outlen + len + 1); + memcpy(*out + outlen, buf, len); + outlen += len; + } + + (*out)[outlen] = '\0'; + fclose(fp); + + return OK; +} + +/* check export cert data 'actual' against expected data 'expected */ +static int check_exported_data(const char *actual, const char *expected) +{ + ONN("could not export cert", actual == NULL); + + ONN("export data contained newline", + strchr(actual, '\r') || strchr(actual, '\n')); + + ONV(strcmp(actual, expected), ("exported cert differed from expected:\n" + "actual: %s\nexpected: %s", + actual, expected)); + return OK; +} + +/* Test import and export of certificates. The export format is PEM + * without the line feeds and wrapping; compare against . */ +static int import_export(void) +{ + char *expected, *actual; + ne_ssl_certificate *cert, *imp; + + CALL(flatten_pem("server.cert", &expected)); + + cert = ne_ssl_cert_read("server.cert"); + ONN("could not load server.cert", cert == NULL); + + /* export the cert to and compare it with the PEM file */ + actual = ne_ssl_cert_export(cert); + CALL(check_exported_data(actual, expected)); + + /* import the exported cert data, check it looks the same */ + imp = ne_ssl_cert_import(actual); + ONN("failed to import exported cert", imp == NULL); + ONN("imported cert was different to original", + ne_ssl_cert_cmp(imp, cert)); + + /* re-export the imported cert and check that looks the same */ + ne_free(actual); + actual = ne_ssl_cert_export(imp); + CALL(check_exported_data(actual, expected)); + ne_ssl_cert_free(imp); + + /* try importing from bogus data */ + imp = ne_ssl_cert_import("!!"); + ONN("imported bogus cert from bogus base64", imp != NULL); + imp = ne_ssl_cert_import("aaaa"); + ONN("imported bogus cert from valid base64", imp != NULL); + + ne_ssl_cert_free(cert); + ne_free(actual); + ne_free(expected); + return OK; +} + +/* Test write/read */ +static int read_write(void) +{ + ne_ssl_certificate *c1, *c2; + + c1 = ne_ssl_cert_read("server.cert"); + ONN("could not load server.cert", c1 == NULL); + + ONN("could not write output.pem", ne_ssl_cert_write(c1, "output.pem")); + + ONN("wrote to nonexistent directory", + ne_ssl_cert_write(c1, "nonesuch/output.pem") == 0); + + c2 = ne_ssl_cert_read("output.pem"); + ONN("could not read output.pem", c2 == NULL); + + ONN("read of output.pem differs from original", + ne_ssl_cert_cmp(c2, c1)); + + ne_ssl_cert_free(c1); + ne_ssl_cert_free(c2); + + return OK; +} + +/* A verification callback which caches the passed cert. */ +static int verify_cache(void *userdata, int fs, + const ne_ssl_certificate *cert) +{ + char **cache = userdata; + + if (*cache == NULL) { + *cache = ne_ssl_cert_export(cert); + return 0; + } else { + return -1; + } +} + +/* Test a common use of the SSL API; cache the server cert across + * sessions. */ +static int cache_cert(void) +{ + ne_session *sess = DEFSESS; + char *cache = NULL; + ne_ssl_certificate *cert; + + ONREQ(any_ssl_request(sess, serve_ssl, "ssigned.pem", CA_CERT, + verify_cache, &cache)); + ne_session_destroy(sess); + + ONN("no cert was cached", cache == NULL); + + /* make a real cert */ + cert = ne_ssl_cert_import(cache); + ONN("could not import cached cert", cert == NULL); + ne_free(cache); + + /* create a new session */ + sess = DEFSESS; + /* trust the cert */ + ne_ssl_trust_cert(sess, cert); + ne_ssl_cert_free(cert); + /* now, the request should succeed without manual verification */ + ONREQ(any_ssl_request(sess, serve_ssl, "ssigned.pem", CA_CERT, + NULL, NULL)); + ne_session_destroy(sess); + return OK; +} + +/* TODO: code paths still to test in cert verification: + * - server cert changes between connections: Mozilla gives + * a "bad MAC decode" error for this; can do better? + * - server presents no certificate (using ADH ciphers)... can + * only really happen if they mess with the SSL_CTX and enable + * ADH cipher manually; but good to check the failure case is + * safe. + * From the SSL book: + * - an early FIN should be returned as a possible truncation attack, + * NOT just an NE_SOCK_CLOSED. + * - unexpected close_notify is an error but not an attack. + * - never attempt session resumption after any aborted connection. + */ + +ne_test tests[] = { + T_LEAKY(init), + + T(load_server_certs), + T(trust_default_ca), + + T(cert_fingerprint), + T(cert_identities), + T(cert_validity), + T(cert_compare), + T(dname_compare), + T(dname_readable), + T(import_export), + T(read_write), + + T(load_client_cert), + + T(simple), + T(simple_eof), + T(empty_truncated_eof), + T(fail_truncated_eof), + T(fail_not_ssl), + T(cache_cert), + + T(client_cert_pkcs12), + T(ccert_unencrypted), + T(client_cert_provided), + T(cc_provided_dnames), + + T(parse_cert), + T(parse_chain), + + T(no_verify), + T(cache_verify), + T_LEAKY(wildcard_init), + T(wildcard_match), + T(caseless_match), + + T(subject_altname), + T(two_subject_altname), + T(two_subject_altname2), + T(notdns_altname), + + T(multi_commonName), + T(commonName_first), + + T(fail_wrongCN), + T(fail_expired), + T(fail_notvalid), + T(fail_untrusted_ca), + T(fail_self_signed), + T(fail_missing_CN), + + T(session_cache), + + T(fail_tunnel), + T(proxy_tunnel), + T(auth_proxy_tunnel), + + T(NULL) +}; diff --git a/test/string-tests.c b/test/string-tests.c new file mode 100644 index 0000000..93c8508 --- /dev/null +++ b/test/string-tests.c @@ -0,0 +1,492 @@ +/* + String handling tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> /* for the ENOENT definitions in str_errors */ +#endif + +#include "ne_string.h" + +#include "tests.h" + +#undef ONCMP +#define ONCMP(a,b) ONV(strcmp(a, b), \ + ("result was [%s] not [%s]", a, b)) + +static int simple(void) { + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_zappend(s, "abcde"); + ONCMP(s->data, "abcde"); + ON(ne_buffer_size(s) != 5); + ne_buffer_destroy(s); + return OK; +} + +static int buf_concat(void) +{ + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_concat(s, "a", "b", "c", "d", "e", "f", "g", NULL); + ONCMP(s->data, "abcdefg"); + ON(ne_buffer_size(s) != 7); + ne_buffer_destroy(s); + return OK; +} + +static int buf_concat2(void) +{ +#define RES "alphabetagammadeltaepsilonetatheta" + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_concat(s, "alpha", "beta", "gamma", "delta", "epsilon", + "eta", "theta", NULL); + ONCMP(s->data, RES); + ON(ne_buffer_size(s) != strlen(RES)); + ne_buffer_destroy(s); + return OK; +} + +static int buf_concat3(void) +{ + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_zappend(s, "foobar"); + ne_buffer_concat(s, "norman", NULL); + ONCMP(s->data, "foobarnorman"); + ON(ne_buffer_size(s) != 12); + ne_buffer_destroy(s); + return OK; +} + +static int append(void) { + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_append(s, "a", 1); + ne_buffer_append(s, "b", 1); + ne_buffer_append(s, "c", 1); + ONCMP(s->data, "abc"); + ON(ne_buffer_size(s) != 3); + ne_buffer_destroy(s); + return OK; +} + +static int grow(void) +{ + ne_buffer *s = ne_buffer_ncreate(2); + ON(s == NULL); + ne_buffer_append(s, "a", 1); + ne_buffer_grow(s, 4); + ONCMP(s->data, "a"); + ne_buffer_destroy(s); + return OK; +} + +static int alter(void) { + ne_buffer *s = ne_buffer_create(); + char *d; + ON(s == NULL); + ne_buffer_zappend(s, "abcdefg"); + d = s->data; + ON(d == NULL); + d[2] = '\0'; + ne_buffer_altered(s); + ONCMP(s->data, "ab"); + ON(ne_buffer_size(s) != 2); + ne_buffer_zappend(s, "hijkl"); + ONCMP(s->data, "abhijkl"); + ne_buffer_destroy(s); + return OK; +} + +/* Macros for testing ne_token. */ + +#define TEST(res) do { \ + char *tok = ne_token(&pnt, ','); \ + ONN(res ": return", tok == NULL); \ + ONN(res ": compare", strcmp(tok, (res))); \ + ONN(res ": modify", pnt == NULL); \ +} while (0) + +#define LASTTEST(res) do { \ + char *tok = ne_token(&pnt, ','); \ + ONN(res ": last return", tok == NULL); \ + ONN(res ": last compare", strcmp(tok, (res))); \ + ONN(res ": last modify", pnt != NULL); \ +} while (0) + +#define QTEST(res) do { \ + char *tok = ne_qtoken(&pnt, ',', QUOTES); \ + ONN(res ": return", tok == NULL); \ + ONN(res ": compare", strcmp(tok, (res))); \ + ONN(res ": modify", pnt == NULL); \ +} while (0) + +#define QLASTTEST(res) do { \ + char *tok = ne_qtoken(&pnt, ',', QUOTES); \ + ONN(res ": last return", tok == NULL); \ + ONN(res ": last compare", strcmp(tok, (res))); \ + ONN(res ": last modify", pnt != NULL); \ +} while (0) + +static int token1(void) +{ + char *str = ne_strdup("a,b,c,d"), *pnt = str; + + TEST("a"); TEST("b"); TEST("c"); LASTTEST("d"); + + ne_free(str); + return OK; +} + +static int token2(void) +{ + char *str = ne_strdup("norman,fishing, elsewhere"), *pnt = str; + + TEST("norman"); TEST("fishing"); LASTTEST(" elsewhere"); + + ne_free(str); + return OK; +} + +static int nulls(void) +{ + char *str = ne_strdup("alpha,,gamma"), *pnt = str; + + TEST("alpha"); TEST(""); LASTTEST("gamma"); + ne_free(str); + + pnt = str = ne_strdup(",,,wooo"); + TEST(""); TEST(""); TEST(""); LASTTEST("wooo"); + ne_free(str); + + pnt = str = ne_strdup("wooo,,,"); + TEST("wooo"); TEST(""); TEST(""); LASTTEST(""); + ne_free(str); + + return OK; +} + +static int empty(void) +{ + char *str = ne_strdup(""), *pnt = str; + + LASTTEST(""); + ne_free(str); + + return OK; +} + +#undef QUOTES +#define QUOTES "'" + +static int quoted(void) +{ + char *str = + ne_strdup("alpha,'beta, a fish called HELLO!?',sandwiches"); + char *pnt = str; + + QTEST("alpha"); + QTEST("'beta, a fish called HELLO!?'"); + QLASTTEST("sandwiches"); + + ne_free(str); + return OK; +} + +static int badquotes(void) +{ + char *str = ne_strdup("alpha,'blah"), *pnt = str; + + QTEST("alpha"); + ON(ne_qtoken(&pnt, ',', QUOTES) != NULL); + + ne_free(str); + return OK; +} + +/* for testing ne_shave. */ +#undef TEST +#define TEST(str, ws, res) do { \ + char *s = ne_strdup((str)); \ + char *r = ne_shave(s, (ws)); \ + ONN("[" str "]", strcmp(r, (res))); \ + ne_free(s); \ +} while (0) + +static int shave(void) +{ + TEST(" b ", " ", "b"); + TEST("b", " ", "b"); + TEST(" b ", " ", "b"); + TEST("--bbb-----", "-", "bbb"); + TEST("hello, world ", " ", "hello, world"); + TEST("<<><<<><<this is foo<<><<<<><<", "<>", "this is foo"); + TEST("09809812342347I once saw an helicopter0012312312398", "0123456789", + "I once saw an helicopter"); + return OK; +} + +/* Regression test for ne_shave call which should produce an empty + * string. */ +static int shave_regress(void) +{ + TEST("\"\"", "\"", ""); + return OK; +} + +/* Test the ne_token/ne_shave combination. */ + +#undef TEST +#undef LASTTEST + +#define TEST(res) do { \ + char *tok = ne_token(&pnt, ','); \ + ONN(res ": return", tok == NULL); \ + tok = ne_shave(tok, " "); \ + ONN(res ": shave", tok == NULL); \ + ONN(res ": compare", strcmp(tok, (res))); \ + ONN(res ": modify", pnt == NULL); \ +} while (0) + + +#define LASTTEST(res) do { \ + char *tok = ne_token(&pnt, ','); \ + ONN(res ": last return", tok == NULL); \ + tok = ne_shave(tok, " "); \ + ONN(res ": last shave", tok == NULL); \ + ONN(res ": last compare", strcmp(tok, (res))); \ + ONN(res ": last modify", pnt != NULL); \ +} while (0) + +/* traditional use of ne_token/ne_shave. */ +static int combo(void) +{ + char *str = ne_strdup(" fred , mary, jim , alice, david"), *pnt = str; + + TEST("fred"); TEST("mary"); TEST("jim"); TEST("alice"); + LASTTEST("david"); + + ne_free(str); + return 0; +} + +static int concat(void) +{ +#define CAT(res, args) do { char *str = ne_concat args; \ +ONCMP(str, res); \ +ne_free(str); } while (0) + CAT("alphabeta", ("alpha", "beta", NULL)); + CAT("alpha", ("alpha", "", "", NULL)); + CAT("", ("", NULL)); + CAT("", ("", "", "", NULL)); + CAT("alpha", ("", "a", "lph", "", "a", NULL)); + return OK; +} + +static int str_errors(void) +{ + char expect[200], actual[200]; + + strncpy(expect, strerror(ENOENT), sizeof(expect)); + ONN("ne_strerror did not return passed-in buffer", + ne_strerror(ENOENT, actual, sizeof(actual)) != actual); + + ONV(strcmp(expect, actual), + ("error from ENOENT was `%s' not `%s'", actual, expect)); + + /* Test truncated error string is still NUL-terminated. */ + ne_strerror(ENOENT, actual, 6); + ONN("truncated string had wrong length", strlen(actual) != 5); + + return OK; +} + +static int strnzcpy(void) +{ + char buf[5]; + + ne_strnzcpy(buf, "abcdefghi", sizeof buf); + ONV(strcmp(buf, "abcd"), ("result was `%s' not `abcd'", buf)); + + ne_strnzcpy(buf, "ab", sizeof buf); + ONV(strcmp(buf, "ab"), ("result was `%s' not `ab'", buf)); + + return OK; +} + +#define FOX_STRING "The quick brown fox jumped over the lazy dog" +#define PUNC_STRING "<>,.;'#:@~[]{}!\"$%^&*()_+-=" + +static int cleaner(void) +{ + static const char *strings[] = { + "alpha", "alpha", + "pretty\033[41mcolours", "pretty [41mcolours", + "beta\n", "beta ", + "del\rt\na", "del t a", + FOX_STRING, FOX_STRING, + "0123456789", "0123456789", + PUNC_STRING, PUNC_STRING, + "\01blah blee\05bloo", " blah blee bloo", + NULL, + }; + unsigned int n; + + for (n = 0; strings[n]; n+=2) { + char *act = ne_strclean(ne_strdup(strings[n])); + + ONV(strcmp(act, strings[n+1]), + ("cleansed to `%s' not `%s'", act, strings[n+1])); + + ne_free(act); + } + + return OK; +} + +/* Check that raw data 'raw', of length 'len', has base64 encoding + * of 'expected'. */ +static int b64_check(const unsigned char *raw, size_t len, + const char *expected) +{ + char *encoded = ne_base64(raw, len); + unsigned char *decoded; + size_t dlen; + + ONV(strcmp(encoded, expected), + ("base64(\"%s\") gave \"%s\" not \"%s\"", raw, encoded, expected)); + + dlen = ne_unbase64(encoded, &decoded); + ONV(dlen != len, + ("decoded `%s' length was %" NE_FMT_SIZE_T " not %" NE_FMT_SIZE_T, + expected, dlen, len)); + + ONV(memcmp(raw, decoded, dlen), + ("decoded `%s' as `%.*s' not `%.*s'", + expected, dlen, decoded, dlen, raw)); + + ne_free(decoded); + ne_free(encoded); + return OK; +} + +/* ALLBITS: base64 encoding of "\0..\377" */ +#define ALLBITS \ +"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKiss" \ +"LS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZ" \ +"WltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWG" \ +"h4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz" \ +"tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g" \ +"4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==" + +static int base64(void) +{ + unsigned char bits[256]; + size_t n; + +#define B64B(x, l, y) CALL(b64_check(x, l, y)) +#define B64(x, y) B64B(x, strlen(x), y) + + /* invent these with + * $ printf "string" | uuencode -m blah + */ + B64("a", "YQ=="); + B64("bb", "YmI="); + B64("ccc", "Y2Nj"); + B64("Hello, world", "SGVsbG8sIHdvcmxk"); + B64("Aladdin:open sesame", "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); + B64("I once saw a dog called norman.\n", + "SSBvbmNlIHNhdyBhIGRvZyBjYWxsZWQgbm9ybWFuLgo="); + B64("The quick brown fox jumped over the lazy dog", + "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2c="); + + /* binary data.. + * $ printf "string" | wc -c # get the length + * $ printf "string" | uuencode -m blah # get the base64 + */ + B64B("\0\0\0\0\0\n", 6, "AAAAAAAK"); + B64B("I once wished \0 upon a \0 fish.", 30, + "SSBvbmNlIHdpc2hlZCAAIHVwb24gYSAAIGZpc2gu"); + B64B("\201\202\203\204", 4, "gYKDhA=="); + + for (n = 0; n < sizeof bits; n++) + bits[n] = (unsigned char)n; + CALL(b64_check(bits, sizeof bits, ALLBITS)); + +#undef B64 +#undef B64B + return OK; +} + +static int unbase64(void) +{ + static const char *ts[] = { + "", "a", "ab", "abc", + "}bcd", "a}cd", "ab}d", "abc}", " ", + "^bcd", "a^cd", "ab^d", "abc^", + "====", "=bcd", "a=cd", "ab=d", "a==d", "a=c=", + NULL + }; + size_t n; + + for (n = 0; ts[n]; n++) { + unsigned char *tmp; + ONV(ne_unbase64(ts[n], &tmp) != 0, + ("invalid string `%s' was decoded", ts[n])); + } + + return OK; +} + +ne_test tests[] = { + T(simple), + T(buf_concat), + T(buf_concat2), + T(buf_concat3), + T(append), + T(grow), + T(alter), + T(token1), + T(token2), + T(nulls), + T(empty), + T(quoted), + T(badquotes), + T(shave), + T(shave_regress), + T(combo), + T(concat), + T(str_errors), + T(strnzcpy), + T(cleaner), + T(base64), + T(unbase64), + T(NULL) +}; + diff --git a/test/stubs.c b/test/stubs.c new file mode 100644 index 0000000..494f8f0 --- /dev/null +++ b/test/stubs.c @@ -0,0 +1,171 @@ +/* + neon test suite + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** These tests show that the stub functions produce appropriate + * results to provide ABI-compatibility when a particular feature is + * not supported by the library. + **/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" +#include "ne_compress.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +#if defined(NEON_ZLIB) && defined(NEON_SSL) +#define NO_TESTS 1 +#endif + +#ifndef NEON_ZLIB +static int sd_result = OK; + +static void sd_reader(void *ud, const char *block, size_t len) +{ + const char *expect = ud; + if (strncmp(expect, block, len) != 0) { + sd_result = FAIL; + t_context("decompress reader got bad data"); + } +} + +static int stub_decompress(void) +{ + ne_session *sess; + ne_decompress *dc; + ne_request *req; + int ret; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.1 200 OK" EOL + "Connection: close" EOL EOL + "abcde")); + + req = ne_request_create(sess, "GET", "/foo"); + + dc = ne_decompress_reader(req, ne_accept_2xx, sd_reader, "abcde"); + + ret = ne_request_dispatch(req); + + CALL(await_server()); + + ONREQ(ret); + + ONN("decompress_destroy failed", ne_decompress_destroy(dc)); + + ne_request_destroy(req); + ne_session_destroy(sess); + + /* This is a skeleton test suite file. */ + return sd_result; +} +#endif + +#ifndef NEON_SSL +static int stub_ssl(void) +{ + ne_session *sess = ne_session_create("https", "localhost", 7777); + ne_ssl_certificate *cert; + ne_ssl_client_cert *cc; + + /* these should all fail when SSL is not supported. */ + cert = ne_ssl_cert_read("Makefile"); + if (cert) { + char *dn, digest[60], date[NE_SSL_VDATELEN]; + const ne_ssl_certificate *issuer; + + /* This branch should never be executed, but lets pretend it + * will to prevent the compiler optimising this code away if + * it's placed after the cert != NULL test. And all that + * needs to be tested is that these functions link OK. */ + dn = ne_ssl_readable_dname(ne_ssl_cert_subject(cert)); + ONN("this code shouldn't run", dn != NULL); + dn = ne_ssl_readable_dname(ne_ssl_cert_issuer(cert)); + ONN("this code shouldn't run", dn != NULL); + issuer = ne_ssl_cert_signedby(cert); + ONN("this code shouldn't run", issuer != NULL); + ONN("this code shouldn't run", ne_ssl_cert_digest(cert, digest)); + ne_ssl_cert_validity(cert, date, date); + ONN("this code shouldn't run", + ne_ssl_dname_cmp(ne_ssl_cert_subject(cert), + ne_ssl_cert_issuer(cert))); + ONN("this code shouldn't run", ne_ssl_cert_identity(issuer) != NULL); + ONN("this code shouldn't run", ne_ssl_cert_export(cert) != NULL); + } + + ONN("this code shouldn't run", ne_ssl_cert_import("foo") != NULL); + ONN("this code shouldn't run", ne_ssl_cert_read("Makefile") != NULL); + ONN("this code shouldn't succeed", ne_ssl_cert_cmp(NULL, NULL) == 0); + + ONN("certificate load succeeded", cert != NULL); + ne_ssl_cert_free(cert); + + cc = ne_ssl_clicert_read("Makefile"); + if (cc) { + const char *name; + /* dead branch as above. */ + cert = (void *)ne_ssl_clicert_owner(cc); + ONN("this code shouldn't run", cert != NULL); + name = ne_ssl_clicert_name(cc); + ONN("this code shouldn't run", name != NULL); + ONN("this code shouldn't run", ne_ssl_clicert_decrypt(cc, "fubar")); + ne_ssl_set_clicert(sess, cc); + } + + ONN("client certificate load succeeded", cc != NULL); + ne_ssl_clicert_free(cc); + + ne_ssl_trust_default_ca(sess); + + ne_session_destroy(sess); + return OK; +} +#endif + +#ifdef NO_TESTS +static int null_test(void) { return OK; } +#endif + +ne_test tests[] = { +#ifndef NEON_ZLIB + T(stub_decompress), +#endif +#ifndef NEON_SSL + T(stub_ssl), +#endif +/* to prevent failure when SSL and zlib are supported. */ +#ifdef NO_TESTS + T(null_test), +#endif + T(NULL) +}; + diff --git a/test/uri-tests.c b/test/uri-tests.c new file mode 100644 index 0000000..ce35f22 --- /dev/null +++ b/test/uri-tests.c @@ -0,0 +1,377 @@ +/* + URI tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "ne_uri.h" +#include "ne_alloc.h" + +#include "tests.h" + +static int simple(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("http://www.webdav.org/foo", &p)); + ON(strcmp(p.host, "www.webdav.org")); + ON(strcmp(p.path, "/foo")); + ON(strcmp(p.scheme, "http")); + ON(p.port); + ON(p.authinfo != NULL); + ne_uri_free(&p); + return 0; +} + +static int simple_ssl(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("https://webdav.org/", &p)); + ON(strcmp(p.scheme, "https")); + ON(p.port); + ne_uri_free(&p); + return OK; +} + +static int no_path(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("https://webdav.org", &p)); + ON(strcmp(p.path, "/")); + ne_uri_free(&p); + return OK; +} + +#define STR "/a¹²³¼½/" +static int escapes(void) +{ + char *un, *esc; + esc = ne_path_escape(STR); + ON(esc == NULL); + un = ne_path_unescape(esc); + ON(un == NULL); + ON(strcmp(un, STR)); + ne_free(un); + ne_free(esc); + ONN("unescape accepted invalid URI", + ne_path_unescape("/foo%zzbar") != NULL); + /* no-escape path */ + esc = ne_path_escape("/foobar"); + ON(strcmp(esc, "/foobar")); + ne_free(esc); + return OK; +} + +static int parents(void) +{ + static const struct { + const char *path, *parent; + } ps[] = { + { "/a/b/c", "/a/b/" }, + { "/a/b/c/", "/a/b/" }, + { "/alpha/beta", "/alpha/" }, + { "/foo", "/" }, + { "norman", NULL }, + { "/", NULL }, + { "", NULL }, + { NULL, NULL } + }; + int n; + + for (n = 0; ps[n].path != NULL; n++) { + char *p = ne_path_parent(ps[n].path); + if (ps[n].parent == NULL) { + ONV(p != NULL, ("parent of `%s' was `%s' not NULL", + ps[n].path, p)); + } else { + ONV(p == NULL, ("parent of `%s' was NULL", ps[n].path)); + ONV(strcmp(p, ps[n].parent), + ("parent of `%s' was `%s' not `%s'", + ps[n].path, p, ps[n].parent)); + ne_free(p); + } + } + + return OK; +} + +static int compares(void) +{ + const char *alpha = "/alpha"; + + ON(ne_path_compare("/a", "/a/") != 0); + ON(ne_path_compare("/a/", "/a") != 0); + ON(ne_path_compare("/ab", "/a/") == 0); + ON(ne_path_compare("/a/", "/ab") == 0); + ON(ne_path_compare("/a/", "/a/") != 0); + ON(ne_path_compare("/alpha/", "/beta/") == 0); + ON(ne_path_compare("/alpha", "/b") == 0); + ON(ne_path_compare("/alpha/", "/alphash") == 0); + ON(ne_path_compare("/fish/", "/food") == 0); + ON(ne_path_compare(alpha, alpha) != 0); + ON(ne_path_compare("/a/b/c/d", "/a/b/c/") == 0); + return OK; +} + +/* Checks that a URI comparison of 'u1' and 'u2', which have differing + * 'field', doesn't compare to equal; and that they are ordered + * correctly. */ +static int cmp_differ(const char *field, + const ne_uri *u1, const ne_uri *u2) +{ + ONV(ne_uri_cmp(u1, u2) == 0, + ("URIs with different %s were equal", field)); + + ONV(ne_uri_cmp(u2, u1) == 0, + ("URIs with different %s were equal (reversed)", field)); + + /* relies on strcmp return value being of equal magnitude when + * arguments are reversed; not sure if this is portable + * assumption. */ + ONV(ne_uri_cmp(u1, u2) + ne_uri_cmp(u2, u1) != 0, + ("relative ordering of URIs with different %s incorrect", field)); + + return OK; +} + +static int cmp(void) +{ + ne_uri alpha, beta; + + alpha.path = "/alpha"; + alpha.scheme = "http"; + alpha.host = "example.com"; + alpha.port = 80; + + beta = alpha; /* structure copy. */ + + ONN("equal URIs not equal", ne_uri_cmp(&alpha, &beta) != 0); + + beta.path = "/beta"; + CALL(cmp_differ("path", &alpha, &beta)); + + beta = alpha; beta.scheme = "https"; + CALL(cmp_differ("scheme", &alpha, &beta)); + + beta = alpha; beta.port = 433; + CALL(cmp_differ("port", &alpha, &beta)); + + beta = alpha; beta.host = "fish.com"; + CALL(cmp_differ("host", &alpha, &beta)); + + beta = alpha; beta.host = "EXAMPLE.CoM"; + ONN("hostname comparison not case-insensitive", + ne_uri_cmp(&alpha, &beta) != 0); + + beta = alpha; beta.scheme = "HtTp"; + ONN("scheme comparison not case-insensitive", + ne_uri_cmp(&alpha, &beta) != 0); + + beta = alpha; beta.path = ""; alpha.path = "/"; + ONN("empty abspath doesn't match '/'", + ne_uri_cmp(&alpha, &beta) != 0); + ONN("'/' doesn't match empty abspath", + ne_uri_cmp(&beta, &alpha) != 0); + + beta = alpha; alpha.path = ""; beta.path = "/foo"; + ONN("empty abspath matched '/foo'", ne_uri_cmp(&alpha, &beta) == 0); + ONN("'/foo' matched empty abspath ", ne_uri_cmp(&beta, &alpha) == 0); + + return OK; +} + +static int children(void) +{ + ON(ne_path_childof("/a", "/a/b") == 0); + ON(ne_path_childof("/a/", "/a/b") == 0); + ON(ne_path_childof("/aa/b/c", "/a/b/c/d/e") != 0); + ON(ne_path_childof("////", "/a") != 0); + return OK; +} + +static int slash(void) +{ + ON(ne_path_has_trailing_slash("/a/") == 0); + ON(ne_path_has_trailing_slash("/a") != 0); + { + /* check the uri == "" case. */ + char *foo = "/"; + ON(ne_path_has_trailing_slash(&foo[1])); + } + return OK; +} + +static int just_hostname(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("host.name.com", &p)); + ON(strcmp(p.host, "host.name.com")); + ne_uri_free(&p); + return 0; +} + +static int just_path(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("/argh", &p)); + ON(strcmp(p.path, "/argh")); + ne_uri_free(&p); + return 0; +} + +static int default_port(void) +{ + ONN("default http: port incorrect", ne_uri_defaultport("http") != 80); + ONN("default https: port incorrect", ne_uri_defaultport("https") != 443); + ONN("unspecified scheme: port incorrect", ne_uri_defaultport("ldap") != 0); + return OK; +} + +static int parse(void) +{ + static const struct test_uri { + const char *uri, *scheme, *host; + unsigned int port; + const char *path, *authinfo; + } uritests[] = { + { "http://webdav.org/norman", "http", "webdav.org", 0, "/norman", NULL }, + { "http://webdav.org:/norman", "http", "webdav.org", 0, "/norman", NULL }, + { "https://webdav.org/foo", "https", "webdav.org", 0, "/foo", NULL }, + { "http://webdav.org:8080/bar", "http", "webdav.org", 8080, "/bar", NULL }, + { "http://a/b", "http", "a", 0, "/b", NULL }, + { "http://webdav.org/bar:fish", "http", "webdav.org", 0, "/bar:fish", NULL }, + { "http://webdav.org", "http", "webdav.org", 0, "/", NULL }, + { "http://webdav.org/fish@food", "http", "webdav.org", 0, "/fish@food", NULL }, + /* authinfo */ + { "ftp://jim:bob@jim.com", "ftp", "jim.com", 0, "/", "jim:bob" }, + { "ldap://fred:bloggs@fish.com/foobar", "ldap", "fish.com", 0, + "/foobar", "fred:bloggs" }, + /* relativeURIs accepted for dubious legacy reasons. */ + { "a/b", NULL, "a", 0, "/b", NULL }, + { "a:8080/b", NULL, "a", 8080, "/b", NULL }, + { "/fish", NULL, NULL, 0, "/fish", NULL }, + { "webdav.org:8080", NULL, "webdav.org", 8080, "/", NULL }, + /* IPv6 hex strings allowed even if IPv6 not supported. */ + { "http://[::1]/foo", "http", "[::1]", 0, "/foo", NULL }, + { "http://[a:a:a:a::0]/foo", "http", "[a:a:a:a::0]", 0, "/foo", NULL }, + { "http://[::1]:8080/bar", "http", "[::1]", 8080, "/bar", NULL }, + { "ftp://[feed::cafe]:555", "ftp", "[feed::cafe]", 555, "/", NULL }, + { "http://fish/[foo]/bar", "http", "fish", 0, "/[foo]/bar", NULL }, + /* and some dubious ones: */ + { "[::1]/foo", NULL, "[::1]", 0, "/foo", NULL }, + { "[::1]:8000/foo", NULL, "[::1]", 8000, "/foo", NULL }, + { NULL } + }; + int n; + + for (n = 0; uritests[n].uri != NULL; n++) { + ne_uri res; + const struct test_uri *exp = &uritests[n]; + ONV(ne_uri_parse(exp->uri, &res) != 0, + ("%s: parse failed", exp->uri)); + ONV(res.port != exp->port, + ("%s: parsed port was %d not %d", exp->uri, res.port, exp->port)); + ONCMP(exp->scheme, res.scheme, exp->uri, "scheme"); + ONCMP(exp->host, res.host, exp->uri, "host"); + ONV(strcmp(res.path, exp->path), + ("%s: parsed path was %s not %s", exp->uri, res.path, exp->path)); + ONCMP(exp->authinfo, res.authinfo, exp->uri, "authinfo"); + ne_uri_free(&res); + } + + return OK; +} + +static int failparse(void) +{ + static const char *uris[] = { + "", + "http://[::1/", + NULL + }; + int n; + + for (n = 0; uris[n] != NULL; n++) { + ne_uri p; + ONV(ne_uri_parse(uris[n], &p) == 0, + ("`%s' did not fail to parse", uris[n])); + ne_uri_free(&p); + } + + return 0; +} + +static int unparse(void) +{ + const char *uris[] = { + "http://foo.com/bar", + "https://bar.com/foo/wishbone", + "http://www.random.com:8000/", + "http://[::1]:8080/", + "ftp://ftp.foo.bar/abc/def", + NULL + }; + int n; + + for (n = 0; uris[n] != NULL; n++) { + ne_uri parsed; + char *unp; + + ONV(ne_uri_parse(uris[n], &parsed), + ("failed to parse %s", uris[n])); + + if (parsed.port == 0) + parsed.port = ne_uri_defaultport(parsed.scheme); + + unp = ne_uri_unparse(&parsed); + + ONV(strcmp(unp, uris[n]), + ("unparse got %s from %s", unp, uris[n])); + + ne_uri_free(&parsed); + ne_free(unp); + } + + return OK; +} + +ne_test tests[] = { + T(simple), + T(simple_ssl), + T(no_path), + T(escapes), + T(parents), + T(compares), + T(cmp), + T(children), + T(slash), + T(just_hostname), + T(just_path), + T(default_port), + T(parse), + T(failparse), + T(unparse), + T(NULL) +}; diff --git a/test/util-tests.c b/test/util-tests.c new file mode 100644 index 0000000..3a774bc --- /dev/null +++ b/test/util-tests.c @@ -0,0 +1,256 @@ +/* + utils tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "ne_utils.h" +#include "ne_md5.h" +#include "ne_alloc.h" +#include "ne_dates.h" +#include "ne_string.h" + +#include "tests.h" + +static const struct { + const char *status; + int major, minor, code; + const char *rp; +} accept_sl[] = { + /* These are really valid. */ + { "HTTP/1.1 200 OK", 1, 1, 200, "OK" }, + { "HTTP/1.1000 200 OK", 1, 1000, 200, "OK" }, + { "HTTP/1000.1000 200 OK", 1000, 1000, 200, "OK" }, + { "HTTP/00001.1 200 OK", 1, 1, 200, "OK" }, + { "HTTP/1.00001 200 OK", 1, 1, 200, "OK" }, + { "HTTP/99.99 999 99999", 99, 99, 999, "99999" }, + { "HTTP/1.1 100 ", 1, 1, 100, "" }, + + /* these aren't really valid but we should be able to parse them. */ + { "HTTP/1.1 100", 1, 1, 100, "" }, + { "HTTP/1.1 200 OK", 1, 1, 200, "OK" }, + { "HTTP/1.1 200 \t OK", 1, 1, 200, "OK" }, + { " HTTP/1.1 200 OK", 1, 1, 200, "OK" }, + { "Norman is a dog HTTP/1.1 200 OK", 1, 1, 200, "OK" }, + { NULL } +}; + +static const char *bad_sl[] = { + "", + "HTTP/1.1 1000 OK", + "HTTP/1.1 1000", + "HTTP/-1.1 100 OK", + "HTTP/1.1 -100 OK", + "HTTP/ 200 OK", + "HTTP/", + "HTTP/1.1A 100 OK", + "HTTP/1.", + "HTTP/1.1 1", + "Fish/1.1 100 OK", + "HTTP/1.1 10", + "HTTP", + "H\0TP/1.1 100 OK", + NULL +}; + +static int status_lines(void) +{ + ne_status s; + int n; + + for (n = 0; accept_sl[n].status != NULL; n++) { + ONV(ne_parse_statusline(accept_sl[n].status, &s), + ("valid #%d: parse", n)); + ONV(accept_sl[n].major != s.major_version, ("valid #%d: major", n)); + ONV(accept_sl[n].minor != s.minor_version, ("valid #%d: minor", n)); + ONV(accept_sl[n].code != s.code, ("valid #%d: code", n)); + ONV(strcmp(accept_sl[n].rp, s.reason_phrase), + ("valid #%d: reason phrase", n)); + ne_free(s.reason_phrase); + } + + for (n = 0; bad_sl[n] != NULL; n++) { + ONV(ne_parse_statusline(bad_sl[n], &s) == 0, + ("invalid #%d", n)); + } + + return OK; +} + +/* Write MD5 of 'len' bytes of 'str' to 'digest' */ +static unsigned char *digest_md5(const char *data, size_t len, unsigned char digest[16]) +{ + struct ne_md5_ctx ctx; + +#define CHUNK 100 + ne_md5_init_ctx(&ctx); + /* exercise the buffering interface */ + while (len > CHUNK) { + ne_md5_process_bytes(data, CHUNK, &ctx); + len -= CHUNK; + data += CHUNK; + } + ne_md5_process_bytes(data, len, &ctx); + ne_md5_finish_ctx(&ctx, digest); + + return digest; +} + +static int md5(void) +{ + unsigned char buf[17] = {0}, buf2[17] = {0}; + char ascii[33] = {0}; + char zzzs[500]; + + ne_md5_to_ascii(digest_md5("", 0, buf), ascii); + ONN("MD5(null)", strcmp(ascii, "d41d8cd98f00b204e9800998ecf8427e")); + + ne_md5_to_ascii(digest_md5("foobar", 7, buf), ascii); + ONN("MD5(foobar)", strcmp(ascii, "b4258860eea29e875e2ee4019763b2bb")); + + /* $ perl -e 'printf "z"x500' | md5sum + * 8b9323bd72250ea7f1b2b3fb5046391a - */ + memset(zzzs, 'z', sizeof zzzs); + ne_md5_to_ascii(digest_md5(zzzs, sizeof zzzs, buf), ascii); + ONN("MD5(\"z\"x512)", strcmp(ascii, "8b9323bd72250ea7f1b2b3fb5046391a")); + + ne_ascii_to_md5(ascii, buf2); + ON(memcmp(buf, buf2, 16)); + + return OK; +} + +static int md5_alignment(void) +{ + char *bb = ne_malloc(66); + struct ne_md5_ctx ctx; + + /* regression test for a bug in md5.c in <0.15.0 on SPARC, where + * the process_bytes function would SIGBUS if the buffer argument + * isn't 32-bit aligned. Won't trigger on x86 though. */ + ne_md5_init_ctx(&ctx); + ne_md5_process_bytes(bb + 1, 65, &ctx); + ne_free(bb); + + return OK; +} + +static const struct { + const char *str; + time_t time; + enum { d_rfc1123, d_iso8601, d_rfc1036 } type; +} good_dates[] = { + { "Fri, 08 Jun 2001 22:59:46 GMT", 992041186, d_rfc1123 }, + { "Friday, 08-Jun-01 22:59:46 GMT", 992041186, d_rfc1036 }, + /* some different types of ISO8601 dates. */ + { "2001-06-08T22:59:46Z", 992041186, d_iso8601 }, + { "2001-06-08T22:59:46.9Z", 992041186, d_iso8601 }, + { "2001-06-08T26:00:46+03:01", 992041186, d_iso8601 }, + { "2001-06-08T20:58:46-02:01", 992041186, d_iso8601 }, + { NULL } +}; + +static int parse_dates(void) +{ + int n; + + for (n = 0; good_dates[n].str != NULL; n++) { + time_t res; + const char *str = good_dates[n].str; + + switch (good_dates[n].type) { + case d_rfc1036: res = ne_rfc1036_parse(str); break; + case d_iso8601: res = ne_iso8601_parse(str); break; + case d_rfc1123: res = ne_rfc1123_parse(str); break; + default: res = -1; break; + } + + ONV(res == -1, ("date %d parse", n)); + +#define FT "%" NE_FMT_TIME_T + ONV(res != good_dates[n].time, ( + "date %d incorrect (" FT " not " FT ")", n, + res, good_dates[n].time)); + } + + return OK; +} + +static int versioning(void) +{ +#define GOOD(n,m,msg) ONV(ne_version_match(n,m), \ +("match of " msg " failed (%d.%d)", n, m)) +#define BAD(n,m,msg) ONV(ne_version_match(n,m) == 0, \ +("match of " msg " succeeded (%d.%d)", n, m)) + GOOD(NEON_VERSION_MAJOR, NEON_VERSION_MINOR, "current version"); + BAD(NEON_VERSION_MAJOR, NEON_VERSION_MINOR + 1, "later minor"); + BAD(NEON_VERSION_MAJOR + 1, 0, "later major"); +#if NEON_VERSION_MINOR > 0 + GOOD(NEON_VERSION_MAJOR, NEON_VERSION_MINOR - 1, "earlier minor"); +#endif +#if NEON_VERSION_MAJOR > 0 + BAD(NEON_VERSION_MAJOR - 1, 0, "earlier major"); +#endif +#undef GOOD +#undef BAD + return OK; +} + +/* basic ne_version_string() sanity tests */ +static int version_string(void) +{ + char buf[1024]; + + ne_snprintf(buf, sizeof buf, "%s", ne_version_string()); + + NE_DEBUG(NE_DBG_HTTP, "Version string: %s\n", buf); + + ONN("version string too long", strlen(buf) > 200); + ONN("version string contained newline", strchr(buf, '\n') != NULL); + + return OK; +} + +static int support(void) +{ +#ifdef NEON_SSL + ONN("SSL support not advertised", !ne_supports_ssl()); +#else + ONN("SSL support advertised", ne_supports_ssl()); +#endif + return OK; +} + +ne_test tests[] = { + T(status_lines), + T(md5), + T(md5_alignment), + T(parse_dates), + T(versioning), + T(version_string), + T(support), + T(NULL) +}; diff --git a/test/utils.c b/test/utils.c new file mode 100644 index 0000000..47e1410 --- /dev/null +++ b/test/utils.c @@ -0,0 +1,107 @@ +/* + Utility functions for HTTP client tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* for sleep() */ +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "ne_session.h" + +#include "child.h" +#include "tests.h" +#include "utils.h" + +int make_session(ne_session **sess, server_fn fn, void *ud) +{ + *sess = ne_session_create("http", "localhost", 7777); + return spawn_server(7777, fn, ud); +} + +static int serve_response(ne_socket *s, const char *response) +{ + CALL(discard_request(s)); + CALL(discard_body(s)); + ONN("failed to send response", SEND_STRING(s, response)); + return OK; +} + +int single_serve_string(ne_socket *s, void *userdata) +{ + const char *str = userdata; + return serve_response(s, str); +} + +int sleepy_server(ne_socket *sock, void *userdata) +{ + sleep(10); + return 0; +} + +int many_serve_string(ne_socket *s, void *userdata) +{ + int n; + struct many_serve_args *args = userdata; + + for (n = 0; n < args->count; n++) { + NE_DEBUG(NE_DBG_HTTP, "Serving response %d\n", n); + CALL(serve_response(s, args->str)); + } + + return OK; +} + +int any_request(ne_session *sess, const char *uri) +{ + ne_request *req = ne_request_create(sess, "GET", uri); + int ret = ne_request_dispatch(req); + ne_request_destroy(req); + return ret; +} + +int any_2xx_request(ne_session *sess, const char *uri) +{ + ne_request *req = ne_request_create(sess, "GET", uri); + int ret = ne_request_dispatch(req); + ONV(ret != NE_OK || ne_get_status(req)->klass != 2, + ("request failed: %s\n", ne_get_error(sess))); + ne_request_destroy(req); + return ret; +} + +int any_2xx_request_body(ne_session *sess, const char *uri) +{ + ne_request *req = ne_request_create(sess, "GET", uri); +#define BSIZE 5000 + char *body = memset(ne_malloc(BSIZE), 'A', BSIZE); + int ret; + ne_set_request_body_buffer(req, body, BSIZE); + ret = ne_request_dispatch(req); + ne_free(body); + ONV(ret != NE_OK || ne_get_status(req)->klass != 2, + ("request failed: %s\n", ne_get_error(sess))); + ne_request_destroy(req); + return ret; +} + diff --git a/test/utils.h b/test/utils.h new file mode 100644 index 0000000..55c61ab --- /dev/null +++ b/test/utils.h @@ -0,0 +1,57 @@ +/* + neon-specific test utils + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef UTILS_H +#define UTILS_H 1 + +#include "ne_request.h" + +#define ONREQ(x) do { int _ret = (x); if (_ret) { t_context("line %d: HTTP error:\n%s", __LINE__, ne_get_error(sess)); return FAIL; } } while (0); + +int single_serve_string(ne_socket *s, void *userdata); + +struct many_serve_args { + int count; + const char *str; +}; + +/* Serves args->str response args->count times down a single + * connection. */ +int many_serve_string(ne_socket *s, void *userdata); + +/* Run a request using URI on the session. */ +int any_request(ne_session *sess, const char *uri); + +/* Run a request using URI on the session; fail on a non-2xx response. + */ +int any_2xx_request(ne_session *sess, const char *uri); + +/* As above but with a request body. */ +int any_2xx_request_body(ne_session *sess, const char *uri); + +/* makes *session, spawns server which will run 'fn(userdata, + * socket)'. sets error context if returns non-zero, i.e use like: + * CALL(make_session(...)); */ +int make_session(ne_session **sess, server_fn fn, void *userdata); + +/* Server which sleeps for 10 seconds then closes the socket. */ +int sleepy_server(ne_socket *sock, void *userdata); + +#endif /* UTILS_H */ diff --git a/test/xml.c b/test/xml.c new file mode 100644 index 0000000..9d76215 --- /dev/null +++ b/test/xml.c @@ -0,0 +1,444 @@ +/* + neon test suite + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_xml.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +/* A set of SAX handlers which serialize SAX events back into a + * pseudo-XML-like string. */ +static int startelm(void *userdata, int state, + const char *nspace, const char *name, + const char **atts) +{ + ne_buffer *buf = userdata; + int n; + + if (strcmp(name, "decline") == 0) + return NE_XML_DECLINE; + + ne_buffer_concat(buf, "<", "{", nspace, "}", name, NULL); + for (n = 0; atts && atts[n] != NULL; n+=2) { + ne_buffer_concat(buf, " ", atts[n], "='", atts[n+1], "'", NULL); + } + ne_buffer_zappend(buf, ">"); + + return state + 1; +} + +static int chardata(void *userdata, int state, const char *cdata, size_t len) +{ + ne_buffer *buf = userdata; + ne_buffer_append(buf, cdata, len); + return !strncmp(cdata, "!ABORT!", len); +} + +static int endelm(void *userdata, int state, + const char *nspace, const char *name) +{ + ne_buffer *buf = userdata; + ne_buffer_concat(buf, "</{", nspace, "}", name, ">", NULL); + return 0; +} + +/* A set of SAX handlers which do as above, but change some element + * names; used to check nested SAX handling is working properly. */ +static int startelm_xform(void *userdata, int state, + const char *nspace, const char *name, + const char **atts) +{ + if (strcmp(nspace, "two") == 0) + return startelm(userdata, state, nspace, "xform", atts); + else + return NE_XML_DECLINE; +} + +static int endelm_xform(void *userdata, int state, + const char *nspace, const char *name) +{ + if (strcmp(nspace, "two") == 0) + return endelm(userdata, state, nspace, "xform"); + else + return NE_XML_DECLINE; +} + +/* A set of SAX handlers which verify that state handling is working + * correctly. */ +static int startelm_state(void *userdata, int parent, + const char *nspace, const char *name, + const char **atts) +{ + int n; + + if (strcmp(nspace, "state") != 0) + return NE_XML_DECLINE; + + for (n = 0; atts[n]; n += 2) { + if (strcmp(atts[n], "parent") == 0) { + int expected = atoi(atts[n+1]); + + if (expected != parent) { + char err[50]; + sprintf(err, "parent state of %s was %d not %d", name, parent, + expected); + ne_buffer_zappend(userdata, err); + } + } + } + + return atoi(name+1); +} + +static int endelm_state(void *userdata, int state, + const char *nspace, const char *name) +{ + int expected = atoi(name + 1); + ne_buffer *buf = userdata; + + if (state != expected) + ne_buffer_concat(buf, "wrong state in endelm of ", name, NULL); + + return 0; +} + +/* A set of SAX handlers which verify that abort handling is working + * correctly. */ +static int startelm_abort(void *buf, int parent, + const char *nspace, const char *name, + const char **atts) +{ + if (strcmp(name, "abort-start") == 0) { + ne_buffer_zappend(buf, "ABORT"); + return NE_XML_ABORT; + } else + return startelm(buf, parent, nspace, name, atts); +} + +static int endelm_abort(void *buf, int state, + const char *nspace, const char *name) +{ + if (strcmp(name, "abort-end") == 0) { + ne_buffer_zappend(buf, "ABORT"); + return -1; + } else + return 0; +} + +enum match_type { + match_valid = 0, + match_invalid, + match_nohands, + match_encoding +}; + +static int parse_match(const char *doc, const char *result, enum match_type t) +{ + ne_xml_parser *p = ne_xml_create(); + ne_buffer *buf = ne_buffer_create(); + + if (t == match_invalid) + ne_xml_push_handler(p, startelm_abort, chardata, endelm_abort, buf); + if (t != match_encoding && t != match_nohands) { + ne_xml_push_handler(p, startelm_state, NULL, endelm_state, buf); + ne_xml_push_handler(p, startelm, chardata, endelm, buf); + ne_xml_push_handler(p, startelm_xform, chardata, endelm_xform, buf); + } + + ne_xml_parse(p, doc, strlen(doc)); + ne_xml_parse(p, "", 0); + + if (t == match_invalid) + ONV(ne_xml_valid(p), ("parse did not fail: %s", buf->data)); + else + ONV(!ne_xml_valid(p), ("parse failed: %s", ne_xml_get_error(p))); + + if (t == match_encoding) { + const char *enc = ne_xml_doc_encoding(p); + ONV(strcmp(enc, result), ("encoding was `%s' not `%s'", enc, result)); + } else if (t == match_valid) + ONV(strcmp(result, buf->data), + ("result mismatch: %s not %s", buf->data, result)); + + ne_xml_destroy(p); + ne_buffer_destroy(buf); + + return OK; +} + +static int matches(void) +{ +#define PFX "<?xml version='1.0'?>\r\n" +#define E(ns, n) "<{" ns "}" n "></{" ns "}" n ">" + static const struct { + const char *in, *out; + enum match_type invalid; + } ms[] = { + + /*** Simplest tests ***/ + { PFX "<hello/>", "<{}hello></{}hello>"}, + { PFX "<hello foo='bar'/>", + "<{}hello foo='bar'></{}hello>"}, + + /*** Tests for character data handling. ***/ + { PFX "<hello> world</hello>", "<{}hello> world</{}hello>"}, + /* test for cdata between elements. */ + { PFX "<hello>\r\n<wide> world</wide></hello>", + "<{}hello>\n<{}wide> world</{}wide></{}hello>"}, + + /*** Tests for namespace handling. ***/ +#define NSA "xmlns:foo='bar'" + { PFX "<foo:widget " NSA "/>", + "<{bar}widget " NSA ">" + "</{bar}widget>" }, + /* inherited namespace expansion. */ + { PFX "<widget " NSA "><foo:norman/></widget>", + "<{}widget " NSA ">" E("bar", "norman") "</{}widget>"}, + { PFX "<widget " NSA " xmlns:abc='def' xmlns:g='z'>" + "<foo:norman/></widget>", + "<{}widget " NSA " xmlns:abc='def' xmlns:g='z'>" + E("bar", "norman") "</{}widget>"}, + /* empty namespace default takes precedence. */ + { PFX "<widget xmlns='foo'><smidgen xmlns=''><norman/>" + "</smidgen></widget>", + "<{foo}widget xmlns='foo'><{}smidgen xmlns=''>" + E("", "norman") + "</{}smidgen></{foo}widget>" }, + /* inherited empty namespace default */ + { PFX "<bar xmlns='foo'><grok xmlns=''><fish/></grok></bar>", + "<{foo}bar xmlns='foo'><{}grok xmlns=''>" + E("", "fish") "</{}grok></{foo}bar>" }, + + /* regression test for neon <= 0.23.5 with libxml2, where the + * "dereference entities" flag was not set by default. */ + { PFX "<widget foo=\"no&body\"/>", + "<{}widget foo='no&body'></{}widget>" }, + { PFX "<widget foo=\"no body\"/>", + "<{}widget foo='no body'></{}widget>" }, + + /* tests for declined branches */ + { PFX "<hello><decline>fish</decline>" + "<world><decline/>yes</world>goodbye<decline/></hello>", + "<{}hello><{}world>yes</{}world>goodbye</{}hello>" }, + { PFX + "<hello><decline><nested>fish</nested>bar</decline><fish/></hello>", + "<{}hello>" E("", "fish") "</{}hello>" }, + /* tests for nested SAX handlers */ + { PFX "<hello xmlns='two'><decline/></hello>", + "<{two}hello xmlns='two'>" E("two", "xform") "</{two}hello>"}, + + /* tests for state handling */ + { PFX "<a55 xmlns='state'/>", "" }, + { PFX "<a777 xmlns='state' parent='0'/>", "" }, + { PFX "<a99 xmlns='state'><f77 parent='99'/>blah</a99>", "" }, + + /* tests for abort handling */ + { PFX "<hello><merry><abort-start/></merry></hello>", + "<{}hello><{}merry>ABORT", match_invalid }, + { PFX "<hello><merry><abort-end/>fish</merry></hello>", + "<{}hello><{}merry><{}abort-end>ABORT", match_invalid }, + { PFX "<hello>!ABORT!</hello>", "<{}hello>!ABORT!", match_invalid }, + { PFX "<hello>!ABORT!<foo/></hello>", "<{}hello>!ABORT!", match_invalid }, + { PFX "<hello>!ABORT!</fish>", "<{}hello>!ABORT!", match_invalid }, + + /* tests for encodings */ + { "<?xml version='1.0' encoding='ISO-8859-1'?><hello/>", + "ISO-8859-1", match_encoding }, + + { "<?xml version='1.0' encoding='UTF-8'?><hello/>", + "UTF-8", match_encoding }, + + /* test that parse is valid even with no handlers registered. */ + { PFX "<hello><old>world</old></hello>", "", match_nohands }, + + /* regression test for prefix matching bug fixed in 0.18.0 */ +#define THENS "xmlns:d='foo' xmlns:dd='bar'" + { PFX "<d:hello " THENS "/>", + "<{foo}hello " THENS "></{foo}hello>" }, + + /**** end of list ****/ { NULL, NULL } + }; + int n; + + for (n = 0; ms[n].in != NULL; n++) { + CALL(parse_match(ms[n].in, ms[n].out, ms[n].invalid)); + } + + return OK; +} + +static int mapping(void) +{ + static const struct ne_xml_idmap map[] = { + { "fee", "bar", 1 }, + { "foo", "bar", 2 }, + { "bar", "foo", 3 }, + { "", "bob", 4 }, + { "balloon", "buffoon", 5}, + { NULL, NULL, 0} + }; + int n; + + for (n = 0; map[n].id; n++) { + int id = ne_xml_mapid(map, NE_XML_MAPLEN(map) - 1, + map[n].nspace, map[n].name); + ONV(id != map[n].id, ("mapped to id %d not %d", id, map[n].id)); + } + + n = ne_xml_mapid(map, NE_XML_MAPLEN(map) - 1, "no-such", "element"); + ONV(n != 0, ("unknown element got id %d not zero", n)); + + return OK; +} + +/* Test for some parse failures */ +static int fail_parse(void) +{ + static const char *docs[] = { + "foo", + PFX "<bar:foo/>", + /* malformed namespace declarations */ + PFX "<foo xmlns:D=''/>", + PFX "<foo xmlns:='fish'/>", + PFX "<foo: xmlns:foo='bar'/>", + NULL + }; + int n; + + for (n = 0; docs[n]; n++) { + ne_xml_parser *p = ne_xml_create(); + const char *err; + + ne_xml_parse(p, docs[n], strlen(docs[n])); + ne_xml_parse(p, "", 0); + ONV(ne_xml_valid(p), ("`%s' was valid", docs[n])); + + err = ne_xml_get_error(p); + ONV(strstr(err, "parse error") == NULL, + ("bad error %s", err)); + + ne_xml_destroy(p); + } + + return OK; +} + +static int check_attrib(ne_xml_parser *p, const char **atts, + const char *nspace, const char *name, + const char *value) +{ + const char *act = ne_xml_get_attr(p, atts, nspace, name); + char err[50]; + int ret = 0; + + if (value == NULL) { + if (act != NULL) { + sprintf(err, "attribute %s was set to %s", name, act); + ret = NE_XML_ABORT; + } + } else { + if (act == NULL) { + sprintf(err, "attribute %s not found", name); + ret = NE_XML_ABORT; + } else if (strcmp(act, value) != 0) { + sprintf(err, "attribute %s was %s not %s", name, act, value); + ret = NE_XML_ABORT; + } + } + if (ret == NE_XML_ABORT) ne_xml_set_error(p, err); + return ret; +} + +static int startelm_attrib(void *userdata, int state, + const char *nspace, const char *name, + const char **atts) +{ + ne_xml_parser *p = userdata; + + if (strcmp(name, "hello") == 0) { + CALL(check_attrib(p, atts, NULL, "first", "second")); + CALL(check_attrib(p, atts, NULL, "third", "")); + CALL(check_attrib(p, atts, "garth", "bar", "asda")); + CALL(check_attrib(p, atts, "giraffe", "bar", NULL)); + CALL(check_attrib(p, atts, "hot", "dog", NULL)); + CALL(check_attrib(p, atts, NULL, "nonesuch", NULL)); + } else if (strcmp(name, "goodbye") == 0) { + if (atts[0] != NULL) { + ne_xml_set_error(p, "non-empty attrib array"); + return 1; + } + } + + return 1; +} + +static int attributes(void) +{ + ne_xml_parser *p = ne_xml_create(); + static const char doc[] = PFX + "<hello xmlns:foo='garth' first='second' third='' " + "foo:bar='asda' goof:bar='jar'><goodbye/></hello>"; + + ne_xml_push_handler(p, startelm_attrib, NULL, NULL, p); + + ne_xml_parse_v(p, doc, strlen(doc)); + + ONV(!ne_xml_valid(p), ("parse error: %s", ne_xml_get_error(p))); + + ne_xml_destroy(p); + return OK; +} + +/* Test for the get/set error interface */ +static int errors(void) +{ + ne_xml_parser *p = ne_xml_create(); + const char *err; + + ne_xml_set_error(p, "Fish food"); + err = ne_xml_get_error(p); + + ONV(strcmp(err, "Fish food"), ("wrong error %s!", err)); + + ne_xml_destroy(p); + return 0; +} + +ne_test tests[] = { + T(matches), + T(mapping), + T(fail_parse), + T(attributes), + T(errors), + T(NULL) +}; + |