diff options
263 files changed, 48161 insertions, 0 deletions
diff --git a/neon/.cvsignore b/neon/.cvsignore new file mode 100644 index 000000000..ba4ab4f6a --- /dev/null +++ b/neon/.cvsignore @@ -0,0 +1,9 @@ +config.h.in +config.h +configure +config.status +config.log +Makefile +aclocal.m4 +nget +config.cache diff --git a/neon/.package b/neon/.package new file mode 100644 index 000000000..493f404ab --- /dev/null +++ b/neon/.package @@ -0,0 +1 @@ +announce-list=neon@webdav.org diff --git a/neon/.release.sh b/neon/.release.sh new file mode 100755 index 000000000..78cdcc526 --- /dev/null +++ b/neon/.release.sh @@ -0,0 +1,14 @@ +#!/bin/sh +major=`echo $1 | sed "s/\..*//g"` +minor=`echo $1 | sed "s/^[0-9]*\.\(.*\)\.[0-9]*$/\1/g"` +rel=`echo $1 | sed "s/^.*\./\1/g"` +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 diff --git a/neon/AUTHORS b/neon/AUTHORS new file mode 100644 index 000000000..f64fe1852 --- /dev/null +++ b/neon/AUTHORS @@ -0,0 +1 @@ +Joe Orton <joe@orton.demon.co.uk> diff --git a/neon/BUGS b/neon/BUGS new file mode 100644 index 000000000..5a4e3e260 --- /dev/null +++ b/neon/BUGS @@ -0,0 +1,7 @@ + +Known problems/bugs in neon -*- text -*- +--------------------------- Id: BUGS,v 1.2 2000/07/27 20:59:19 joe Exp + +1. Each new SSL request is opening a new connection to the server. + VERY slow. + diff --git a/neon/ChangeLog b/neon/ChangeLog new file mode 100644 index 000000000..34811560e --- /dev/null +++ b/neon/ChangeLog @@ -0,0 +1,24 @@ +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/neon/INSTALL.win32 b/neon/INSTALL.win32 new file mode 100644 index 000000000..04e161189 --- /dev/null +++ b/neon/INSTALL.win32 @@ -0,0 +1,14 @@ +
+To install neon on Windows you need expat-lite already installed on your system.
+It can be taken from the Apache distribution or downloaded from the expat website.
+Now simply point make to the expat sources:
+
+ nmake /f neon.mak EXPAT_SRC=\path\to\expat-lite
+
+This should work with Microsoft VC++ 5 and 6.
+
+After compiling the Release subdirectory contains neons.lib, against which you can
+link your program. When you run your program make sure the XMLPARSE.DLL from
+expat is accessable, i.e. is in your PATH.
+
+
diff --git a/neon/Makefile.in b/neon/Makefile.in new file mode 100644 index 000000000..3389afb73 --- /dev/null +++ b/neon/Makefile.in @@ -0,0 +1,80 @@ + +CFLAGS = -Ilib -I. -Isrc @CFLAGS@ @DEFS@ +LDFLAGS = -L. @LDFLAGS@ +LIBS = @LIBS@ +CC = @CC@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL = @INSTALL@ +AR = ar +RANLIB = @RANLIB@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +man1dir = @mandir@/man1 +# Hmmmmm... the i18n stuff seems to want this: +datadir = $(prefix)/@DATADIRNAME@ +# Previously I had: +# datadir = @datadir@/sitecopy +docdir = $(prefix)/doc/sitecopy +sc_datadir = $(datadir)/sitecopy +includedir = @includedir@ +neonincludes = $(includedir)/libneon + +top_srcdir = @top_srcdir@ +# Where does top_builddir come from? +top_builddir = @top_srcdir@ +srcdir = @srcdir@ +# intl stuff +localedir = $(datadir)/locale +gnulocaledir = $(prefix)/share/locale +gettextsrcdir = $(prefix)/share/gettext +aliaspath = $(localedir):. +@SET_MAKE@ + +OBJECTS = src/http_request.o src/http_basic.o src/dav_basic.o src/dav_207.o \ + src/string_utils.o src/dates.o src/xalloc.o src/hip_xml.o \ + src/base64.o src/md5.o src/http_utils.o src/uri.o src/socket.o \ + src/http_auth.o + +DIST_HEADERS = http_request.h http_utils.h uri.h socket.h http_basic.h \ + dav_basic.h dav_207.h dav_props.h hip_xml.h dates.h string_utils.h + +libneon.a: $(OBJECTS) + $(AR) cru $@ $(OBJECTS) + $(RANLIB) $@ + +shared: libneon.so + +libneon.so: $(OBJECTS) + gcc -shared -o $@ $(OBJECTS) + +examples: nget + +nget: libneon.a example/nget.o lib/basename.o + $(CC) $(LDFLAGS) -o $@ example/nget.o lib/basename.o -lneon + +install: install-static install-headers + +install-static: libneon.a + $(INSTALL) -d $(libdir) + cp libneon.a $(libdir)/libneon.a + +install-shared: libneon.so + $(INSTALL) -d $(libdir) + cp libneon.so $(libdir)/libneon.so + +install-headers: + $(INSTALL) -d $(neonincludes) + @for h in $(DIST_HEADERS); do \ + echo Installing $$h into $(neonincludes); \ + $(INSTALL_DATA) src/$$h $(neonincludes)/$$h; \ + done + +neon_dir = src +config_h = config.h + +include src/Makefile.incl + diff --git a/neon/NEWS b/neon/NEWS new file mode 100644 index 000000000..5b96550a5 --- /dev/null +++ b/neon/NEWS @@ -0,0 +1,2 @@ +Changes in release 0.1.0 +- Initial release. diff --git a/neon/README b/neon/README new file mode 100644 index 000000000..a2a3478c8 --- /dev/null +++ b/neon/README @@ -0,0 +1,25 @@ + +neon is an HTTP and WebDAV client library. + +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) + - Generic WebDAV 207 XML response handling mechanism + - XML parsing using expat or libxml 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). + +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. + +Joe Orton +<joe@orton.demon.co.uk> diff --git a/neon/THANKS b/neon/THANKS new file mode 100644 index 000000000..accdb9753 --- /dev/null +++ b/neon/THANKS @@ -0,0 +1,10 @@ + +Greg Stein <gstein@lyra.org> +Michael Sobolev <mss@despair.spb.ru> + +Originators of stolen code: + +Daniel Veillard <Daniel.Veillard@w3.org> +Eric S Raymond <esr@snark.thyrsus.com> +Ulrich Drepper <drepper@gnu.org> + diff --git a/neon/TODO b/neon/TODO new file mode 100644 index 000000000..555670638 --- /dev/null +++ b/neon/TODO @@ -0,0 +1,104 @@ + +To Do List for neon -*- text -*- +------------------- Id: TODO,v 1.3 2000/05/13 20:52:43 joe Exp + +Please submit feature requests to <mailto:neon@webdav.org> + +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 + +2. Add proper domain support to authentication code. (requires full + URI parsing support). Need to tell the auth layer the server + details. + +3. Add a callback to determine whether a specific request should go + through the proxy server or not... based on URI, maybe method too + since lots of HTTP/1.0 proxies break on DAV requests? + +4. Better cnonce generation for authentication: use /dev/random or + whatever like mod_auth_digest. + +5. Add request hooks to remove all authentication code from + http_request.c. + +6. PUT/GET with ranges... http_get_range + +7. hip_xml/dav_207/dav_prop might need a revamp. + + hip_xml: use an expat-esque interface; i.e. make hip_xml_parser + opaque and add API to set handlers rather than initialize + structures directly. Two problems need to be solved more elegantly: + + 1) Allowing "sub-handler" for allowing parsing property element + contents independantly of the overally 207 response parse. + + 2) Allow "mixed-mode" parsing of XML which mixes cdata + elements. + + Probably, make hip_xml more of a "utility" layer rather than a + "driving" layer; abstract out expat/libxml, namespace lookups, + element name -> id mapping, easy CDATA handling... + +8. WebDAV class 2 locking. Problems: this requires parsing the XML + within a property element. This could be achieved by doing a + completely new XML parse of the property value returned by end_prop + from the 207 code, but this is a bit noddy. Options: + + a) Ideally: extend hip_xml and the 207 layer to allow doing a + proper start/end_element/cdata parse of the XML *within* a + property. This is tricky since it means extending hip_xml to + *dynamically* determine whether to be in "collect" mode or not. Add + another callback for this? + +9. DeltaV support (http://www.webdav.org/deltav/). See also the + inversion project (http://inversion.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). + +12. SSL/TLS support... make it pluggable so we don't have to export + crypto-related code at ALL? + +13. Should we really be abort()'ing on out-of-memory? It makes a lot + of code MUCH simpler (esp. sbuffer_* usage). + +14. Nicer request-header manipulation... some kind of indexed data + structure, so we're sure we don't add the same header to the + request twice (e.g. Cache-Control). Must be at least as flexible + as sbuffer usage, though. + +16. Socket status notification (socket.c:sock_register_*) is awful. + +17. Should we really be i18n'izing the low-level error messages in + http_request.c, dav_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. + +18. PROPFIND/propnames support. + +19. libtool support, for proper shared libraries. + +20. Add full 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 + http_auth_session, so I log into e.g. /foo/ and /bar/ (which + are not in the same authentication domain) + switch between them without having to re-enter passwords all the + time. + +22. Handle PROPFIND property error responses properly. + +23. Mechanism for aborting a request mid-response; e.g., when a GET + fails due to out of disk space, abort the download. + +24. In a PROPFIND response, if a property were to include e.g., a + DAV:multistatus element, this would not be handled correctly. + +25. A BSD C library has an MD5 implementation in the C Library... + support this. (someone who runs a BSD will need to do this) + diff --git a/neon/autogen.sh b/neon/autogen.sh new file mode 100644 index 000000000..2bd9617bb --- /dev/null +++ b/neon/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/sh +rm -f config.cache +([ ! -d macros ] || (echo -n aclocal...\ && aclocal -I macros)) && \ +([ ! -r acconfig.h ] || (echo -n autoheader...\ && autoheader)) && \ +([ ! -r macros/acconfig.h ] || (echo -n autoheader...\ && autoheader -l macros)) && \ +echo -n autoconf...\ && autoconf diff --git a/neon/config.guess b/neon/config.guess new file mode 100755 index 000000000..a1b76ce2f --- /dev/null +++ b/neon/config.guess @@ -0,0 +1,1034 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 93, 94, 95, 96, 97, 1998 Free Software Foundation, Inc. +# +# This file 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Written by Per Bothner <bothner@cygnus.com>. +# The master version of this file is at the FSF in /home/gd/gnu/lib. +# Please send patches to the Autoconf mailing list <autoconf@gnu.org>. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit system type (host/target name). +# +# Only a few systems have been added to this list; please add others +# (but try to keep the structure clean). +# + +# Use $HOST_CC if defined. $CC may point to a cross-compiler +if test x"$CC_FOR_BUILD" = x; then + if test x"$HOST_CC" != x; then + CC_FOR_BUILD="$HOST_CC" + else + if test x"$CC" != x; then + CC_FOR_BUILD="$CC" + else + CC_FOR_BUILD=cc + fi + fi +fi + + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 8/24/94.) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +dummy=dummy-$$ +trap 'rm -f $dummy.c $dummy.o $dummy; exit 1' 1 2 15 + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + alpha:OSF1:*:*) + if test $UNAME_RELEASE = "V4.0"; then + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + fi + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + cat <<EOF >$dummy.s + .globl main + .ent main +main: + .frame \$30,0,\$26,0 + .prologue 0 + .long 0x47e03d80 # implver $0 + lda \$2,259 + .long 0x47e20c21 # amask $2,$1 + srl \$1,8,\$2 + sll \$2,2,\$2 + sll \$0,3,\$0 + addl \$1,\$0,\$0 + addl \$2,\$0,\$0 + ret \$31,(\$26),1 + .end main +EOF + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy + case "$?" in + 7) + UNAME_MACHINE="alpha" + ;; + 15) + UNAME_MACHINE="alphaev5" + ;; + 14) + UNAME_MACHINE="alphaev56" + ;; + 10) + UNAME_MACHINE="alphapca56" + ;; + 16) + UNAME_MACHINE="alphaev6" + ;; + esac + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr [[A-Z]] [[a-z]]` + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-cbm-sysv4 + exit 0;; + amiga:NetBSD:*:*) + echo m68k-cbm-netbsd${UNAME_RELEASE} + exit 0 ;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + arc64:OpenBSD:*:*) + echo mips64el-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hkmips:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + arm32:NetBSD:*:*) + echo arm-unknown-netbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + SR2?01:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:*|MIS*:OSx*:*:*|MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(head -1 /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + atari*:NetBSD:*:*) + echo m68k-atari-netbsd${UNAME_RELEASE} + exit 0 ;; + atari*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + sun3*:NetBSD:*:*) + echo m68k-sun-netbsd${UNAME_RELEASE} + exit 0 ;; + sun3*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:NetBSD:*:*) + echo m68k-apple-netbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + macppc:NetBSD:*:*) + echo powerpc-apple-netbsd${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy \ + && ./$dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 -o $UNAME_PROCESSOR = mc88110 ] ; then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx \ + -o ${TARGET_BINARY_INTERFACE}x = x ] ; then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i?86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + sed 's/^ //' << EOF >$dummy.c + #include <sys/systemcfg.h> + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:4) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | head -1 | awk '{ print $1 }'` + if /usr/sbin/lsattr -EHl ${IBM_CPU_ID} | grep POWER >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=4.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC NetBSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/6?? | 9000/7?? | 9000/80[024] | 9000/8?[136790] | 9000/892 ) + sed 's/^ //' << EOF >$dummy.c + #include <stdlib.h> + #include <unistd.h> + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + ($CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null ) && HP_ARCH=`./$dummy` + rm -f $dummy.c $dummy + esac + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + sed 's/^ //' << EOF >$dummy.c + #include <unistd.h> + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE*:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i?86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*X-MP:*:*:*) + echo xmp-cray-unicos + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} + exit 0 ;; + CRAY*T3E:*:*:*) + echo t3e-cray-unicosmk${UNAME_RELEASE} + exit 0 ;; + CRAY-2:*:*:*) + echo cray2-cray-unicos + exit 0 ;; + F300:UNIX_System_V:*:*) + FUJITSU_SYS=`uname -p | tr [A-Z] [a-z] | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "f300-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + F301:UNIX_System_V:*:*) + echo f301-fujitsu-uxpv`echo $UNAME_RELEASE | sed 's/ .*//'` + exit 0 ;; + hp3[0-9][05]:NetBSD:*:*) + echo m68k-hp-netbsd${UNAME_RELEASE} + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + i?86:BSD/386:*:* | i?86:BSD/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + if test -x /usr/bin/objformat; then + if test "elf" = "`/usr/bin/objformat`"; then + echo ${UNAME_MACHINE}-unknown-freebsdelf`echo ${UNAME_RELEASE}|sed -e 's/[-_].*//'` + exit 0 + fi + fi + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; + *:NetBSD:*:*) + echo ${UNAME_MACHINE}-unknown-netbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + *:Linux:*:*) + # uname on the ARM produces all sorts of strangeness, and we need to + # filter it out. + case "$UNAME_MACHINE" in + armv*) UNAME_MACHINE=$UNAME_MACHINE ;; + arm* | sa110*) UNAME_MACHINE="arm" ;; + esac + + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + ld_help_string=`cd /; ld --help 2>&1` + ld_supported_emulations=`echo $ld_help_string \ + | sed -ne '/supported emulations:/!d + s/[ ][ ]*/ /g + s/.*supported emulations: *// + s/ .*// + p'` + case "$ld_supported_emulations" in + i?86linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" ; exit 0 ;; + i?86coff) echo "${UNAME_MACHINE}-pc-linux-gnucoff" ; exit 0 ;; + sparclinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + armlinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + m68klinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + elf32ppc) echo "powerpc-unknown-linux-gnu" ; exit 0 ;; + esac + + if test "${UNAME_MACHINE}" = "alpha" ; then + sed 's/^ //' <<EOF >$dummy.s + .globl main + .ent main + main: + .frame \$30,0,\$26,0 + .prologue 0 + .long 0x47e03d80 # implver $0 + lda \$2,259 + .long 0x47e20c21 # amask $2,$1 + srl \$1,8,\$2 + sll \$2,2,\$2 + sll \$0,3,\$0 + addl \$1,\$0,\$0 + addl \$2,\$0,\$0 + ret \$31,(\$26),1 + .end main +EOF + LIBC="" + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy + case "$?" in + 7) + UNAME_MACHINE="alpha" + ;; + 15) + UNAME_MACHINE="alphaev5" + ;; + 14) + UNAME_MACHINE="alphaev56" + ;; + 10) + UNAME_MACHINE="alphapca56" + ;; + 16) + UNAME_MACHINE="alphaev6" + ;; + esac + + objdump --private-headers $dummy | \ + grep ld.so.1 > /dev/null + if test "$?" = 0 ; then + LIBC="libc1" + fi + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} ; exit 0 + elif test "${UNAME_MACHINE}" = "mips" ; then + cat >$dummy.c <<EOF +#ifdef __cplusplus + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif +#ifdef __MIPSEB__ + printf ("%s-unknown-linux-gnu\n", argv[1]); +#endif +#ifdef __MIPSEL__ + printf ("%sel-unknown-linux-gnu\n", argv[1]); +#endif + return 0; +} +EOF + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + else + # Either a pre-BFD a.out linker (linux-gnuoldld) + # or one that does not give us useful --help. + # GCC wants to distinguish between linux-gnuoldld and linux-gnuaout. + # If ld does not provide *any* "supported emulations:" + # that means it is gnuoldld. + echo "$ld_help_string" | grep >/dev/null 2>&1 "supported emulations:" + test $? != 0 && echo "${UNAME_MACHINE}-pc-linux-gnuoldld" && exit 0 + + case "${UNAME_MACHINE}" in + i?86) + VENDOR=pc; + ;; + *) + VENDOR=unknown; + ;; + esac + # Determine whether the default compiler is a.out or elf + cat >$dummy.c <<EOF +#include <features.h> +#ifdef __cplusplus + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif +#ifdef __ELF__ +# ifdef __GLIBC__ +# if __GLIBC__ >= 2 + printf ("%s-${VENDOR}-linux-gnu\n", argv[1]); +# else + printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]); +# endif +# else + printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]); +# endif +#else + printf ("%s-${VENDOR}-linux-gnuaout\n", argv[1]); +#endif + return 0; +} +EOF + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + fi ;; +# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. earlier versions +# are messed up and put the nodename in both sysname and nodename. + i?86:DYNIX/ptx:4*:*) + echo i386-sequent-sysv4 + exit 0 ;; + i?86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i?86:*:4.*:* | i?86:SYSTEM_V:4.*:*) + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_RELEASE} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_RELEASE} + fi + exit 0 ;; + i?86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name` + echo ${UNAME_MACHINE}-pc-isc$UNAME_REL + elif /bin/uname -X 2>/dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')` + (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + i?86:UnixWare:*:*) + if /bin/uname -X 2>/dev/null >/dev/null ; then + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + fi + echo ${UNAME_MACHINE}-unixware-${UNAME_RELEASE}-${UNAME_VERSION} + exit 0 ;; + pc:*:*:*) + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[34]??:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 4850:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + i?86:LynxOS:2.*:* | i?86:LynxOS:3.[01]*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:* | PowerPC:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:CPunix:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says <Richard.M.Bartel@ccMail.Census.GOV> + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes <hewes@openmarket.com>. + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:*:6*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R4000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +cat >$dummy.c <<EOF +#ifdef _SEQUENT_ +# include <sys/types.h> +# include <sys/utsname.h> +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include <sys/param.h> + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +#if !defined (ultrix) + printf ("vax-dec-bsd\n"); exit (0); +#else + printf ("vax-dec-ultrix\n"); exit (0); +#endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy && rm $dummy.c $dummy && exit 0 +rm -f $dummy.c $dummy + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +#echo '(Unable to guess system type)' 1>&2 + +exit 1 diff --git a/neon/config.hw b/neon/config.hw new file mode 100644 index 000000000..9b235ff72 --- /dev/null +++ b/neon/config.hw @@ -0,0 +1,38 @@ +/* + Win32 config.h + Copyright (C) 1999-2000, Peter Boos <pedib@colorfullife.com> + + 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 + + Id: config.hw,v 1.1 2000/12/13 20:15:06 joe Exp +*/ + +#ifdef WIN32 +#define HAVE_STRING_H +#define HAVE_EXPAT +#define HAVE_MEMCPY + + +// 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 + +#include <io.h> +#define read _read +#endif diff --git a/neon/config.hw.in b/neon/config.hw.in new file mode 100644 index 000000000..3d86b2768 --- /dev/null +++ b/neon/config.hw.in @@ -0,0 +1,53 @@ +/* + Win32 config.h + Copyright (C) 1999-2000, Peter Boos <pedib@colorfullife.com> + + 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 + +*/ +#ifdef _WIN32
+#define WIN32
+#endif
+ +#ifdef WIN32 +
+#define NEON_VERSION "@VERSION@"
+#define NEON_VERSION_MAJOR (@MAJOR@)
+#define NEON_VERSION_MINOR (@MINOR@)
+
+
+#define HAVE_STRING_H +#define HAVE_EXPAT +#define HAVE_OLD_EXPAT +#define HAVE_MEMCPY + +#define HAVE_STDLIB_H +#define HAVE_STRING_H +#define HAVE_LIMITS_H + +/* 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/neon/config.sub b/neon/config.sub new file mode 100755 index 000000000..692de9b61 --- /dev/null +++ b/neon/config.sub @@ -0,0 +1,993 @@ +#! /bin/sh +# Configuration validation subroutine script, version 1.1. +# Copyright (C) 1991, 92-97, 1998 Free Software Foundation, Inc. +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +if [ x$1 = x ] +then + echo Configuration name missing. 1>&2 + echo "Usage: $0 CPU-MFR-OPSYS" 1>&2 + echo "or $0 ALIAS" 1>&2 + echo where ALIAS is a recognized configuration type. 1>&2 + exit 1 +fi + +# First pass through any local machine types. +case $1 in + *local*) + echo $1 + exit 0 + ;; + *) + ;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + linux-gnu*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple) + os= + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + tahoe | i860 | m32r | m68k | m68000 | m88k | ns32k | arc | arm \ + | arme[lb] | pyramid | mn10200 | mn10300 | tron | a29k \ + | 580 | i960 | h8300 \ + | hppa | hppa1.0 | hppa1.1 | hppa2.0 | hppa2.0w \ + | alpha | alphaev[4-7] | alphaev56 | alphapca5[67] \ + | we32k | ns16k | clipper | i370 | sh | powerpc | powerpcle \ + | 1750a | dsp16xx | pdp11 | mips64 | mipsel | mips64el \ + | mips64orion | mips64orionel | mipstx39 | mipstx39el \ + | sparc | sparclet | sparclite | sparc64 | v850) + basic_machine=$basic_machine-unknown + ;; + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i[34567]86) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + vax-* | tahoe-* | i[34567]86-* | i860-* | m32r-* | m68k-* | m68000-* \ + | m88k-* | sparc-* | ns32k-* | fx80-* | arc-* | arm-* | c[123]* \ + | mips-* | pyramid-* | tron-* | a29k-* | romp-* | rs6000-* \ + | power-* | none-* | 580-* | cray2-* | h8300-* | i960-* \ + | xmp-* | ymp-* \ + | hppa-* | hppa1.0-* | hppa1.1-* | hppa2.0-* | hppa2.0w-* \ + | alpha-* | alphaev[4-7]-* | alphaev56-* | alphapca5[67] \ + | we32k-* | cydra-* | ns16k-* | pn-* | np1-* | xps100-* \ + | clipper-* | orion-* \ + | sparclite-* | pdp11-* | sh-* | powerpc-* | powerpcle-* \ + | sparc64-* | mips64-* | mipsel-* \ + | mips64el-* | mips64orion-* | mips64orionel-* \ + | mipstx39-* | mipstx39el-* \ + | f301-* | armv*-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-cbm + ;; + amigaos | amigados) + basic_machine=m68k-cbm + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-cbm + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | ymp) + basic_machine=ymp-cray + os=-unicos + ;; + cray2) + basic_machine=cray2-cray + os=-unicos + ;; + [ctj]90-cray) + basic_machine=c90-cray + os=-unicos + ;; + crds | unos) + basic_machine=m68k-crds + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k7[0-9][0-9] | hp7[0-9][0-9] | hp9k8[0-9]7 | hp8[0-9]7) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + os=-mpeix + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + os=-mpeix + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + os=-mvs + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i[34567]86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i[34567]86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i[34567]86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i[34567]86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + miniframe) + basic_machine=m68000-convergent + ;; + mipsel*-linux*) + basic_machine=mipsel-unknown + os=-linux-gnu + ;; + mips*-linux*) + basic_machine=mips-unknown + os=-linux-gnu + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netwinder) + basic_machine=armv4l-corel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + np1) + basic_machine=np1-gould + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | nexen) + basic_machine=i586-pc + ;; + pentiumpro | p6 | k6 | 6x86) + basic_machine=i686-pc + ;; + pentiumii | pentium2) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | nexen-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | k6-* | 6x86-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=rs6000-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + xmp) + basic_machine=xmp-cray + os=-unicos + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + *mint | *MiNT) + basic_machine=m68k-atari + os=-mint + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + mips) + if [ x$os = x-linux-gnu ]; then + basic_machine=mips-unknown + else + basic_machine=mips-mips + fi + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sparc) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \ + | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -rhapsody* \ + | -openstep* | -mpeix* | -oskit*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -ctix* | -uts*) + os=-sysv + ;; + -ns2 ) + os=-nextstep2 + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -xenix) + os=-xenix + ;; + -*mint | -*MiNT) + os=-mint + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-corel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f301-fujitsu) + os=-uxpv + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -vxsim* | -vxworks*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -*mint | -*MiNT) + vendor=atari + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os diff --git a/neon/configure.in b/neon/configure.in new file mode 100644 index 000000000..ba6c745eb --- /dev/null +++ b/neon/configure.in @@ -0,0 +1,42 @@ + +AC_INIT(src/http_request.c) + +AC_CONFIG_HEADER(config.h) + +AC_DEFINE([_GNU_SOURCE]) + +AC_PROG_CC +AC_PROG_MAKE_SET +AC_ISC_POSIX +AC_PROG_INSTALL +AC_PROG_RANLIB +AC_LANG_C +AC_C_INLINE +AC_C_CONST + +AC_HEADER_STDC + +AC_EXEEXT + +NEON_XML_PARSER + +LIBNEON_SOURCE_CHECKS + +AC_OUTPUT(Makefile) + +cat <<EOF + +Using configuration: + + Install prefix: ${prefix} + Compiler: ${CC} + XML Parser: ${neon_xml_parser_message} + +Now run: 'make' to compile the neon library (libneon.a) + 'make shared' to compile a shared library with GCC (libneon.so) + 'make examples' to compile the example programs + + (Note: You might need to use GNU make if this is not the default 'make'. + GNU make could be installed as 'gmake' on your system.) + +EOF diff --git a/neon/doc/.cvsignore b/neon/doc/.cvsignore new file mode 100644 index 000000000..2c46ef53f --- /dev/null +++ b/neon/doc/.cvsignore @@ -0,0 +1,8 @@ +*.? +*.html +*.pdf +*.ps +*.tex +*.sgml +*.junk +html diff --git a/neon/doc/TODO b/neon/doc/TODO new file mode 100644 index 000000000..febed3054 --- /dev/null +++ b/neon/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/neon/doc/html.xsl b/neon/doc/html.xsl new file mode 100644 index 000000000..42fc397ba --- /dev/null +++ b/neon/doc/html.xsl @@ -0,0 +1,38 @@ +<?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">ansi</xsl:variable> + +<!-- 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"/> + +</xsl:stylesheet> + diff --git a/neon/doc/man.xsl b/neon/doc/man.xsl new file mode 100644 index 000000000..cad974474 --- /dev/null +++ b/neon/doc/man.xsl @@ -0,0 +1,14 @@ +<?xml version='1.0'?> + +<!-- This file wraps around the xmlto db2man XSL stylesheet to +customise some parameters. --> + +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<xsl:import href="/usr/share/xmlto/xsl/db2man/docbook.xsl"/> + +<!-- use an improved xref --> +<xsl:import href="./xref-man.xsl"/> + +</xsl:stylesheet> diff --git a/neon/doc/manual.css b/neon/doc/manual.css new file mode 100644 index 000000000..034d7a7e7 --- /dev/null +++ b/neon/doc/manual.css @@ -0,0 +1,29 @@ + +p, pre.funcsynopsisinfo { margin-left: 0.4em; margin-right: 0.4em; } + +div.funcsynopsis { line-height: 0.5em; } + +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; } + +pre.programlisting { + background-color: #dddddd; + margin-left: 0.6em; margin-right: 1em; + padding: 0.3em; width: 50em; +} + +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.1em; padding-top: 0.1em; } + diff --git a/neon/doc/manual.xml b/neon/doc/manual.xml new file mode 100644 index 000000000..14b8a68e2 --- /dev/null +++ b/neon/doc/manual.xml @@ -0,0 +1,519 @@ +<?xml version='1.0'?> <!-- -*- DocBook -*- --> + +<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ + +<!ENTITY fdl SYSTEM "fdl.sgml"> + +]> + +<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</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> + <title>Introduction to neon</title> + <para>This is neon, an HTTP and WebDAV client library</para> + + <sect1> + <title>How to use neon from your application</title> + + <para>The neon source package is designed to be easily + incorporated into applications:</para> + + <itemizedlist> + <listitem> + + <para>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).</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> + + <literal> + NEON_BUNDLED(libneon, ...) + </literal> + + + <para>If your application supports builds where srcdir != builddir, + you should use the NEON_VPATH_BUNDLED macro like this:</para> + + <literal> + NEON_VPATH_BUNDLED(${srcdir}/libneon, libneon, ...) + </literal> + + <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> + + <literal> + NEON_BUNDLED(libneon, [ + NEON_NORMAL_BUILD + NEON_XML_PARSER + SUBDIRS="libneon $SUBDIRS" + ]) + </literal> + + <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> + <title>neon API guidelines</title> + + <para>neon reserves the namespace <literal>ne_*</literal>: an + application which uses neon may not use symbols within this + namespace.</para> + + </sect1> + + <sect1> + <title>Protocol compliance</title> + + <para>neon is intended to be compliant with all relevant IETF and W3C + standards.</para> + + <sect2><title>RFC2518</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 the correct behaviour by the WebDAV + WG and is likely to change in a future revision of the spec.</para> + + </sect2> + + <sect2> + <title>RFC2616</title> + + <para>The redirect interface is deliberately not compliant with + section 10.3, and will automatically follow redirects for the + <literal>PROPFIND</literal> and <literal>OPTIONS</literal> + methods as well as <literal>GET</literal> and + <literal>HEAD</literal>. It <ulink url="http://www.apachelabs.org/apache-mbox/200102.mbox/%3C20010224232203.G799@waka.ebuilt.net%3E">has been stated</ulink> that this was the intent of the specification by one of the authors.</para> + + </sect2> + + </sect1> + + </chapter> + + <chapter> + <title>The neon API for the C language</title> + + <sect1> + <title>Sessions</title> + + <para>An HTTP session is created using the + <citerefentry><refentrytitle>ne_session_create</refentrytitle></citerefentry> + function</para> + </sect1> + + <sect1> + <title>Low-level request interface</title> + </sect1> + + <sect1> + <title>Basic HTTP and WebDAV methods</title> + <para>ne_basic.h</para> + </sect1> + + <sect1> + <title>HTTP authentication</title> + + <para>Authentication is supported using callbacks: using the + <citerefentry><refentrytitle>ne_set_server_auth</refentrytitle></citerefentry> + function, a callback can be registered which will supply + authentication credentials upon demand. In an interactive + application, this will typically be done using some form of + username/password prompt.</para> + + <para>Two types of authentication are supported: server + authentication (via the <function>ne_set_server_auth</function> + function), and proxy authentication (via the + <function>ne_set_proxy_auth</function> function).</para> + </sect1> + + <sect1> + <title>Parsing XML</title> + <para>ne_xml.h functions</para> + </sect1> + + <sect1> + <title>WebDAV properties</title> + <para>ne_props.h functions</para> + </sect1> + + <sect1> + <title>WebDAV locking</title> + <para>ne_locks.h functions</para> + </sect1> + + <sect1> + <title>Utility functions</title> + <para>stuff</para> + <sect2> + <title>String handling</title> + </sect2> + <sect2> + <title>Date/time manipulation</title> + </sect2> + </sect1> + + </chapter> + + <reference> + + <title>neon API reference</title> + + <refentry> + + <refentryinfo> + <title>neon</title> + </refentryinfo> + + <refmeta> + <refentrytitle>ne_session_create</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>ne_session_create</refname> + <refname>ne_session_close_connection</refname> + <refname>ne_session_server</refname> + <refname>ne_session_destroy</refname> + <refpurpose>Manipulate HTTP sessions</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + <funcsynopsisinfo>#include <ne_session.h></funcsynopsisinfo> + <funcprototype> + <funcdef><type>ne_session *</type><function>ne_session_create</function></funcdef> + <void/> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_session_server</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>hostname</parameter></paramdef> + <paramdef>int <parameter>port</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>ne_close_connection</function></funcdef> + <paramdef>ne_sesssion *<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 is used to group a + sequence of HTTP requests made to a server, enabling the + use of a persistent connection to be used across all the + requests, shared authentication credentials, and more.</para> + + <para>The <function>ne_session_server</function> call sets the + server to be used for the session. This function must be + called before any requests are made using the session.</para> + </refsect1> + + <refsect1> + <title>Return Values</title> + <para>On success, <function>ne_session_server</function> + returns <returnvalue>0</returnvalue> (<errorname>NE_OK</errorname>), + or a non-zero value if an error occurred.</para> + </refsect1> + + <refsect1> + <title>Errors</title> + <para><errorname>NE_LOOKUP</errorname>: the hostname cannot be resolved.</para> + </refsect1> + + <refsect1> + <title>Examples</title> + <para>Create and initialize a session:</para> + <programlisting> + + ne_session *sess = ne_session_create(); + if (ne_session_server(sess, "my.host.name", 80) == NE_LOOKUP) { + printf("Host not found!"); + } else { + /* ... use sess ... */ + } + ne_session_destroy(sess); + </programlisting> + </refsect1> + </refentry> + + <refentry> + + <refmeta> + <refentrytitle>ne_session_proxy</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>ne_session_proxy</refname> + <refname>ne_session_decide_proxy</refname> + <refpurpose>Proxy server settings</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcprototype> + <funcdef>int <function>ne_session_proxy</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>hostname</parameter></paramdef> + <paramdef>int <parameter>port</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_set_error</function></funcdef> + <paramdef>ne_sesssion *<parameter>session</parameter></paramdef> + <paramdef>const char *<parameter>error</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>const char *<function>ne_get_error</function></funcdef> + <paramdef>ne_sesssion *<parameter>session</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> + + <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_expect100</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>int <parameter>use_expect100</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_set_persist</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>int <parameter>use_persist</parameter></paramdef> + </funcprototype> + + <!-- not sure if this is really the best way to represent a + function typedef; all the examples in-line it, which is nasty. + --> + <funcprototype> + <funcdef>typedef int (*ne_use_proxy)</funcdef> + <paramdef>void *<parameter>userdata</parameter></paramdef> + <paramdef>const char *<parameter>scheme</parameter></paramdef> + <paramdef>const char *<parameter>hostname</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_session_decide_proxy</function></funcdef> + <paramdef>ne_session *<parameter>session</parameter></paramdef> + <paramdef>ne_use_proxy <parameter>use_proxy</parameter></paramdef> + <paramdef>void *<parameter>userdata</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + <para>An <type>ne_session *</type> object is a foo.</para> + </refsect1> + + </refentry> + + <refentry> + + <refentryinfo><title>neon</title></refentryinfo> + + <refmeta> + <refentrytitle>neon-config</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv> + <refname>neon-config</refname> + <refpurpose>provide information about installed copy of neon + library</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <cmdsynopsis> + <command>neon-config</command> + <arg choice="opt"><option>--prefix</option></arg><sbr/> + <group> + <arg><option>--cflags</option></arg> + <arg><option>--libs</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><para>Print the flags which should be passed to +the C compiler when compiling object files, when the object files use +neon header files.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--libs</option></term> + <listitem><para>Print the flags which should be passed to +the linker when linking an application which uses the neon +library</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--version</option></term> + <listitem><para>Print the version of the library</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--prefix</option> <replaceable>dir</replaceable></term> + <listitem><para>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.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--support</option> <replaceable>feature</replaceable></term> + <listitem><para>The script exits with success if +<replaceable>feature</replaceable> is supported by the +library.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--help</option></term> + <listitem><para>Print help message; includes list of known + features and whether they are supported or not.</para></listitem> + </varlistentry> + + </variablelist> + + </refsect1> + + <refsect1> + <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> + + </reference> + +&fdl; + +</book> diff --git a/neon/doc/parsing-xml.txt b/neon/doc/parsing-xml.txt new file mode 100644 index 000000000..dc3e467a5 --- /dev/null +++ b/neon/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/neon/doc/ref/alloc.xml b/neon/doc/ref/alloc.xml new file mode 100644 index 000000000..1101a824a --- /dev/null +++ b/neon/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/neon/doc/ref/auth.xml b/neon/doc/ref/auth.xml new file mode 100644 index 000000000..f56cea672 --- /dev/null +++ b/neon/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 <literal>NUL</literal>-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/neon/doc/ref/buf.xml b/neon/doc/ref/buf.xml new file mode 100644 index 000000000..3f8987cf5 --- /dev/null +++ b/neon/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> + + <programlisting>#include <ne_string.h> + +typedef struct { + char *data; + size_t used; + size_t length; +} <type>ne_buffer</type>;</programlisting> + + </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/neon/doc/ref/bufapp.xml b/neon/doc/ref/bufapp.xml new file mode 100644 index 000000000..a75ce8137 --- /dev/null +++ b/neon/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/neon/doc/ref/bufcr.xml b/neon/doc/ref/bufcr.xml new file mode 100644 index 000000000..0c572a6ea --- /dev/null +++ b/neon/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/neon/doc/ref/bufdest.xml b/neon/doc/ref/bufdest.xml new file mode 100644 index 000000000..5dc4dfc4c --- /dev/null +++ b/neon/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/neon/doc/ref/bufutil.xml b/neon/doc/ref/bufutil.xml new file mode 100644 index 000000000..3f3f3c5dc --- /dev/null +++ b/neon/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/neon/doc/ref/err.xml b/neon/doc/ref/err.xml new file mode 100644 index 000000000..50551b5ab --- /dev/null +++ b/neon/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/neon/doc/ref/getst.xml b/neon/doc/ref/getst.xml new file mode 100644 index 000000000..9d44277a7 --- /dev/null +++ b/neon/doc/ref/getst.xml @@ -0,0 +1,60 @@ + <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 +(once dispatched). The object persists until the associated request +is destroyed.</para> + + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_get_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/neon/doc/ref/init.xml b/neon/doc/ref/init.xml new file mode 100644 index 000000000..32dcda5ba --- /dev/null +++ b/neon/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/neon/doc/ref/neon.xml b/neon/doc/ref/neon.xml new file mode 100644 index 000000000..219f682c0 --- /dev/null +++ b/neon/doc/ref/neon.xml @@ -0,0 +1,75 @@ +<refentry id="refneon"> + + <refmeta> + <refentrytitle>neon</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>neon</refname> + <refpurpose>neon API conventions</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para>neon is an HTTP and WebDAV client library. The major + abstractions exposed are of an HTTP <emphasis>session</emphasis>, + created by <xref linkend="ne_session_create"/>; and an 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> + + </refsect1> + + <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 automatically. WebDAV + property names and values must be used un UTF-8.</para> + + </refsect2> + + <refsect2> + <title>Memory handling</title> + + <para>neon does not attempt to cope gracefully with an + out-of-memory situation; instead, by default, + <function>abort</function> is called to terminate the application. + Optionally an application-provided function be called before + abort; see <xref linkend="ne_oom_callback"/>.</para> + + </refsect2> + + <refsect2> + <title>Callbacks and userdata</title> + + <para>Whenever a callback is registered, a + <literal>userdata</literal> variable 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> + <title>See also</title> + + <para><xref linkend="refsess"/>, <xref linkend="refalloc"/></para> + </refsect1> + +</refentry> + diff --git a/neon/doc/ref/opts.xml b/neon/doc/ref/opts.xml new file mode 100644 index 000000000..fe8f368a5 --- /dev/null +++ b/neon/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/neon/doc/ref/req.xml b/neon/doc/ref/req.xml new file mode 100644 index 000000000..85394cb53 --- /dev/null +++ b/neon/doc/ref/req.xml @@ -0,0 +1,170 @@ + <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. The +<parameter>path</parameter> string used must conform to the +<literal>abs_path</literal> definition given in RFC2396.</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 <xref +linkend="ne_begin_request"/>.</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 <xref +linkend="ne_set_request_body_buffer"/>, <xref +linkend="ne_set_request_body_fd"/>, or <xref +linkend="ne_set_request_body_provider"/> 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, use <function><xref linkend="ne_get_status"/></function>, 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 a the request object has been completed, 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> + + <refsect1> + <title>Notes</title> + + <para><!-- TODO: abs_path description --></para> + + </refsect1> + + + <refsect1> + <title>Errors</title> + + <variablelist> + <varlistentry><term><errorcode>NE_ERROR</errorcode></term> + <listitem> + <para>Request failed (see session error string)</para> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_LOOKUP</errorcode></term> + <listitem> + <para>The DNS lookup for the server (or proxy server) failed.</para> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_AUTH</errorcode></term> + <listitem> + <para>Authentication failed on the server.</para> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_PROXYAUTH</errorcode></term> + <listitem> + <para>Authentication failed on the proxy server.</para> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_CONNECT</errorcode></term> + <listitem> + <para>A connection to the server could not be established.</para> + </listitem> + </varlistentry> + <varlistentry><term><errorcode>NE_TIMEOUT</errorcode></term> + <listitem> + <para>A timeout occurred while waiting for the server to respond.</para> + </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/neon/doc/ref/reqbody.xml b/neon/doc/ref/reqbody.xml new file mode 100644 index 000000000..e2c8eb3c4 --- /dev/null +++ b/neon/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/neon/doc/ref/reqhdr.xml b/neon/doc/ref/reqhdr.xml new file mode 100644 index 000000000..4d811b135 --- /dev/null +++ b/neon/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/neon/doc/ref/resolve.xml b/neon/doc/ref/resolve.xml new file mode 100644 index 000000000..9314880b6 --- /dev/null +++ b/neon/doc/ref/resolve.xml @@ -0,0 +1,160 @@ +<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_print">ne_addr_print</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>ne_inet_addr *<function>ne_addr_first</function></funcdef> + <paramdef>ne_sock_addr *<parameter>addr</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>ne_inet_addr *<function>ne_addr_next</function></funcdef> + <paramdef>ne_sock_addr *<parameter>addr</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>char *<function>ne_addr_print</function></funcdef> + <paramdef>const ne_inet_addr *<parameter>iaddr</parameter></paramdef> + <paramdef>char *<parameter>buffer</parameter></paramdef> + <paramdef>size_t <parameter>bufsiz</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 + should 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>To return the string representation of a particular network + address, the <function>ne_addr_print</function> function can be + used.</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> and + <function>ne_addr_print</function> both return 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 { + ne_inet_addr *ia; + printf("www.google.com:"); + for (ia = ne_addr_first(addr); ia != NULL; ia = ne_addr_next(addr)) { + printf(" %s", ne_addr_print(ia, buf, sizeof buf)); + } + putchar('\n'); +} +ne_addr_destroy(addr); +</programlisting> + </refsect1> + +<!-- + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_sock_connect"/></para> + </refsect1> +--> + +</refentry> + diff --git a/neon/doc/ref/sess.xml b/neon/doc/ref/sess.xml new file mode 100644 index 000000000..b3b0047fc --- /dev/null +++ b/neon/doc/ref/sess.xml @@ -0,0 +1,111 @@ + <refentry id="refsess"> + + <refentryinfo> + <title>neon</title> + </refentryinfo> + + <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>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>int <parameter>port</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>ne_close_connection</function></funcdef> + <paramdef>ne_sesssion *<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, which groups a set of HTTP requests made to a certain server. +Any requests made using the session can use a persistent connection, +and share cached authentication credentials and 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>).</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>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"/></para> + </refsect1> + + </refentry> diff --git a/neon/doc/ref/shave.xml b/neon/doc/ref/shave.xml new file mode 100644 index 000000000..56881c14d --- /dev/null +++ b/neon/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>whitespace trimmer</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/neon/doc/ref/sslca.xml b/neon/doc/ref/sslca.xml new file mode 100644 index 000000000..cbac071d0 --- /dev/null +++ b/neon/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_ssl_get_error"/>, <xref + linkend="ne_ssl_set_verify"/></para> </refsect1> + + </refentry> diff --git a/neon/doc/ref/sslcert.xml b/neon/doc/ref/sslcert.xml new file mode 100644 index 000000000..9696a4506 --- /dev/null +++ b/neon/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> + + <programlisting>#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>; + +</programlisting> + + </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/neon/doc/ref/ssldname.xml b/neon/doc/ref/ssldname.xml new file mode 100644 index 000000000..70df21ae4 --- /dev/null +++ b/neon/doc/ref/ssldname.xml @@ -0,0 +1,53 @@ + + <refentry id="refssldname"> + + <refmeta> + <refentrytitle>ne_ssl_readable_dname</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname id="ne_ssl_readable_dname">ne_ssl_readable_dname</refname> <refpurpose>create a + single-line readable string from a distinguised + name</refpurpose> + </refnamediv> + + <refsynopsisdiv> + + <funcsynopsis> + + <funcsynopsisinfo>#include <ne_session.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>const char *<function>ne_ssl_readable_dname</function></funcdef> + <paramdef>const ne_ssl_dname *<parameter>dname</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> + + </refsect1> + + <refsect1> + <title>Return value</title> + + <para><function>malloc</function>-allocated string.</para> + </refsect1> + + <refsect1> + <title>See also</title> + + <para><xref linkend="ne_ssl_certificate"/></para> + </refsect1> + + </refentry> diff --git a/neon/doc/ref/sslvfy.xml b/neon/doc/ref/sslvfy.xml new file mode 100644 index 000000000..ade5028bb --- /dev/null +++ b/neon/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) +{ + /* this leaks 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/neon/doc/ref/status.xml b/neon/doc/ref/status.xml new file mode 100644 index 000000000..0290ac250 --- /dev/null +++ b/neon/doc/ref/status.xml @@ -0,0 +1,75 @@ + <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> + + <programlisting>#include <ne_utils.h> + +typedef struct { + int major_version, minor_version; + int code, klass; + const char *reason_phrase; +} <type>ne_status</type>;</programlisting> + + </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"/>, <xref +linkend="ne_parse_status_line"/></para> </refsect1> + + </refentry> diff --git a/neon/doc/ref/tok.xml b/neon/doc/ref/tok.xml new file mode 100644 index 000000000..2e6211f97 --- /dev/null +++ b/neon/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/neon/doc/ref/vers.xml b/neon/doc/ref/vers.xml new file mode 100644 index 000000000..9d180feb6 --- /dev/null +++ b/neon/doc/ref/vers.xml @@ -0,0 +1,61 @@ +<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>.</para> + + <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/neon/doc/refentry.xml b/neon/doc/refentry.xml new file mode 100644 index 000000000..6610ac743 --- /dev/null +++ b/neon/doc/refentry.xml @@ -0,0 +1,59 @@ + + <!-- ******************************************************************* --> + + <refentry id="refXXXX"> + + <refmeta> + <refentrytitle>this_api_call</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>this_api_call</refname> + <refname>other_api_call</refname> + <refpurpose>general purpose of group of functions</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/neon/doc/using-neon.txt b/neon/doc/using-neon.txt new file mode 100644 index 000000000..54751525b --- /dev/null +++ b/neon/doc/using-neon.txt @@ -0,0 +1,132 @@ + +Guide to neon +============= Id: using-neon.txt,v 1.4 2000/07/16 16:20:12 joe Exp + +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. The NEON_LIBRARY autoconf macro can fall back to + compile and link this source code into your application if libneon + is not found on the system. Place the source code in a subdirectory + of your package named 'libneon', and pass the 'bundled' argument to + the macro, as follows: + + NEON_LIBRARY([bundled]) + + In your Makefile, $NEONOBJS will be added to $LIBOBJS if the bundled + neon is required, so you need to set this variable to the set of + neon object files which you require in your application, e.g.: + + NEONOBJS = libneon/http_utils.o libneon/http_request.o \ + libneon/http_auth.o libneon/string_utils.o \ + libneon/socket.o libneon/md5.o libneon/dates.o + LIBOBJS = @LIBOBJS@ + LIBS = @LIBS@ + CFLAGS = @CFLAGS@ etc... + + CFLAGS and LIBS will be adjusted by the NEON_LIBRARY as appropriate + for compiling against the bundled sources or the existing library. + +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. + +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. + +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 'http_session_create' call. Before +creating a request for the session, the server details must be set, as +follows: + + http_session *sess; + /* Initialize the socket library */ + sock_init(); + /* Create the session */ + sess = http_session_create(); + /* Optionally, set a proxy server */ + http_session_proxy(sess, "proxy.myisp.com", 8080); + /* Set the server */ + http_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 +http_session_server call, which may well fail if the client is +firewalled. http_session_{proxy,server} will return HTTP_LOOKUP if +the DNS lookup fails; otherwise HTTP_OK. + +The 'http_set_expect100' call can be used to determine whether the +"Expect: 100-continue" header is sent with requests. This header, +when supported correctly by the server, means that an error response +(e.g. 401) can be returned before the request body is sent. This is +useful if you are doing a PUT with a 100mb request body. Note that +Apache/1.3.6 and before do not implement this feature correctly, so +use with care. http_options can tell you whether the Apache + +The 'http_set_persist' call can be used to turn off persistent +connection handling: it is on by default. + +The 'http_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 +http_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/neon/doc/xref-man.xsl b/neon/doc/xref-man.xsl new file mode 100644 index 000000000..249bda5b8 --- /dev/null +++ b/neon/doc/xref-man.xsl @@ -0,0 +1,46 @@ +<?xml version='1.0'?> +<!-- vim:set sts=2 shiftwidth=2 syntax=sgml: --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- THIS FILE IS UNDER WHATEVER LICENSE xmlto's XSL is --> + +<xsl:template match="xref"> + <xsl:variable name="targets" select="id(@linkend)"/> + <xsl:variable name="target" select="$targets[1]"/> + <xsl:variable name="type" select="local-name($target)"/> + + <xsl:choose> + <xsl:when test="$type=''"> + <xsl:message> + <xsl:text>xref to nonexistent id </xsl:text> + <xsl:value-of select="@linkend"/> + </xsl:message> + </xsl:when> + + <xsl:when test="$type='refentry'"> + <xsl:call-template name="do-citerefentry"> + <xsl:with-param name="refentrytitle" + select="$target/refmeta/refentrytitle[1]"/> + <xsl:with-param name="manvolnum" + select="$target/refmeta/manvolnum"/> + </xsl:call-template> + </xsl:when> + + <xsl:when test="$type='refname'"> + <xsl:call-template name="do-citerefentry"> + <xsl:with-param name="refentrytitle" select="$target"/> + <xsl:with-param name="manvolnum" + select="$target/../../refmeta/manvolnum"/> + </xsl:call-template> + </xsl:when> + + <xsl:otherwise> + <xsl:text>[xref to </xsl:text> + <xsl:value-of select="$type"/> + <xsl:text>]</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/neon/example/.cvsignore b/neon/example/.cvsignore new file mode 100644 index 000000000..e19f12339 --- /dev/null +++ b/neon/example/.cvsignore @@ -0,0 +1,2 @@ +*.lo + diff --git a/neon/example/nbrowse.glade b/neon/example/nbrowse.glade new file mode 100644 index 000000000..d557925c4 --- /dev/null +++ b/neon/example/nbrowse.glade @@ -0,0 +1,284 @@ +<?xml version="1.0"?> +<GTK-Interface> + +<project> + <name>nbrowse</name> + <program_name>nbrowse</program_name> + <directory></directory> + <source_directory>nbrowse</source_directory> + <pixmaps_directory>nbrowse</pixmaps_directory> + <language>C</language> + <gnome_support>True</gnome_support> + <gettext_support>True</gettext_support> + <output_support_files>False</output_support_files> + <output_build_files>False</output_build_files> +</project> + +<widget> + <class>GtkWindow</class> + <name>nbrowse</name> + <signal> + <name>remove</name> + <handler>gtk_main_quit</handler> + <last_modification_time>Sun, 18 Jun 2000 11:49:12 GMT</last_modification_time> + </signal> + <title>WebDAV Browser</title> + <type>GTK_WINDOW_TOPLEVEL</type> + <position>GTK_WIN_POS_CENTER</position> + <modal>False</modal> + <default_width>400</default_width> + <default_height>300</default_height> + <allow_shrink>False</allow_shrink> + <allow_grow>True</allow_grow> + <auto_shrink>False</auto_shrink> + + <widget> + <class>GtkVBox</class> + <name>vbox1</name> + <homogeneous>False</homogeneous> + <spacing>0</spacing> + + <widget> + <class>GtkMenuBar</class> + <name>menubar1</name> + <shadow_type>GTK_SHADOW_OUT</shadow_type> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> + + <widget> + <class>GtkMenuItem</class> + <name>file1</name> + <stock_item>GNOMEUIINFO_MENU_FILE_TREE</stock_item> + + <widget> + <class>GtkMenu</class> + <name>file1_menu</name> + + <widget> + <class>GtkMenuItem</class> + <name>change_server1</name> + <signal> + <name>activate</name> + <handler>on_change_server1_activate</handler> + <last_modification_time>Thu, 15 Jun 2000 10:47:10 GMT</last_modification_time> + </signal> + <label>Change server...</label> + <right_justify>False</right_justify> + </widget> + + <widget> + <class>GtkMenuItem</class> + <name>separator3</name> + <right_justify>False</right_justify> + </widget> + + <widget> + <class>GtkPixmapMenuItem</class> + <name>exit1</name> + <signal> + <name>activate</name> + <handler>on_exit1_activate</handler> + <last_modification_time>Thu, 15 Jun 2000 10:46:15 GMT</last_modification_time> + </signal> + <stock_item>GNOMEUIINFO_MENU_EXIT_ITEM</stock_item> + </widget> + </widget> + </widget> + </widget> + + <widget> + <class>GtkHPaned</class> + <name>hpaned1</name> + <handle_size>10</handle_size> + <gutter_size>6</gutter_size> + <position>149</position> + <child> + <padding>0</padding> + <expand>True</expand> + <fill>True</fill> + </child> + + <widget> + <class>GtkScrolledWindow</class> + <name>scrolledwindow2</name> + <hscrollbar_policy>GTK_POLICY_ALWAYS</hscrollbar_policy> + <vscrollbar_policy>GTK_POLICY_ALWAYS</vscrollbar_policy> + <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy> + <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy> + <child> + <shrink>True</shrink> + <resize>False</resize> + </child> + + <widget> + <class>GtkTree</class> + <name>tree</name> + <selection_mode>GTK_SELECTION_BROWSE</selection_mode> + <view_mode>GTK_TREE_VIEW_ITEM</view_mode> + <view_line>True</view_line> + </widget> + </widget> + + <widget> + <class>GtkScrolledWindow</class> + <name>scrolledwindow1</name> + <hscrollbar_policy>GTK_POLICY_ALWAYS</hscrollbar_policy> + <vscrollbar_policy>GTK_POLICY_ALWAYS</vscrollbar_policy> + <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy> + <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy> + <child> + <shrink>True</shrink> + <resize>True</resize> + </child> + + <widget> + <class>GnomeIconList</class> + <name>iconlist1</name> + <can_focus>True</can_focus> + <selection_mode>GTK_SELECTION_SINGLE</selection_mode> + <icon_width>78</icon_width> + <row_spacing>4</row_spacing> + <column_spacing>2</column_spacing> + <text_spacing>2</text_spacing> + <text_editable>False</text_editable> + <text_static>False</text_static> + </widget> + </widget> + </widget> + </widget> +</widget> + +<widget> + <class>GnomeDialog</class> + <name>enter_url</name> + <title>Enter URL</title> + <type>GTK_WINDOW_DIALOG</type> + <position>GTK_WIN_POS_CENTER</position> + <modal>True</modal> + <allow_shrink>False</allow_shrink> + <allow_grow>False</allow_grow> + <auto_shrink>False</auto_shrink> + <auto_close>False</auto_close> + <hide_on_close>False</hide_on_close> + + <widget> + <class>GtkVBox</class> + <child_name>GnomeDialog:vbox</child_name> + <name>dialog-vbox1</name> + <homogeneous>False</homogeneous> + <spacing>8</spacing> + <child> + <padding>4</padding> + <expand>True</expand> + <fill>True</fill> + </child> + + <widget> + <class>GtkHButtonBox</class> + <child_name>GnomeDialog:action_area</child_name> + <name>dialog-action_area1</name> + <layout_style>GTK_BUTTONBOX_END</layout_style> + <spacing>8</spacing> + <child_min_width>85</child_min_width> + <child_min_height>27</child_min_height> + <child_ipad_x>7</child_ipad_x> + <child_ipad_y>0</child_ipad_y> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>True</fill> + <pack>GTK_PACK_END</pack> + </child> + + <widget> + <class>GtkButton</class> + <name>button1</name> + <can_default>True</can_default> + <can_focus>True</can_focus> + <stock_button>GNOME_STOCK_BUTTON_OK</stock_button> + </widget> + + <widget> + <class>GtkButton</class> + <name>button3</name> + <can_default>True</can_default> + <can_focus>True</can_focus> + <stock_button>GNOME_STOCK_BUTTON_CANCEL</stock_button> + </widget> + </widget> + + <widget> + <class>GtkHBox</class> + <name>hbox1</name> + <homogeneous>False</homogeneous> + <spacing>0</spacing> + <child> + <padding>0</padding> + <expand>True</expand> + <fill>True</fill> + </child> + + <widget> + <class>GnomePixmap</class> + <name>pixmap1</name> + <filename>gnome-question.png</filename> + <child> + <padding>0</padding> + <expand>True</expand> + <fill>True</fill> + </child> + </widget> + + <widget> + <class>GtkVBox</class> + <name>vbox2</name> + <border_width>7</border_width> + <homogeneous>False</homogeneous> + <spacing>6</spacing> + <child> + <padding>0</padding> + <expand>True</expand> + <fill>True</fill> + </child> + + <widget> + <class>GtkLabel</class> + <name>label1</name> + <label>Enter a URL to browse: +(for example, http://www.driveway.com/user.myname/)</label> + <justify>GTK_JUSTIFY_CENTER</justify> + <wrap>False</wrap> + <xalign>0.5</xalign> + <yalign>0.5</yalign> + <xpad>0</xpad> + <ypad>0</ypad> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> + </widget> + + <widget> + <class>GtkEntry</class> + <name>entry1</name> + <can_focus>True</can_focus> + <editable>True</editable> + <text_visible>True</text_visible> + <text_max_length>0</text_max_length> + <text></text> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> + </widget> + </widget> + </widget> + </widget> +</widget> + +</GTK-Interface> diff --git a/neon/example/nbrowse/callbacks.c b/neon/example/nbrowse/callbacks.c new file mode 100644 index 000000000..3e9f7e942 --- /dev/null +++ b/neon/example/nbrowse/callbacks.c @@ -0,0 +1,40 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <gnome.h> + +#include "callbacks.h" +#include "interface.h" +#include "support.h" + + +void change_server() +{ + +} + + +void +on_change_server1_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + +} + + +void +on_exit1_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + +} + + +void +on_about1_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + +} + diff --git a/neon/example/nbrowse/callbacks.h b/neon/example/nbrowse/callbacks.h new file mode 100644 index 000000000..e3db59c3c --- /dev/null +++ b/neon/example/nbrowse/callbacks.h @@ -0,0 +1,14 @@ +#include <gnome.h> + + +void +on_change_server1_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_exit1_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_about1_activate (GtkMenuItem *menuitem, + gpointer user_data); diff --git a/neon/example/nbrowse/interface.c b/neon/example/nbrowse/interface.c new file mode 100644 index 000000000..6332abdad --- /dev/null +++ b/neon/example/nbrowse/interface.c @@ -0,0 +1,240 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> + +#include <gnome.h> + +#include "callbacks.h" +#include "interface.h" +#include "support.h" + +GtkWidget *tree; +GtkWidget *iconlist; + +static GnomeUIInfo file1_menu_uiinfo[] = +{ + { + GNOME_APP_UI_ITEM, N_("Change server..."), + NULL, + on_change_server1_activate, NULL, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + GNOMEUIINFO_SEPARATOR, + GNOMEUIINFO_MENU_EXIT_ITEM (on_exit1_activate, NULL), + GNOMEUIINFO_END +}; + +static GnomeUIInfo menubar1_uiinfo[] = +{ + GNOMEUIINFO_MENU_FILE_TREE (file1_menu_uiinfo), + GNOMEUIINFO_END +}; + +GtkWidget* +create_nbrowse (void) +{ + GtkWidget *nbrowse; + GtkWidget *vbox1; + GtkWidget *menubar1; + GtkWidget *hpaned1; + GtkWidget *scrolledwindow2; + GtkWidget *scrolledwindow1; + + nbrowse = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_object_set_data (GTK_OBJECT (nbrowse), "nbrowse", nbrowse); + gtk_window_set_title (GTK_WINDOW (nbrowse), _("neon WebDAV Browser")); + /*gtk_window_set_position (GTK_WINDOW (nbrowse), GTK_WIN_POS_CENTER); */ + gtk_window_set_default_size (GTK_WINDOW (nbrowse), 400, 300); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_widget_ref (vbox1); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "vbox1", vbox1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (nbrowse), vbox1); + + menubar1 = gtk_menu_bar_new (); + gtk_widget_ref (menubar1); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "menubar1", menubar1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (menubar1); + gtk_box_pack_start (GTK_BOX (vbox1), menubar1, FALSE, FALSE, 0); + gnome_app_fill_menu (GTK_MENU_SHELL (menubar1), menubar1_uiinfo, + NULL, FALSE, 0); + + gtk_widget_ref (menubar1_uiinfo[0].widget); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "file1", + menubar1_uiinfo[0].widget, + (GtkDestroyNotify) gtk_widget_unref); + + gtk_widget_ref (file1_menu_uiinfo[0].widget); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "change_server1", + file1_menu_uiinfo[0].widget, + (GtkDestroyNotify) gtk_widget_unref); + + gtk_widget_ref (file1_menu_uiinfo[1].widget); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "separator3", + file1_menu_uiinfo[1].widget, + (GtkDestroyNotify) gtk_widget_unref); + + gtk_widget_ref (file1_menu_uiinfo[2].widget); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "exit1", + file1_menu_uiinfo[2].widget, + (GtkDestroyNotify) gtk_widget_unref); + + hpaned1 = gtk_hpaned_new (); + gtk_widget_ref (hpaned1); + gtk_paned_set_gutter_size( GTK_HPANED(hpaned1), 12 ); + + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "hpaned1", hpaned1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (hpaned1); + gtk_box_pack_start (GTK_BOX (vbox1), hpaned1, TRUE, TRUE, 0); + gtk_paned_set_position (GTK_PANED (hpaned1), 149); + + scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_ref (scrolledwindow2); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "scrolledwindow2", scrolledwindow2, + (GtkDestroyNotify) gtk_widget_unref); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow2), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); + gtk_widget_show (scrolledwindow2); + gtk_paned_pack1 (GTK_PANED (hpaned1), scrolledwindow2, FALSE, TRUE); + + tree = gtk_tree_new (); + gtk_widget_ref (tree); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "tree", tree, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (tree); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolledwindow2), tree); + gtk_tree_set_selection_mode (GTK_TREE (tree), GTK_SELECTION_SINGLE); + gtk_tree_set_view_mode (GTK_TREE (tree), GTK_TREE_VIEW_ITEM); + + scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_ref (scrolledwindow1); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow1), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "scrolledwindow1", scrolledwindow1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (scrolledwindow1); + gtk_paned_pack2 (GTK_PANED (hpaned1), scrolledwindow1, TRUE, TRUE); + + iconlist = gnome_icon_list_new_flags (78, NULL, 0); + gtk_widget_ref (iconlist); + gnome_icon_list_set_selection_mode( GNOME_ICON_LIST(iconlist), GTK_SELECTION_MULTIPLE ); + gtk_object_set_data_full (GTK_OBJECT (nbrowse), "iconlist", iconlist, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (iconlist); + gtk_container_add (GTK_CONTAINER (scrolledwindow1), iconlist); + + gtk_signal_connect (GTK_OBJECT (nbrowse), "remove", + GTK_SIGNAL_FUNC (gtk_main_quit), + NULL); + + return nbrowse; +} + +GtkWidget* +create_enter_url (void) +{ + GtkWidget *enter_url; + GtkWidget *dialog_vbox1; + GtkWidget *hbox1; + gchar *pixmap1_filename; + GtkWidget *pixmap1; + GtkWidget *vbox2; + GtkWidget *label1; + GtkWidget *entry1; + GtkWidget *dialog_action_area1; + GtkWidget *button1; + GtkWidget *button3; + + enter_url = gnome_dialog_new (_("Enter URL"), NULL); + gtk_object_set_data (GTK_OBJECT (enter_url), "enter_url", enter_url); + GTK_WINDOW (enter_url)->type = GTK_WINDOW_DIALOG; + gtk_window_set_position (GTK_WINDOW (enter_url), GTK_WIN_POS_CENTER); + gtk_window_set_modal (GTK_WINDOW (enter_url), TRUE); + gtk_window_set_policy (GTK_WINDOW (enter_url), FALSE, FALSE, FALSE); + + dialog_vbox1 = GNOME_DIALOG (enter_url)->vbox; + gtk_object_set_data (GTK_OBJECT (enter_url), "dialog_vbox1", dialog_vbox1); + gtk_widget_show (dialog_vbox1); + + hbox1 = gtk_hbox_new (FALSE, 0); + gtk_widget_ref (hbox1); + gtk_object_set_data_full (GTK_OBJECT (enter_url), "hbox1", hbox1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (hbox1); + + gtk_box_pack_start (GTK_BOX (dialog_vbox1), hbox1, TRUE, TRUE, 0); + + pixmap1 = gtk_type_new (gnome_pixmap_get_type ()); + pixmap1_filename = gnome_pixmap_file ("nbrowse/gnome-question.png"); + if (pixmap1_filename) + gnome_pixmap_load_file (GNOME_PIXMAP (pixmap1), pixmap1_filename); + else + g_warning (_("Couldn't find pixmap file: %s"), "gnome-question.png"); + g_free (pixmap1_filename); + gtk_widget_ref (pixmap1); + gtk_object_set_data_full (GTK_OBJECT (enter_url), "pixmap1", pixmap1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (pixmap1); + gtk_box_pack_start (GTK_BOX (hbox1), pixmap1, TRUE, TRUE, 0); + + vbox2 = gtk_vbox_new (FALSE, 6); + gtk_widget_ref (vbox2); + gtk_object_set_data_full (GTK_OBJECT (enter_url), "vbox2", vbox2, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (hbox1), vbox2, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 7); + + label1 = gtk_label_new (_("Enter a URL to browse:\n(for example, http://www.driveway.com/user.myname/)")); + gtk_widget_ref (label1); + gtk_object_set_data_full (GTK_OBJECT (enter_url), "label1", label1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (label1); + gtk_box_pack_start (GTK_BOX (vbox2), label1, FALSE, FALSE, 0); + + entry1 = gtk_entry_new (); + gtk_widget_ref (entry1); + gtk_object_set_data_full (GTK_OBJECT (enter_url), "entry1", entry1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (entry1); + gtk_box_pack_start (GTK_BOX (vbox2), entry1, FALSE, FALSE, 0); + + dialog_action_area1 = GNOME_DIALOG (enter_url)->action_area; + gtk_object_set_data (GTK_OBJECT (enter_url), "dialog_action_area1", dialog_action_area1); + gtk_widget_show (dialog_action_area1); + gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1), GTK_BUTTONBOX_END); + gtk_button_box_set_spacing (GTK_BUTTON_BOX (dialog_action_area1), 8); + + gnome_dialog_append_button (GNOME_DIALOG (enter_url), GNOME_STOCK_BUTTON_OK); + button1 = g_list_last (GNOME_DIALOG (enter_url)->buttons)->data; + gtk_widget_ref (button1); + gtk_object_set_data_full (GTK_OBJECT (enter_url), "button1", button1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (button1); + GTK_WIDGET_SET_FLAGS (button1, GTK_CAN_DEFAULT); + + gnome_dialog_append_button (GNOME_DIALOG (enter_url), GNOME_STOCK_BUTTON_CANCEL); + button3 = g_list_last (GNOME_DIALOG (enter_url)->buttons)->data; + gtk_widget_ref (button3); + gtk_object_set_data_full (GTK_OBJECT (enter_url), "button3", button3, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (button3); + GTK_WIDGET_SET_FLAGS (button3, GTK_CAN_DEFAULT); + + return enter_url; +} + diff --git a/neon/example/nbrowse/interface.h b/neon/example/nbrowse/interface.h new file mode 100644 index 000000000..71bb80803 --- /dev/null +++ b/neon/example/nbrowse/interface.h @@ -0,0 +1,8 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + */ + +GtkWidget* create_nbrowse (void); +GtkWidget* create_enter_url (void); +extern GtkWidget *tree; +extern GtkWidget *iconlist; diff --git a/neon/example/nbrowse/main.c b/neon/example/nbrowse/main.c new file mode 100644 index 000000000..c5a2a20b8 --- /dev/null +++ b/neon/example/nbrowse/main.c @@ -0,0 +1,337 @@ +/* + nbrowse, dummy WebDAV browsing in GNOME + Copyright (C) 2000, Joe Orton <joe@orton.demon.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. + + Id: main.c,v 1.3 2000/07/16 16:15:44 joe Exp +*/ + +/* + Aims to demonstrate that a blocking-IO based HTTP library can be + easily integrated with a GUI polling loop. + + TODO: + - Stop it crashing all the time + - Allow one PROPFIND at a time, i.e., ignore clicks once + already loading a tree + - Fetch lockdiscovery and display "locked" icons +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <signal.h> + +#include <pthread.h> + +#include <gnome.h> + +#include <gtk/gtktree.h> + + +#include <neon_config.h> + +#include <dav_props.h> +#include <dav_basic.h> +#include <uri.h> +#include <xalloc.h> + +#include "basename.h" +#include "interface.h" +#include "support.h" + +#define ELM_getcontentlength (1001) +#define ELM_resourcetype (1002) +#define ELM_getlastmodified (1003) +#define ELM_executable (1004) +#define ELM_collection (1005) +#define ELM_displayname (1006) + +enum resource_type { + res_normal, + res_collection, + res_reference, +}; + +http_session *sess; +/* since we let >1 thread spawn at a time, + * enforce mutual exclusion over access to the session state + */ +pthread_mutex_t sess_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct browser { + const char *uri; + GtkTree *tree; + GnomeIconList *list; + unsigned int thread_running:1; + pthread_t thread; +}; + +struct resource { + char *uri; + char *displayname; + enum resource_type type; + size_t size; + time_t modtime; + int is_executable; + struct resource *next; +}; + +static int start_propfind( struct browser *b ); + +static int check_context( hip_xml_elmid child, hip_xml_elmid parent ) +{ + /* FIXME */ + return 0; +} + +/* Does a PROPFIND to load URI at tree */ +static void load_tree( GtkWidget *tree, const char *uri ) +{ + struct browser *b = xmalloc(sizeof(struct browser)); + + /* Create the tree */ + b->tree = GTK_TREE(tree); + b->list = GNOME_ICON_LIST(iconlist); + b->uri = uri; + + start_propfind(b); +} + +static void select_node_cb( GtkTreeItem *item ) +{ + char *uri = gtk_object_get_data( GTK_OBJECT(item), "uri" ); + static char *loaded = "l"; + + printf( "selected: %s\n", uri ); + +#if 0 + if( gtk_object_get_data( GTK_OBJECT(item), "is_loaded" ) == NULL ) { + gtk_object_set_data( GTK_OBJECT(item), "is_loaded", loaded ); + } else { + printf( "already loaded this node.\n" ); + return; + } +#endif + + gnome_icon_list_clear(GNOME_ICON_LIST(iconlist)); + gtk_tree_remove_items (GTK_TREE(item->subtree), GTK_TREE(item->subtree)->children); + load_tree( item->subtree, uri ); +} + +static int end_element( void *userdata, const struct hip_xml_elm *elm, const char *cdata ) +{ + struct resource *r; + r = dav_propfind_get_current_resource( userdata ); + if( r == NULL ) { + return 0; + } + DEBUG( DEBUG_HTTP, "In resource %s\n", r->uri ); + DEBUG( DEBUG_HTTP, "Property [%d], %s@@%s = %s\n", elm->id, + elm->nspace, elm->name, cdata?cdata:"undefined" ); + switch( elm->id ) { + case ELM_collection: + r->type = res_collection; + break; + case ELM_getcontentlength: + if( cdata ) { + r->size = atoi(cdata); + } + break; + case ELM_displayname: + if( cdata ) { + r->displayname = xstrdup(cdata); + } + break; + case ELM_getlastmodified: + if( cdata ) { + r->modtime = http_dateparse(cdata); + /* will be -1 on error */ + } + break; + case ELM_executable: + if( cdata ) { + if( strcasecmp( cdata, "T" ) == 0 ) { + r->is_executable = 1; + } else { + r->is_executable = 0; + } + } + break; + } + return 0; +} + +static void *start_resource( void *userdata, const char *href ) +{ + struct resource *res = xmalloc(sizeof(struct resource)); + memset(res,0,sizeof(struct resource)); + res->uri = xstrdup(href); + return res; +} + +static void end_resource( void *userdata, void *resource, + const char *status_line, const http_status *status, + const char *description ) +{ + struct browser *b = userdata; + char *abspath; + struct resource *res = resource; + char *leaf; + + if( res == NULL ) { + return; + } + + DEBUG( DEBUG_HTTP, "Href: %s\n", res->uri ); + /* char * cast is valid since res->uri is char * */ + abspath = (char *) uri_abspath( res->uri ); + + if( uri_compare( abspath, b->uri ) == 0 ) { + return; + } + + if( uri_has_trailing_slash(abspath) ) { + abspath[strlen(abspath)-1] = '\0'; + } + + leaf = base_name(abspath); + + gdk_threads_enter(); + + if( res->type == res_collection ) { + GtkWidget *sub, *item; + + item = gtk_tree_item_new_with_label( leaf ); + gtk_tree_append( b->tree, item ); + + sub = gtk_tree_new(); + gtk_tree_item_set_subtree( GTK_TREE_ITEM(item), sub ); + gtk_object_set_data_full( GTK_OBJECT(item), "uri", + xstrdup(res->uri), free ); + gtk_signal_connect (GTK_OBJECT(item), "select", + GTK_SIGNAL_FUNC(select_node_cb), item); + gtk_signal_connect (GTK_OBJECT(item), "expand", + GTK_SIGNAL_FUNC(select_node_cb), item); + gtk_widget_show( item ); + + gnome_icon_list_append( b->list, "/usr/share/pixmaps/mc/i-directory.png", leaf ); + + } else { + gnome_icon_list_append( b->list, "/usr/share/pixmaps/mc/i-regular.png", leaf ); + } + + gdk_threads_leave(); + + free( res->uri); + free( res ); +} + +/* Run in a separate thread */ +static void *do_propfind( void *ptr ) +{ + dav_propfind_handler *ph; + static const dav_propname props[] = { + { "DAV:", "getcontentlength" }, + { "DAV:", "resourcetype" }, + { "DAV:", "getlastmodified" }, + { "DAV:", "displayname" }, + { "http://apache.org/dav/props/", "executable" }, + { NULL } + }; + static const struct hip_xml_elm elms[] = { + { "DAV:", "getcontentlength", ELM_getcontentlength, HIP_XML_CDATA }, + { "DAV:", "resourcetype", ELM_resourcetype, 0 }, + { "DAV:", "getlastmodified", ELM_getlastmodified, HIP_XML_CDATA }, + { "http://apache.org/dav/props/", "executable", ELM_executable, HIP_XML_CDATA }, + { "DAV:", "collection", ELM_collection, 0 }, + { "DAV:", "displayname", ELM_displayname, HIP_XML_CDATA }, + { NULL } + }; + struct browser *b = ptr; + + pthread_detach(pthread_self()); + + pthread_mutex_lock( &sess_mutex ); + + ph = dav_propfind_create (sess, b->uri, DAV_DEPTH_ONE ); + + dav_propfind_set_resource_handlers (ph, start_resource, end_resource); + + hip_xml_add_handler (dav_propfind_get_parser(ph), elms, + check_context, NULL, end_element, ph); + + if (dav_propfind_named (ph, props, ptr) != HTTP_OK) { + printf( "PROPFIND failed: %s\n", http_get_error(sess) ); + } + + pthread_mutex_unlock( &sess_mutex ); + + free( b ); + + return NULL; +} + + +static int start_propfind( struct browser *b ) +{ + b->thread_running = 1; + return pthread_create( &b->thread, NULL, do_propfind, b ); +} + +int +main (int argc, char **argv) +{ + GtkWidget *nbrowse; + + if( argc < 3 ) { + printf("nbrowse: Usage 'nbrowse server.name.com /path/'.\n"); + return -1; + } + +#ifdef ENABLE_NLS + bindtextdomain (PACKAGE, PACKAGE_LOCALE_DIR); + textdomain (PACKAGE); +#endif + + /* Initialize threading */ + g_thread_init(NULL); + + signal( SIGPIPE, SIG_IGN ); + + gnome_init ("nbrowse", NEON_VERSION, argc, argv); + + /* Create the main window */ + nbrowse = create_nbrowse (); + gtk_widget_show (nbrowse); + + /* Create a new session. */ + sess = http_session_create(); + + printf( "Using server: %s - path %s\n", argv[1], argv[2] ); + + http_session_server( sess, argv[1], 80 ); + http_set_useragent( sess, "nbrowser/" NEON_VERSION ); + + load_tree( tree, argv[2] ); + + gdk_threads_enter(); + gtk_main(); + gdk_threads_leave(); + return 0; +} + diff --git a/neon/example/nbrowse/support.c b/neon/example/nbrowse/support.c new file mode 100644 index 000000000..c76f33308 --- /dev/null +++ b/neon/example/nbrowse/support.c @@ -0,0 +1,146 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> + +#include <gnome.h> + +#include "support.h" + +/* This is an internally used function to create pixmaps. */ +static GtkWidget* create_dummy_pixmap (GtkWidget *widget, + gboolean gnome_pixmap); + +GtkWidget* +lookup_widget (GtkWidget *widget, + const gchar *widget_name) +{ + GtkWidget *parent, *found_widget; + + for (;;) + { + if (GTK_IS_MENU (widget)) + parent = gtk_menu_get_attach_widget (GTK_MENU (widget)); + else + parent = widget->parent; + if (parent == NULL) + break; + widget = parent; + } + + found_widget = (GtkWidget*) gtk_object_get_data (GTK_OBJECT (widget), + widget_name); + if (!found_widget) + g_warning ("Widget not found: %s", widget_name); + return found_widget; +} + +/* This is a dummy pixmap we use when a pixmap can't be found. */ +static char *dummy_pixmap_xpm[] = { +/* columns rows colors chars-per-pixel */ +"1 1 1 1", +" c None", +/* pixels */ +" ", +" " +}; + +/* This is an internally used function to create pixmaps. */ +static GtkWidget* +create_dummy_pixmap (GtkWidget *widget, + gboolean gnome_pixmap) +{ + GdkColormap *colormap; + GdkPixmap *gdkpixmap; + GdkBitmap *mask; + GtkWidget *pixmap; + + if (gnome_pixmap) + { + return gnome_pixmap_new_from_xpm_d (dummy_pixmap_xpm); + } + + colormap = gtk_widget_get_colormap (widget); + gdkpixmap = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, + NULL, dummy_pixmap_xpm); + if (gdkpixmap == NULL) + g_error ("Couldn't create replacement pixmap."); + pixmap = gtk_pixmap_new (gdkpixmap, mask); + gdk_pixmap_unref (gdkpixmap); + gdk_bitmap_unref (mask); + return pixmap; +} + +/* This is an internally used function to create pixmaps. */ +GtkWidget* +create_pixmap (GtkWidget *widget, + const gchar *filename, + gboolean gnome_pixmap) +{ + GtkWidget *pixmap; + GdkColormap *colormap; + GdkPixmap *gdkpixmap; + GdkBitmap *mask; + gchar *pathname; + + if (!filename || !filename[0]) + return create_dummy_pixmap (widget, gnome_pixmap); + + pathname = gnome_pixmap_file (filename); + if (!pathname) + { + g_warning (_("Couldn't find pixmap file: %s"), filename); + return create_dummy_pixmap (widget, gnome_pixmap); + } + + if (gnome_pixmap) + { + pixmap = gnome_pixmap_new_from_file (pathname); + g_free (pathname); + return pixmap; + } + + colormap = gtk_widget_get_colormap (widget); + gdkpixmap = gdk_pixmap_colormap_create_from_xpm (NULL, colormap, &mask, + NULL, pathname); + if (gdkpixmap == NULL) + { + g_warning (_("Couldn't create pixmap from file: %s"), pathname); + g_free (pathname); + return create_dummy_pixmap (widget, gnome_pixmap); + } + g_free (pathname); + + pixmap = gtk_pixmap_new (gdkpixmap, mask); + gdk_pixmap_unref (gdkpixmap); + gdk_bitmap_unref (mask); + return pixmap; +} + +/* This is an internally used function to create imlib images. */ +GdkImlibImage* +create_image (const gchar *filename) +{ + GdkImlibImage *image; + gchar *pathname; + + pathname = gnome_pixmap_file (filename); + if (!pathname) + { + g_warning (_("Couldn't find pixmap file: %s"), filename); + return NULL; + } + + image = gdk_imlib_load_image (pathname); + g_free (pathname); + return image; +} + diff --git a/neon/example/nbrowse/support.h b/neon/example/nbrowse/support.h new file mode 100644 index 000000000..d9bb0728a --- /dev/null +++ b/neon/example/nbrowse/support.h @@ -0,0 +1,34 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + */ + +#include <gnome.h> + +/* + * Public Functions. + */ + +/* + * This function returns a widget in a component created by Glade. + * Call it with the toplevel widget in the component (i.e. a window/dialog), + * or alternatively any widget in the component, and the name of the widget + * you want returned. + */ +GtkWidget* lookup_widget (GtkWidget *widget, + const gchar *widget_name); + +/* get_widget() is deprecated. Use lookup_widget instead. */ +#define get_widget lookup_widget + + +/* + * Private Functions. + */ + +/* This is used to create the pixmaps in the interface. */ +GtkWidget* create_pixmap (GtkWidget *widget, + const gchar *filename, + gboolean gnome_pixmap); + +GdkImlibImage* create_image (const gchar *filename); + diff --git a/neon/example/nget.c b/neon/example/nget.c new file mode 100644 index 000000000..3cc43dc5c --- /dev/null +++ b/neon/example/nget.c @@ -0,0 +1,164 @@ +/* + nget, neon HTTP GET tester + Copyright (C) 2000, Joe Orton <joe@orton.demon.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. + + Id: nget.c,v 1.3 2000/05/10 18:16:56 joe Exp +*/ + +#include <sys/types.h> + +#include <sys/stat.h> +#include <sys/time.h> + +#include <stdio.h> +#include <errno.h> + +#include "http_request.h" +#include "http_basic.h" +#include "uri.h" +#include "socket.h" + +#include "basename.h" + +int in_progress = 0, use_stdout = 0; + +static void pretty_progress_bar( void *ud, size_t progress, size_t total ); + +static const char *use_filename( struct uri *uri ) +{ + const char *fname; + + fname = base_name( uri->path ); + if( strcmp( fname, "/" ) == 0 || strlen( fname ) == 0 ) { + fname = "index.html"; + } + + return fname; +} + +int main( int argc, char **argv ) +{ + http_session *sess; + struct uri uri = {0}, defaults = {0}; + struct stat st; + int ret; + const char *fname; + FILE *f; + + if( argc < 2 || argc > 3 ) { + printf( "nget: Usage: \n" + " `nget url': download url using appropriate filename\n" + " `nget url filename': download url to given filename\n" + " `nget url -': download url and display on stdout\n" ); + return -1; + } + + sock_register_progress( pretty_progress_bar, NULL ); + + defaults.port = 80; + + if( uri_parse( argv[1], &uri, &defaults ) || + !uri.path || !uri.host || uri.port == -1 ) { + printf( "nget: Invalid URL.\n" ); + return -1; + } + + if( argc == 3 ) { + fname = argv[2]; + } else { + fname = use_filename( &uri ); + } + if( strcmp( fname, "-" ) == 0 ) { + f = stdout; + use_stdout = 1; + } else if( stat( fname, &st ) == 0 ) { + printf( "nget: File `%s' already exists.\n", fname ); + return -1; + } else { + f = fopen( fname, "w" ); + if( f == NULL ) { + printf( "nget: Could not open %s: %s\n", fname, strerror(errno) ); + return -1; + } + } + + sess = http_session_init(); + + http_session_server( sess, uri.host, uri.port ); + + if( !use_stdout ) { + printf( "nget: Downloading %s to %s\n", argv[1], fname ); + } + ret = http_get( sess, uri.path, f ); + + if( in_progress ) { + printf( "\n" ); + } + + if( ret == HTTP_OK ) { + if( !use_stdout ) printf( "nget: Download complete.\n" ); + } else { + fprintf( stderr, + "nget: Download error: %s\n", http_get_error(sess)); + } + + if( !use_stdout ) + fclose( f ); + + return ret; +} + +/* Smooth progress bar from cadaver. + * Doesn't update the bar more than once every 100ms, since this + * might give flicker, and would be bad if we are displaying on + * a slow link anyway. + */ +static void pretty_progress_bar( void *ud, size_t progress, size_t total ) +{ + int len, n; + double pc; + static struct timeval last_call = {0}; + struct timeval this_call; + if( use_stdout ) return; + in_progress = 1; + if( total == -1 ) { + printf( "\rProgress: %d bytes", progress ); + return; + } + if( progress < total && gettimeofday( &this_call, NULL ) == 0 ) { + struct timeval diff; + timersub( &this_call, &last_call, &diff ); + if( diff.tv_sec == 0 && diff.tv_usec < 100000 ) { + return; + } + last_call = this_call; + } + if( progress == 0 || total == 0 ) { + pc = 0; + } else { + pc = (double)progress / total; + } + len = pc * 30; + printf( "\rProgress: [" ); + for( n = 0; n<30; n++ ) { + putchar( (n<len-1)?'=': + (n==(len-1)?'>':' ') ); + } + printf( "] %5.1f%% of %d bytes", pc*100, total ); + fflush( stdout ); +} + diff --git a/neon/example/nserver.c b/neon/example/nserver.c new file mode 100644 index 000000000..7b8bf168c --- /dev/null +++ b/neon/example/nserver.c @@ -0,0 +1,147 @@ +/* + nserver, neon Server checker. + Copyright (C) 2000, Joe Orton <joe@orton.demon.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. + + Id: nserver.c,v 1.2 2000/07/16 16:15:44 joe Exp +*/ + +#include <sys/types.h> + +#include <sys/stat.h> +#include <sys/time.h> + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#include <neon_config.h> +#include <http_request.h> +#include <http_basic.h> +#include <uri.h> +#include <socket.h> + +#include <getopt.h> + +#include "basename.h" + +static void conn_notify( void *userdata, sock_status status, const char *info ) +{ + switch( status ) { + case sock_namelookup: + case sock_connecting: + case sock_connected: + break; + case sock_secure_details: + fprintf( stderr, "nserver: Using a secure connection (%s)\n", info ); + break; + } +} + +int main( int argc, char **argv ) +{ + struct uri uri = {0}, defaults = {0}; + http_session *sess; + http_req *req; + http_status status; + char *server = NULL, *pnt; + int ret; + + neon_debug_init( stderr, 0 ); + + sess = http_session_create(); + + defaults.path = "/"; + defaults.port = 0; + defaults.host = NULL; + defaults.scheme = "http"; + + if( argc < 2 ) { + printf( "nserver: Usage: %s http[s]://server.host.name[:port]\n", + argv[0] ); + return -1; + } + + pnt = strchr( argv[1], '/' ); + if( pnt == NULL ) { + uri.host = argv[1]; + pnt = strchr( uri.host, ':' ); + if( pnt == NULL ) { + uri.port = 80; + } else { + uri.port = atoi(pnt+1); + *pnt = '\0'; + } + uri.path = "/"; + uri.scheme = "http"; + } else { + if( uri_parse( argv[1], &uri, &defaults ) || !uri.host ) { + printf( "nserver: Could not parse URL `%s'\n", argv[1] ); + return -1; + } + if( strcasecmp( uri.scheme, "https" ) == 0 ) { + if( uri.port == 0 ) { + uri.port = 443; + } + if( http_set_secure( sess, 1 ) ) { + fprintf( stderr, "nserver: SSL not supported.\n" ); + exit( -1 ); + } + } + } + + sock_init(); + sock_register_notify( conn_notify, NULL ); + + if( uri.port == 0 ) { + uri.port = 80; + } + + printf( "nserver: Retrieving server string for server at %s (port %d):\n", + uri.host, uri.port ); + + req = http_request_create(sess, "HEAD", uri.path ); + if( http_session_server( sess, uri.host, uri.port ) != HTTP_OK ) { + printf( "nserver: Hostname `%s' not found.\n", uri.host ); + return -1; + } + + /* Use a standard strdup-er handler */ + http_add_response_header_handler( req, "Server", + http_duplicate_header, &server ); + + switch( http_request_dispatch( req, &status ) ) { + case HTTP_OK: + if( server == NULL ) { + printf( "nserver: No server string was given.\n" ); + ret = 1; + } else { + printf( "Server string: %s\n", server ); + ret = 0; + } + break; + default: + printf( "nserver: Failed: %s\n", http_get_error(sess) ); + ret = -1; + break; + } + + http_request_destroy( req ); + http_session_destroy( sess ); + + return ret; +} diff --git a/neon/install-sh b/neon/install-sh new file mode 100755 index 000000000..ebc66913e --- /dev/null +++ b/neon/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/neon/lib/basename.c b/neon/lib/basename.c new file mode 100644 index 000000000..358f5e8d0 --- /dev/null +++ b/neon/lib/basename.c @@ -0,0 +1,57 @@ +/* basename.c -- return the last element in a path + Copyright (C) 1990, 1998, 1999 Free Software Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#include <basename.h> + +#ifndef FILESYSTEM_PREFIX_LEN +# define FILESYSTEM_PREFIX_LEN(Filename) 0 +#endif + +#ifndef ISSLASH +# define ISSLASH(C) ((C) == '/') +#endif + +/* In general, we can't use the builtin `basename' function if available, + since it has different meanings in different environments. + In some environments the builtin `basename' modifies its argument. + If NAME is all slashes, be sure to return `/'. */ + +char * +base_name (char const *name) +{ + char const *base = name += FILESYSTEM_PREFIX_LEN (name); + int all_slashes = 1; + char const *p; + + for (p = name; *p; p++) + { + if (ISSLASH (*p)) + base = p + 1; + else + all_slashes = 0; + } + + /* If NAME is all slashes, arrange to return `/'. */ + if (*base == '\0' && ISSLASH (*name) && all_slashes) + --base; + + return (char *) base; +} diff --git a/neon/lib/basename.h b/neon/lib/basename.h new file mode 100644 index 000000000..6b022126e --- /dev/null +++ b/neon/lib/basename.h @@ -0,0 +1,2 @@ + +char *base_name (char const *name); diff --git a/neon/lib/dirname.c b/neon/lib/dirname.c new file mode 100644 index 000000000..c8cca9ede --- /dev/null +++ b/neon/lib/dirname.c @@ -0,0 +1,75 @@ +/* dirname.c -- return all but the last element in a path + Copyright (C) 1990, 1998 Free Software Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +/******************************************************* + * THIS IS A MODIFIED dirname IMPLEMENTATION: + * - sitecopy wants "" if there is no directory name, + * standard GNU implementation gives us "." + * - sitecopy wants the trailing slash. + *******************************************************/ + +#include "xalloc.h" + +#if defined STDC_HEADERS || defined HAVE_STRING_H +# include <string.h> +#else +# include <strings.h> +# ifndef strrchr +# define strrchr rindex +# endif +#endif + +#include "dirname.h" + +/* Return the leading directories part of PATH, + allocated with malloc. If out of memory, return 0. + Assumes that trailing slashes have already been + removed. */ + +char * +dir_name (const char *path) +{ + char *newpath; + char *slash; + int length; /* Length of result, not including NUL. */ + + slash = strrchr (path, '/'); + if (slash == 0) + { + /* File is in the current directory. */ + path = ""; + length = 0; + } + else + { + /* Remove any trailing slashes from the result. + while (slash > path && *slash == '/') + --slash; */ + + length = slash - path + 1; + } + newpath = (char *) xmalloc (length + 1); + if (newpath == 0) + return 0; + strncpy (newpath, path, length); + newpath[length] = 0; + return newpath; +} diff --git a/neon/lib/dirname.h b/neon/lib/dirname.h new file mode 100644 index 000000000..fc4669960 --- /dev/null +++ b/neon/lib/dirname.h @@ -0,0 +1,31 @@ +/* Copyright (C) 1998 Free Software Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#ifndef DIRNAME_H_ +# define DIRNAME_H_ 1 + +# ifndef PARAMS +# if defined PROTOTYPES || (defined __STDC__ && __STDC__) +# define PARAMS(Args) Args +# else +# define PARAMS(Args) () +# endif +# endif + +char * +dir_name PARAMS ((const char *path)); + +#endif /* not DIRNAME_H_ */ diff --git a/neon/lib/fnmatch.c b/neon/lib/fnmatch.c new file mode 100644 index 000000000..4bc7cd9f8 --- /dev/null +++ b/neon/lib/fnmatch.c @@ -0,0 +1,212 @@ +/* Copyright (C) 1991, 1992, 1993 Free Software Foundation, Inc. + + NOTE: The canonical source of this file is maintained with the GNU C + Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#include <errno.h> +#include <fnmatch.h> +#include <ctype.h> + +#if defined (STDC_HEADERS) || !defined (isascii) +# define ISASCII(c) 1 +#else +# define ISASCII(c) isascii(c) +#endif + +#define ISUPPER(c) (ISASCII (c) && isupper (c)) + + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +# ifndef errno +extern int errno; +# endif + +/* Match STRING against the filename pattern PATTERN, returning zero if + it matches, nonzero if not. */ +int +fnmatch (pattern, string, flags) + const char *pattern; + const char *string; + int flags; +{ + register const char *p = pattern, *n = string; + register char c; + +/* Note that this evalutes C many times. */ +# define FOLD(c) ((flags & FNM_CASEFOLD) && ISUPPER (c) ? tolower (c) : (c)) + + while ((c = *p++) != '\0') + { + c = FOLD (c); + + switch (c) + { + case '?': + if (*n == '\0') + return FNM_NOMATCH; + else if ((flags & FNM_FILE_NAME) && *n == '/') + return FNM_NOMATCH; + else if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_FILE_NAME) && n[-1] == '/'))) + return FNM_NOMATCH; + break; + + case '\\': + if (!(flags & FNM_NOESCAPE)) + { + c = *p++; + c = FOLD (c); + } + if (FOLD (*n) != c) + return FNM_NOMATCH; + break; + + case '*': + if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_FILE_NAME) && n[-1] == '/'))) + return FNM_NOMATCH; + + for (c = *p++; c == '?' || c == '*'; c = *p++, ++n) + if (((flags & FNM_FILE_NAME) && *n == '/') || + (c == '?' && *n == '\0')) + return FNM_NOMATCH; + + if (c == '\0') + return 0; + + { + char c1 = (!(flags & FNM_NOESCAPE) && c == '\\') ? *p : c; + c1 = FOLD (c1); + for (--p; *n != '\0'; ++n) + if ((c == '[' || FOLD (*n) == c1) && + fnmatch (p, n, flags & ~FNM_PERIOD) == 0) + return 0; + return FNM_NOMATCH; + } + + case '[': + { + /* Nonzero if the sense of the character class is inverted. */ + register int not; + + if (*n == '\0') + return FNM_NOMATCH; + + if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_FILE_NAME) && n[-1] == '/'))) + return FNM_NOMATCH; + + not = (*p == '!' || *p == '^'); + if (not) + ++p; + + c = *p++; + for (;;) + { + register char cstart = c, cend = c; + + if (!(flags & FNM_NOESCAPE) && c == '\\') + cstart = cend = *p++; + + cstart = cend = FOLD (cstart); + + if (c == '\0') + /* [ (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + c = FOLD (c); + + if ((flags & FNM_FILE_NAME) && c == '/') + /* [/] can never match. */ + return FNM_NOMATCH; + + if (c == '-' && *p != ']') + { + cend = *p++; + if (!(flags & FNM_NOESCAPE) && cend == '\\') + cend = *p++; + if (cend == '\0') + return FNM_NOMATCH; + cend = FOLD (cend); + + c = *p++; + } + + if (FOLD (*n) >= cstart && FOLD (*n) <= cend) + goto matched; + + if (c == ']') + break; + } + if (!not) + return FNM_NOMATCH; + break; + + matched:; + /* Skip the rest of the [...] that already matched. */ + while (c != ']') + { + if (c == '\0') + /* [... (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + if (!(flags & FNM_NOESCAPE) && c == '\\') + /* XXX 1003.2d11 is unclear if this is right. */ + ++p; + } + if (not) + return FNM_NOMATCH; + } + break; + + default: + if (c != FOLD (*n)) + return FNM_NOMATCH; + } + + ++n; + } + + if (*n == '\0') + return 0; + + if ((flags & FNM_LEADING_DIR) && *n == '/') + /* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */ + return 0; + + return FNM_NOMATCH; + +# undef FOLD +} + +#endif /* _LIBC or not __GNU_LIBRARY__. */ diff --git a/neon/lib/fnmatch.h b/neon/lib/fnmatch.h new file mode 100644 index 000000000..af1dcf523 --- /dev/null +++ b/neon/lib/fnmatch.h @@ -0,0 +1,69 @@ +/* Copyright (C) 1991, 1992, 1993 Free Software Foundation, Inc. + +NOTE: The canonical source of this file is maintained with the GNU C Library. +Bugs can be reported to bug-glibc@prep.ai.mit.edu. + +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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#ifndef _FNMATCH_H + +#define _FNMATCH_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined (__cplusplus) || (defined (__STDC__) && __STDC__) +#undef __P +#define __P(protos) protos +#else /* Not C++ or ANSI C. */ +#undef __P +#define __P(protos) () +/* We can get away without defining `const' here only because in this file + it is used only inside the prototype for `fnmatch', which is elided in + non-ANSI C where `const' is problematical. */ +#endif /* C++ or ANSI C. */ + + +/* We #undef these before defining them because some losing systems + (HP-UX A.08.07 for example) define these in <unistd.h>. */ +#undef FNM_PATHNAME +#undef FNM_NOESCAPE +#undef FNM_PERIOD + +/* Bits set in the FLAGS argument to `fnmatch'. */ +#define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ +#define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */ +#define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */ + +#if !defined (_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 2 || defined (_GNU_SOURCE) +#define FNM_FILE_NAME FNM_PATHNAME /* Preferred GNU name. */ +#define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */ +#define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */ +#endif + +/* Value returned by `fnmatch' if STRING does not match PATTERN. */ +#define FNM_NOMATCH 1 + +/* Match STRING against the filename pattern PATTERN, + returning zero if it matches, FNM_NOMATCH if not. */ +extern int fnmatch __P ((const char *__pattern, const char *__string, + int __flags)); + +#ifdef __cplusplus +} +#endif + +#endif /* fnmatch.h */ diff --git a/neon/lib/getopt.c b/neon/lib/getopt.c new file mode 100644 index 000000000..6557bc1c3 --- /dev/null +++ b/neon/lib/getopt.c @@ -0,0 +1,1052 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to drepper@gnu.org + before changing it! + + Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98 + Free Software Foundation, Inc. + + NOTE: The canonical source of this file is maintained with the GNU C Library. + Bugs can be reported to bug-glibc@prep.ai.mit.edu. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. + Ditto for AIX 3.2 and <stdlib.h>. */ +#ifndef _NO_PROTO +# define _NO_PROTO +#endif + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +# ifndef const +# define const +# endif +#endif + +#include <stdio.h> + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +# include <gnu-versions.h> +# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +# define ELIDE_CODE +# endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +# include <stdlib.h> +# include <unistd.h> +#endif /* GNU C library. */ + +#ifdef VMS +# include <unixlib.h> +# if HAVE_STRING_H - 0 +# include <string.h> +# endif +#endif + +#ifndef _ +/* This is for other GNU distributions with internationalized messages. + When compiling libc, the _ macro is predefined. */ +# ifdef HAVE_LIBINTL_H +# include <libintl.h> +# define _(msgid) gettext (msgid) +# else +# define _(msgid) (msgid) +# endif +#endif + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = NULL; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int optind = 1; + +/* Formerly, initialization of getopt depended on optind==0, which + causes problems with re-calling getopt as programs generally don't + know that. */ + +int __getopt_initialized = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return -1 with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +# include <string.h> +# define my_index strchr +#else + +# if HAVE_STRING_H +# include <string.h> +# else +# include <strings.h> +# endif + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +#ifndef getenv +extern char *getenv (); +#endif + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +# if (!defined __STDC__ || !__STDC__) && !defined strlen +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +# endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +#ifdef _LIBC +/* Bash 2.0 gives us an environment variable containing flags + indicating ARGV elements that should not be considered arguments. */ + +/* Defined in getopt_init.c */ +extern char *__getopt_nonoption_flags; + +static int nonoption_flags_max_len; +static int nonoption_flags_len; + +static int original_argc; +static char *const *original_argv; + +/* Make sure the environment variable bash 2.0 puts in the environment + is valid for the getopt call we must make sure that the ARGV passed + to getopt is that one passed to the process. */ +static void +__attribute__ ((unused)) +store_args_and_env (int argc, char *const *argv) +{ + /* XXX This is no good solution. We should rather copy the args so + that we can compare them later. But we must not use malloc(3). */ + original_argc = argc; + original_argv = argv; +} +# ifdef text_set_element +text_set_element (__libc_subinit, store_args_and_env); +# endif /* text_set_element */ + +# define SWAP_FLAGS(ch1, ch2) \ + if (nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ + } +#else /* !_LIBC */ +# define SWAP_FLAGS(ch1, ch2) +#endif /* _LIBC */ + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +#if defined __STDC__ && __STDC__ +static void exchange (char **); +#endif + +static void +exchange (argv) + char **argv; +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + +#ifdef _LIBC + /* First make sure the handling of the `__getopt_nonoption_flags' + string can work normally. Our top argument must be in the range + of the string. */ + if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) + { + /* We must extend the array. The user plays games with us and + presents new arguments. */ + char *new_str = xmalloc (top + 1); + if (new_str == NULL) + nonoption_flags_len = nonoption_flags_max_len = 0; + else + { + memset (__mempcpy (new_str, __getopt_nonoption_flags, + nonoption_flags_max_len), + '\0', top + 1 - nonoption_flags_max_len); + nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } + } +#endif + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +#if defined __STDC__ && __STDC__ +static const char *_getopt_initialize (int, char *const *, const char *); +#endif +static const char * +_getopt_initialize (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + +#ifdef _LIBC + if (posixly_correct == NULL + && argc == original_argc && argv == original_argv) + { + if (nonoption_flags_max_len == 0) + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = nonoption_flags_max_len = strlen (orig_str); + if (nonoption_flags_max_len < argc) + nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + (char *) xmalloc (nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + nonoption_flags_max_len = -1; + else + memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), + '\0', nonoption_flags_max_len - len); + } + } + nonoption_flags_len = nonoption_flags_max_len; + } + else + nonoption_flags_len = 0; +#endif + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns -1. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + optarg = NULL; + + if (optind == 0 || !__getopt_initialized) + { + if (optind == 0) + optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize (argc, argv, optstring); + __getopt_initialized = 1; + } + + /* Test whether ARGV[optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#ifdef _LIBC +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ + || (optind < nonoption_flags_len \ + && __getopt_nonoption_flags[optind] == '1')) +#else +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') +#endif + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (last_nonopt > optind) + last_nonopt = optind; + if (first_nonopt > optind) + first_nonopt = optind; + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc && NONOPTION_P) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) + { + if (ordering == REQUIRE_ORDER) + return -1; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) + == (unsigned int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[optind - 1][0], pfound->name); + + nextchar += strlen (nextchar); + + optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], c); + } + optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + + /* optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); + + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == -1) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/neon/lib/getopt.h b/neon/lib/getopt.h new file mode 100644 index 000000000..c4adc30bb --- /dev/null +++ b/neon/lib/getopt.h @@ -0,0 +1,133 @@ +/* Declarations for getopt. + Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc. + + NOTE: The canonical source of this file is maintained with the GNU C Library. + Bugs can be reported to bug-glibc@prep.ai.mit.edu. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +#ifndef _GETOPT_H +#define _GETOPT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +#if defined (__STDC__) && __STDC__ + const char *name; +#else + char *name; +#endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +#if defined (__STDC__) && __STDC__ +#ifdef __GNU_LIBRARY__ +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int argc, char *const *argv, const char *shortopts); +#else /* not __GNU_LIBRARY__ */ +extern int getopt (); +#endif /* __GNU_LIBRARY__ */ +extern int getopt_long (int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); +extern int getopt_long_only (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); +#else /* not __STDC__ */ +extern int getopt (); +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +#endif /* __STDC__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* getopt.h */ diff --git a/neon/lib/getopt1.c b/neon/lib/getopt1.c new file mode 100644 index 000000000..4ce10655d --- /dev/null +++ b/neon/lib/getopt1.c @@ -0,0 +1,190 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 + Free Software Foundation, Inc. + + NOTE: The canonical source of this file is maintained with the GNU C Library. + Bugs can be reported to bug-glibc@prep.ai.mit.edu. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "getopt.h" + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include <stdio.h> + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +#include <gnu-versions.h> +#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +#define ELIDE_CODE +#endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include <stdlib.h> +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +#include <stdio.h> + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == -1) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/neon/lib/linelen.c b/neon/lib/linelen.c new file mode 100644 index 000000000..15c6ebe55 --- /dev/null +++ b/neon/lib/linelen.c @@ -0,0 +1,57 @@ +/* Line length calculation routine. + Taken from fileutils-4.0i/src/ls.c + Copyright (C) 85, 88, 90, 91, 1995-1999 Free Software Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Id: linelen.c,v 1.1 1999/06/25 19:26:46 joe Exp + */ + +#if HAVE_TERMIOS_H +# include <termios.h> +#endif + +#ifdef GWINSZ_IN_SYS_IOCTL +# include <sys/ioctl.h> +#endif + +#ifdef WINSIZE_IN_PTEM +# include <sys/stream.h> +# include <sys/ptem.h> +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* For STDOUT_FILENO */ +#endif + +#define DEFAULT_LINE_LENGTH 80 + +int get_line_length( void ) { + int ret = DEFAULT_LINE_LENGTH; + +/* joe: Some systems do this with a 'struct ttysize'? + * There is code in tin to do it, but tin has a dodgy license. + * DIY, if you have such a system. + */ + +#ifdef TIOCGWINSZ + struct winsize ws; + + if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col != 0) + ret = ws.ws_col; + +#endif + return ret; +} diff --git a/neon/lib/netrc.c b/neon/lib/netrc.c new file mode 100644 index 000000000..b6af1c323 --- /dev/null +++ b/neon/lib/netrc.c @@ -0,0 +1,423 @@ +/* netrc.c -- parse the .netrc file to get hosts, accounts, and passwords + + Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996 + + 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. + + For license terms, see the file COPYING in this directory. + + Compile with -DSTANDALONE to test this module. */ + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include <config.h> +#include "netrc.h" + +#include "xalloc.h" + +/* Normally defined in xmalloc.c */ +# define xrealloc realloc + +#define POPBUFSIZE BUFSIZ + +/* Maybe add NEWENTRY to the account information list, LIST. NEWENTRY is + set to a ready-to-use netrc_entry, in any event. */ +static void +maybe_add_to_list (netrc_entry **newentry, netrc_entry **list) +{ + netrc_entry *a, *l; + a = *newentry; + l = *list; + + /* We need an account name in order to add the entry to the list. */ + if (a && ! a->account) + { + /* Free any allocated space. */ + if (a->host) + free (a->host); + if (a->password) + free (a->password); + } + else + { + if (a) + { + /* Add the current machine into our list. */ + a->next = l; + l = a; + } + + /* Allocate a new netrc_entry structure. */ + a = (netrc_entry *) xmalloc (sizeof (netrc_entry)); + } + + /* Zero the structure, so that it is ready to use. */ + memset (a, 0, sizeof(*a)); + + /* Return the new pointers. */ + *newentry = a; + *list = l; + return; +} + + +/* Parse FILE as a .netrc file (as described in ftp(1)), and return a + list of entries. NULL is returned if the file could not be + parsed. */ +netrc_entry * +parse_netrc (file) + char *file; +{ + FILE *fp; + char buf[POPBUFSIZE+1], *p, *tok; + const char *premature_token; + netrc_entry *current, *retval; + int ln; + + /* The latest token we've seen in the file. */ + enum + { + tok_nothing, tok_account, tok_login, tok_macdef, tok_machine, tok_password + } last_token = tok_nothing; + + current = retval = NULL; + + fp = fopen (file, "r"); + if (!fp) + { + /* Just return NULL if we can't open the file. */ + return NULL; + } + + /* Initialize the file data. */ + ln = 0; + premature_token = NULL; + + /* While there are lines in the file... */ + while (fgets(buf, POPBUFSIZE, fp)) + { + ln++; + + /* Strip trailing CRLF */ + for (p = buf + strlen(buf) - 1; (p >= buf) && isspace((unsigned)*p); p--) + *p = '\0'; + + /* Parse the line. */ + p = buf; + + /* If the line is empty... */ + if (!*p) { + if (last_token == tok_macdef) { /* end of macro */ + last_token = tok_nothing; + } else { + continue; /* otherwise ignore it */ + } + } + + /* If we are defining macros, then skip parsing the line. */ + while (*p && last_token != tok_macdef) + { + char quote_char = 0; + char *pp; + + /* Skip any whitespace. */ + while (*p && isspace ((unsigned)*p)) + p++; + + /* Discard end-of-line comments. */ + if (*p == '#') + break; + + tok = pp = p; + + /* Find the end of the token. */ + while (*p && (quote_char || !isspace ((unsigned)*p))) + { + if (quote_char) + { + if (quote_char == *p) + { + quote_char = 0; + p ++; + } + else + { + *pp = *p; + p ++; + pp ++; + } + } + else + { + if (*p == '"' || *p == '\'') + quote_char = *p; + else + { + *pp = *p; + pp ++; + } + p ++; + } + } + /* Null-terminate the token, if it isn't already. */ + if (*p) + *p ++ = '\0'; + *pp = 0; + + switch (last_token) + { + case tok_login: + if (current) + current->account = (char *) xstrdup (tok); + else + premature_token = "login"; + break; + + case tok_machine: + /* Start a new machine entry. */ + maybe_add_to_list (¤t, &retval); + current->host = (char *) xstrdup (tok); + break; + + case tok_password: + if (current) + current->password = (char *) xstrdup (tok); + else + premature_token = "password"; + break; + + /* We handle most of tok_macdef above. */ + case tok_macdef: + if (!current) + premature_token = "macdef"; + break; + + /* We don't handle the account keyword at all. */ + case tok_account: + if (!current) + premature_token = "account"; + break; + + /* We handle tok_nothing below this switch. */ + case tok_nothing: + break; + } + + if (premature_token) + { +#ifdef HAVE_ERROR + error_at_line (0, file, ln, + "warning: found \"%s\" before any host names", + premature_token); +#else + fprintf (stderr, + "%s:%d: warning: found \"%s\" before any host names\n", + file, ln, premature_token); +#endif + premature_token = NULL; + } + + if (last_token != tok_nothing) + /* We got a value, so reset the token state. */ + last_token = tok_nothing; + else + { + /* Fetch the next token. */ + if (!strcmp (tok, "default")) + { + maybe_add_to_list (¤t, &retval); + } + else if (!strcmp (tok, "login")) + last_token = tok_login; + + else if (!strcmp (tok, "user")) + last_token = tok_login; + + else if (!strcmp (tok, "macdef")) + last_token = tok_macdef; + + else if (!strcmp (tok, "machine")) + last_token = tok_machine; + + else if (!strcmp (tok, "password")) + last_token = tok_password; + + else if (!strcmp (tok, "passwd")) + last_token = tok_password; + + else if (!strcmp (tok, "account")) + last_token = tok_account; + + else + { + fprintf (stderr, "%s:%d: warning: unknown token \"%s\"\n", + file, ln, tok); + } + } + } + } + + fclose (fp); + + /* Finalize the last machine entry we found. */ + maybe_add_to_list (¤t, &retval); + free (current); + + /* Reverse the order of the list so that it appears in file order. */ + current = retval; + retval = NULL; + while (current) + { + netrc_entry *saved_reference; + + /* Change the direction of the pointers. */ + saved_reference = current->next; + current->next = retval; + + /* Advance to the next node. */ + retval = current; + current = saved_reference; + } + + return retval; +} + + +/* Return the netrc entry from LIST corresponding to HOST. NULL is + returned if no such entry exists. */ +netrc_entry * +search_netrc (list, host) + netrc_entry *list; + const char *host; +{ + /* Look for the HOST in LIST. */ + while (list) + { + if (!list->host) + /* We hit the default entry. */ + break; + + else if (!strcmp (list->host, host)) + /* We found a matching entry. */ + break; + + list = list->next; + } + + /* Return the matching entry, or NULL. */ + return list; +} + + +#ifdef STANDALONE +#include <sys/types.h> +#include <sys/stat.h> + +extern int errno; + +int +main (argc, argv) + int argc; + char **argv; +{ + struct stat sb; + char *program_name, *file, *target; + netrc_entry *head, *a; + + if (argc < 2) + { + fprintf (stderr, "Usage: %s NETRC [HOSTNAME]...\n", argv[0]); + exit (1); + } + + program_name = argv[0]; + file = argv[1]; + target = argv[2]; + + if (stat (file, &sb)) + { + fprintf (stderr, "%s: cannot stat %s: %s\n", argv[0], file, + strerror (errno)); + exit (1); + } + + head = parse_netrc (file); + if (!head) + { + fprintf (stderr, "%s: no entries found in %s\n", argv[0], file); + exit (1); + } + + if (argc > 2) + { + int i, status; + status = 0; + for (i = 2; i < argc; i++) + { + /* Print out the host that we are checking for. */ + fputs (argv[i], stdout); + + a = search_netrc (head, argv[i]); + if (a) + { + /* Print out the account and password (if any). */ + fputc (' ', stdout); + fputs (a->account, stdout); + if (a->password) + { + fputc (' ', stdout); + fputs (a->password, stdout); + } + } + else + status = 1; + + fputc ('\n', stdout); + } + exit (status); + } + + /* Print out the entire contents of the netrc. */ + a = head; + while (a) + { + /* Print the host name. */ + if (a->host) + fputs (a->host, stdout); + else + fputs ("DEFAULT", stdout); + + fputc (' ', stdout); + + /* Print the account name. */ + fputs (a->account, stdout); + + if (a->password) + { + /* Print the password, if there is any. */ + fputc (' ', stdout); + fputs (a->password, stdout); + } + + fputc ('\n', stdout); + a = a->next; + } + + exit (0); +} +#endif /* STANDALONE */ diff --git a/neon/lib/netrc.h b/neon/lib/netrc.h new file mode 100644 index 000000000..66916b0e1 --- /dev/null +++ b/neon/lib/netrc.h @@ -0,0 +1,65 @@ +/* netrc.h -- declarations for netrc.c + Copyright (C) 1996, Free Software Foundation, Inc. + Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996 + + 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 _NETRC_H_ +#define _NETRC_H_ 1 + +# undef __BEGIN_DECLS +# undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +#undef __P +#if defined (__STDC__) || defined (_AIX) || (defined (__mips) && defined (_SYSTYPE_SVR4)) || defined(WIN32) || defined(__cplusplus) +# define __P(protos) protos +#else +# define __P(protos) () +#endif + +/* The structure used to return account information from the .netrc. */ +typedef struct _netrc_entry { + /* The exact host name given in the .netrc, NULL if default. */ + char *host; + + /* The name of the account. */ + char *account; + + /* Password for the account (NULL, if none). */ + char *password; + + /* Pointer to the next entry in the list. */ + struct _netrc_entry *next; +} netrc_entry; + +__BEGIN_DECLS +/* Parse FILE as a .netrc file (as described in ftp(1)), and return a + list of entries. NULL is returned if the file could not be + parsed. */ +netrc_entry *parse_netrc __P((char *file)); + +/* Return the netrc entry from LIST corresponding to HOST. NULL is + returned if no such entry exists. */ +netrc_entry *search_netrc __P((netrc_entry *list, const char *host)); +__END_DECLS + +#endif /* _NETRC_H_ */ diff --git a/neon/lib/rpmatch.c b/neon/lib/rpmatch.c new file mode 100644 index 000000000..aff3832ca --- /dev/null +++ b/neon/lib/rpmatch.c @@ -0,0 +1,88 @@ +/* Determine whether string value is affirmation or negative response + according to current locale's data. + Copyright (C) 1996, 1998 Free Software Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#if STDC_HEADERS || _LIBC +# include <stddef.h> +# include <stdlib.h> +#else +# ifndef NULL +# define NULL 0 +# endif +#endif + +#if defined(ENABLE_NLS) && defined(HAVE_REGEX_H) +#define RP_USE_REGEX +#endif + +#ifdef RP_USE_REGEX +# include <sys/types.h> +# include <regex.h> +# include <libintl.h> +# define _(Text) gettext (Text) + +static int +try (const char *response, const char *pattern, const int match, + const int nomatch, const char **lastp, regex_t *re) +{ + if (pattern != *lastp) + { + /* The pattern has changed. */ + if (*lastp) + { + /* Free the old compiled pattern. */ + regfree (re); + *lastp = NULL; + } + /* Compile the pattern and cache it for future runs. */ + if (regcomp (re, pattern, REG_EXTENDED) != 0) + return -1; + *lastp = pattern; + } + + /* See if the regular expression matches RESPONSE. */ + return regexec (re, response, 0, NULL, 0) == 0 ? match : nomatch; +} +#endif + + +int +rpmatch (const char *response) +{ +#ifdef RP_USE_REGEX + /* Match against one of the response patterns, compiling the pattern + first if necessary. */ + + /* We cache the response patterns and compiled regexps here. */ + static const char *yesexpr, *noexpr; + static regex_t yesre, nore; + int result; + + return ((result = try (response, _("^[yY]"), 1, 0, + &yesexpr, &yesre)) + ? result + : try (response, _("^[nN]"), 0, -1, &noexpr, &nore)); +#else + /* Test against "^[yY]" and "^[nN]", hardcoded to avoid requiring regex */ + return (*response == 'y' || *response == 'Y' ? 1 + : *response == 'n' || *response == 'N' ? 0 : -1); +#endif +} diff --git a/neon/lib/snprintf.c b/neon/lib/snprintf.c new file mode 100644 index 000000000..49841177b --- /dev/null +++ b/neon/lib/snprintf.c @@ -0,0 +1,835 @@ + +/* + Unix snprintf implementation. + Version 1.1 + + 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. + + Revision History: + + sitecopy changes: + renamed dtoa -> doubletoa to avoid dtoa conflict with cygwin. + + + 1.1: + * added changes from Miles Bader + * corrected a bug with %f + * added support for %#g + * added more comments :-) + 1.0: + * supporting must ANSI syntaxic_sugars + 0.0: + * suppot %s %c %d + + THANKS(for the patches and ideas): + Miles Bader + Cyrille Rustom + Jacek Slabocewiz + Mike Parker(mouse) + +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "snprintf.h" + +#ifdef HAVE_STRING_H +#include <string.h> /* for strlen() */ +#endif +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> /* for atoi() */ +#endif +#include <ctype.h> + + +/* + * Find the nth power of 10 + */ +PRIVATE double +#ifdef __STDC__ +pow_10(int n) +#else +pow_10(n) +int n; +#endif +{ + int i; + double P; + + if (n < 0) + for (i = 1, P = 1., n = -n ; i <= n ; i++) {P *= .1;} + else + for (i = 1, P = 1. ; i <= n ; i++) {P *= 10.0;} + return P; +} + +/* + * Find the integral part of the log in base 10 + * Note: this not a real log10() + I just need and approximation(integerpart) of x in: + 10^x ~= r + * log_10(200) = 2; + * log_10(250) = 2; + */ +PRIVATE int +#ifdef __STDC__ +log_10(double r) +#else +log_10(r) +double r; +#endif +{ + int i = 0; + double result = 1.; + + if (r < 0.) + r = -r; + + if (r < 1.) { + while (result >= r) {result *= .1; i++;} + return (-i); + } else { + while (result <= r) {result *= 10.; i++;} + return (i - 1); + } +} + +/* + * This function return the fraction part of a double + * and set in ip the integral part. + * In many ways it resemble the modf() found on most Un*x + */ +PRIVATE double +#ifdef __STDC__ +integral(double real, double * ip) +#else +integral(real, ip) +double real; +double * ip; +#endif +{ + int j; + double i, s, p; + double real_integral = 0.; + +/* take care of the obvious */ +/* equal to zero ? */ + if (real == 0.) { + *ip = 0.; + return (0.); + } + +/* negative number ? */ + if (real < 0.) + real = -real; + +/* a fraction ? */ + if ( real < 1.) { + *ip = 0.; + return real; + } +/* the real work :-) */ + for (j = log_10(real); j >= 0; j--) { + p = pow_10(j); + s = (real - real_integral)/p; + i = 0.; + while (i + 1. <= s) {i++;} + real_integral += i*p; + } + *ip = real_integral; + return (real - real_integral); +} + +#define PRECISION 1.e-6 +/* + * return an ascii representation of the integral part of the number + * and set fract to be an ascii representation of the fraction part + * the container for the fraction and the integral part or staticly + * declare with fix size + */ +PRIVATE char * +#ifdef __STDC__ +numtoa(double number, int base, int precision, char ** fract) +#else +numtoa(number, base, precision, fract) +double number; +int base; +int precision; +char ** fract; +#endif +{ + register int i, j; + double ip, fp; /* integer and fraction part */ + double fraction; + int digits = MAX_INT - 1; + static char integral_part[MAX_INT]; + static char fraction_part[MAX_FRACT]; + double sign; + int ch; + +/* taking care of the obvious case: 0.0 */ + if (number == 0.) { + integral_part[0] = '0'; + integral_part[1] = '\0'; + fraction_part[0] = '0'; + fraction_part[1] = '\0'; + return integral_part; + } + +/* for negative numbers */ + if ((sign = number) < 0.) { + number = -number; + digits--; /* sign consume one digit */ + } + + fraction = integral(number, &ip); + number = ip; +/* do the integral part */ + if ( ip == 0.) { + integral_part[0] = '0'; + i = 1; + } else { + for ( i = 0; i < digits && number != 0.; ++i) { + number /= base; + fp = integral(number, &ip); + ch = (int)((fp + PRECISION)*base); /* force to round */ + integral_part[i] = (ch <= 9) ? ch + '0' : ch + 'a' - 10; + if (! isxdigit(integral_part[i])) /* bail out overflow !! */ + break; + number = ip; + } + } + +/* Oh No !! out of bound, ho well fill it up ! */ + if (number != 0.) + for (i = 0; i < digits; ++i) + integral_part[i] = '9'; + +/* put the sign ? */ + if (sign < 0.) + integral_part[i++] = '-'; + + integral_part[i] = '\0'; + +/* reverse every thing */ + for ( i--, j = 0; j < i; j++, i--) + SWAP_INT(integral_part[i], integral_part[j]); + +/* the fractionnal part */ + for (i=0, fp=fraction; precision > 0 && i < MAX_FRACT ; i++, precision-- ) { + fraction_part[i] = (int)((fp + PRECISION)*10. + '0'); + if (! isdigit(fraction_part[i])) /* underflow ? */ + break; + fp = (fp*10.0) - (double)(long)((fp + PRECISION)*10.); + } + fraction_part[i] = '\0'; + + if (fract != (char **)0) + *fract = fraction_part; + + return integral_part; + +} + +/* for %d and friends, it puts in holder + * the representation with the right padding + */ +PRIVATE void +#ifdef __STDC__ +decimal(struct DATA *p, double d) +#else +decimal(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp; + + tmp = itoa(d); + p->width -= strlen(tmp); + PAD_RIGHT(p); + PUT_PLUS(d, p); + PUT_SPACE(d, p); + while (*tmp) { /* the integral */ + PUT_CHAR(*tmp, p); + tmp++; + } + PAD_LEFT(p); +} + +/* for %o octal representation */ +PRIVATE void +#ifdef __STDC__ +octal(struct DATA *p, double d) +#else +octal(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp; + + tmp = otoa(d); + p->width -= strlen(tmp); + PAD_RIGHT(p); + if (p->square == FOUND) /* had prefix '0' for octal */ + PUT_CHAR('0', p); + while (*tmp) { /* octal */ + PUT_CHAR(*tmp, p); + tmp++; + } + PAD_LEFT(p); +} + +/* for %x %X hexadecimal representation */ +PRIVATE void +#ifdef __STDC__ +hexa(struct DATA *p, double d) +#else +hexa(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp; + + tmp = htoa(d); + p->width -= strlen(tmp); + PAD_RIGHT(p); + if (p->square == FOUND) { /* prefix '0x' for hexa */ + PUT_CHAR('0', p); PUT_CHAR(*p->pf, p); + } + while (*tmp) { /* hexa */ + PUT_CHAR((*p->pf == 'X' ? toupper(*tmp) : *tmp), p); + tmp++; + } + PAD_LEFT(p); +} + +/* %s strings */ +PRIVATE void +#ifdef __STDC__ +strings(struct DATA *p, char *tmp) +#else +strings(p, tmp) +struct DATA *p; +char *tmp; +#endif +{ + int i; + + i = strlen(tmp); + if (p->precision != NOT_FOUND) /* the smallest number */ + i = (i < p->precision ? i : p->precision); + p->width -= i; + PAD_RIGHT(p); + while (i-- > 0) { /* put the sting */ + PUT_CHAR(*tmp, p); + tmp++; + } + PAD_LEFT(p); +} + +/* %f or %g floating point representation */ +PRIVATE void +#ifdef __STDC__ +floating(struct DATA *p, double d) +#else +floating(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp, *tmp2; + int i; + + DEF_PREC(p); + d = ROUND(d, p); + tmp = doubletoa(d, p->precision, &tmp2); + /* calculate the padding. 1 for the dot */ + p->width = p->width - + ((d > 0. && p->justify == RIGHT) ? 1:0) - + ((p->space == FOUND) ? 1:0) - + strlen(tmp) - p->precision - 1; + PAD_RIGHT(p); + PUT_PLUS(d, p); + PUT_SPACE(d, p); + while (*tmp) { /* the integral */ + PUT_CHAR(*tmp, p); + tmp++; + } + if (p->precision != 0 || p->square == FOUND) + PUT_CHAR('.', p); /* put the '.' */ + if (*p->pf == 'g' || *p->pf == 'G') /* smash the trailing zeros */ + for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--) + tmp2[i] = '\0'; + for (; *tmp2; tmp2++) + PUT_CHAR(*tmp2, p); /* the fraction */ + + PAD_LEFT(p); +} + +/* %e %E %g exponent representation */ +PRIVATE void +#ifdef __STDC__ +exponent(struct DATA *p, double d) +#else +exponent(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp, *tmp2; + int j, i; + + DEF_PREC(p); + j = log_10(d); + d = d / pow_10(j); /* get the Mantissa */ + d = ROUND(d, p); + tmp = doubletoa(d, p->precision, &tmp2); + /* 1 for unit, 1 for the '.', 1 for 'e|E', + * 1 for '+|-', 3 for 'exp' */ + /* calculate how much padding need */ + p->width = p->width - + ((d > 0. && p->justify == RIGHT) ? 1:0) - + ((p->space == FOUND) ? 1:0) - p->precision - 7; + PAD_RIGHT(p); + PUT_PLUS(d, p); + PUT_SPACE(d, p); + while (*tmp) {/* the integral */ + PUT_CHAR(*tmp, p); + tmp++; + } + if (p->precision != 0 || p->square == FOUND) + PUT_CHAR('.', p); /* the '.' */ + if (*p->pf == 'g' || *p->pf == 'G') /* smash the trailing zeros */ + for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--) + tmp2[i] = '\0'; + for (; *tmp2; tmp2++) + PUT_CHAR(*tmp2, p); /* the fraction */ + + if (*p->pf == 'g' || *p->pf == 'e') { /* the exponent put the 'e|E' */ + PUT_CHAR('e', p); + } else + PUT_CHAR('E', p); + if (j > 0) { /* the sign of the exp */ + PUT_CHAR('+', p); + } else { + PUT_CHAR('-', p); + j = -j; + } + tmp = itoa((double)j); + if (j < 9) { /* need to pad the exponent with 0 '000' */ + PUT_CHAR('0', p); PUT_CHAR('0', p); + } else if (j < 99) + PUT_CHAR('0', p); + while (*tmp) { /* the exponent */ + PUT_CHAR(*tmp, p); + tmp++; + } + PAD_LEFT(p); +} + +/* initialize the conversion specifiers */ +PRIVATE void +#ifdef __STDC__ +conv_flag(char * s, struct DATA * p) +#else +conv_flag(s, p) +char * s; +struct DATA * p; +#endif +{ + char number[MAX_FIELD/2]; + int i; + + p->precision = p->width = NOT_FOUND; + p->star_w = p->star_p = NOT_FOUND; + p->square = p->space = NOT_FOUND; + p->a_long = p->justify = NOT_FOUND; + p->pad = ' '; + + for(;s && *s ;s++) { + switch(*s) { + case ' ': p->space = FOUND; break; + case '#': p->square = FOUND; break; + case '*': if (p->width == NOT_FOUND) + p->width = p->star_w = FOUND; + else + p->precision = p->star_p = FOUND; + break; + case '+': p->justify = RIGHT; break; + case '-': p->justify = LEFT; break; + case '.': if (p->width == NOT_FOUND) + p->width = 0; + break; + case '0': p->pad = '0'; break; + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': /* gob all the digits */ + for (i = 0; isdigit(*s); i++, s++) + if (i < MAX_FIELD/2 - 1) + number[i] = *s; + number[i] = '\0'; + if (p->width == NOT_FOUND) + p->width = atoi(number); + else + p->precision = atoi(number); + s--; /* went to far go back */ + break; + } + } +} + +PUBLIC int +#ifdef __STDC__ +vsnprintf(char *string, size_t length, const char * format, va_list args) +#else +vsnprintf(string, length, format, args) +char *string; +size_t length; +char * format; +va_list args; +#endif +{ + struct DATA data; + char conv_field[MAX_FIELD]; + double d; /* temporary holder */ + int state; + int i; + + data.length = length - 1; /* leave room for '\0' */ + data.holder = string; + data.pf = format; + data.counter = 0; + + +/* sanity check, the string must be > 1 */ + if (length < 1) + return -1; + + + for (; *data.pf && (data.counter < data.length); data.pf++) { + if ( *data.pf == '%' ) { /* we got a magic % cookie */ + conv_flag((char *)0, &data); /* initialise format flags */ + for (state = 1; *data.pf && state;) { + switch (*(++data.pf)) { + case '\0': /* a NULL here ? ? bail out */ + *data.holder = '\0'; + return data.counter; + break; + case 'f': /* float, double */ + STAR_ARGS(&data); + d = va_arg(args, double); + floating(&data, d); + state = 0; + break; + case 'g': + case 'G': + STAR_ARGS(&data); + DEF_PREC(&data); + d = va_arg(args, double); + i = log_10(d); + /* + * for '%g|%G' ANSI: use f if exponent + * is in the range or [-4,p] exclusively + * else use %e|%E + */ + if (-4 < i && i < data.precision) + floating(&data, d); + else + exponent(&data, d); + state = 0; + break; + case 'e': + case 'E': /* Exponent double */ + STAR_ARGS(&data); + d = va_arg(args, double); + exponent(&data, d); + state = 0; + break; + case 'u': + case 'd': /* decimal */ + STAR_ARGS(&data); + if (data.a_long == FOUND) + d = va_arg(args, long); + else + d = va_arg(args, int); + decimal(&data, d); + state = 0; + break; + case 'o': /* octal */ + STAR_ARGS(&data); + if (data.a_long == FOUND) + d = va_arg(args, long); + else + d = va_arg(args, int); + octal(&data, d); + state = 0; + break; + case 'x': + case 'X': /* hexadecimal */ + STAR_ARGS(&data); + if (data.a_long == FOUND) + d = va_arg(args, long); + else + d = va_arg(args, int); + hexa(&data, d); + state = 0; + break; + case 'c': /* character */ + d = va_arg(args, int); + PUT_CHAR(d, &data); + state = 0; + break; + case 's': /* string */ + STAR_ARGS(&data); + strings(&data, va_arg(args, char *)); + state = 0; + break; + case 'n': + *(va_arg(args, int *)) = data.counter; /* what's the count ? */ + state = 0; + break; + case 'l': + data.a_long = FOUND; + break; + case 'h': + break; + case '%': /* nothing just % */ + PUT_CHAR('%', &data); + state = 0; + break; + case '#': case ' ': case '+': case '*': + case '-': case '.': case '0': case '1': + case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + /* initialize width and precision */ + for (i = 0; isflag(*data.pf); i++, data.pf++) + if (i < MAX_FIELD - 1) + conv_field[i] = *data.pf; + conv_field[i] = '\0'; + conv_flag(conv_field, &data); + data.pf--; /* went to far go back */ + break; + default: + /* is this an error ? maybe bail out */ + state = 0; + break; + } /* end switch */ + } /* end of for state */ + } else { /* not % */ + PUT_CHAR(*data.pf, &data); /* add the char the string */ + } + } + + *data.holder = '\0'; /* the end ye ! */ + + return data.counter; +} + +#ifndef HAVE_SNPRINTF + +PUBLIC int +#if defined(HAVE_STDARG_H) && defined(__STDC__) && __STDC__ +snprintf(char *string, size_t length, const char * format, ...) +#else +snprintf(string, length, format, va_alist) +char *string; +size_t length; +char * format; +va_dcl +#endif +{ + int rval; + va_list args; + +#if defined(HAVE_STDARG_H) && defined(__STDC__) && __STDC__ + va_start(args, format); +#else + va_start(args); +#endif + + rval = vsnprintf (string, length, format, args); + + va_end(args); + + return rval; +} + +#endif /* HAVE_SNPRINTF */ + + +#ifdef DRIVER + +#include <stdio.h> + +/* set of small tests for snprintf() */ +void main() +{ + char holder[100]; + int i; + +/* + printf("Suite of test for snprintf:\n"); + printf("a_format\n"); + printf("printf() format\n"); + printf("snprintf() format\n\n"); +*/ +/* Checking the field widths */ + + printf("/%%d/, 336\n"); + snprintf(holder, sizeof holder, "/%d/\n", 336); + printf("/%d/\n", 336); + printf("%s\n", holder); + + printf("/%%2d/, 336\n"); + snprintf(holder, sizeof holder, "/%2d/\n", 336); + printf("/%2d/\n", 336); + printf("%s\n", holder); + + printf("/%%10d/, 336\n"); + snprintf(holder, sizeof holder, "/%10d/\n", 336); + printf("/%10d/\n", 336); + printf("%s\n", holder); + + printf("/%%-10d/, 336\n"); + snprintf(holder, sizeof holder, "/%-10d/\n", 336); + printf("/%-10d/\n", 336); + printf("%s\n", holder); + + +/* floating points */ + + printf("/%%f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%f/\n", 1234.56); + printf("/%f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%e/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%e/\n", 1234.56); + printf("/%e/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%4.2f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%4.2f/\n", 1234.56); + printf("/%4.2f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%3.1f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%3.1f/\n", 1234.56); + printf("/%3.1f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%10.3f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%10.3f/\n", 1234.56); + printf("/%10.3f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%10.3e/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%10.3e/\n", 1234.56); + printf("/%10.3e/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%+4.2f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%+4.2f/\n", 1234.56); + printf("/%+4.2f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%010.2f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%010.2f/\n", 1234.56); + printf("/%010.2f/\n", 1234.56); + printf("%s\n", holder); + +#define BLURB "Outstanding acting !" +/* strings precisions */ + + printf("/%%2s/, \"%s\"\n", BLURB); + snprintf(holder, sizeof holder, "/%2s/\n", BLURB); + printf("/%2s/\n", BLURB); + printf("%s\n", holder); + + printf("/%%22s/ %s\n", BLURB); + snprintf(holder, sizeof holder, "/%22s/\n", BLURB); + printf("/%22s/\n", BLURB); + printf("%s\n", holder); + + printf("/%%22.5s/ %s\n", BLURB); + snprintf(holder, sizeof holder, "/%22.5s/\n", BLURB); + printf("/%22.5s/\n", BLURB); + printf("%s\n", holder); + + printf("/%%-22.5s/ %s\n", BLURB); + snprintf(holder, sizeof holder, "/%-22.5s/\n", BLURB); + printf("/%-22.5s/\n", BLURB); + printf("%s\n", holder); + +/* see some flags */ + + printf("%%x %%X %%#x, 31, 31, 31\n"); + snprintf(holder, sizeof holder, "%x %X %#x\n", 31, 31, 31); + printf("%x %X %#x\n", 31, 31, 31); + printf("%s\n", holder); + + printf("**%%d**%% d**%% d**, 42, 42, -42\n"); + snprintf(holder, sizeof holder, "**%d**% d**% d**\n", 42, 42, -42); + printf("**%d**% d**% d**\n", 42, 42, -42); + printf("%s\n", holder); + +/* other flags */ + + printf("/%%g/, 31.4\n"); + snprintf(holder, sizeof holder, "/%g/\n", 31.4); + printf("/%g/\n", 31.4); + printf("%s\n", holder); + + printf("/%%.6g/, 31.4\n"); + snprintf(holder, sizeof holder, "/%.6g/\n", 31.4); + printf("/%.6g/\n", 31.4); + printf("%s\n", holder); + + printf("/%%.1G/, 31.4\n"); + snprintf(holder, sizeof holder, "/%.1G/\n", 31.4); + printf("/%.1G/\n", 31.4); + printf("%s\n", holder); + + printf("abc%%n\n"); + printf("abc%n", &i); printf("%d\n", i); + snprintf(holder, sizeof holder, "abc%n", &i); + printf("%s", holder); printf("%d\n\n", i); + + printf("%%*.*s --> 10.10\n"); + snprintf(holder, sizeof holder, "%*.*s\n", 10, 10, BLURB); + printf("%*.*s\n", 10, 10, BLURB); + printf("%s\n", holder); + + printf("%%%%%%%%\n"); + snprintf(holder, sizeof holder, "%%%%\n"); + printf("%%%%\n"); + printf("%s\n", holder); + +#define BIG "Hello this is a too big string for the buffer" +/* printf("A buffer to small of 10, trying to put this:\n");*/ + printf("<%%>, %s\n", BIG); + i = snprintf(holder, 10, "%s\n", BIG); + printf("<%s>\n", BIG); + printf("<%s>\n", holder); +} +#endif diff --git a/neon/lib/snprintf.h b/neon/lib/snprintf.h new file mode 100644 index 000000000..511decabf --- /dev/null +++ b/neon/lib/snprintf.h @@ -0,0 +1,245 @@ +/* + Unix snprintf implementation. + Version 1.1 + + 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. + + Revision History: + + sitecopy changes: + added sys/types.h include to pick up size_t define. + renamed dtoa -> doubletoa to avoid dtoa conflict with cygwin. + + 1.1: + * added changes from Miles Bader + * corrected a bug with %f + * added support for %#g + * added more comments :-) + 1.0: + * supporting must ANSI syntaxic_sugars(see below) + 0.0: + * suppot %s %c %d + + it understands: + Integer: + %lu %lu %u + %hd %ld %d decimal + %ho %lo %o octal + %hx %lx %x %X hexa + Floating points: + %g %G %e %E %f double + Strings: + %s %c string + %% % + + Formating conversion flags: + - justify left + + Justify right or put a plus if number + # prefix 0x, 0X for hexa and 0 for octal + * precision/witdth is specify as an (int) in the arguments + ' ' leave a blank for number with no sign + l the later should be a long + h the later should be a short + +format: + snprintf(holder, sizeof_holder, format, ...) + +Return values: + (sizeof_holder - 1) + + + THANKS(for the patches and ideas): + Miles Bader + Cyrille Rustom + Jacek Slabocewiz + Mike Parker(mouse) + +Alain Magloire: alainm@rcsm.ee.mcgill.ca +*/ + +#if defined(HAVE_STDARG_H) && defined(__STDC__) && __STDC__ +/**** changed for sitecopy ****/ +#include <sys/types.h> +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +/* + * For the FLOATING POINT FORMAT : + * the challenge was finding a way to + * manipulate the Real numbers without having + * to resort to mathematical function(it + * would require to link with -lm) and not + * going down to the bit pattern(not portable) + * + * so a number, a real is: + + real = integral + fraction + + integral = ... + a(2)*10^2 + a(1)*10^1 + a(0)*10^0 + fraction = b(1)*10^-1 + b(2)*10^-2 + ... + + where: + 0 <= a(i) => 9 + 0 <= b(i) => 9 + + from then it was simple math + */ + +/* + * size of the buffer for the integral part + * and the fraction part + */ +#define MAX_INT 99 + 1 /* 1 for the null */ +#define MAX_FRACT 29 + 1 + +/* + * numtoa() uses PRIVATE buffers to store the results, + * So this function is not reentrant + */ +#define itoa(n) numtoa(n, 10, 0, (char **)0) +#define otoa(n) numtoa(n, 8, 0, (char **)0) +#define htoa(n) numtoa(n, 16, 0, (char **)0) +#define doubletoa(n, p, f) numtoa(n, 10, p, f) + +#define SWAP_INT(a,b) {int t; t = (a); (a) = (b); (b) = t;} + +/* this struct holds everything we need */ +struct DATA { + int length; + char *holder; + int counter; +#ifdef __STDC__ + const char *pf; +#else + char *pf; +#endif +/* FLAGS */ + int width, precision; + int justify; char pad; + int square, space, star_w, star_p, a_long ; +}; + +#define PRIVATE static +#define PUBLIC extern +/* signature of the functions */ +#ifdef __STDC__ +/* the floating point stuff */ + PRIVATE double pow_10(int); + PRIVATE int log_10(double); + PRIVATE double integral(double, double *); + PRIVATE char * numtoa(double, int, int, char **); + +/* for the format */ + PRIVATE void conv_flag(char *, struct DATA *); + PRIVATE void floating(struct DATA *, double); + PRIVATE void exponent(struct DATA *, double); + PRIVATE void decimal(struct DATA *, double); + PRIVATE void octal(struct DATA *, double); + PRIVATE void hexa(struct DATA *, double); + PRIVATE void strings(struct DATA *, char *); + +#else +/* the floating point stuff */ + PRIVATE double pow_10(); + PRIVATE int log_10(); + PRIVATE double integral(); + PRIVATE char * numtoa(); + +/* for the format */ + PRIVATE void conv_flag(); + PRIVATE void floating(); + PRIVATE void exponent(); + PRIVATE void decimal(); + PRIVATE void octal(); + PRIVATE void hexa(); + PRIVATE void strings(); +#endif + +/* those are defines specific to snprintf to hopefully + * make the code clearer :-) + */ +#define RIGHT 1 +#define LEFT 0 +#define NOT_FOUND -1 +#define FOUND 1 +#define MAX_FIELD 15 + +/* the conversion flags */ +#define isflag(c) ((c) == '#' || (c) == ' ' || \ + (c) == '*' || (c) == '+' || \ + (c) == '-' || (c) == '.' || \ + isdigit(c)) + +/* round off to the precision */ +#define ROUND(d, p) \ + (d < 0.) ? \ + d - pow_10(-(p)->precision) * 0.5 : \ + d + pow_10(-(p)->precision) * 0.5 + +/* set default precision */ +#define DEF_PREC(p) \ + if ((p)->precision == NOT_FOUND) \ + (p)->precision = 6 + +/* put a char */ +#define PUT_CHAR(c, p) \ + if ((p)->counter < (p)->length) { \ + *(p)->holder++ = (c); \ + (p)->counter++; \ + } + +#define PUT_PLUS(d, p) \ + if ((d) > 0. && (p)->justify == RIGHT) \ + PUT_CHAR('+', p) + +#define PUT_SPACE(d, p) \ + if ((p)->space == FOUND && (d) > 0.) \ + PUT_CHAR(' ', p) + +/* pad right */ +#define PAD_RIGHT(p) \ + if ((p)->width > 0 && (p)->justify != LEFT) \ + for (; (p)->width > 0; (p)->width--) \ + PUT_CHAR((p)->pad, p) + +/* pad left */ +#define PAD_LEFT(p) \ + if ((p)->width > 0 && (p)->justify == LEFT) \ + for (; (p)->width > 0; (p)->width--) \ + PUT_CHAR((p)->pad, p) + +/* if width and prec. in the args */ +#define STAR_ARGS(p) \ + if ((p)->star_w == FOUND) \ + (p)->width = va_arg(args, int); \ + if ((p)->star_p == FOUND) \ + (p)->precision = va_arg(args, int) + + +PUBLIC int +#if defined(HAVE_STDARG_H) && defined(__STDC__) && __STDC__ +snprintf(char *string, size_t length, const char * format, ...); +#else +snprintf(string, length, format, va_alist); +char *string; +size_t length; +char * format; +va_dcl +#endif + + + diff --git a/neon/lib/strcasecmp.c b/neon/lib/strcasecmp.c new file mode 100644 index 000000000..366bcf2e9 --- /dev/null +++ b/neon/lib/strcasecmp.c @@ -0,0 +1,49 @@ +/* Copyright (C) 1991, 1992 Free Software Foundation, Inc. + +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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> +#include <ctype.h> + +/* Compare S1 and S2, ignoring case, returning less than, equal to or + greater than zero if S1 is lexiographically less than, + equal to or greater than S2. */ +int +strcasecmp (s1, s2) + const char *s1; + const char *s2; +{ + register const unsigned char *p1 = (const unsigned char *) s1; + register const unsigned char *p2 = (const unsigned char *) s2; + unsigned char c1, c2; + + if (p1 == p2) + return 0; + + do + { + c1 = tolower (*p1++); + c2 = tolower (*p2++); + if (c1 == '\0') + break; + } + while (c1 == c2); + + return c1 - c2; +} diff --git a/neon/lib/yesno.c b/neon/lib/yesno.c new file mode 100644 index 000000000..8aaaf3d4b --- /dev/null +++ b/neon/lib/yesno.c @@ -0,0 +1,52 @@ +/* yesno.c -- read a yes/no response from stdin + Copyright (C) 1990, 1998 Free Software Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#include <ctype.h> +#if HAVE_STDLIB_H +# include <stdlib.h> +#endif +#include <stdio.h> + +/* Read one line from standard input + and return nonzero if that line begins with y or Y, + otherwise return 0. */ + +int rpmatch (); + +int +yesno () +{ + /* We make some assumptions here: + a) leading white space in the response are not vital + b) the first 128 characters of the answer are enough (the rest can + be ignored) + I cannot think for a situation where this is not ok. --drepper@gnu */ + char buf[128]; + int len = 0; + int c; + + while ((c = getchar ()) != EOF && c != '\n') + if ((len > 0 && len < 127) || (len == 0 && !isspace (c))) + buf[len++] = c; + buf[len] = '\0'; + + return rpmatch (buf) == 1; +} diff --git a/neon/ltconfig b/neon/ltconfig new file mode 100755 index 000000000..e3c5a9544 --- /dev/null +++ b/neon/ltconfig @@ -0,0 +1,2908 @@ +#! /bin/sh + +# ltconfig - Create a system-specific libtool. +# Copyright (C) 1996-1999 Free Software Foundation, Inc. +# Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996 +# +# This file 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# A lot of this script is taken from autoconf-2.10. + +# Check that we are running under the correct shell. +SHELL=${CONFIG_SHELL-/bin/sh} +echo=echo +if test "X$1" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift +elif test "X$1" = X--fallback-echo; then + # Avoid inline document here, it may be left over + : +elif test "X`($echo '\t') 2>/dev/null`" = 'X\t'; then + # Yippee, $echo works! + : +else + # Restart under the correct shell. + exec "$SHELL" "$0" --no-reexec ${1+"$@"} +fi + +if test "X$1" = X--fallback-echo; then + # used as fallback echo + shift + cat <<EOF +$* +EOF + exit 0 +fi + +# Find the correct PATH separator. Usually this is `:', but +# DJGPP uses `;' like DOS. +if test "X${PATH_SEPARATOR+set}" != "Xset"; then + UNAME=${UNAME-`uname 2>/dev/null`} + case X$UNAME in + *-DOS) PATH_SEPARATOR=';' ;; + *) PATH_SEPARATOR=':' ;; + esac +fi + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +if test "${CDPATH+set}" = set; then CDPATH=; export CDPATH; fi + +if test "X${echo_test_string+set}" != "Xset"; then + # find a string as large as possible, as long as the shell can cope with it + for cmd in 'sed 50q "$0"' 'sed 20q "$0"' 'sed 10q "$0"' 'sed 2q "$0"' 'echo test'; do + # expected sizes: less than 2Kb, 1Kb, 512 bytes, 16 bytes, ... + if (echo_test_string="`eval $cmd`") 2>/dev/null && + echo_test_string="`eval $cmd`" && + (test "X$echo_test_string" = "X$echo_test_string") 2>/dev/null; then + break + fi + done +fi + +if test "X`($echo '\t') 2>/dev/null`" != 'X\t' || + test "X`($echo "$echo_test_string") 2>/dev/null`" != X"$echo_test_string"; then + # The Solaris, AIX, and Digital Unix default echo programs unquote + # backslashes. This makes it impossible to quote backslashes using + # echo "$something" | sed 's/\\/\\\\/g' + # + # So, first we look for a working echo in the user's PATH. + + IFS="${IFS= }"; save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR}" + for dir in $PATH /usr/ucb; do + if (test -f $dir/echo || test -f $dir/echo$ac_exeext) && + test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' && + test "X`($dir/echo "$echo_test_string") 2>/dev/null`" = X"$echo_test_string"; then + echo="$dir/echo" + break + fi + done + IFS="$save_ifs" + + if test "X$echo" = Xecho; then + # We didn't find a better echo, so look for alternatives. + if test "X`(print -r '\t') 2>/dev/null`" = 'X\t' && + test "X`(print -r "$echo_test_string") 2>/dev/null`" = X"$echo_test_string"; then + # This shell has a builtin print -r that does the trick. + echo='print -r' + elif (test -f /bin/ksh || test -f /bin/ksh$ac_exeext) && + test "X$CONFIG_SHELL" != X/bin/ksh; then + # If we have ksh, try running ltconfig again with it. + ORIGINAL_CONFIG_SHELL="${CONFIG_SHELL-/bin/sh}" + export ORIGINAL_CONFIG_SHELL + CONFIG_SHELL=/bin/ksh + export CONFIG_SHELL + exec "$CONFIG_SHELL" "$0" --no-reexec ${1+"$@"} + else + # Try using printf. + echo='printf "%s\n"' + if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + test "X`($echo "$echo_test_string") 2>/dev/null`" = X"$echo_test_string"; then + # Cool, printf works + : + elif test "X`("$ORIGINAL_CONFIG_SHELL" "$0" --fallback-echo '\t') 2>/dev/null`" = 'X\t' && + test "X`("$ORIGINAL_CONFIG_SHELL" "$0" --fallback-echo "$echo_test_string") 2>/dev/null`" = X"$echo_test_string"; then + CONFIG_SHELL="$ORIGINAL_CONFIG_SHELL" + export CONFIG_SHELL + SHELL="$CONFIG_SHELL" + export SHELL + echo="$CONFIG_SHELL $0 --fallback-echo" + elif test "X`("$CONFIG_SHELL" "$0" --fallback-echo '\t') 2>/dev/null`" = 'X\t' && + test "X`("$CONFIG_SHELL" "$0" --fallback-echo "$echo_test_string") 2>/dev/null`" = X"$echo_test_string"; then + echo="$CONFIG_SHELL $0 --fallback-echo" + else + # maybe with a smaller string... + prev=: + + for cmd in 'echo test' 'sed 2q "$0"' 'sed 10q "$0"' 'sed 20q "$0"' 'sed 50q "$0"'; do + if (test "X$echo_test_string" = "X`eval $cmd`") 2>/dev/null; then + break + fi + prev="$cmd" + done + + if test "$prev" != 'sed 50q "$0"'; then + echo_test_string=`eval $prev` + export echo_test_string + exec "${ORIGINAL_CONFIG_SHELL}" "$0" ${1+"$@"} + else + # Oops. We lost completely, so just stick with echo. + echo=echo + fi + fi + fi + fi +fi + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +Xsed='sed -e s/^X//' +sed_quote_subst='s/\([\\"\\`$\\\\]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([\\"\\`\\\\]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# The name of this program. +progname=`$echo "X$0" | $Xsed -e 's%^.*/%%'` + +# Constants: +PROGRAM=ltconfig +PACKAGE=libtool +VERSION=1.3 +TIMESTAMP=" (1.385.2.117 1999/04/29 13:07:13)" +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.c 1>&5' +ac_link='${CC-cc} -o conftest $CFLAGS $CPPFLAGS $LDFLAGS conftest.c $LIBS 1>&5' +rm="rm -f" + +help="Try \`$progname --help' for more information." + +# Global variables: +default_ofile=libtool +can_build_shared=yes +enable_shared=yes +# All known linkers require a `.a' archive for static linking. +enable_static=yes +enable_fast_install=yes +enable_dlopen=unknown +enable_win32_dll=no +ltmain= +silent= +srcdir= +ac_config_guess= +ac_config_sub= +host= +nonopt= +ofile="$default_ofile" +verify_host=yes +with_gcc=no +with_gnu_ld=no +need_locks=yes +ac_ext=c +objext=o +libext=a +cache_file= + +old_AR="$AR" +old_CC="$CC" +old_CFLAGS="$CFLAGS" +old_CPPFLAGS="$CPPFLAGS" +old_LDFLAGS="$LDFLAGS" +old_LD="$LD" +old_LN_S="$LN_S" +old_LIBS="$LIBS" +old_NM="$NM" +old_RANLIB="$RANLIB" +old_DLLTOOL="$DLLTOOL" +old_OBJDUMP="$OBJDUMP" +old_AS="$AS" + +# Parse the command line options. +args= +prev= +for option +do + case "$option" in + -*=*) optarg=`echo "$option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) optarg= ;; + esac + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + eval "$prev=\$option" + prev= + continue + fi + + case "$option" in + --help) cat <<EOM +Usage: $progname [OPTION]... [HOST [LTMAIN]] + +Generate a system-specific libtool script. + + --debug enable verbose shell tracing + --disable-shared do not build shared libraries + --disable-static do not build static libraries + --disable-fast-install do not optimize for fast installation + --enable-dlopen enable dlopen support + --enable-win32-dll enable building dlls on win32 hosts + --help display this help and exit + --no-verify do not verify that HOST is a valid host type +-o, --output=FILE specify the output file [default=$default_ofile] + --quiet same as \`--silent' + --silent do not print informational messages + --srcdir=DIR find \`config.guess' in DIR + --version output version information and exit + --with-gcc assume that the GNU C compiler will be used + --with-gnu-ld assume that the C compiler uses the GNU linker + --disable-lock disable file locking + --cache-file=FILE configure cache file + +LTMAIN is the \`ltmain.sh' shell script fragment or \`ltmain.c' program +that provides basic libtool functionality. + +HOST is the canonical host system name [default=guessed]. +EOM + exit 0 + ;; + + --debug) + echo "$progname: enabling shell trace mode" + set -x + ;; + + --disable-shared) enable_shared=no ;; + + --disable-static) enable_static=no ;; + + --disable-fast-install) enable_fast_install=no ;; + + --enable-dlopen) enable_dlopen=yes ;; + + --enable-win32-dll) enable_win32_dll=yes ;; + + --quiet | --silent) silent=yes ;; + + --srcdir) prev=srcdir ;; + --srcdir=*) srcdir="$optarg" ;; + + --no-verify) verify_host=no ;; + + --output | -o) prev=ofile ;; + --output=*) ofile="$optarg" ;; + + --version) echo "$PROGRAM (GNU $PACKAGE) $VERSION$TIMESTAMP"; exit 0 ;; + + --with-gcc) with_gcc=yes ;; + --with-gnu-ld) with_gnu_ld=yes ;; + + --disable-lock) need_locks=no ;; + + --cache-file=*) cache_file="$optarg" ;; + + -*) + echo "$progname: unrecognized option \`$option'" 1>&2 + echo "$help" 1>&2 + exit 1 + ;; + + *) + if test -z "$ltmain"; then + ltmain="$option" + elif test -z "$host"; then +# This generates an unnecessary warning for sparc-sun-solaris4.1.3_U1 +# if test -n "`echo $option| sed 's/[-a-z0-9.]//g'`"; then +# echo "$progname: warning \`$option' is not a valid host type" 1>&2 +# fi + host="$option" + else + echo "$progname: too many arguments" 1>&2 + echo "$help" 1>&2 + exit 1 + fi ;; + esac +done + +if test -z "$ltmain"; then + echo "$progname: you must specify a LTMAIN file" 1>&2 + echo "$help" 1>&2 + exit 1 +fi + +if test ! -f "$ltmain"; then + echo "$progname: \`$ltmain' does not exist" 1>&2 + echo "$help" 1>&2 + exit 1 +fi + +# Quote any args containing shell metacharacters. +ltconfig_args= +for arg +do + case "$arg" in + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ltconfig_args="$ltconfig_args '$arg'" ;; + *) ltconfig_args="$ltconfig_args $arg" ;; + esac +done + +# A relevant subset of AC_INIT. + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 5 compiler messages saved in config.log +# 6 checking for... messages and results +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>>./config.log + +# NLS nuisances. +# Only set LANG and LC_ALL to C if already set. +# These must not be set unconditionally because not all systems understand +# e.g. LANG=C (notably SCO). +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LANG+set}" = set; then LANG=C; export LANG; fi + +if test -n "$cache_file" && test -r "$cache_file"; then + echo "loading cache $cache_file within ltconfig" + . $cache_file +fi + +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + +if test -z "$srcdir"; then + # Assume the source directory is the same one as the path to LTMAIN. + srcdir=`$echo "X$ltmain" | $Xsed -e 's%/[^/]*$%%'` + test "$srcdir" = "$ltmain" && srcdir=. +fi + +trap "$rm conftest*; exit 1" 1 2 15 +if test "$verify_host" = yes; then + # Check for config.guess and config.sub. + ac_aux_dir= + for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/config.guess; then + ac_aux_dir=$ac_dir + break + fi + done + if test -z "$ac_aux_dir"; then + echo "$progname: cannot find config.guess in $srcdir $srcdir/.. $srcdir/../.." 1>&2 + echo "$help" 1>&2 + exit 1 + fi + ac_config_guess=$ac_aux_dir/config.guess + ac_config_sub=$ac_aux_dir/config.sub + + # Make sure we can run config.sub. + if $SHELL $ac_config_sub sun4 >/dev/null 2>&1; then : + else + echo "$progname: cannot run $ac_config_sub" 1>&2 + echo "$help" 1>&2 + exit 1 + fi + + echo $ac_n "checking host system type""... $ac_c" 1>&6 + + host_alias=$host + case "$host_alias" in + "") + if host_alias=`$SHELL $ac_config_guess`; then : + else + echo "$progname: cannot guess host type; you must specify one" 1>&2 + echo "$help" 1>&2 + exit 1 + fi ;; + esac + host=`$SHELL $ac_config_sub $host_alias` + echo "$ac_t$host" 1>&6 + + # Make sure the host verified. + test -z "$host" && exit 1 + +elif test -z "$host"; then + echo "$progname: you must specify a host type if you use \`--no-verify'" 1>&2 + echo "$help" 1>&2 + exit 1 +else + host_alias=$host +fi + +# Transform linux* to *-*-linux-gnu*, to support old configure scripts. +case "$host_os" in +linux-gnu*) ;; +linux*) host=`echo $host | sed 's/^\(.*-.*-linux\)\(.*\)$/\1-gnu\2/'` +esac + +host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` + +case "$host_os" in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test "${COLLECT_NAMES+set}" != set; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR cru $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +# Set a sane default for `AR'. +test -z "$AR" && AR=ar + +# Set a sane default for `OBJDUMP'. +test -z "$OBJDUMP" && OBJDUMP=objdump + +# If RANLIB is not set, then run the test. +if test "${RANLIB+set}" != "set"; then + result=no + + echo $ac_n "checking for ranlib... $ac_c" 1>&6 + IFS="${IFS= }"; save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR}" + for dir in $PATH; do + test -z "$dir" && dir=. + if test -f $dir/ranlib || test -f $dir/ranlib$ac_exeext; then + RANLIB="ranlib" + result="ranlib" + break + fi + done + IFS="$save_ifs" + + echo "$ac_t$result" 1>&6 +fi + +if test -n "$RANLIB"; then + old_archive_cmds="$old_archive_cmds~\$RANLIB \$oldlib" + old_postinstall_cmds="\$RANLIB \$oldlib~$old_postinstall_cmds" +fi + +# Set sane defaults for `DLLTOOL', `OBJDUMP', and `AS', used on cygwin. +test -z "$DLLTOOL" && DLLTOOL=dlltool +test -z "$OBJDUMP" && OBJDUMP=objdump +test -z "$AS" && AS=as + +# Check to see if we are using GCC. +if test "$with_gcc" != yes || test -z "$CC"; then + # If CC is not set, then try to find GCC or a usable CC. + if test -z "$CC"; then + echo $ac_n "checking for gcc... $ac_c" 1>&6 + IFS="${IFS= }"; save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR}" + for dir in $PATH; do + test -z "$dir" && dir=. + if test -f $dir/gcc || test -f $dir/gcc$ac_exeext; then + CC="gcc" + break + fi + done + IFS="$save_ifs" + + if test -n "$CC"; then + echo "$ac_t$CC" 1>&6 + else + echo "$ac_t"no 1>&6 + fi + fi + + # Not "gcc", so try "cc", rejecting "/usr/ucb/cc". + if test -z "$CC"; then + echo $ac_n "checking for cc... $ac_c" 1>&6 + IFS="${IFS= }"; save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR}" + cc_rejected=no + for dir in $PATH; do + test -z "$dir" && dir=. + if test -f $dir/cc || test -f $dir/cc$ac_exeext; then + if test "$dir/cc" = "/usr/ucb/cc"; then + cc_rejected=yes + continue + fi + CC="cc" + break + fi + done + IFS="$save_ifs" + if test $cc_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $CC + shift + if test $# -gt 0; then + # We chose a different compiler from the bogus one. + # However, it has the same name, so the bogon will be chosen + # first if we set CC to just the name; use the full file name. + shift + set dummy "$dir/cc" "$@" + shift + CC="$@" + fi + fi + + if test -n "$CC"; then + echo "$ac_t$CC" 1>&6 + else + echo "$ac_t"no 1>&6 + fi + + if test -z "$CC"; then + echo "$progname: error: no acceptable cc found in \$PATH" 1>&2 + exit 1 + fi + fi + + # Now see if the compiler is really GCC. + with_gcc=no + echo $ac_n "checking whether we are using GNU C... $ac_c" 1>&6 + echo "$progname:579: checking whether we are using GNU C" >&5 + + $rm conftest.c + cat > conftest.c <<EOF +#ifdef __GNUC__ + yes; +#endif +EOF + if { ac_try='${CC-cc} -E conftest.c'; { (eval echo $progname:587: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then + with_gcc=yes + fi + $rm conftest.c + echo "$ac_t$with_gcc" 1>&6 +fi + +# Allow CC to be a program name with arguments. +set dummy $CC +compiler="$2" + +echo $ac_n "checking for object suffix... $ac_c" 1>&6 +$rm conftest* +echo 'int i = 1;' > conftest.c +echo "$progname:601: checking for object suffix" >& 5 +if { (eval echo $progname:602: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>conftest.err; }; then + # Append any warnings to the config.log. + cat conftest.err 1>&5 + + for ac_file in conftest.*; do + case $ac_file in + *.c) ;; + *) objext=`echo $ac_file | sed -e s/conftest.//` ;; + esac + done +else + cat conftest.err 1>&5 + echo "$progname: failed program was:" >&5 + cat conftest.c >&5 +fi +$rm conftest* +echo "$ac_t$objext" 1>&6 + +echo $ac_n "checking for $compiler option to produce PIC... $ac_c" 1>&6 +pic_flag= +special_shlib_compile_flags= +wl= +link_static_flag= +no_builtin_flag= + +if test "$with_gcc" = yes; then + wl='-Wl,' + link_static_flag='-static' + + case "$host_os" in + beos* | irix5* | irix6* | osf3* | osf4*) + # PIC is the default for these OSes. + ;; + aix*) + # Below there is a dirty hack to force normal static linking with -ldl + # The problem is because libdl dynamically linked with both libc and + # libC (AIX C++ library), which obviously doesn't included in libraries + # list by gcc. This cause undefined symbols with -static flags. + # This hack allows C programs to be linked with "-static -ldl", but + # we not sure about C++ programs. + link_static_flag="$link_static_flag ${wl}-lC" + ;; + cygwin* | mingw* | os2*) + # We can build DLLs from non-PIC. + ;; + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + pic_flag='-m68020 -resident32 -malways-restore-a4' + ;; + *) + pic_flag='-fPIC' + ;; + esac +else + # PORTME Check for PIC flags for the system compiler. + case "$host_os" in + aix3* | aix4*) + # All AIX code is PIC. + link_static_flag='-bnso -bI:/lib/syscalls.exp' + ;; + + hpux9* | hpux10* | hpux11*) + # Is there a better link_static_flag that works with the bundled CC? + wl='-Wl,' + link_static_flag="${wl}-a ${wl}archive" + pic_flag='+Z' + ;; + + irix5* | irix6*) + wl='-Wl,' + link_static_flag='-non_shared' + # PIC (with -KPIC) is the default. + ;; + + cygwin* | mingw* | os2*) + # We can build DLLs from non-PIC. + ;; + + osf3* | osf4*) + # All OSF/1 code is PIC. + wl='-Wl,' + link_static_flag='-non_shared' + ;; + + sco3.2v5*) + pic_flag='-Kpic' + link_static_flag='-dn' + special_shlib_compile_flags='-belf' + ;; + + solaris*) + pic_flag='-KPIC' + link_static_flag='-Bstatic' + wl='-Wl,' + ;; + + sunos4*) + pic_flag='-PIC' + link_static_flag='-Bstatic' + wl='-Qoption ld ' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + pic_flag='-KPIC' + link_static_flag='-Bstatic' + wl='-Wl,' + ;; + + uts4*) + pic_flag='-pic' + link_static_flag='-Bstatic' + ;; + + *) + can_build_shared=no + ;; + esac +fi + +if test -n "$pic_flag"; then + echo "$ac_t$pic_flag" 1>&6 + + # Check to make sure the pic_flag actually works. + echo $ac_n "checking if $compiler PIC flag $pic_flag works... $ac_c" 1>&6 + $rm conftest* + echo "int some_variable = 0;" > conftest.c + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $pic_flag -DPIC" + echo "$progname:732: checking if $compiler PIC flag $pic_flag works" >&5 + if { (eval echo $progname:733: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>conftest.err; } && test -s conftest.$objext; then + # Append any warnings to the config.log. + cat conftest.err 1>&5 + + case "$host_os" in + hpux9* | hpux10* | hpux11*) + # On HP-UX, both CC and GCC only warn that PIC is supported... then they + # create non-PIC objects. So, if there were any warnings, we assume that + # PIC is not supported. + if test -s conftest.err; then + echo "$ac_t"no 1>&6 + can_build_shared=no + pic_flag= + else + echo "$ac_t"yes 1>&6 + pic_flag=" $pic_flag" + fi + ;; + *) + echo "$ac_t"yes 1>&6 + pic_flag=" $pic_flag" + ;; + esac + else + # Append any errors to the config.log. + cat conftest.err 1>&5 + can_build_shared=no + pic_flag= + echo "$ac_t"no 1>&6 + fi + CFLAGS="$save_CFLAGS" + $rm conftest* +else + echo "$ac_t"none 1>&6 +fi + +# Check to see if options -o and -c are simultaneously supported by compiler +echo $ac_n "checking if $compiler supports -c -o file.o... $ac_c" 1>&6 +$rm -r conftest 2>/dev/null +mkdir conftest +cd conftest +$rm conftest* +echo "int some_variable = 0;" > conftest.c +mkdir out +# According to Tom Tromey, Ian Lance Taylor reported there are C compilers +# that will create temporary files in the current directory regardless of +# the output directory. Thus, making CWD read-only will cause this test +# to fail, enabling locking or at least warning the user not to do parallel +# builds. +chmod -w . +save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -o out/conftest2.o" +echo "$progname:785: checking if $compiler supports -c -o file.o" >&5 +if { (eval echo $progname:786: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>out/conftest.err; } && test -s out/conftest2.o; then + + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s out/conftest.err; then + echo "$ac_t"no 1>&6 + compiler_c_o=no + else + echo "$ac_t"yes 1>&6 + compiler_c_o=yes + fi +else + # Append any errors to the config.log. + cat out/conftest.err 1>&5 + compiler_c_o=no + echo "$ac_t"no 1>&6 +fi +CFLAGS="$save_CFLAGS" +chmod u+w . +$rm conftest* out/* +rmdir out +cd .. +rmdir conftest +$rm -r conftest 2>/dev/null + +if test x"$compiler_c_o" = x"yes"; then + # Check to see if we can write to a .lo + echo $ac_n "checking if $compiler supports -c -o file.lo... $ac_c" 1>&6 + $rm conftest* + echo "int some_variable = 0;" > conftest.c + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -c -o conftest.lo" + echo "$progname:818: checking if $compiler supports -c -o file.lo" >&5 +if { (eval echo $progname:819: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>conftest.err; } && test -s conftest.lo; then + + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + echo "$ac_t"no 1>&6 + compiler_o_lo=no + else + echo "$ac_t"yes 1>&6 + compiler_o_lo=yes + fi + else + # Append any errors to the config.log. + cat conftest.err 1>&5 + compiler_o_lo=no + echo "$ac_t"no 1>&6 + fi + CFLAGS="$save_CFLAGS" + $rm conftest* +else + compiler_o_lo=no +fi + +# Check to see if we can do hard links to lock some files if needed +hard_links="nottested" +if test "$compiler_c_o" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + echo $ac_n "checking if we can lock with hard links... $ac_c" 1>&6 + hard_links=yes + $rm conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + echo "$ac_t$hard_links" 1>&6 + $rm conftest* + if test "$hard_links" = no; then + echo "*** WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2 + need_locks=warn + fi +else + need_locks=no +fi + +if test "$with_gcc" = yes; then + # Check to see if options -fno-rtti -fno-exceptions are supported by compiler + echo $ac_n "checking if $compiler supports -fno-rtti -fno-exceptions ... $ac_c" 1>&6 + $rm conftest* + echo "int some_variable = 0;" > conftest.c + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -fno-rtti -fno-exceptions -c conftest.c" + echo "$progname:870: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 + if { (eval echo $progname:871: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>conftest.err; } && test -s conftest.o; then + + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + echo "$ac_t"no 1>&6 + compiler_rtti_exceptions=no + else + echo "$ac_t"yes 1>&6 + compiler_rtti_exceptions=yes + fi + else + # Append any errors to the config.log. + cat conftest.err 1>&5 + compiler_rtti_exceptions=no + echo "$ac_t"no 1>&6 + fi + CFLAGS="$save_CFLAGS" + $rm conftest* + + if test "$compiler_rtti_exceptions" = "yes"; then + no_builtin_flag=' -fno-builtin -fno-rtti -fno-exceptions' + else + no_builtin_flag=' -fno-builtin' + fi + +fi + +# Check for any special shared library compilation flags. +if test -n "$special_shlib_compile_flags"; then + echo "$progname: warning: \`$CC' requires \`$special_shlib_compile_flags' to build shared libraries" 1>&2 + if echo "$old_CC $old_CFLAGS " | egrep -e "[ ]$special_shlib_compile_flags[ ]" >/dev/null; then : + else + echo "$progname: add \`$special_shlib_compile_flags' to the CC or CFLAGS env variable and reconfigure" 1>&2 + can_build_shared=no + fi +fi + +echo $ac_n "checking if $compiler static flag $link_static_flag works... $ac_c" 1>&6 +$rm conftest* +echo 'main(){return(0);}' > conftest.c +save_LDFLAGS="$LDFLAGS" +LDFLAGS="$LDFLAGS $link_static_flag" +echo "$progname:914: checking if $compiler static flag $link_static_flag works" >&5 +if { (eval echo $progname:915: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + echo "$ac_t$link_static_flag" 1>&6 +else + echo "$ac_t"none 1>&6 + link_static_flag= +fi +LDFLAGS="$save_LDFLAGS" +$rm conftest* + +if test -z "$LN_S"; then + # Check to see if we can use ln -s, or we need hard links. + echo $ac_n "checking whether ln -s works... $ac_c" 1>&6 + $rm conftest.dat + if ln -s X conftest.dat 2>/dev/null; then + $rm conftest.dat + LN_S="ln -s" + else + LN_S=ln + fi + if test "$LN_S" = "ln -s"; then + echo "$ac_t"yes 1>&6 + else + echo "$ac_t"no 1>&6 + fi +fi + +# Make sure LD is an absolute path. +if test -z "$LD"; then + ac_prog=ld + if test "$with_gcc" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + echo $ac_n "checking for ld used by GCC... $ac_c" 1>&6 + echo "$progname:947: checking for ld used by GCC" >&5 + ac_prog=`($CC -print-prog-name=ld) 2>&5` + case "$ac_prog" in + # Accept absolute paths. + [\\/]* | [A-Za-z]:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the path of ld + ac_prog=`echo $ac_prog| sed 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we are not using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac + elif test "$with_gnu_ld" = yes; then + echo $ac_n "checking for GNU ld... $ac_c" 1>&6 + echo "$progname:971: checking for GNU ld" >&5 + else + echo $ac_n "checking for non-GNU ld""... $ac_c" 1>&6 + echo "$progname:974: checking for non-GNU ld" >&5 + fi + + if test -z "$LD"; then + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR}" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some GNU ld's only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + if "$LD" -v 2>&1 < /dev/null | egrep '(GNU|with BFD)' > /dev/null; then + test "$with_gnu_ld" != no && break + else + test "$with_gnu_ld" != yes && break + fi + fi + done + IFS="$ac_save_ifs" + fi + + if test -n "$LD"; then + echo "$ac_t$LD" 1>&6 + else + echo "$ac_t"no 1>&6 + fi + + if test -z "$LD"; then + echo "$progname: error: no acceptable ld found in \$PATH" 1>&2 + exit 1 + fi +fi + +# Check to see if it really is or is not GNU ld. +echo $ac_n "checking if the linker ($LD) is GNU ld... $ac_c" 1>&6 +# I'd rather use --version here, but apparently some GNU ld's only accept -v. +if $LD -v 2>&1 </dev/null | egrep '(GNU|with BFD)' 1>&5; then + with_gnu_ld=yes +else + with_gnu_ld=no +fi +echo "$ac_t$with_gnu_ld" 1>&6 + +# See if the linker supports building shared libraries. +echo $ac_n "checking whether the linker ($LD) supports shared libraries... $ac_c" 1>&6 + +allow_undefined_flag= +no_undefined_flag= +need_lib_prefix=unknown +need_version=unknown +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +archive_cmds= +archive_expsym_cmds= +old_archive_from_new_cmds= +export_dynamic_flag_spec= +whole_archive_flag_spec= +thread_safe_flag_spec= +hardcode_libdir_flag_spec= +hardcode_libdir_separator= +hardcode_direct=no +hardcode_minus_L=no +hardcode_shlibpath_var=unsupported +runpath_var= +always_export_symbols=no +export_symbols_cmds='$NM $libobjs | $global_symbol_pipe | sed '\''s/.* //'\'' | sort | uniq > $export_symbols' +# include_expsyms should be a list of space-separated symbols to be *always* +# included in the symbol list +include_expsyms= +# exclude_expsyms can be an egrep regular expression of symbols to exclude +# it will be wrapped by ` (' and `)$', so one must not match beginning or +# end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', +# as well as any symbol that contains `d'. +exclude_expsyms="_GLOBAL_OFFSET_TABLE_" +# Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out +# platforms (ab)use it in PIC code, but their linkers get confused if +# the symbol is explicitly referenced. Since portable code cannot +# rely on this symbol name, it's probably fine to never include it in +# preloaded symbol tables. + +case "$host_os" in +cygwin* | mingw*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$with_gcc" != yes; then + with_gnu_ld=no + fi + ;; + +esac + +ld_shlibs=yes +if test "$with_gnu_ld" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # See if GNU ld supports shared libraries. + case "$host_os" in + aix3* | aix4*) + # On AIX, the GNU linker is very broken + ld_shlibs=no + cat <<EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.9.1, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to modify your PATH +*** so that a non-GNU linker is found, and then restart. + +EOF + ;; + + amigaos*) + archive_cmds='$rm $objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $objdir/a2ixlibrary.data~$AR cru $lib $libobjs~$RANLIB $lib~(cd $objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + + # Samuel A. Falvo II <kc5tja@dolphin.openprojects.net> reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can use + # them. + ld_shlibs=no + ;; + + beos*) + if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag=unsupported + # Joseph Beckenbach <jrb3@best.com> says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds='$CC -nostart $libobjs $deplibs $linkopts ${wl}-soname $wl$soname -o $lib' + else + ld_shlibs=no + fi + ;; + + cygwin* | mingw*) + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + allow_undefined_flag=unsupported + always_export_symbols=yes + + # Extract the symbol export list from an `--export-all' def file, + # then regenerate the def file from the symbol export list, so that + # the compiled dll only exports the symbol export list. + export_symbols_cmds='rm -f $objdir/$soname-ltdll.c~ + sed -e "/^# \/\* ltdll\.c starts here \*\//,/^# \/\* ltdll.c ends here \*\// { s/^# //; p; }" -e d < $0 > $objdir/$soname-ltdll.c~ + (cd $objdir && $CC -c $soname-ltdll.c)~ + $DLLTOOL --export-all --exclude-symbols DllMain@12,_cygwin_dll_entry@12,_cygwin_noncygwin_dll_entry@12 --output-def $objdir/$soname-def $objdir/$soname-ltdll.$objext $libobjs~ + sed -e "1,/EXPORTS/d" -e "s/ @ [0-9]* ; *//" < $objdir/$soname-def > $export_symbols' + + archive_expsym_cmds='echo EXPORTS > $objdir/$soname-def~ + _lt_hint=1; + for symbol in `cat $export_symbols`; do + echo " \$symbol @ \$_lt_hint ; " >> $objdir/$soname-def; + _lt_hint=`expr 1 + \$_lt_hint`; + done~ + $CC -Wl,--base-file,$objdir/$soname-base -Wl,--dll -nostartfiles -Wl,-e,__cygwin_dll_entry@12 -o $lib $objdir/$soname-ltdll.$objext $libobjs $deplibs $linkopts~ + $DLLTOOL --as=$AS --dllname $soname --exclude-symbols DllMain@12,_cygwin_dll_entry@12,_cygwin_noncygwin_dll_entry@12 --def $objdir/$soname-def --base-file $objdir/$soname-base --output-exp $objdir/$soname-exp~ + $CC -Wl,--base-file,$objdir/$soname-base $objdir/$soname-exp -Wl,--dll -nostartfiles -Wl,-e,__cygwin_dll_entry@12 -o $lib $objdir/$soname-ltdll.$objext $libobjs $deplibs $linkopts~ + $DLLTOOL --as=$AS --dllname $soname --exclude-symbols DllMain@12,_cygwin_dll_entry@12,_cygwin_noncygwin_dll_entry@12 --def $objdir/$soname-def --base-file $objdir/$soname-base --output-exp $objdir/$soname-exp~ + $CC $objdir/$soname-exp -Wl,--dll -nostartfiles -Wl,-e,__cygwin_dll_entry@12 -o $lib $objdir/$soname-ltdll.$objext $libobjs $deplibs $linkopts' + + old_archive_from_new_cmds='$DLLTOOL --as=$AS --dllname $soname --def $objdir/$soname-def --output-lib $objdir/$libname.a' + ;; + + netbsd*) + if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $linkopts ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $linkopts ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + archive_cmds='$LD -Bshareable $libobjs $deplibs $linkopts -o $lib' + # can we support soname and/or expsyms with a.out? -oliva + fi + ;; + + sunos4*) + archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linkopts' + wlarc= + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + *) + if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $linkopts ${wl}-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $linkopts ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + + if test "$ld_shlibs" = yes; then + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec='${wl}--rpath ${wl}$libdir' + export_dynamic_flag_spec='${wl}--export-dynamic' + whole_archive_flag_spec="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + fi +else + # PORTME fill in a description of your system's linker (not GNU ld) + case "$host_os" in + aix3*) + allow_undefined_flag=unsupported + always_export_symbols=yes + archive_expsym_cmds='$LD -o $objdir/$soname $libobjs $deplibs $linkopts -bE:$export_symbols -T512 -H512 -bM:SRE~$AR cru $lib $objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test "$with_gcc" = yes && test -z "$link_static_flag"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + + aix4*) + hardcode_libdir_flag_spec='${wl}-b ${wl}nolibpath ${wl}-b ${wl}libpath:$libdir:/usr/lib:/lib' + hardcode_libdir_separator=':' + if test "$with_gcc" = yes; then + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + hardcode_direct=yes + else + # We have old collect2 + hardcode_direct=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + shared_flag='-shared' + else + shared_flag='${wl}-bM:SRE' + hardcode_direct=yes + fi + allow_undefined_flag=' ${wl}-berok' + archive_cmds="\$CC $shared_flag"' -o $objdir/$soname $libobjs $deplibs $linkopts ${wl}-bexpall ${wl}-bnoentry${allow_undefined_flag}' + archive_expsym_cmds="\$CC $shared_flag"' -o $objdir/$soname $libobjs $deplibs $linkopts ${wl}-bE:$export_symbols ${wl}-bnoentry${allow_undefined_flag}' + case "$host_os" in aix4.[01]|aix4.[01].*) + # According to Greg Wooledge, -bexpall is only supported from AIX 4.2 on + always_export_symbols=yes ;; + esac + ;; + + amigaos*) + archive_cmds='$rm $objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $objdir/a2ixlibrary.data~$AR cru $lib $libobjs~$RANLIB $lib~(cd $objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + # see comment about different semantics on the GNU ld section + ld_shlibs=no + ;; + + cygwin* | mingw*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $lib $libobjs $linkopts `echo "$deplibs" | sed -e '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_from_new_cmds='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds='lib /OUT:$oldlib$oldobjs' + fix_srcfile_path='`cygpath -w $srcfile`' + ;; + + freebsd1*) + ld_shlibs=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linkopts /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linkopts' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd*) + archive_cmds='$CC -shared -o $lib $libobjs $deplibs $linkopts' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + hpux9* | hpux10* | hpux11*) + case "$host_os" in + hpux9*) archive_cmds='$rm $objdir/$soname~$LD -b +b $install_libdir -o $objdir/$soname $libobjs $deplibs $linkopts~test $objdir/$soname = $lib || mv $objdir/$soname $lib' ;; + *) archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linkopts' ;; + esac + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + hardcode_minus_L=yes # Not in the search PATH, but as the default + # location of the library. + export_dynamic_flag_spec='${wl}-E' + ;; + + irix5* | irix6*) + if test "$with_gcc" = yes; then + archive_cmds='$CC -shared $libobjs $deplibs $linkopts ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${objdir}/so_locations -o $lib' + else + archive_cmds='$LD -shared $libobjs $deplibs $linkopts -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib' + fi + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linkopts' # a.out + else + archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linkopts' # ELF + fi + hardcode_libdir_flag_spec='${wl}-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + openbsd*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linkopts' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + archive_cmds='$echo "LIBRARY $libname INITINSTANCE" > $objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $objdir/$libname.def~$echo DATA >> $objdir/$libname.def~$echo " SINGLE NONSHARED" >> $objdir/$libname.def~$echo EXPORTS >> $objdir/$libname.def~emxexp $libobjs >> $objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $linkopts $objdir/$libname.def' + old_archive_from_new_cmds='emximp -o $objdir/$libname.a $objdir/$libname.def' + ;; + + osf3* | osf4*) + if test "$with_gcc" = yes; then + allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*' + archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $linkopts ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${objdir}/so_locations -o $lib' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linkopts -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib' + fi + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + + sco3.2v5*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linkopts' + hardcode_shlibpath_var=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ;; + + solaris*) + no_undefined_flag=' -z text' + # $CC -shared without GNU ld will not create a library from C++ + # object files and a static libstdc++, better avoid it by now + archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linkopts' + archive_expsym_cmds='$echo "{ global:" > $lib.exp~cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linkopts~$rm $lib.exp' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_shlibpath_var=no + case "$host_os" in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) # Supported since Solaris 2.6 (maybe 2.5.1?) + whole_archive_flag_spec='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' ;; + esac + ;; + + sunos4*) + archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linkopts' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + sysv4) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linkopts' + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var=no + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + + sysv4.3*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linkopts' + hardcode_shlibpath_var=no + export_dynamic_flag_spec='-Bexport' + ;; + + uts4*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linkopts' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + dgux*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linkopts' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + *) + ld_shlibs=no + ;; + esac +fi +echo "$ac_t$ld_shlibs" 1>&6 +test "$ld_shlibs" = no && can_build_shared=no + +if test -z "$NM"; then + echo $ac_n "checking for BSD-compatible nm... $ac_c" 1>&6 + case "$NM" in + [\\/]* | [A-Za-z]:[\\/]*) ;; # Let the user override the test with a path. + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR}" + for ac_dir in $PATH /usr/ucb /usr/ccs/bin /bin; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/nm || test -f $ac_dir/nm$ac_exeext; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the `sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + if ($ac_dir/nm -B /dev/null 2>&1 | sed '1q'; exit 0) | egrep /dev/null >/dev/null; then + NM="$ac_dir/nm -B" + break + elif ($ac_dir/nm -p /dev/null 2>&1 | sed '1q'; exit 0) | egrep /dev/null >/dev/null; then + NM="$ac_dir/nm -p" + break + else + NM=${NM="$ac_dir/nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + fi + fi + done + IFS="$ac_save_ifs" + test -z "$NM" && NM=nm + ;; + esac + echo "$ac_t$NM" 1>&6 +fi + +# Check for command to grab the raw symbol name followed by C symbol from nm. +echo $ac_n "checking command to parse $NM output... $ac_c" 1>&6 + +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[BCDEGRST]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([_A-Za-z][_A-Za-z0-9]*\)' + +# Transform the above into a raw symbol and a C symbol. +symxfrm='\1 \2\3 \3' + +# Transform an extracted symbol line into a proper C declaration +global_symbol_to_cdecl="sed -n -e 's/^. .* \(.*\)$/extern char \1;/p'" + +# Define system-specific variables. +case "$host_os" in +aix*) + symcode='[BCDT]' + ;; +cygwin* | mingw*) + symcode='[ABCDGISTW]' + ;; +hpux*) # Its linker distinguishes data from code symbols + global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern char \1();/p' -e 's/^. .* \(.*\)$/extern char \1;/p'" + ;; +irix*) + symcode='[BCDEGRST]' + ;; +solaris*) + symcode='[BDT]' + ;; +sysv4) + symcode='[DFNSTU]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +if $NM -V 2>&1 | egrep '(GNU|with BFD)' > /dev/null; then + symcode='[ABCDGISTW]' +fi + +# Try without a prefix undercore, then with it. +for ac_symprfx in "" "_"; do + + # Write the raw and C identifiers. + global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode\)[ ][ ]*\($ac_symprfx\)$sympat$/$symxfrm/p'" + + # Check to see that the pipe works correctly. + pipe_works=no + $rm conftest* + cat > conftest.c <<EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(){} +#ifdef __cplusplus +} +#endif +main(){nm_test_var='a';nm_test_func();return(0);} +EOF + + echo "$progname:1507: checking if global_symbol_pipe works" >&5 + if { (eval echo $progname:1508: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; } && test -s conftest.$objext; then + # Now try to grab the symbols. + nlist=conftest.nm + if { echo "$progname:1511: eval \"$NM conftest.$objext | $global_symbol_pipe > $nlist\"" >&5; eval "$NM conftest.$objext | $global_symbol_pipe > $nlist 2>&5"; } && test -s "$nlist"; then + + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if egrep ' nm_test_var$' "$nlist" >/dev/null; then + if egrep ' nm_test_func$' "$nlist" >/dev/null; then + cat <<EOF > conftest.c +#ifdef __cplusplus +extern "C" { +#endif + +EOF + # Now generate the symbol file. + eval "$global_symbol_to_cdecl"' < "$nlist" >> conftest.c' + + cat <<EOF >> conftest.c +#if defined (__STDC__) && __STDC__ +# define lt_ptr_t void * +#else +# define lt_ptr_t char * +# define const +#endif + +/* The mapping between symbol names and symbols. */ +const struct { + const char *name; + lt_ptr_t address; +} +lt_preloaded_symbols[] = +{ +EOF + sed 's/^. \(.*\) \(.*\)$/ {"\2", (lt_ptr_t) \&\2},/' < "$nlist" >> conftest.c + cat <<\EOF >> conftest.c + {0, (lt_ptr_t) 0} +}; + +#ifdef __cplusplus +} +#endif +EOF + # Now try linking the two files. + mv conftest.$objext conftstm.$objext + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="conftstm.$objext" + CFLAGS="$CFLAGS$no_builtin_flag" + if { (eval echo $progname:1563: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + pipe_works=yes + else + echo "$progname: failed program was:" >&5 + cat conftest.c >&5 + fi + LIBS="$save_LIBS" + else + echo "cannot find nm_test_func in $nlist" >&5 + fi + else + echo "cannot find nm_test_var in $nlist" >&5 + fi + else + echo "cannot run $global_symbol_pipe" >&5 + fi + else + echo "$progname: failed program was:" >&5 + cat conftest.c >&5 + fi + $rm conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test "$pipe_works" = yes; then + break + else + global_symbol_pipe= + fi +done +if test "$pipe_works" = yes; then + echo "${ac_t}ok" 1>&6 +else + echo "${ac_t}failed" 1>&6 +fi + +if test -z "$global_symbol_pipe"; then + global_symbol_to_cdecl= +fi + +# Check hardcoding attributes. +echo $ac_n "checking how to hardcode library paths into programs... $ac_c" 1>&6 +hardcode_action= +if test -n "$hardcode_libdir_flag_spec" || \ + test -n "$runpath_var"; then + + # We can hardcode non-existant directories. + if test "$hardcode_direct" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$hardcode_shlibpath_var" != no && + test "$hardcode_minus_L" != no; then + # Linking always hardcodes the temporary library directory. + hardcode_action=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action=unsupported +fi +echo "$ac_t$hardcode_action" 1>&6 + + +reload_flag= +reload_cmds='$LD$reload_flag -o $output$reload_objs' +echo $ac_n "checking for $LD option to reload object files... $ac_c" 1>&6 +# PORTME Some linkers may need a different reload flag. +reload_flag='-r' +echo "$ac_t$reload_flag" 1>&6 +test -n "$reload_flag" && reload_flag=" $reload_flag" + +# PORTME Fill in your ld.so characteristics +library_names_spec= +libname_spec='lib$name' +soname_spec= +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +file_magic_cmd= +file_magic_test_file= +deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# `unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [regex]' -- check by looking for files in library path +# which responds to the $file_magic_cmd with a given egrep regex. +# If you have `file' or equivalent on your system and you're not sure +# whether `pass_all' will *always* work, you probably want this one. +echo $ac_n "checking dynamic linker characteristics... $ac_c" 1>&6 +case "$host_os" in +aix3*) + version_type=linux + library_names_spec='${libname}${release}.so$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}.so$major' + ;; + +aix4*) + version_type=linux + # AIX has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + # We preserve .a as extension for shared libraries though AIX4.2 + # and later linker supports .so + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.a' + shlibpath_var=LIBPATH + deplibs_check_method=pass_all + ;; + +amigaos*) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "(cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a)"; (cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a) || exit 1; done' + ;; + +beos*) + library_names_spec='${libname}.so' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + lt_cv_dlopen="load_add_on" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + +bsdi4*) + version_type=linux + library_names_spec='${libname}.so$major ${libname}.so' + soname_spec='${libname}.so' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + deplibs_check_method='file_magic ELF 32-bit LSB shared object' + file_magic_cmd=/usr/bin/file + file_magic_test_file=/shlib/libc.so + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw*) + version_type=windows + if test "$with_gcc" = yes; then + library_names_spec='${libname}`echo ${release} | sed -e 's/[.]/-/g'`${versuffix}.dll $libname.a' + else + library_names_spec='${libname}`echo ${release} | sed -e 's/[.]/-/g'`${versuffix}.dll $libname.lib' + fi + dynamic_linker='Win32 ld.exe' + deplibs_check_method='file_magic file format pei*-i386(.*architecture: i386)?' + file_magic_cmd='${OBJDUMP} -f' + need_lib_prefix=no + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + lt_cv_dlopen="LoadLibrary" + lt_cv_dlopen_libs= + ;; + +freebsd1*) + dynamic_linker=no + ;; + +freebsd*) + objformat=`test -x /usr/bin/objformat && /usr/bin/objformat || echo aout` + version_type=freebsd-$objformat + case "$version_type" in + freebsd-elf*) + deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB shared object' + file_magic_cmd=/usr/bin/file + file_magic_test_file=`echo /usr/lib/libc.so*` + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so $libname.so' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + deplibs_check_method=unknown + library_names_spec='${libname}${release}.so$versuffix $libname.so$versuffix' + need_version=yes + ;; + esac + finish_cmds='PATH="\$PATH:/sbin" OBJFORMAT="'"$objformat"'" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + ;; + +gnu*) + version_type=linux + library_names_spec='${libname}${release}.so$versuffix ${libname}.so' + shlibpath_var=LD_LIBRARY_PATH + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + dynamic_linker="$host_os dld.sl" + version_type=sunos + need_lib_prefix=no + need_version=no + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}.sl$versuffix ${libname}${release}.sl$major $libname.sl' + soname_spec='${libname}${release}.sl$major' + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +irix5* | irix6*) + version_type=irix + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}.so.$major' + library_names_spec='${libname}${release}.so.$versuffix ${libname}${release}.so.$major ${libname}${release}.so $libname.so' + case "$host_os" in + irix5*) + libsuff= shlibsuff= + # this will be overridden with pass_all, but let us keep it just in case + deplibs_check_method="file_magic ELF 32-bit MSB dynamic lib MIPS - version 1" + ;; + *) + case "$LD" in # libtool.m4 will add one of these switches to LD + *-32|*"-32 ") libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 ") libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 ") libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + # this will be overridden with pass_all, but let us keep it just in case + deplibs_check_method="file_magic ELF ${libmagic} MSB mips-[1234] dynamic lib MIPS - version 1" + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + file_magic_cmd=/usr/bin/file + file_magic_test_file=`echo /lib${libsuff}/libc.so*` + deplibs_check_method='pass_all' + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux-gnuoldld* | linux-gnuaout* | linux-gnucoff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux-gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so' + soname_spec='${libname}${release}.so$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' + file_magic_cmd=/usr/bin/file + file_magic_test_file=`echo /lib/libc.so* /lib/libc-*.so` + + if test -f /lib/ld.so.1; then + dynamic_linker='GNU ld.so' + else + # Only the GNU ld.so supports shared libraries on MkLinux. + case "$host_cpu" in + powerpc*) dynamic_linker=no ;; + *) dynamic_linker='Linux ld.so' ;; + esac + fi + ;; + +netbsd*) + version_type=sunos + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major ${libname}${release}.so ${libname}.so' + soname_spec='${libname}${release}.so$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + ;; + +openbsd*) + version_type=sunos + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + need_version=no + fi + library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + ;; + +os2*) + libname_spec='$name' + need_lib_prefix=no + library_names_spec='$libname.dll $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4*) + version_type=osf + need_version=no + soname_spec='${libname}${release}.so' + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so $libname.so' + shlibpath_var=LD_LIBRARY_PATH + # this will be overridden with pass_all, but let us keep it just in case + deplibs_check_method='file_magic COFF format alpha shared library' + file_magic_cmd=/usr/bin/file + file_magic_test_file=/shlib/libc.so + deplibs_check_method='pass_all' + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +sco3.2v5*) + version_type=osf + soname_spec='${libname}${release}.so$major' + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so' + shlibpath_var=LD_LIBRARY_PATH + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so' + soname_spec='${libname}${release}.so$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + deplibs_check_method="file_magic ELF [0-9][0-9]-bit [LM]SB dynamic lib" + file_magic_cmd=/usr/bin/file + file_magic_test_file=/lib/libc.so + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + version_type=linux + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so' + soname_spec='${libname}${release}.so$major' + shlibpath_var=LD_LIBRARY_PATH + case "$host_vendor" in + ncr) + deplibs_check_method='pass_all' + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' + file_magic_cmd=/usr/bin/file + file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + esac + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so' + soname_spec='${libname}${release}.so$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so' + soname_spec='${libname}${release}.so$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +echo "$ac_t$dynamic_linker" 1>&6 +test "$dynamic_linker" = no && can_build_shared=no + +# Report the final consequences. +echo "checking if libtool supports shared libraries... $can_build_shared" 1>&6 + +# Only try to build win32 dlls if AC_LIBTOOL_WIN32_DLL was used in +# configure.in, otherwise build static only libraries. +case "$host_os" in +cygwin* | mingw* | os2*) + if test x$can_build_shared = xyes; then + test x$enable_win32_dll = xno && can_build_shared=no + echo "checking if package supports dlls... $can_build_shared" 1>&6 + fi +;; +esac + +if test -n "$file_magic_test_file" && test -n "$file_magic_cmd"; then + case "$deplibs_check_method" in + "file_magic "*) + file_magic_regex="`expr \"$deplibs_check_method\" : \"file_magic \(.*\)\"`" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + egrep "$file_magic_regex" > /dev/null; then + : + else + cat <<EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +EOF + fi ;; + esac +fi + +echo $ac_n "checking whether to build shared libraries... $ac_c" 1>&6 +test "$can_build_shared" = "no" && enable_shared=no + +# On AIX, shared libraries and static libraries use the same namespace, and +# are all built from PIC. +case "$host_os" in +aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + +aix4*) + test "$enable_shared" = yes && enable_static=no + ;; +esac + +echo "$ac_t$enable_shared" 1>&6 + +# Make sure either enable_shared or enable_static is yes. +test "$enable_shared" = yes || enable_static=yes + +echo "checking whether to build static libraries... $enable_static" 1>&6 + +if test "$hardcode_action" = relink; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi + +echo $ac_n "checking for objdir... $ac_c" 1>&6 +rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + objdir=_libs +fi +rmdir .libs 2>/dev/null +echo "$ac_t$objdir" 1>&6 + +if test "x$enable_dlopen" != xyes; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else +if eval "test \"`echo '$''{'lt_cv_dlopen'+set}'`\" != set"; then + lt_cv_dlopen=no lt_cv_dlopen_libs= +echo $ac_n "checking for dlopen""... $ac_c" 1>&6 +echo "$progname:2063: checking for dlopen" >&5 +if eval "test \"`echo '$''{'ac_cv_func_dlopen'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2068 "ltconfig" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char dlopen(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char dlopen(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_dlopen) || defined (__stub___dlopen) +choke me +#else +dlopen(); +#endif + +; return 0; } +EOF +if { (eval echo $progname:2090: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_dlopen=yes" +else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_dlopen=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'dlopen`\" = yes"; then + echo "$ac_t""yes" 1>&6 + lt_cv_dlopen="dlopen" +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for dlopen in -ldl""... $ac_c" 1>&6 +echo "$progname:2108: checking for dlopen in -ldl" >&5 +ac_lib_var=`echo dl'_'dlopen | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldl $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2116 "ltconfig" +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char dlopen(); + +int main() { +dlopen() +; return 0; } +EOF +if { (eval echo $progname:2126: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl" +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for dld_link in -ldld""... $ac_c" 1>&6 +echo "$progname:2145: checking for dld_link in -ldld" >&5 +ac_lib_var=`echo dld'_'dld_link | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldld $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2153 "ltconfig" +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char dld_link(); + +int main() { +dld_link() +; return 0; } +EOF +if { (eval echo $progname:2163: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld" +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for shl_load""... $ac_c" 1>&6 +echo "$progname:2182: checking for shl_load" >&5 +if eval "test \"`echo '$''{'ac_cv_func_shl_load'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2187 "ltconfig" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char shl_load(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char shl_load(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_shl_load) || defined (__stub___shl_load) +choke me +#else +shl_load(); +#endif + +; return 0; } +EOF +if { (eval echo $progname:2209: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_shl_load=yes" +else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_shl_load=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'shl_load`\" = yes"; then + echo "$ac_t""yes" 1>&6 + lt_cv_dlopen="shl_load" +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for shl_load in -ldld""... $ac_c" 1>&6 +echo "$progname:2227: checking for shl_load in -ldld" >&5 +ac_lib_var=`echo dld'_'shl_load | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldld $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2235 "ltconfig" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char shl_load(); + +int main() { +shl_load() +; return 0; } +EOF +if { (eval echo $progname:2246: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld" +else + echo "$ac_t""no" 1>&6 +fi + + +fi + + +fi + + +fi + + +fi + +fi + + if test "x$lt_cv_dlopen" != xno; then + enable_dlopen=yes + fi + + case "$lt_cv_dlopen" in + dlopen) +for ac_hdr in dlfcn.h; do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "$progname:2289: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2294 "ltconfig" +#include <$ac_hdr> +int fnord = 0; +EOF +ac_try="$ac_compile conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo $progname:2299: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi +done + + if test "x$ac_cv_header_dlfcn_h" = xyes; then + CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + fi + eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + LIBS="$lt_cv_dlopen_libs $LIBS" + + echo $ac_n "checking whether a program can dlopen itself""... $ac_c" 1>&6 +echo "$progname:2327: checking whether a program can dlopen itself" >&5 +if test "${lt_cv_dlopen_self+set}" = set; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + lt_cv_dlopen_self=cross + else + cat > conftest.c <<EOF +#line 2335 "ltconfig" + +#if HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#include <stdio.h> + +#ifdef RTLD_GLOBAL +# define LTDL_GLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LTDL_GLOBAL DL_GLOBAL +# else +# define LTDL_GLOBAL 0 +# endif +#endif + +/* We may have to define LTDL_LAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LTDL_LAZY_OR_NOW +# ifdef RTLD_LAZY +# define LTDL_LAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LTDL_LAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LTDL_LAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LTDL_LAZY_OR_NOW DL_NOW +# else +# define LTDL_LAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +fnord() { int i=42;} +main() { void *self, *ptr1, *ptr2; self=dlopen(0,LTDL_GLOBAL|LTDL_LAZY_OR_NOW); + if(self) { ptr1=dlsym(self,"fnord"); ptr2=dlsym(self,"_fnord"); + if(ptr1 || ptr2) exit(0); } exit(1); } + +EOF +if { (eval echo $progname:2381: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + lt_cv_dlopen_self=yes +else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + lt_cv_dlopen_self=no +fi +rm -fr conftest* +fi + +fi + +echo "$ac_t""$lt_cv_dlopen_self" 1>&6 + + if test "$lt_cv_dlopen_self" = yes; then + LDFLAGS="$LDFLAGS $link_static_flag" + echo $ac_n "checking whether a statically linked program can dlopen itself""... $ac_c" 1>&6 +echo "$progname:2400: checking whether a statically linked program can dlopen itself" >&5 +if test "${lt_cv_dlopen_self_static+set}" = set; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + lt_cv_dlopen_self_static=cross + else + cat > conftest.c <<EOF +#line 2408 "ltconfig" + +#if HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#include <stdio.h> + +#ifdef RTLD_GLOBAL +# define LTDL_GLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LTDL_GLOBAL DL_GLOBAL +# else +# define LTDL_GLOBAL 0 +# endif +#endif + +/* We may have to define LTDL_LAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LTDL_LAZY_OR_NOW +# ifdef RTLD_LAZY +# define LTDL_LAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LTDL_LAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LTDL_LAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LTDL_LAZY_OR_NOW DL_NOW +# else +# define LTDL_LAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +fnord() { int i=42;} +main() { void *self, *ptr1, *ptr2; self=dlopen(0,LTDL_GLOBAL|LTDL_LAZY_OR_NOW); + if(self) { ptr1=dlsym(self,"fnord"); ptr2=dlsym(self,"_fnord"); + if(ptr1 || ptr2) exit(0); } exit(1); } + +EOF +if { (eval echo $progname:2454: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + lt_cv_dlopen_self_static=yes +else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + lt_cv_dlopen_self_static=no +fi +rm -fr conftest* +fi + +fi + +echo "$ac_t""$lt_cv_dlopen_self_static" 1>&6 +fi + ;; + esac + + case "$lt_cv_dlopen_self" in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case "$lt_cv_dlopen_self_static" in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi + +# Copy echo and quote the copy, instead of the original, because it is +# used later. +ltecho="$echo" +if test "X$ltecho" = "X$CONFIG_SHELL $0 --fallback-echo"; then + ltecho="$CONFIG_SHELL \$0 --fallback-echo" +fi +LTSHELL="$SHELL" + +LTCONFIG_VERSION="$VERSION" + +# Only quote variables if we're using ltmain.sh. +case "$ltmain" in +*.sh) + # Now quote all the things that may contain metacharacters. + for var in ltecho old_CC old_CFLAGS old_CPPFLAGS \ + old_LD old_LDFLAGS old_LIBS \ + old_NM old_RANLIB old_LN_S old_DLLTOOL old_OBJDUMP old_AS \ + AR CC LD LN_S NM LTSHELL LTCONFIG_VERSION \ + reload_flag reload_cmds wl \ + pic_flag link_static_flag no_builtin_flag export_dynamic_flag_spec \ + thread_safe_flag_spec whole_archive_flag_spec libname_spec \ + library_names_spec soname_spec \ + RANLIB old_archive_cmds old_archive_from_new_cmds old_postinstall_cmds \ + old_postuninstall_cmds archive_cmds archive_expsym_cmds postinstall_cmds postuninstall_cmds \ + file_magic_cmd export_symbols_cmds deplibs_check_method allow_undefined_flag no_undefined_flag \ + finish_cmds finish_eval global_symbol_pipe global_symbol_to_cdecl \ + hardcode_libdir_flag_spec hardcode_libdir_separator \ + sys_lib_search_path_spec sys_lib_dlsearch_path_spec \ + compiler_c_o compiler_o_lo need_locks exclude_expsyms include_expsyms; do + + case "$var" in + reload_cmds | old_archive_cmds | old_archive_from_new_cmds | \ + old_postinstall_cmds | old_postuninstall_cmds | \ + export_symbols_cmds | archive_cmds | archive_expsym_cmds | \ + postinstall_cmds | postuninstall_cmds | \ + finish_cmds | sys_lib_search_path_spec | sys_lib_dlsearch_path_spec) + # Double-quote double-evaled strings. + eval "$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\"" + ;; + *) + eval "$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\"" + ;; + esac + done + + case "$ltecho" in + *'\$0 --fallback-echo"') + ltecho=`$echo "X$ltecho" | $Xsed -e 's/\\\\\\\$0 --fallback-echo"$/$0 --fallback-echo"/'` + ;; + esac + + trap "$rm \"$ofile\"; exit 1" 1 2 15 + echo "creating $ofile" + $rm "$ofile" + cat <<EOF > "$ofile" +#! $SHELL + +# `$echo "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services. +# Generated automatically by $PROGRAM (GNU $PACKAGE $VERSION$TIMESTAMP) +# NOTE: Changes made to this file will be lost: look at ltconfig or ltmain.sh. +# +# Copyright (C) 1996-1999 Free Software Foundation, Inc. +# Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996 +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="sed -e s/^X//" + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +if test "\${CDPATH+set}" = set; then CDPATH=; export CDPATH; fi + +### BEGIN LIBTOOL CONFIG +EOF + cfgfile="$ofile" + ;; + +*) + # Double-quote the variables that need it (for aesthetics). + for var in old_CC old_CFLAGS old_CPPFLAGS \ + old_LD old_LDFLAGS old_LIBS \ + old_NM old_RANLIB old_LN_S old_DLLTOOL old_OBJDUMP old_AS; do + eval "$var=\\\"\$var\\\"" + done + + # Just create a config file. + cfgfile="$ofile.cfg" + trap "$rm \"$cfgfile\"; exit 1" 1 2 15 + echo "creating $cfgfile" + $rm "$cfgfile" + cat <<EOF > "$cfgfile" +# `$echo "$cfgfile" | sed 's%^.*/%%'` - Libtool configuration file. +# Generated automatically by $PROGRAM (GNU $PACKAGE $VERSION$TIMESTAMP) +EOF + ;; +esac + +cat <<EOF >> "$cfgfile" +# Libtool was configured as follows, on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# +# CC=$old_CC CFLAGS=$old_CFLAGS CPPFLAGS=$old_CPPFLAGS \\ +# LD=$old_LD LDFLAGS=$old_LDFLAGS LIBS=$old_LIBS \\ +# NM=$old_NM RANLIB=$old_RANLIB LN_S=$old_LN_S \\ +# DLLTOOL=$old_DLLTOOL OBJDUMP=$old_OBJDUMP AS=$old_AS \\ +# $0$ltconfig_args +# +# Compiler and other test output produced by $progname, useful for +# debugging $progname, is in ./config.log if it exists. + +# The version of $progname that generated this script. +LTCONFIG_VERSION=$LTCONFIG_VERSION + +# Shell to use when invoking shell scripts. +SHELL=$LTSHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host + +# An echo program that does not interpret backslashes. +echo=$ltecho + +# The archiver. +AR=$AR + +# The default C compiler. +CC=$CC + +# The linker used to build libraries. +LD=$LD + +# Whether we need hard or soft links. +LN_S=$LN_S + +# A BSD-compatible nm program. +NM=$NM + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$reload_flag +reload_cmds=$reload_cmds + +# How to pass a linker flag through the compiler. +wl=$wl + +# Object file suffix (normally "o"). +objext="$objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Additional compiler flags for building library objects. +pic_flag=$pic_flag + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$compiler_c_o + +# Can we write directly to a .lo ? +compiler_o_lo=$compiler_o_lo + +# Must we lock files when doing compilation ? +need_locks=$need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$link_static_flag + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$no_builtin_flag + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$export_dynamic_flag_spec + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$whole_archive_flag_spec + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$thread_safe_flag_spec + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$RANLIB +old_archive_cmds=$old_archive_cmds +old_postinstall_cmds=$old_postinstall_cmds +old_postuninstall_cmds=$old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$old_archive_from_new_cmds + +# Commands used to build and install a shared archive. +archive_cmds=$archive_cmds +archive_expsym_cmds=$archive_expsym_cmds +postinstall_cmds=$postinstall_cmds +postuninstall_cmds=$postuninstall_cmds + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$allow_undefined_flag + +# Flag that forces no undefined symbols. +no_undefined_flag=$no_undefined_flag + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$global_symbol_to_cdecl + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$hardcode_libdir_flag_spec + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$hardcode_libdir_separator + +# Set to yes if using DIR/libNAME.so during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$hardcode_direct + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$hardcode_minus_L + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path="$fix_srcfile_path" + +# Set to yes if exported symbols are required. +always_export_symbols=$always_export_symbols + +# The commands to list exported symbols. +export_symbols_cmds=$export_symbols_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$exclude_expsyms + +# Symbols that must always be exported. +include_expsyms=$include_expsyms + +EOF + +case "$ltmain" in +*.sh) + echo '### END LIBTOOL CONFIG' >> "$ofile" + echo >> "$ofile" + case "$host_os" in + aix3*) + cat <<\EOF >> "$ofile" + +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test "${COLLECT_NAMES+set}" != set; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +EOF + ;; + esac + + # Append the ltmain.sh script. + cat "$ltmain" >> "$ofile" || (rm -f "$ofile"; exit 1) + + chmod +x "$ofile" + ;; + +*) + # Compile the libtool program. + echo "FIXME: would compile $ltmain" + ;; +esac + +test -n "$cache_file" || exit 0 + +# AC_CACHE_SAVE +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, don't put newlines in cache variables' values. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + case `(ac_space=' '; set | grep ac_space) 2>&1` in + *ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote substitution + # turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + -e "s/'/'\\\\''/g" \ + -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" + ;; + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' + ;; + esac >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +exit 0 + +# Local Variables: +# mode:shell-script +# sh-indentation:2 +# End: diff --git a/neon/ltmain.sh b/neon/ltmain.sh new file mode 100644 index 000000000..f1b998611 --- /dev/null +++ b/neon/ltmain.sh @@ -0,0 +1,3892 @@ +# ltmain.sh - Provide generalized library-building support services. +# NOTE: Changing this file will not affect anything until you rerun ltconfig. +# +# Copyright (C) 1996-1999 Free Software Foundation, Inc. +# Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996 +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Check that we have a working $echo. +if test "X$1" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift +elif test "X$1" = X--fallback-echo; then + # Avoid inline document here, it may be left over + : +elif test "X`($echo '\t') 2>/dev/null`" = 'X\t'; then + # Yippee, $echo works! + : +else + # Restart under the correct shell, and then maybe $echo will work. + exec $SHELL "$0" --no-reexec ${1+"$@"} +fi + +if test "X$1" = X--fallback-echo; then + # used as fallback echo + shift + cat <<EOF +$* +EOF + exit 0 +fi + +# The name of this program. +progname=`$echo "$0" | sed 's%^.*/%%'` +modename="$progname" + +# Constants. +PROGRAM=ltmain.sh +PACKAGE=libtool +VERSION=1.3 +TIMESTAMP=" (1.385.2.117 1999/04/29 13:07:13)" + +default_mode= +help="Try \`$progname --help' for more information." +magic="%%%MAGIC variable%%%" +mkdir="mkdir" +mv="mv -f" +rm="rm -f" + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +Xsed='sed -e 1s/^X//' +sed_quote_subst='s/\([\\`\\"$\\\\]\)/\\\1/g' +SP2NL='tr \040 \012' +NL2SP='tr \012 \040' + +# NLS nuisances. +# Only set LANG and LC_ALL to C if already set. +# These must not be set unconditionally because not all systems understand +# e.g. LANG=C (notably SCO). +# We save the old values to restore during execute mode. +if test "${LC_ALL+set}" = set; then + save_LC_ALL="$LC_ALL"; LC_ALL=C; export LC_ALL +fi +if test "${LANG+set}" = set; then + save_LANG="$LANG"; LANG=C; export LANG +fi + +if test "$LTCONFIG_VERSION" != "$VERSION"; then + echo "$modename: ltconfig version \`$LTCONFIG_VERSION' does not match $PROGRAM version \`$VERSION'" 1>&2 + echo "Fatal configuration error. See the $PACKAGE docs for more information." 1>&2 + exit 1 +fi + +if test "$build_libtool_libs" != yes && test "$build_old_libs" != yes; then + echo "$modename: not configured to build any kind of library" 1>&2 + echo "Fatal configuration error. See the $PACKAGE docs for more information." 1>&2 + exit 1 +fi + +# Global variables. +mode=$default_mode +nonopt= +prev= +prevopt= +run= +show="$echo" +show_help= +execute_dlfiles= +lo2o="s/\\.lo\$/.${objext}/" +o2lo="s/\\.${objext}\$/.lo/" + +# Parse our command line options once, thoroughly. +while test $# -gt 0 +do + arg="$1" + shift + + case "$arg" in + -*=*) optarg=`$echo "X$arg" | $Xsed -e 's/[-_a-zA-Z0-9]*=//'` ;; + *) optarg= ;; + esac + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + case "$prev" in + execute_dlfiles) + eval "$prev=\"\$$prev \$arg\"" + ;; + *) + eval "$prev=\$arg" + ;; + esac + + prev= + prevopt= + continue + fi + + # Have we seen a non-optional argument yet? + case "$arg" in + --help) + show_help=yes + ;; + + --version) + echo "$PROGRAM (GNU $PACKAGE) $VERSION$TIMESTAMP" + exit 0 + ;; + + --config) + sed -e '1,/^### BEGIN LIBTOOL CONFIG/d' -e '/^### END LIBTOOL CONFIG/,$d' $0 + exit 0 + ;; + + --debug) + echo "$progname: enabling shell trace mode" + set -x + ;; + + --dry-run | -n) + run=: + ;; + + --features) + echo "host: $host" + if test "$build_libtool_libs" = yes; then + echo "enable shared libraries" + else + echo "disable shared libraries" + fi + if test "$build_old_libs" = yes; then + echo "enable static libraries" + else + echo "disable static libraries" + fi + exit 0 + ;; + + --finish) mode="finish" ;; + + --mode) prevopt="--mode" prev=mode ;; + --mode=*) mode="$optarg" ;; + + --quiet | --silent) + show=: + ;; + + -dlopen) + prevopt="-dlopen" + prev=execute_dlfiles + ;; + + -*) + $echo "$modename: unrecognized option \`$arg'" 1>&2 + $echo "$help" 1>&2 + exit 1 + ;; + + *) + nonopt="$arg" + break + ;; + esac +done + +if test -n "$prevopt"; then + $echo "$modename: option \`$prevopt' requires an argument" 1>&2 + $echo "$help" 1>&2 + exit 1 +fi + +if test -z "$show_help"; then + + # Infer the operation mode. + if test -z "$mode"; then + case "$nonopt" in + *cc | *++ | gcc* | *-gcc*) + mode=link + for arg + do + case "$arg" in + -c) + mode=compile + break + ;; + esac + done + ;; + *db | *dbx | *strace | *truss) + mode=execute + ;; + *install*|cp|mv) + mode=install + ;; + *rm) + mode=uninstall + ;; + *) + # If we have no mode, but dlfiles were specified, then do execute mode. + test -n "$execute_dlfiles" && mode=execute + + # Just use the default operation mode. + if test -z "$mode"; then + if test -n "$nonopt"; then + $echo "$modename: warning: cannot infer operation mode from \`$nonopt'" 1>&2 + else + $echo "$modename: warning: cannot infer operation mode without MODE-ARGS" 1>&2 + fi + fi + ;; + esac + fi + + # Only execute mode is allowed to have -dlopen flags. + if test -n "$execute_dlfiles" && test "$mode" != execute; then + $echo "$modename: unrecognized option \`-dlopen'" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + # Change the help message to a mode-specific one. + generic_help="$help" + help="Try \`$modename --help --mode=$mode' for more information." + + # These modes are in order of execution frequency so that they run quickly. + case "$mode" in + # libtool compile mode + compile) + modename="$modename: compile" + # Get the compilation command and the source file. + base_compile= + lastarg= + srcfile="$nonopt" + suppress_output= + + user_target=no + for arg + do + # Accept any command-line options. + case "$arg" in + -o) + if test "$user_target" != "no"; then + $echo "$modename: you cannot specify \`-o' more than once" 1>&2 + exit 1 + fi + user_target=next + ;; + + -static) + build_old_libs=yes + continue + ;; + esac + + case "$user_target" in + next) + # The next one is the -o target name + user_target=yes + continue + ;; + yes) + # We got the output file + user_target=set + libobj="$arg" + continue + ;; + esac + + # Accept the current argument as the source file. + lastarg="$srcfile" + srcfile="$arg" + + # Aesthetically quote the previous argument. + + # Backslashify any backslashes, double quotes, and dollar signs. + # These are the only characters that are still specially + # interpreted inside of double-quoted scrings. + lastarg=`$echo "X$lastarg" | $Xsed -e "$sed_quote_subst"` + + # Double-quote args containing other shell metacharacters. + # Many Bourne shells cannot handle close brackets correctly in scan + # sets, so we specify it separately. + case "$lastarg" in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*) + lastarg="\"$lastarg\"" + ;; + esac + + # Add the previous argument to base_compile. + if test -z "$base_compile"; then + base_compile="$lastarg" + else + base_compile="$base_compile $lastarg" + fi + done + + case "$user_target" in + set) + ;; + no) + # Get the name of the library object. + libobj=`$echo "X$srcfile" | $Xsed -e 's%^.*/%%'` + ;; + *) + $echo "$modename: you must specify a target with \`-o'" 1>&2 + exit 1 + ;; + esac + + # Recognize several different file suffixes. + # If the user specifies -o file.o, it is replaced with file.lo + xform='[cCFSfmso]' + case "$libobj" in + *.ada) xform=ada ;; + *.adb) xform=adb ;; + *.ads) xform=ads ;; + *.asm) xform=asm ;; + *.c++) xform=c++ ;; + *.cc) xform=cc ;; + *.cpp) xform=cpp ;; + *.cxx) xform=cxx ;; + *.f90) xform=f90 ;; + *.for) xform=for ;; + esac + + libobj=`$echo "X$libobj" | $Xsed -e "s/\.$xform$/.lo/"` + + case "$libobj" in + *.lo) obj=`$echo "X$libobj" | $Xsed -e "$lo2o"` ;; + *) + $echo "$modename: cannot determine name of library object from \`$libobj'" 1>&2 + exit 1 + ;; + esac + + if test -z "$base_compile"; then + $echo "$modename: you must specify a compilation command" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + # Delete any leftover library objects. + if test "$build_old_libs" = yes; then + removelist="$obj $libobj" + else + removelist="$libobj" + fi + + $run $rm $removelist + trap "$run $rm $removelist; exit 1" 1 2 15 + + # Calculate the filename of the output object if compiler does + # not support -o with -c + if test "$compiler_c_o" = no; then + output_obj=`$echo "X$srcfile" | $Xsed -e 's%^.*/%%' -e 's%\..*$%%'`.${objext} + lockfile="$output_obj.lock" + removelist="$removelist $output_obj $lockfile" + trap "$run $rm $removelist; exit 1" 1 2 15 + else + need_locks=no + lockfile= + fi + + # Lock this critical section if it is needed + # We use this script file to make the link, it avoids creating a new file + if test "$need_locks" = yes; then + until ln "$0" "$lockfile" 2>/dev/null; do + $show "Waiting for $lockfile to be removed" + sleep 2 + done + elif test "$need_locks" = warn; then + if test -f "$lockfile"; then + echo "\ +*** ERROR, $lockfile exists and contains: +`cat $lockfile 2>/dev/null` + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support \`-c' and \`-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $run $rm $removelist + exit 1 + fi + echo $srcfile > "$lockfile" + fi + + if test -n "$fix_srcfile_path"; then + eval srcfile=\"$fix_srcfile_path\" + fi + + # Only build a PIC object if we are building libtool libraries. + if test "$build_libtool_libs" = yes; then + # Without this assignment, base_compile gets emptied. + fbsd_hideous_sh_bug=$base_compile + + # All platforms use -DPIC, to notify preprocessed assembler code. + command="$base_compile $pic_flag -DPIC $srcfile" + if test "$build_old_libs" = yes; then + lo_libobj="$libobj" + dir=`$echo "X$libobj" | $Xsed -e 's%/[^/]*$%%'` + if test "X$dir" = "X$libobj"; then + dir="$objdir" + else + dir="$dir/$objdir" + fi + libobj="$dir/"`$echo "X$libobj" | $Xsed -e 's%^.*/%%'` + + if test -d "$dir"; then + $show "$rm $libobj" + $run $rm $libobj + else + $show "$mkdir $dir" + $run $mkdir $dir + status=$? + if test $status -ne 0 && test ! -d $dir; then + exit $status + fi + fi + fi + if test "$compiler_o_lo" = yes; then + output_obj="$libobj" + command="$command -o $output_obj" + elif test "$compiler_c_o" = yes; then + output_obj="$obj" + command="$command -o $output_obj" + fi + + $show "$command" + if $run eval "$command"; then : + else + test -n "$output_obj" && $run $rm $removelist + exit 1 + fi + + if test "$need_locks" = warn && + test x"`cat $lockfile 2>/dev/null`" != x"$srcfile"; then + echo "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support \`-c' and \`-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $run $rm $removelist + exit 1 + fi + + # Just move the object if needed, then go on to compile the next one + if test x"$output_obj" != x"$libobj"; then + $show "$mv $output_obj $libobj" + if $run $mv $output_obj $libobj; then : + else + error=$? + $run $rm $removelist + exit $error + fi + fi + + # If we have no pic_flag, then copy the object into place and finish. + if test -z "$pic_flag" && test "$build_old_libs" = yes; then + # Rename the .lo from within objdir to obj + if test -f $obj; then + $show $rm $obj + $run $rm $obj + fi + + $show "$mv $libobj $obj" + if $run $mv $libobj $obj; then : + else + error=$? + $run $rm $removelist + exit $error + fi + + # Now arrange that obj and lo_libobj become the same file + $show "$LN_S $obj $lo_libobj" + if $run $LN_S $obj $lo_libobj; then + exit 0 + else + error=$? + $run $rm $removelist + exit $error + fi + fi + + # Allow error messages only from the first compilation. + suppress_output=' >/dev/null 2>&1' + fi + + # Only build a position-dependent object if we build old libraries. + if test "$build_old_libs" = yes; then + command="$base_compile $srcfile" + if test "$compiler_c_o" = yes; then + command="$command -o $obj" + output_obj="$obj" + fi + + # Suppress compiler output if we already did a PIC compilation. + command="$command$suppress_output" + $show "$command" + if $run eval "$command"; then : + else + $run $rm $removelist + exit 1 + fi + + if test "$need_locks" = warn && + test x"`cat $lockfile 2>/dev/null`" != x"$srcfile"; then + echo "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support \`-c' and \`-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $run $rm $removelist + exit 1 + fi + + # Just move the object if needed + if test x"$output_obj" != x"$obj"; then + $show "$mv $output_obj $obj" + if $run $mv $output_obj $obj; then : + else + error=$? + $run $rm $removelist + exit $error + fi + fi + + # Create an invalid libtool object if no PIC, so that we do not + # accidentally link it into a program. + if test "$build_libtool_libs" != yes; then + $show "echo timestamp > $libobj" + $run eval "echo timestamp > \$libobj" || exit $? + else + # Move the .lo from within objdir + $show "$mv $libobj $lo_libobj" + if $run $mv $libobj $lo_libobj; then : + else + error=$? + $run $rm $removelist + exit $error + fi + fi + fi + + # Unlock the critical section if it was locked + if test "$need_locks" != no; then + $rm "$lockfile" + fi + + exit 0 + ;; + + # libtool link mode + link) + modename="$modename: link" + C_compiler="$CC" # save it, to compile generated C sources + CC="$nonopt" + case "$host" in + *-*-cygwin* | *-*-mingw* | *-*-os2*) + # It is impossible to link a dll without this setting, and + # we shouldn't force the makefile maintainer to figure out + # which system we are compiling for in order to pass an extra + # flag for every libtool invokation. + # allow_undefined=no + + # FIXME: Unfortunately, there are problems with the above when trying + # to make a dll which has undefined symbols, in which case not + # even a static library is built. For now, we need to specify + # -no-undefined on the libtool link line when we can be certain + # that all symbols are satisfied, otherwise we get a static library. + allow_undefined=yes + + # This is a source program that is used to create dlls on Windows + # Don't remove nor modify the starting and closing comments +# /* ltdll.c starts here */ +# #define WIN32_LEAN_AND_MEAN +# #include <windows.h> +# #undef WIN32_LEAN_AND_MEAN +# #include <stdio.h> +# +# #ifdef __cplusplus +# extern "C" { +# #endif +# BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved); +# #ifdef __cplusplus +# } +# #endif +# +# #include <cygwin/cygwin_dll.h> +# DECLARE_CYGWIN_DLL( DllMain ); +# HINSTANCE __hDllInstance_base; +# +# BOOL APIENTRY +# DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved) +# { +# __hDllInstance_base = hInst; +# return TRUE; +# } +# /* ltdll.c ends here */ + # This is a source program that is used to create import libraries + # on Windows for dlls which lack them. Don't remove nor modify the + # starting and closing comments +# /* impgen.c starts here */ +# /* Copyright (C) 1999 Free Software Foundation, Inc. +# +# This file is part of GNU libtool. +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# */ +# +# #include <stdio.h> /* for printf() */ +# #include <unistd.h> /* for open(), lseek(), read() */ +# #include <fcntl.h> /* for O_RDONLY, O_BINARY */ +# #include <string.h> /* for strdup() */ +# +# static unsigned int +# pe_get16 (fd, offset) +# int fd; +# int offset; +# { +# unsigned char b[2]; +# lseek (fd, offset, SEEK_SET); +# read (fd, b, 2); +# return b[0] + (b[1]<<8); +# } +# +# static unsigned int +# pe_get32 (fd, offset) +# int fd; +# int offset; +# { +# unsigned char b[4]; +# lseek (fd, offset, SEEK_SET); +# read (fd, b, 4); +# return b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24); +# } +# +# static unsigned int +# pe_as32 (ptr) +# void *ptr; +# { +# unsigned char *b = ptr; +# return b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24); +# } +# +# int +# main (argc, argv) +# int argc; +# char *argv[]; +# { +# int dll; +# unsigned long pe_header_offset, opthdr_ofs, num_entries, i; +# unsigned long export_rva, export_size, nsections, secptr, expptr; +# unsigned long name_rvas, nexp; +# unsigned char *expdata, *erva; +# char *filename, *dll_name; +# +# filename = argv[1]; +# +# dll = open(filename, O_RDONLY|O_BINARY); +# if (!dll) +# return 1; +# +# dll_name = filename; +# +# for (i=0; filename[i]; i++) +# if (filename[i] == '/' || filename[i] == '\\' || filename[i] == ':') +# dll_name = filename + i +1; +# +# pe_header_offset = pe_get32 (dll, 0x3c); +# opthdr_ofs = pe_header_offset + 4 + 20; +# num_entries = pe_get32 (dll, opthdr_ofs + 92); +# +# if (num_entries < 1) /* no exports */ +# return 1; +# +# export_rva = pe_get32 (dll, opthdr_ofs + 96); +# export_size = pe_get32 (dll, opthdr_ofs + 100); +# nsections = pe_get16 (dll, pe_header_offset + 4 +2); +# secptr = (pe_header_offset + 4 + 20 + +# pe_get16 (dll, pe_header_offset + 4 + 16)); +# +# expptr = 0; +# for (i = 0; i < nsections; i++) +# { +# char sname[8]; +# unsigned long secptr1 = secptr + 40 * i; +# unsigned long vaddr = pe_get32 (dll, secptr1 + 12); +# unsigned long vsize = pe_get32 (dll, secptr1 + 16); +# unsigned long fptr = pe_get32 (dll, secptr1 + 20); +# lseek(dll, secptr1, SEEK_SET); +# read(dll, sname, 8); +# if (vaddr <= export_rva && vaddr+vsize > export_rva) +# { +# expptr = fptr + (export_rva - vaddr); +# if (export_rva + export_size > vaddr + vsize) +# export_size = vsize - (export_rva - vaddr); +# break; +# } +# } +# +# expdata = (unsigned char*)malloc(export_size); +# lseek (dll, expptr, SEEK_SET); +# read (dll, expdata, export_size); +# erva = expdata - export_rva; +# +# nexp = pe_as32 (expdata+24); +# name_rvas = pe_as32 (expdata+32); +# +# printf ("EXPORTS\n"); +# for (i = 0; i<nexp; i++) +# { +# unsigned long name_rva = pe_as32 (erva+name_rvas+i*4); +# printf ("\t%s @ %ld ;\n", erva+name_rva, 1+ i); +# } +# +# return 0; +# } +# /* impgen.c ends here */ + ;; + *) + allow_undefined=yes + ;; + esac + compile_command="$CC" + finalize_command="$CC" + + compile_rpath= + finalize_rpath= + compile_shlibpath= + finalize_shlibpath= + convenience= + old_convenience= + deplibs= + linkopts= + + if test -n "$shlibpath_var"; then + # get the directories listed in $shlibpath_var + eval lib_search_path=\`\$echo \"X \${$shlibpath_var}\" \| \$Xsed -e \'s/:/ /g\'\` + else + lib_search_path= + fi + # now prepend the system-specific ones + eval lib_search_path=\"$sys_lib_search_path_spec\$lib_search_path\" + eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\" + + avoid_version=no + dlfiles= + dlprefiles= + dlself=no + export_dynamic=no + export_symbols= + export_symbols_regex= + generated= + libobjs= + link_against_libtool_libs= + ltlibs= + module=no + objs= + prefer_static_libs=no + preload=no + prev= + prevarg= + release= + rpath= + xrpath= + perm_rpath= + temp_rpath= + thread_safe=no + vinfo= + + # We need to know -static, to get the right output filenames. + for arg + do + case "$arg" in + -all-static | -static) + if test "X$arg" = "X-all-static"; then + if test "$build_libtool_libs" = yes && test -z "$link_static_flag"; then + $echo "$modename: warning: complete static linking is impossible in this configuration" 1>&2 + fi + if test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + else + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + fi + build_libtool_libs=no + build_old_libs=yes + prefer_static_libs=yes + break + ;; + esac + done + + # See if our shared archives depend on static archives. + test -n "$old_archive_from_new_cmds" && build_old_libs=yes + + # Go through the arguments, transforming them on the way. + while test $# -gt 0; do + arg="$1" + shift + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + case "$prev" in + output) + compile_command="$compile_command @OUTPUT@" + finalize_command="$finalize_command @OUTPUT@" + ;; + esac + + case "$prev" in + dlfiles|dlprefiles) + if test "$preload" = no; then + # Add the symbol object into the linking commands. + compile_command="$compile_command @SYMFILE@" + finalize_command="$finalize_command @SYMFILE@" + preload=yes + fi + case "$arg" in + *.la | *.lo) ;; # We handle these cases below. + self) + if test "$prev" = dlprefiles; then + dlself=yes + elif test "$prev" = dlfiles && test "$dlopen_self" != yes; then + dlself=yes + else + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + *) + if test "$prev" = dlfiles; then + dlfiles="$dlfiles $arg" + else + dlprefiles="$dlprefiles $arg" + fi + prev= + ;; + esac + ;; + expsyms) + export_symbols="$arg" + if test ! -f "$arg"; then + $echo "$modename: symbol file \`$arg' does not exist" + exit 1 + fi + prev= + continue + ;; + expsyms_regex) + export_symbols_regex="$arg" + prev= + continue + ;; + release) + release="-$arg" + prev= + continue + ;; + rpath | xrpath) + # We need an absolute path. + case "$arg" in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + $echo "$modename: only absolute run-paths are allowed" 1>&2 + exit 1 + ;; + esac + if test "$prev" = rpath; then + case "$rpath " in + *" $arg "*) ;; + *) rpath="$rpath $arg" ;; + esac + else + case "$xrpath " in + *" $arg "*) ;; + *) xrpath="$xrpath $arg" ;; + esac + fi + prev= + continue + ;; + *) + eval "$prev=\"\$arg\"" + prev= + continue + ;; + esac + fi + + prevarg="$arg" + + case "$arg" in + -all-static) + if test -n "$link_static_flag"; then + compile_command="$compile_command $link_static_flag" + finalize_command="$finalize_command $link_static_flag" + fi + continue + ;; + + -allow-undefined) + # FIXME: remove this flag sometime in the future. + $echo "$modename: \`-allow-undefined' is deprecated because it is the default" 1>&2 + continue + ;; + + -avoid-version) + avoid_version=yes + continue + ;; + + -dlopen) + prev=dlfiles + continue + ;; + + -dlpreopen) + prev=dlprefiles + continue + ;; + + -export-dynamic) + export_dynamic=yes + continue + ;; + + -export-symbols | -export-symbols-regex) + if test -n "$export_symbols" || test -n "$export_symbols_regex"; then + $echo "$modename: not more than one -exported-symbols argument allowed" + exit 1 + fi + if test "X$arg" = "X-export-symbols"; then + prev=expsyms + else + prev=expsyms_regex + fi + continue + ;; + + -L*) + dir=`$echo "X$arg" | $Xsed -e 's/^-L//'` + # We need an absolute path. + case "$dir" in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + absdir=`cd "$dir" && pwd` + if test -z "$absdir"; then + $echo "$modename: cannot determine absolute directory name of \`$dir'" 1>&2 + exit 1 + fi + dir="$absdir" + ;; + esac + case " $deplibs " in + *" $arg "*) ;; + *) deplibs="$deplibs $arg";; + esac + case " $lib_search_path " in + *" $dir "*) ;; + *) lib_search_path="$lib_search_path $dir";; + esac + case "$host" in + *-*-cygwin* | *-*-mingw* | *-*-os2*) + dllsearchdir=`cd "$dir" && pwd || echo "$dir"` + case ":$dllsearchpath:" in + ::) dllsearchpath="$dllsearchdir";; + *":$dllsearchdir:"*) ;; + *) dllsearchpath="$dllsearchpath:$dllsearchdir";; + esac + ;; + esac + ;; + + -l*) + if test "$arg" = "-lc"; then + case "$host" in + *-*-cygwin* | *-*-mingw* | *-*-os2* | *-*-beos*) + # These systems don't actually have c library (as such) + continue + ;; + esac + elif test "$arg" = "-lm"; then + case "$host" in + *-*-cygwin* | *-*-beos*) + # These systems don't actually have math library (as such) + continue + ;; + esac + fi + deplibs="$deplibs $arg" + ;; + + -module) + module=yes + continue + ;; + + -no-undefined) + allow_undefined=no + continue + ;; + + -o) prev=output ;; + + -release) + prev=release + continue + ;; + + -rpath) + prev=rpath + continue + ;; + + -R) + prev=xrpath + continue + ;; + + -R*) + dir=`$echo "X$arg" | $Xsed -e 's/^-R//'` + # We need an absolute path. + case "$dir" in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + $echo "$modename: only absolute run-paths are allowed" 1>&2 + exit 1 + ;; + esac + case "$xrpath " in + *" $dir "*) ;; + *) xrpath="$xrpath $dir" ;; + esac + continue + ;; + + -static) + # If we have no pic_flag, then this is the same as -all-static. + if test -z "$pic_flag" && test -n "$link_static_flag"; then + compile_command="$compile_command $link_static_flag" + finalize_command="$finalize_command $link_static_flag" + fi + continue + ;; + + -thread-safe) + thread_safe=yes + continue + ;; + + -version-info) + prev=vinfo + continue + ;; + + # Some other compiler flag. + -* | +*) + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case "$arg" in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*) + arg="\"$arg\"" + ;; + esac + ;; + + *.o | *.obj | *.a | *.lib) + # A standard object. + objs="$objs $arg" + ;; + + *.lo) + # A library object. + if test "$prev" = dlfiles; then + dlfiles="$dlfiles $arg" + if test "$build_libtool_libs" = yes && test "$dlopen" = yes; then + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + if test "$prev" = dlprefiles; then + # Preload the old-style object. + dlprefiles="$dlprefiles "`$echo "X$arg" | $Xsed -e "$lo2o"` + prev= + fi + libobjs="$libobjs $arg" + ;; + + *.la) + # A libtool-controlled library. + + dlname= + libdir= + library_names= + old_library= + + # Check to see that this really is a libtool archive. + if (sed -e '2q' $arg | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then : + else + $echo "$modename: \`$arg' is not a valid libtool archive" 1>&2 + exit 1 + fi + + # If the library was installed with an old release of libtool, + # it will not redefine variable installed. + installed=yes + + # Read the .la file + # If there is no directory component, then add one. + case "$arg" in + */* | *\\*) . $arg ;; + *) . ./$arg ;; + esac + + # Get the name of the library we link against. + linklib= + for l in $old_library $library_names; do + linklib="$l" + done + + if test -z "$linklib"; then + $echo "$modename: cannot find name of link library for \`$arg'" 1>&2 + exit 1 + fi + + # Find the relevant object directory and library name. + name=`$echo "X$arg" | $Xsed -e 's%^.*/%%' -e 's/\.la$//' -e 's/^lib//'` + + if test "X$installed" = Xyes; then + dir="$libdir" + else + dir=`$echo "X$arg" | $Xsed -e 's%/[^/]*$%%'` + if test "X$dir" = "X$arg"; then + dir="$objdir" + else + dir="$dir/$objdir" + fi + fi + + if test -n "$dependency_libs"; then + # Extract -R from dependency_libs + temp_deplibs= + for deplib in $dependency_libs; do + case "$deplib" in + -R*) temp_xrpath=`$echo "X$deplib" | $Xsed -e 's/^-R//'` + case " $rpath $xrpath " in + *" $temp_xrpath "*) ;; + *) xrpath="$xrpath $temp_xrpath";; + esac;; + -L*) case "$compile_command $temp_deplibs " in + *" $deplib "*) ;; + *) temp_deplibs="$temp_deplibs $deplib";; + esac;; + *) temp_deplibs="$temp_deplibs $deplib";; + esac + done + dependency_libs="$temp_deplibs" + fi + + if test -z "$libdir"; then + # It is a libtool convenience library, so add in its objects. + convenience="$convenience $dir/$old_library" + old_convenience="$old_convenience $dir/$old_library" + deplibs="$deplibs$dependency_libs" + compile_command="$compile_command $dir/$old_library$dependency_libs" + finalize_command="$finalize_command $dir/$old_library$dependency_libs" + continue + fi + + # This library was specified with -dlopen. + if test "$prev" = dlfiles; then + dlfiles="$dlfiles $arg" + if test -z "$dlname" || test "$dlopen" != yes || test "$build_libtool_libs" = no; then + # If there is no dlname, no dlopen support or we're linking statically, + # we need to preload. + prev=dlprefiles + else + # We should not create a dependency on this library, but we + # may need any libraries it requires. + compile_command="$compile_command$dependency_libs" + finalize_command="$finalize_command$dependency_libs" + prev= + continue + fi + fi + + # The library was specified with -dlpreopen. + if test "$prev" = dlprefiles; then + # Prefer using a static library (so that no silly _DYNAMIC symbols + # are required to link). + if test -n "$old_library"; then + dlprefiles="$dlprefiles $dir/$old_library" + else + dlprefiles="$dlprefiles $dir/$linklib" + fi + prev= + fi + + if test -n "$library_names" && + { test "$prefer_static_libs" = no || test -z "$old_library"; }; then + link_against_libtool_libs="$link_against_libtool_libs $arg" + if test -n "$shlibpath_var"; then + # Make sure the rpath contains only unique directories. + case "$temp_rpath " in + *" $dir "*) ;; + *) temp_rpath="$temp_rpath $dir" ;; + esac + fi + + # We need an absolute path. + case "$dir" in + [\\/] | [A-Za-z]:[\\/]*) absdir="$dir" ;; + *) + absdir=`cd "$dir" && pwd` + if test -z "$absdir"; then + $echo "$modename: cannot determine absolute directory name of \`$dir'" 1>&2 + exit 1 + fi + ;; + esac + + # This is the magic to use -rpath. + # Skip directories that are in the system default run-time + # search path, unless they have been requested with -R. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) compile_rpath="$compile_rpath $absdir" + esac + ;; + esac + + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) finalize_rpath="$finalize_rpath $libdir" + esac + ;; + esac + + lib_linked=yes + case "$hardcode_action" in + immediate | unsupported) + if test "$hardcode_direct" = no; then + compile_command="$compile_command $dir/$linklib" + deplibs="$deplibs $dir/$linklib" + case "$host" in + *-*-cygwin* | *-*-mingw* | *-*-os2*) + dllsearchdir=`cd "$dir" && pwd || echo "$dir"` + if test -n "$dllsearchpath"; then + dllsearchpath="$dllsearchpath:$dllsearchdir" + else + dllsearchpath="$dllsearchdir" + fi + ;; + esac + elif test "$hardcode_minus_L" = no; then + case "$host" in + *-*-sunos*) + compile_shlibpath="$compile_shlibpath$dir:" + ;; + esac + case "$compile_command " in + *" -L$dir "*) ;; + *) compile_command="$compile_command -L$dir";; + esac + compile_command="$compile_command -l$name" + deplibs="$deplibs -L$dir -l$name" + elif test "$hardcode_shlibpath_var" = no; then + case ":$compile_shlibpath:" in + *":$dir:"*) ;; + *) compile_shlibpath="$compile_shlibpath$dir:";; + esac + compile_command="$compile_command -l$name" + deplibs="$deplibs -l$name" + else + lib_linked=no + fi + ;; + + relink) + if test "$hardcode_direct" = yes; then + compile_command="$compile_command $absdir/$linklib" + deplibs="$deplibs $absdir/$linklib" + elif test "$hardcode_minus_L" = yes; then + case "$compile_command " in + *" -L$absdir "*) ;; + *) compile_command="$compile_command -L$absdir";; + esac + compile_command="$compile_command -l$name" + deplibs="$deplibs -L$absdir -l$name" + elif test "$hardcode_shlibpath_var" = yes; then + case ":$compile_shlibpath:" in + *":$absdir:"*) ;; + *) compile_shlibpath="$compile_shlibpath$absdir:";; + esac + compile_command="$compile_command -l$name" + deplibs="$deplibs -l$name" + else + lib_linked=no + fi + ;; + + *) + lib_linked=no + ;; + esac + + if test "$lib_linked" != yes; then + $echo "$modename: configuration error: unsupported hardcode properties" + exit 1 + fi + + # Finalize command for both is simple: just hardcode it. + if test "$hardcode_direct" = yes; then + finalize_command="$finalize_command $libdir/$linklib" + elif test "$hardcode_minus_L" = yes; then + case "$finalize_command " in + *" -L$libdir "*) ;; + *) finalize_command="$finalize_command -L$libdir";; + esac + finalize_command="$finalize_command -l$name" + elif test "$hardcode_shlibpath_var" = yes; then + case ":$finalize_shlibpath:" in + *":$libdir:"*) ;; + *) finalize_shlibpath="$finalize_shlibpath$libdir:";; + esac + finalize_command="$finalize_command -l$name" + else + # We cannot seem to hardcode it, guess we'll fake it. + case "$finalize_command " in + *" -L$dir "*) ;; + *) finalize_command="$finalize_command -L$libdir";; + esac + finalize_command="$finalize_command -l$name" + fi + else + # Transform directly to old archives if we don't build new libraries. + if test -n "$pic_flag" && test -z "$old_library"; then + $echo "$modename: cannot find static library for \`$arg'" 1>&2 + exit 1 + fi + + # Here we assume that one of hardcode_direct or hardcode_minus_L + # is not unsupported. This is valid on all known static and + # shared platforms. + if test "$hardcode_direct" != unsupported; then + test -n "$old_library" && linklib="$old_library" + compile_command="$compile_command $dir/$linklib" + finalize_command="$finalize_command $dir/$linklib" + else + case "$compile_command " in + *" -L$dir "*) ;; + *) compile_command="$compile_command -L$dir";; + esac + compile_command="$compile_command -l$name" + case "$finalize_command " in + *" -L$dir "*) ;; + *) finalize_command="$finalize_command -L$dir";; + esac + finalize_command="$finalize_command -l$name" + fi + fi + + # Add in any libraries that this one depends upon. + compile_command="$compile_command$dependency_libs" + finalize_command="$finalize_command$dependency_libs" + continue + ;; + + # Some other compiler argument. + *) + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case "$arg" in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*) + arg="\"$arg\"" + ;; + esac + ;; + esac + + # Now actually substitute the argument into the commands. + if test -n "$arg"; then + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + fi + done + + if test -n "$prev"; then + $echo "$modename: the \`$prevarg' option requires an argument" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + if test "$export_dynamic" = yes && test -n "$export_dynamic_flag_spec"; then + eval arg=\"$export_dynamic_flag_spec\" + compile_command="$compile_command $arg" + finalize_command="$finalize_command $arg" + fi + + oldlibs= + # calculate the name of the file, without its directory + outputname=`$echo "X$output" | $Xsed -e 's%^.*/%%'` + libobjs_save="$libobjs" + + case "$output" in + "") + $echo "$modename: you must specify an output file" 1>&2 + $echo "$help" 1>&2 + exit 1 + ;; + + *.a | *.lib) + if test -n "$link_against_libtool_libs"; then + $echo "$modename: error: cannot link libtool libraries into archives" 1>&2 + exit 1 + fi + + if test -n "$deplibs"; then + $echo "$modename: warning: \`-l' and \`-L' are ignored for archives" 1>&2 + fi + + if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then + $echo "$modename: warning: \`-dlopen' is ignored for archives" 1>&2 + fi + + if test -n "$rpath"; then + $echo "$modename: warning: \`-rpath' is ignored for archives" 1>&2 + fi + + if test -n "$xrpath"; then + $echo "$modename: warning: \`-R' is ignored for archives" 1>&2 + fi + + if test -n "$vinfo"; then + $echo "$modename: warning: \`-version-info' is ignored for archives" 1>&2 + fi + + if test -n "$release"; then + $echo "$modename: warning: \`-release' is ignored for archives" 1>&2 + fi + + if test -n "$export_symbols" || test -n "$export_symbols_regex"; then + $echo "$modename: warning: \`-export-symbols' is ignored for archives" 1>&2 + fi + + # Now set the variables for building old libraries. + build_libtool_libs=no + oldlibs="$output" + ;; + + *.la) + # Make sure we only generate libraries of the form `libNAME.la'. + case "$outputname" in + lib*) + name=`$echo "X$outputname" | $Xsed -e 's/\.la$//' -e 's/^lib//'` + eval libname=\"$libname_spec\" + ;; + *) + if test "$module" = no; then + $echo "$modename: libtool library \`$output' must begin with \`lib'" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + if test "$need_lib_prefix" != no; then + # Add the "lib" prefix for modules if required + name=`$echo "X$outputname" | $Xsed -e 's/\.la$//'` + eval libname=\"$libname_spec\" + else + libname=`$echo "X$outputname" | $Xsed -e 's/\.la$//'` + fi + ;; + esac + + output_objdir=`$echo "X$output" | $Xsed -e 's%/[^/]*$%%'` + if test "X$output_objdir" = "X$output"; then + output_objdir="$objdir" + else + output_objdir="$output_objdir/$objdir" + fi + + if test -n "$objs"; then + $echo "$modename: cannot build libtool library \`$output' from non-libtool objects:$objs" 2>&1 + exit 1 + fi + + # How the heck are we supposed to write a wrapper for a shared library? + if test -n "$link_against_libtool_libs"; then + $echo "$modename: error: cannot link shared libraries into libtool libraries" 1>&2 + exit 1 + fi + + if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then + $echo "$modename: warning: \`-dlopen' is ignored for libtool libraries" 1>&2 + fi + + set dummy $rpath + if test $# -gt 2; then + $echo "$modename: warning: ignoring multiple \`-rpath's for a libtool library" 1>&2 + fi + install_libdir="$2" + + oldlibs= + if test -z "$rpath"; then + if test "$build_libtool_libs" = yes; then + # Building a libtool convenience library. + libext=al + oldlibs="$output_objdir/$libname.$libext $oldlibs" + build_libtool_libs=convenience + build_old_libs=yes + fi + dependency_libs="$deplibs" + + if test -n "$vinfo"; then + $echo "$modename: warning: \`-version-info' is ignored for convenience libraries" 1>&2 + fi + + if test -n "$release"; then + $echo "$modename: warning: \`-release' is ignored for convenience libraries" 1>&2 + fi + else + + # Parse the version information argument. + IFS="${IFS= }"; save_ifs="$IFS"; IFS=':' + set dummy $vinfo 0 0 0 + IFS="$save_ifs" + + if test -n "$8"; then + $echo "$modename: too many parameters to \`-version-info'" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + current="$2" + revision="$3" + age="$4" + + # Check that each of the things are valid numbers. + case "$current" in + 0 | [1-9] | [1-9][0-9]*) ;; + *) + $echo "$modename: CURRENT \`$current' is not a nonnegative integer" 1>&2 + $echo "$modename: \`$vinfo' is not valid version information" 1>&2 + exit 1 + ;; + esac + + case "$revision" in + 0 | [1-9] | [1-9][0-9]*) ;; + *) + $echo "$modename: REVISION \`$revision' is not a nonnegative integer" 1>&2 + $echo "$modename: \`$vinfo' is not valid version information" 1>&2 + exit 1 + ;; + esac + + case "$age" in + 0 | [1-9] | [1-9][0-9]*) ;; + *) + $echo "$modename: AGE \`$age' is not a nonnegative integer" 1>&2 + $echo "$modename: \`$vinfo' is not valid version information" 1>&2 + exit 1 + ;; + esac + + if test $age -gt $current; then + $echo "$modename: AGE \`$age' is greater than the current interface number \`$current'" 1>&2 + $echo "$modename: \`$vinfo' is not valid version information" 1>&2 + exit 1 + fi + + # Calculate the version variables. + major= + versuffix= + verstring= + case "$version_type" in + none) ;; + + irix) + major=`expr $current - $age + 1` + versuffix="$major.$revision" + verstring="sgi$major.$revision" + + # Add in all the interfaces that we are compatible with. + loop=$revision + while test $loop != 0; do + iface=`expr $revision - $loop` + loop=`expr $loop - 1` + verstring="sgi$major.$iface:$verstring" + done + ;; + + linux) + major=.`expr $current - $age` + versuffix="$major.$age.$revision" + ;; + + osf) + major=`expr $current - $age` + versuffix=".$current.$age.$revision" + verstring="$current.$age.$revision" + + # Add in all the interfaces that we are compatible with. + loop=$age + while test $loop != 0; do + iface=`expr $current - $loop` + loop=`expr $loop - 1` + verstring="$verstring:${iface}.0" + done + + # Make executables depend on our current version. + verstring="$verstring:${current}.0" + ;; + + sunos) + major=".$current" + versuffix=".$current.$revision" + ;; + + freebsd-aout) + major=".$current" + versuffix=".$current.$revision"; + ;; + + freebsd-elf) + major=".$current" + versuffix=".$current"; + ;; + + windows) + # Like Linux, but with '-' rather than '.', since we only + # want one extension on Windows 95. + major=`expr $current - $age` + versuffix="-$major-$age-$revision" + ;; + + *) + $echo "$modename: unknown library version type \`$version_type'" 1>&2 + echo "Fatal configuration error. See the $PACKAGE docs for more information." 1>&2 + exit 1 + ;; + esac + + # Clear the version info if we defaulted, and they specified a release. + if test -z "$vinfo" && test -n "$release"; then + major= + verstring="0.0" + if test "$need_version" = no; then + versuffix= + else + versuffix=".0.0" + fi + fi + + # Remove version info from name if versioning should be avoided + if test "$avoid_version" = yes && test "$need_version" = no; then + major= + versuffix= + verstring="" + fi + + # Check to see if the archive will have undefined symbols. + if test "$allow_undefined" = yes; then + if test "$allow_undefined_flag" = unsupported; then + $echo "$modename: warning: undefined symbols not allowed in $host shared libraries" 1>&2 + build_libtool_libs=no + build_old_libs=yes + fi + else + # Don't allow undefined symbols. + allow_undefined_flag="$no_undefined_flag" + fi + + dependency_libs="$deplibs" + case "$host" in + *-*-cygwin* | *-*-mingw* | *-*-os2* | *-*-beos*) + # these systems don't actually have a c library (as such)! + ;; + *) + # Add libc to deplibs on all other systems. + deplibs="$deplibs -lc" + ;; + esac + fi + + # Create the output directory, or remove our outputs if we need to. + if test -d $output_objdir; then + $show "${rm}r $output_objdir/$outputname $output_objdir/$libname.* $output_objdir/${libname}${release}.*" + $run ${rm}r $output_objdir/$outputname $output_objdir/$libname.* $output_objdir/${libname}${release}.* + else + $show "$mkdir $output_objdir" + $run $mkdir $output_objdir + status=$? + if test $status -ne 0 && test ! -d $output_objdir; then + exit $status + fi + fi + + # Now set the variables for building old libraries. + if test "$build_old_libs" = yes && test "$build_libtool_libs" != convenience ; then + oldlibs="$oldlibs $output_objdir/$libname.$libext" + + # Transform .lo files to .o files. + oldobjs="$objs "`$echo "X$libobjs" | $SP2NL | $Xsed -e '/\.'${libext}'$/d' -e "$lo2o" | $NL2SP` + fi + + if test "$build_libtool_libs" = yes; then + # Transform deplibs into only deplibs that can be linked in shared. + name_save=$name + libname_save=$libname + release_save=$release + versuffix_save=$versuffix + major_save=$major + # I'm not sure if I'm treating the release correctly. I think + # release should show up in the -l (ie -lgmp5) so we don't want to + # add it in twice. Is that correct? + release="" + versuffix="" + major="" + newdeplibs= + droppeddeps=no + case "$deplibs_check_method" in + pass_all) + # Don't check for shared/static. Everything works. + # This might be a little naive. We might want to check + # whether the library exists or not. But this is on + # osf3 & osf4 and I'm not really sure... Just + # implementing what was already the behaviour. + newdeplibs=$deplibs + ;; + test_compile) + # This code stresses the "libraries are programs" paradigm to its + # limits. Maybe even breaks it. We compile a program, linking it + # against the deplibs as a proxy for the library. Then we can check + # whether they linked in statically or dynamically with ldd. + $rm conftest.c + cat > conftest.c <<EOF + int main() { return 0; } +EOF + $rm conftest + $C_compiler -o conftest conftest.c $deplibs + if test $? -eq 0 ; then + ldd_output=`ldd conftest` + for i in $deplibs; do + name="`expr $i : '-l\(.*\)'`" + # If $name is empty we are operating on a -L argument. + if test "$name" != "" ; then + libname=`eval \\$echo \"$libname_spec\"` + deplib_matches=`eval \\$echo \"$library_names_spec\"` + set dummy $deplib_matches + deplib_match=$2 + if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0 ; then + newdeplibs="$newdeplibs $i" + else + droppeddeps=yes + echo + echo "*** Warning: This library needs some functionality provided by $i." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have." + fi + else + newdeplibs="$newdeplibs $i" + fi + done + else + # Error occured in the first compile. Let's try to salvage the situation: + # Compile a seperate program for each library. + for i in $deplibs; do + name="`expr $i : '-l\(.*\)'`" + # If $name is empty we are operating on a -L argument. + if test "$name" != "" ; then + $rm conftest + $C_compiler -o conftest conftest.c $i + # Did it work? + if test $? -eq 0 ; then + ldd_output=`ldd conftest` + libname=`eval \\$echo \"$libname_spec\"` + deplib_matches=`eval \\$echo \"$library_names_spec\"` + set dummy $deplib_matches + deplib_match=$2 + if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0 ; then + newdeplibs="$newdeplibs $i" + else + droppeddeps=yes + echo + echo "*** Warning: This library needs some functionality provided by $i." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have." + fi + else + droppeddeps=yes + echo + echo "*** Warning! Library $i is needed by this library but I was not able to" + echo "*** make it link in! You will probably need to install it or some" + echo "*** library that it depends on before this library will be fully" + echo "*** functional. Installing it before continuing would be even better." + fi + else + newdeplibs="$newdeplibs $i" + fi + done + fi + ;; + file_magic*) + set dummy $deplibs_check_method + file_magic_regex="`expr \"$deplibs_check_method\" : \"$2 \(.*\)\"`" + for a_deplib in $deplibs; do + name="`expr $a_deplib : '-l\(.*\)'`" + # If $name is empty we are operating on a -L argument. + if test "$name" != "" ; then + libname=`eval \\$echo \"$libname_spec\"` + for i in $lib_search_path; do + potential_libs=`ls $i/$libname[.-]* 2>/dev/null` + for potent_lib in $potential_libs; do + # Follow soft links. + if ls -lLd "$potlib" 2>/dev/null \ + | grep " -> " >/dev/null; then + continue + fi + # The statement above tries to avoid entering an + # endless loop below, in case of cyclic links. + # We might still enter an endless loop, since a link + # loop can be closed while we follow links, + # but so what? + potlib="$potent_lib" + while test -h "$potlib" 2>/dev/null; do + potliblink=`ls -ld $potlib | sed 's/.* -> //'` + case "$potliblink" in + [\\/]* | [A-Za-z]:[\\/]*) potlib="$potliblink";; + *) potlib=`$echo "X$potlib" | $Xsed -e 's,[^/]*$,,'`"$potliblink";; + esac + done + if eval $file_magic_cmd \"\$potlib\" 2>/dev/null \ + | sed 10q \ + | egrep "$file_magic_regex" > /dev/null; then + newdeplibs="$newdeplibs $a_deplib" + a_deplib="" + break 2 + fi + done + done + if test -n "$a_deplib" ; then + droppeddeps=yes + echo + echo "*** Warning: This library needs some functionality provided by $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have." + fi + else + # Add a -L argument. + newdeplibs="$newdeplibs $a_deplib" + fi + done # Gone through all deplibs. + ;; + none | unknown | *) + newdeplibs="" + if $echo "X $deplibs" | $Xsed -e 's/ -lc$//' \ + -e 's/ -[LR][^ ]*//g' -e 's/[ ]//g' | + grep . >/dev/null; then + echo + if test "X$deplibs_check_method" = "Xnone"; then + echo "*** Warning: inter-library dependencies are not supported in this platform." + else + echo "*** Warning: inter-library dependencies are not known to be supported." + fi + echo "*** All declared inter-library dependencies are being dropped." + droppeddeps=yes + fi + ;; + esac + versuffix=$versuffix_save + major=$major_save + release=$release_save + libname=$libname_save + name=$name_save + + if test "$droppeddeps" = yes; then + if test "$module" = yes; then + echo + echo "*** Warning: libtool could not satisfy all declared inter-library" + echo "*** dependencies of module $libname. Therefore, libtool will create" + echo "*** a static module, that should work as long as the dlopening" + echo "*** application is linked with the -dlopen flag." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using \`nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** \`nm' from GNU binutils and a full rebuild may help." + fi + if test "$build_old_libs" = no; then + oldlibs="$output_objdir/$libname.$libext" + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + else + echo "*** The inter-library dependencies that have been dropped here will be" + echo "*** automatically added whenever a program is linked with this library" + echo "*** or is declared to -dlopen it." + fi + fi + # Done checking deplibs! + deplibs=$newdeplibs + fi + + # All the library-specific variables (install_libdir is set above). + library_names= + old_library= + dlname= + + # Test again, we may have decided not to build it any more + if test "$build_libtool_libs" = yes; then + # Get the real and link names of the library. + eval library_names=\"$library_names_spec\" + set dummy $library_names + realname="$2" + shift; shift + + if test -n "$soname_spec"; then + eval soname=\"$soname_spec\" + else + soname="$realname" + fi + + lib="$output_objdir/$realname" + for link + do + linknames="$linknames $link" + done + + # Ensure that we have .o objects for linkers which dislike .lo + # (e.g. aix) incase we are running --disable-static + for obj in $libobjs; do + oldobj=`$echo "X$obj" | $Xsed -e "$lo2o"` + if test ! -f $oldobj; then + $show "${LN_S} $obj $oldobj" + $run ${LN_S} $obj $oldobj || exit $? + fi + done + + # Use standard objects if they are pic + test -z "$pic_flag" && libobjs=`$echo "X$libobjs" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP` + + if test -n "$whole_archive_flag_spec"; then + if test -n "$convenience"; then + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + fi + else + gentop="$output_objdir/${outputname}x" + $show "${rm}r $gentop" + $run ${rm}r "$gentop" + $show "mkdir $gentop" + $run mkdir "$gentop" + status=$? + if test $status -ne 0 && test ! -d "$gentop"; then + exit $status + fi + generated="$generated $gentop" + + for xlib in $convenience; do + # Extract the objects. + case "$xlib" in + [\\/]* | [A-Za-z]:[\\/]*) xabs="$xlib" ;; + *) xabs=`pwd`"/$xlib" ;; + esac + xlib=`$echo "X$xlib" | $Xsed -e 's%^.*/%%'` + xdir="$gentop/$xlib" + + $show "${rm}r $xdir" + $run ${rm}r "$xdir" + $show "mkdir $xdir" + $run mkdir "$xdir" + status=$? + if test $status -ne 0 && test ! -d "$xdir"; then + exit $status + fi + $show "(cd $xdir && $AR x $xabs)" + $run eval "(cd \$xdir && $AR x \$xabs)" || exit $? + + libobjs="$libobjs "`find $xdir -name \*.o -print -o -name \*.lo -print | $NL2SP` + done + fi + + if test "$thread_safe" = yes && test -n "$thread_safe_flag_spec"; then + eval flag=\"$thread_safe_flag_spec\" + linkopts="$linkopts $flag" + fi + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + if test "$always_export_symbols" = yes || test -n "$export_symbols_regex"; then + $show "generating symbol list for \`$libname.la'" + export_symbols="$output_objdir/$libname.exp" + $run $rm $export_symbols + eval cmds=\"$export_symbols_cmds\" + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + if test -n "$export_symbols_regex"; then + $show "egrep -e \"$export_symbols_regex\" \"$export_symbols\" > \"${export_symbols}T\"" + $run eval 'egrep -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + $show "$mv \"${export_symbols}T\" \"$export_symbols\"" + $run eval '$mv "${export_symbols}T" "$export_symbols"' + fi + fi + fi + + if test -n "$export_symbols" && test -n "$include_expsyms"; then + $run eval '$echo "X$include_expsyms" | $SP2NL >> "$export_symbols"' + fi + + # Do each of the archive commands. + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + eval cmds=\"$archive_expsym_cmds\" + else + eval cmds=\"$archive_cmds\" + fi + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + + # Create links to the real library. + for linkname in $linknames; do + if test "$realname" != "$linkname"; then + $show "(cd $output_objdir && $rm $linkname && $LN_S $realname $linkname)" + $run eval '(cd $output_objdir && $rm $linkname && $LN_S $realname $linkname)' || exit $? + fi + done + + # If -module or -export-dynamic was specified, set the dlname. + if test "$module" = yes || test "$export_dynamic" = yes; then + # On all known operating systems, these are identical. + dlname="$soname" + fi + fi + ;; + + *.lo | *.o | *.obj) + if test -n "$link_against_libtool_libs"; then + $echo "$modename: error: cannot link libtool libraries into objects" 1>&2 + exit 1 + fi + + if test -n "$deplibs"; then + $echo "$modename: warning: \`-l' and \`-L' are ignored for objects" 1>&2 + fi + + if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then + $echo "$modename: warning: \`-dlopen' is ignored for objects" 1>&2 + fi + + if test -n "$rpath"; then + $echo "$modename: warning: \`-rpath' is ignored for objects" 1>&2 + fi + + if test -n "$xrpath"; then + $echo "$modename: warning: \`-R' is ignored for objects" 1>&2 + fi + + if test -n "$vinfo"; then + $echo "$modename: warning: \`-version-info' is ignored for objects" 1>&2 + fi + + if test -n "$release"; then + $echo "$modename: warning: \`-release' is ignored for objects" 1>&2 + fi + + case "$output" in + *.lo) + if test -n "$objs"; then + $echo "$modename: cannot build library object \`$output' from non-libtool objects" 1>&2 + exit 1 + fi + libobj="$output" + obj=`$echo "X$output" | $Xsed -e "$lo2o"` + ;; + *) + libobj= + obj="$output" + ;; + esac + + # Delete the old objects. + $run $rm $obj $libobj + + # Create the old-style object. + reload_objs="$objs "`$echo "X$libobjs" | $SP2NL | $Xsed -e '/\.'${libext}$'/d' -e '/\.lib$/d' -e "$lo2o" | $NL2SP` + + output="$obj" + eval cmds=\"$reload_cmds\" + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + + # Exit if we aren't doing a library object file. + test -z "$libobj" && exit 0 + + if test "$build_libtool_libs" != yes; then + # Create an invalid libtool object if no PIC, so that we don't + # accidentally link it into a program. + $show "echo timestamp > $libobj" + $run eval "echo timestamp > $libobj" || exit $? + exit 0 + fi + + if test -n "$pic_flag"; then + # Only do commands if we really have different PIC objects. + reload_objs="$libobjs" + output="$libobj" + eval cmds=\"$reload_cmds\" + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + else + # Just create a symlink. + $show $rm $libobj + $run $rm $libobj + $show "$LN_S $obj $libobj" + $run $LN_S $obj $libobj || exit $? + fi + + exit 0 + ;; + + # Anything else should be a program. + *) + if test -n "$vinfo"; then + $echo "$modename: warning: \`-version-info' is ignored for programs" 1>&2 + fi + + if test -n "$release"; then + $echo "$modename: warning: \`-release' is ignored for programs" 1>&2 + fi + + if test "$preload" = yes; then + if test "$dlopen" = unknown && test "$dlopen_self" = unknown && + test "$dlopen_self_static" = unknown; then + $echo "$modename: warning: \`AC_LIBTOOL_DLOPEN' not used. Assuming no dlopen support." + fi + fi + + if test -n "$rpath$xrpath"; then + # If the user specified any rpath flags, then add them. + for libdir in $rpath $xrpath; do + # This is the magic to use -rpath. + case "$compile_rpath " in + *" $libdir "*) ;; + *) compile_rpath="$compile_rpath $libdir" ;; + esac + case "$finalize_rpath " in + *" $libdir "*) ;; + *) finalize_rpath="$finalize_rpath $libdir" ;; + esac + done + fi + + # Now hardcode the library paths + rpath= + hardcode_libdirs= + for libdir in $compile_rpath $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs="$libdir" + else + # Just accumulate the unique libdirs. + case "$hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator" in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + hardcode_libdirs="$hardcode_libdirs$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + rpath="$rpath $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) perm_rpath="$perm_rpath $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir="$hardcode_libdirs" + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + compile_rpath="$rpath" + + rpath= + hardcode_libdirs= + for libdir in $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs="$libdir" + else + # Just accumulate the unique libdirs. + case "$hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator" in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + hardcode_libdirs="$hardcode_libdirs$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + rpath="$rpath $flag" + fi + elif test -n "$runpath_var"; then + case "$finalize_perm_rpath " in + *" $libdir "*) ;; + *) finalize_perm_rpath="$finalize_perm_rpath $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir="$hardcode_libdirs" + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + finalize_rpath="$rpath" + + output_objdir=`$echo "X$output" | $Xsed -e 's%/[^/]*$%%'` + if test "X$output_objdir" = "X$output"; then + output_objdir="$objdir" + else + output_objdir="$output_objdir/$objdir" + fi + + # Create the binary in the object directory, then wrap it. + if test ! -d $output_objdir; then + $show "$mkdir $output_objdir" + $run $mkdir $output_objdir + status=$? + if test $status -ne 0 && test ! -d $output_objdir; then + exit $status + fi + fi + + if test -n "$libobjs" && test "$build_old_libs" = yes; then + # Transform all the library objects into standard objects. + compile_command=`$echo "X$compile_command" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP` + finalize_command=`$echo "X$finalize_command" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP` + fi + + dlsyms= + if test -n "$dlfiles$dlprefiles" || test "$dlself" = yes; then + if test -n "$NM" && test -n "$global_symbol_pipe"; then + dlsyms="${outputname}S.c" + else + $echo "$modename: not configured to extract global symbols from dlpreopened files" 1>&2 + fi + fi + + if test -n "$dlsyms"; then + case "$dlsyms" in + "") ;; + *.c) + # Discover the nlist of each of the dlfiles. + nlist="$output_objdir/${outputname}.nm" + + $show "$rm $nlist ${nlist}S ${nlist}T" + $run $rm "$nlist" "${nlist}S" "${nlist}T" + + # Parse the name list into a source file. + $show "creating $output_objdir/$dlsyms" + + test -z "$run" && $echo > "$output_objdir/$dlsyms" "\ +/* $dlsyms - symbol resolution table for \`$outputname' dlsym emulation. */ +/* Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP */ + +#ifdef __cplusplus +extern \"C\" { +#endif + +/* Prevent the only kind of declaration conflicts we can make. */ +#define lt_preloaded_symbols some_other_symbol + +/* External symbol declarations for the compiler. */\ +" + + if test "$dlself" = yes; then + $show "generating symbol list for \`$output'" + + test -z "$run" && $echo ': @PROGRAM@ ' > "$nlist" + + # Add our own program objects to the symbol list. + progfiles=`$echo "X$objs" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP` + for arg in $progfiles; do + $show "extracting global C symbols from \`$arg'" + $run eval "$NM $arg | $global_symbol_pipe >> '$nlist'" + done + + if test -n "$exclude_expsyms"; then + $run eval 'egrep -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T' + $run eval '$mv "$nlist"T "$nlist"' + fi + + if test -n "$export_symbols_regex"; then + $run eval 'egrep -e "$export_symbols_regex" "$nlist" > "$nlist"T' + $run eval '$mv "$nlist"T "$nlist"' + fi + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + export_symbols="$output_objdir/$output.exp" + $run $rm $export_symbols + $run eval "sed -n -e '/^: @PROGRAM@$/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"' + else + $run eval "sed -e 's/\([][.*^$]\)/\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$output.exp"' + $run eval 'grep -f "$output_objdir/$output.exp" < "$nlist" > "$nlist"T' + $run eval 'mv "$nlist"T "$nlist"' + fi + fi + + for arg in $dlprefiles; do + $show "extracting global C symbols from \`$arg'" + name=`echo "$arg" | sed -e 's%^.*/%%'` + $run eval 'echo ": $name " >> "$nlist"' + $run eval "$NM $arg | $global_symbol_pipe >> '$nlist'" + done + + if test -z "$run"; then + # Make sure we have at least an empty file. + test -f "$nlist" || : > "$nlist" + + if test -n "$exclude_expsyms"; then + egrep -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T + $mv "$nlist"T "$nlist" + fi + + # Try sorting and uniquifying the output. + if grep -v "^: " < "$nlist" | sort +2 | uniq > "$nlist"S; then + : + else + grep -v "^: " < "$nlist" > "$nlist"S + fi + + if test -f "$nlist"S; then + eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$dlsyms"' + else + echo '/* NONE */' >> "$output_objdir/$dlsyms" + fi + + $echo >> "$output_objdir/$dlsyms" "\ + +#undef lt_preloaded_symbols + +#if defined (__STDC__) && __STDC__ +# define lt_ptr_t void * +#else +# define lt_ptr_t char * +# define const +#endif + +/* The mapping between symbol names and symbols. */ +const struct { + const char *name; + lt_ptr_t address; +} +lt_preloaded_symbols[] = +{\ +" + + sed -n -e 's/^: \([^ ]*\) $/ {\"\1\", (lt_ptr_t) 0},/p' \ + -e 's/^. \([^ ]*\) \([^ ]*\)$/ {"\2", (lt_ptr_t) \&\2},/p' \ + < "$nlist" >> "$output_objdir/$dlsyms" + + $echo >> "$output_objdir/$dlsyms" "\ + {0, (lt_ptr_t) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif\ +" + fi + + pic_flag_for_symtable= + case "$host" in + # compiling the symbol table file with pic_flag works around + # a FreeBSD bug that causes programs to crash when -lm is + # linked before any other PIC object. But we must not use + # pic_flag when linking with -static. The problem exists in + # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1. + *-*-freebsd2*|*-*-freebsd3.0*) + case "$compile_command " in + *" -static "*) ;; + *) pic_flag_for_symtable=" $pic_flag -DPIC -DFREEBSD_WORKAROUND";; + esac + esac + + # Now compile the dynamic symbol file. + $show "(cd $output_objdir && $C_compiler -c$no_builtin_flag$pic_flag_for_symtable \"$dlsyms\")" + $run eval '(cd $output_objdir && $C_compiler -c$no_builtin_flag$pic_flag_for_symtable "$dlsyms")' || exit $? + + # Clean up the generated files. + $show "$rm $output_objdir/$dlsyms $nlist ${nlist}S ${nlist}T" + $run $rm "$output_objdir/$dlsyms" "$nlist" "${nlist}S" "${nlist}T" + + # Transform the symbol file into the correct name. + compile_command=`$echo "X$compile_command" | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}S.${objext}%"` + finalize_command=`$echo "X$finalize_command" | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}S.${objext}%"` + ;; + *) + $echo "$modename: unknown suffix for \`$dlsyms'" 1>&2 + exit 1 + ;; + esac + else + # We keep going just in case the user didn't refer to + # lt_preloaded_symbols. The linker will fail if global_symbol_pipe + # really was required. + + # Nullify the symbol file. + compile_command=`$echo "X$compile_command" | $Xsed -e "s% @SYMFILE@%%"` + finalize_command=`$echo "X$finalize_command" | $Xsed -e "s% @SYMFILE@%%"` + fi + + if test -z "$link_against_libtool_libs" || test "$build_libtool_libs" != yes; then + # Replace the output file specification. + compile_command=`$echo "X$compile_command" | $Xsed -e 's%@OUTPUT@%'"$output"'%g'` + link_command="$compile_command$compile_rpath" + + # We have no uninstalled library dependencies, so finalize right now. + $show "$link_command" + $run eval "$link_command" + status=$? + + # Delete the generated files. + if test -n "$dlsyms"; then + $show "$rm $output_objdir/${outputname}S.${objext}" + $run $rm "$output_objdir/${outputname}S.${objext}" + fi + + exit $status + fi + + if test -n "$shlibpath_var"; then + # We should set the shlibpath_var + rpath= + for dir in $temp_rpath; do + case "$dir" in + [\\/]* | [A-Za-z]:[\\/]*) + # Absolute path. + rpath="$rpath$dir:" + ;; + *) + # Relative path: add a thisdir entry. + rpath="$rpath\$thisdir/$dir:" + ;; + esac + done + temp_rpath="$rpath" + fi + + if test -n "$compile_shlibpath$finalize_shlibpath"; then + compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command" + fi + if test -n "$finalize_shlibpath"; then + finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command" + fi + + compile_var= + finalize_var= + if test -n "$runpath_var"; then + if test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + rpath="$rpath$dir:" + done + compile_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + if test -n "$finalize_perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $finalize_perm_rpath; do + rpath="$rpath$dir:" + done + finalize_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + fi + + if test "$hardcode_action" = relink; then + # Fast installation is not supported + link_command="$compile_var$compile_command$compile_rpath" + relink_command="$finalize_var$finalize_command$finalize_rpath" + + $echo "$modename: warning: this platform does not like uninstalled shared libraries" 1>&2 + $echo "$modename: \`$output' will be relinked during installation" 1>&2 + else + if test "$fast_install" != no; then + link_command="$finalize_var$compile_command$finalize_rpath" + if test "$fast_install" = yes; then + relink_command=`$echo "X$compile_var$compile_command$compile_rpath" | $Xsed -e 's%@OUTPUT@%\$progdir/\$file%g'` + else + # fast_install is set to needless + relink_command= + fi + else + link_command="$compile_var$compile_command$compile_rpath" + relink_command="$finalize_var$finalize_command$finalize_rpath" + fi + fi + + # Replace the output file specification. + link_command=`$echo "X$link_command" | $Xsed -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'` + + # Delete the old output files. + $run $rm $output $output_objdir/$outputname $output_objdir/lt-$outputname + + $show "$link_command" + $run eval "$link_command" || exit $? + + # Now create the wrapper script. + $show "creating $output" + + # Quote the relink command for shipping. + if test -n "$relink_command"; then + relink_command=`$echo "X$relink_command" | $Xsed -e "$sed_quote_subst"` + fi + + # Quote $echo for shipping. + if test "X$echo" = "X$SHELL $0 --fallback-echo"; then + case "$0" in + [\\/]* | [A-Za-z]:[\\/]*) qecho="$SHELL $0 --fallback-echo";; + *) qecho="$SHELL `pwd`/$0 --fallback-echo";; + esac + qecho=`$echo "X$qecho" | $Xsed -e "$sed_quote_subst"` + else + qecho=`$echo "X$echo" | $Xsed -e "$sed_quote_subst"` + fi + + # Only actually do things if our run command is non-null. + if test -z "$run"; then + # win32 will think the script is a binary if it has + # a .exe suffix, so we strip it off here. + case $output in + *.exe) output=`echo $output|sed 's,.exe$,,'` ;; + esac + $rm $output + trap "$rm $output; exit 1" 1 2 15 + + $echo > $output "\ +#! $SHELL + +# $output - temporary wrapper script for $objdir/$outputname +# Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP +# +# The $output program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +Xsed='sed -e 1s/^X//' +sed_quote_subst='$sed_quote_subst' + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +if test \"\${CDPATH+set}\" = set; then CDPATH=; export CDPATH; fi + +relink_command=\"$relink_command\" + +# This environment variable determines our operation mode. +if test \"\$libtool_install_magic\" = \"$magic\"; then + # install mode needs the following variable: + link_against_libtool_libs='$link_against_libtool_libs' +else + # When we are sourced in execute mode, \$file and \$echo are already set. + if test \"\$libtool_execute_magic\" != \"$magic\"; then + echo=\"$qecho\" + file=\"\$0\" + # Make sure echo works. + if test \"X\$1\" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift + elif test \"X\`(\$echo '\t') 2>/dev/null\`\" = 'X\t'; then + # Yippee, \$echo works! + : + else + # Restart under the correct shell, and then maybe \$echo will work. + exec $SHELL \"\$0\" --no-reexec \${1+\"\$@\"} + fi + fi\ +" + $echo >> $output "\ + + # Find the directory that this script lives in. + thisdir=\`\$echo \"X\$file\" | \$Xsed -e 's%/[^/]*$%%'\` + test \"x\$thisdir\" = \"x\$file\" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=\`ls -ld \"\$file\" | sed -n 's/.*-> //p'\` + while test -n \"\$file\"; do + destdir=\`\$echo \"X\$file\" | \$Xsed -e 's%/[^/]*\$%%'\` + + # If there was a directory component, then change thisdir. + if test \"x\$destdir\" != \"x\$file\"; then + case \"\$destdir\" in + [\\/]* | [A-Za-z]:[\\/]*) thisdir=\"\$destdir\" ;; + *) thisdir=\"\$thisdir/\$destdir\" ;; + esac + fi + + file=\`\$echo \"X\$file\" | \$Xsed -e 's%^.*/%%'\` + file=\`ls -ld \"\$thisdir/\$file\" | sed -n 's/.*-> //p'\` + done + + # Try to get the absolute directory name. + absdir=\`cd \"\$thisdir\" && pwd\` + test -n \"\$absdir\" && thisdir=\"\$absdir\" +" + + if test "$fast_install" = yes; then + echo >> $output "\ + program=lt-'$outputname' + progdir=\"\$thisdir/$objdir\" + + if test ! -f \"\$progdir/\$program\" || \\ + { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | sed 1q\`; \\ + test \"X\$file\" != \"X\$progdir/\$program\"; }; then + + file=\"\$\$-\$program\" + + if test ! -d \"\$progdir\"; then + $mkdir \"\$progdir\" + else + $rm \"\$progdir/\$file\" + fi" + + echo >> $output "\ + + # relink executable if necessary + if test -n \"\$relink_command\"; then + if (cd \"\$thisdir\" && eval \$relink_command); then : + else + $rm \"\$progdir/\$file\" + exit 1 + fi + fi + + $mv \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null || + { $rm \"\$progdir/\$program\"; + $mv \"\$progdir/\$file\" \"\$progdir/\$program\"; } + $rm \"\$progdir/\$file\" + fi" + else + echo >> $output "\ + program='$outputname' + progdir=\"\$thisdir/$objdir\" +" + fi + + echo >> $output "\ + + if test -f \"\$progdir/\$program\"; then" + + # Export our shlibpath_var if we have one. + if test "$shlibpath_overrides_runpath" = yes && test -n "$shlibpath_var" && test -n "$temp_rpath"; then + $echo >> $output "\ + # Add our own library path to $shlibpath_var + $shlibpath_var=\"$temp_rpath\$$shlibpath_var\" + + # Some systems cannot cope with colon-terminated $shlibpath_var + # The second colon is a workaround for a bug in BeOS R4 sed + $shlibpath_var=\`\$echo \"X\$$shlibpath_var\" | \$Xsed -e 's/::*\$//'\` + + export $shlibpath_var +" + fi + + # fixup the dll searchpath if we need to. + if test -n "$dllsearchpath"; then + $echo >> $output "\ + # Add the dll search path components to the executable PATH + PATH=$dllsearchpath:\$PATH +" + fi + + $echo >> $output "\ + if test \"\$libtool_execute_magic\" != \"$magic\"; then + # Run the actual program with our arguments. +" + case $host in + *-*-cygwin* | *-*-mingw | *-*-os2*) + # win32 systems need to use the prog path for dll + # lookup to work + $echo >> $output "\ + exec \$progdir\\\\\$program \${1+\"\$@\"} +" + ;; + *) + $echo >> $output "\ + # Export the path to the program. + PATH=\"\$progdir:\$PATH\" + export PATH + + exec \$program \${1+\"\$@\"} +" + ;; + esac + $echo >> $output "\ + \$echo \"\$0: cannot exec \$program \${1+\"\$@\"}\" + exit 1 + fi + else + # The program doesn't exist. + \$echo \"\$0: error: \$progdir/\$program does not exist\" 1>&2 + \$echo \"This script is just a wrapper for \$program.\" 1>&2 + echo \"See the $PACKAGE documentation for more information.\" 1>&2 + exit 1 + fi +fi\ +" + chmod +x $output + fi + exit 0 + ;; + esac + + # See if we need to build an old-fashioned archive. + for oldlib in $oldlibs; do + + if test "$build_libtool_libs" = convenience; then + oldobjs="$libobjs_save" + addlibs="$convenience" + build_libtool_libs=no + else + if test "$build_libtool_libs" = module; then + oldobjs="$libobjs_save" + build_libtool_libs=no + else + oldobjs="$objs "`$echo "X$libobjs_save" | $SP2NL | $Xsed -e '/\.'${libext}'$/d' -e '/\.lib$/d' -e "$lo2o" | $NL2SP` + fi + addlibs="$old_convenience" + fi + + if test -n "$addlibs"; then + gentop="$output_objdir/${outputname}x" + $show "${rm}r $gentop" + $run ${rm}r "$gentop" + $show "mkdir $gentop" + $run mkdir "$gentop" + status=$? + if test $status -ne 0 && test ! -d "$gentop"; then + exit $status + fi + generated="$generated $gentop" + + # Add in members from convenience archives. + for xlib in $addlibs; do + # Extract the objects. + case "$xlib" in + [\\/]* | [A-Za-z]:[\\/]*) xabs="$xlib" ;; + *) xabs=`pwd`"/$xlib" ;; + esac + xlib=`$echo "X$xlib" | $Xsed -e 's%^.*/%%'` + xdir="$gentop/$xlib" + + $show "${rm}r $xdir" + $run ${rm}r "$xdir" + $show "mkdir $xdir" + $run mkdir "$xdir" + status=$? + if test $status -ne 0 && test ! -d "$xdir"; then + exit $status + fi + $show "(cd $xdir && $AR x $xabs)" + $run eval "(cd \$xdir && $AR x \$xabs)" || exit $? + + oldobjs="$oldobjs "`find $xdir -name \*.${objext} -print -o -name \*.lo -print | $NL2SP` + done + fi + + # Do each command in the archive commands. + if test -n "$old_archive_from_new_cmds" && test "$build_libtool_libs" = yes; then + eval cmds=\"$old_archive_from_new_cmds\" + else + # Ensure that we have .o objects in place incase we decided + # not to build a shared library, and have fallen back to building + # static libs even though --disable-static was passed! + for oldobj in $oldobjs; do + if test ! -f $oldobj; then + obj=`$echo "X$oldobj" | $Xsed -e "$o2lo"` + $show "${LN_S} $obj $oldobj" + $run ${LN_S} $obj $oldobj || exit $? + fi + done + + eval cmds=\"$old_archive_cmds\" + fi + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + done + + if test -n "$generated"; then + $show "${rm}r$generated" + $run ${rm}r$generated + fi + + # Now create the libtool archive. + case "$output" in + *.la) + old_library= + test "$build_old_libs" = yes && old_library="$libname.$libext" + $show "creating $output" + + if test -n "$xrpath"; then + temp_xrpath= + for libdir in $xrpath; do + temp_xrpath="$temp_xrpath -R$libdir" + done + dependency_libs="$temp_xrpath $dependency_libs" + fi + + # Only create the output if not a dry run. + if test -z "$run"; then + for installed in no yes; do + if test "$installed" = yes; then + if test -z "$install_libdir"; then + break + fi + output="$output_objdir/$outputname"i + fi + $rm $output + $echo > $output "\ +# $outputname - a libtool library file +# Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='$dlname' + +# Names of this library. +library_names='$library_names' + +# The name of the static archive. +old_library='$old_library' + +# Libraries that this one depends upon. +dependency_libs='$dependency_libs' + +# Version information for $libname. +current=$current +age=$age +revision=$revision + +# Is this an already installed library? +installed=$installed + +# Directory that this library needs to be installed in: +libdir='$install_libdir'\ +" + done + fi + + # Do a symbolic link so that the libtool archive can be found in + # LD_LIBRARY_PATH before the program is installed. + $show "(cd $output_objdir && $rm $outputname && $LN_S ../$outputname $outputname)" + $run eval "(cd $output_objdir && $rm $outputname && $LN_S ../$outputname $outputname)" || exit $? + ;; + esac + exit 0 + ;; + + # libtool install mode + install) + modename="$modename: install" + + # There may be an optional sh(1) argument at the beginning of + # install_prog (especially on Windows NT). + if test "$nonopt" = "$SHELL" || test "$nonopt" = /bin/sh; then + # Aesthetically quote it. + arg=`$echo "X$nonopt" | $Xsed -e "$sed_quote_subst"` + case "$arg" in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*) + arg="\"$arg\"" + ;; + esac + install_prog="$arg " + arg="$1" + shift + else + install_prog= + arg="$nonopt" + fi + + # The real first argument should be the name of the installation program. + # Aesthetically quote it. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case "$arg" in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*) + arg="\"$arg\"" + ;; + esac + install_prog="$install_prog$arg" + + # We need to accept at least all the BSD install flags. + dest= + files= + opts= + prev= + install_type= + isdir=no + stripme= + for arg + do + if test -n "$dest"; then + files="$files $dest" + dest="$arg" + continue + fi + + case "$arg" in + -d) isdir=yes ;; + -f) prev="-f" ;; + -g) prev="-g" ;; + -m) prev="-m" ;; + -o) prev="-o" ;; + -s) + stripme=" -s" + continue + ;; + -*) ;; + + *) + # If the previous option needed an argument, then skip it. + if test -n "$prev"; then + prev= + else + dest="$arg" + continue + fi + ;; + esac + + # Aesthetically quote the argument. + arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"` + case "$arg" in + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*) + arg="\"$arg\"" + ;; + esac + install_prog="$install_prog $arg" + done + + if test -z "$install_prog"; then + $echo "$modename: you must specify an install program" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + if test -n "$prev"; then + $echo "$modename: the \`$prev' option requires an argument" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + if test -z "$files"; then + if test -z "$dest"; then + $echo "$modename: no file or destination specified" 1>&2 + else + $echo "$modename: you must specify a destination" 1>&2 + fi + $echo "$help" 1>&2 + exit 1 + fi + + # Strip any trailing slash from the destination. + dest=`$echo "X$dest" | $Xsed -e 's%/$%%'` + + # Check to see that the destination is a directory. + test -d "$dest" && isdir=yes + if test "$isdir" = yes; then + destdir="$dest" + destname= + else + destdir=`$echo "X$dest" | $Xsed -e 's%/[^/]*$%%'` + test "X$destdir" = "X$dest" && destdir=. + destname=`$echo "X$dest" | $Xsed -e 's%^.*/%%'` + + # Not a directory, so check to see that there is only one file specified. + set dummy $files + if test $# -gt 2; then + $echo "$modename: \`$dest' is not a directory" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + fi + case "$destdir" in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + for file in $files; do + case "$file" in + *.lo) ;; + *) + $echo "$modename: \`$destdir' must be an absolute directory name" 1>&2 + $echo "$help" 1>&2 + exit 1 + ;; + esac + done + ;; + esac + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic="$magic" + + staticlibs= + future_libdirs= + current_libdirs= + for file in $files; do + + # Do each installation. + case "$file" in + *.a | *.lib) + # Do the static libraries later. + staticlibs="$staticlibs $file" + ;; + + *.la) + # Check to see that this really is a libtool archive. + if (sed -e '2q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then : + else + $echo "$modename: \`$file' is not a valid libtool archive" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + library_names= + old_library= + # If there is no directory component, then add one. + case "$file" in + */* | *\\*) . $file ;; + *) . ./$file ;; + esac + + # Add the libdir to current_libdirs if it is the destination. + if test "X$destdir" = "X$libdir"; then + case "$current_libdirs " in + *" $libdir "*) ;; + *) current_libdirs="$current_libdirs $libdir" ;; + esac + else + # Note the libdir as a future libdir. + case "$future_libdirs " in + *" $libdir "*) ;; + *) future_libdirs="$future_libdirs $libdir" ;; + esac + fi + + dir="`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'`/" + test "X$dir" = "X$file/" && dir= + dir="$dir$objdir" + + # See the names of the shared library. + set dummy $library_names + if test -n "$2"; then + realname="$2" + shift + shift + + # Install the shared library and build the symlinks. + $show "$install_prog $dir/$realname $destdir/$realname" + $run eval "$install_prog $dir/$realname $destdir/$realname" || exit $? + test "X$dlname" = "X$realname" && dlname= + + if test $# -gt 0; then + # Delete the old symlinks, and create new ones. + for linkname + do + test "X$dlname" = "X$linkname" && dlname= + if test "$linkname" != "$realname"; then + $show "(cd $destdir && $rm $linkname && $LN_S $realname $linkname)" + $run eval "(cd $destdir && $rm $linkname && $LN_S $realname $linkname)" + fi + done + fi + + if test -n "$dlname"; then + # Install the dynamically-loadable library. + $show "$install_prog $dir/$dlname $destdir/$dlname" + $run eval "$install_prog $dir/$dlname $destdir/$dlname" || exit $? + fi + + # Do each command in the postinstall commands. + lib="$destdir/$realname" + eval cmds=\"$postinstall_cmds\" + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + fi + + # Install the pseudo-library for information purposes. + name=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + instname="$dir/$name"i + $show "$install_prog $instname $destdir/$name" + $run eval "$install_prog $instname $destdir/$name" || exit $? + + # Maybe install the static library, too. + test -n "$old_library" && staticlibs="$staticlibs $dir/$old_library" + ;; + + *.lo) + # Install (i.e. copy) a libtool object. + + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile="$destdir/$destname" + else + destfile=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + destfile="$destdir/$destfile" + fi + + # Deduce the name of the destination old-style object file. + case "$destfile" in + *.lo) + staticdest=`$echo "X$destfile" | $Xsed -e "$lo2o"` + ;; + *.o | *.obj) + staticdest="$destfile" + destfile= + ;; + *) + $echo "$modename: cannot copy a libtool object to \`$destfile'" 1>&2 + $echo "$help" 1>&2 + exit 1 + ;; + esac + + # Install the libtool object if requested. + if test -n "$destfile"; then + $show "$install_prog $file $destfile" + $run eval "$install_prog $file $destfile" || exit $? + fi + + # Install the old object if enabled. + if test "$build_old_libs" = yes; then + # Deduce the name of the old-style object file. + staticobj=`$echo "X$file" | $Xsed -e "$lo2o"` + + $show "$install_prog $staticobj $staticdest" + $run eval "$install_prog \$staticobj \$staticdest" || exit $? + fi + exit 0 + ;; + + *) + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile="$destdir/$destname" + else + destfile=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + destfile="$destdir/$destfile" + fi + + # Do a test to see if this is really a libtool program. + if (sed -e '4q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + link_against_libtool_libs= + relink_command= + + # If there is no directory component, then add one. + case "$file" in + */* | *\\*) . $file ;; + *) . ./$file ;; + esac + + # Check the variables that should have been set. + if test -z "$link_against_libtool_libs"; then + $echo "$modename: invalid libtool wrapper script \`$file'" 1>&2 + exit 1 + fi + + finalize=yes + for lib in $link_against_libtool_libs; do + # Check to see that each library is installed. + libdir= + if test -f "$lib"; then + # If there is no directory component, then add one. + case "$lib" in + */* | *\\*) . $lib ;; + *) . ./$lib ;; + esac + fi + libfile="$libdir/`$echo "X$lib" | $Xsed -e 's%^.*/%%g'`" + if test -n "$libdir" && test ! -f "$libfile"; then + $echo "$modename: warning: \`$lib' has not been installed in \`$libdir'" 1>&2 + finalize=no + fi + done + + outputname= + if test "$fast_install" = no && test -n "$relink_command"; then + if test "$finalize" = yes && test -z "$run"; then + tmpdir="/tmp" + test -n "$TMPDIR" && tmpdir="$TMPDIR" + tmpdir="$tmpdir/libtool-$$" + if $mkdir -p "$tmpdir" && chmod 700 "$tmpdir"; then : + else + $echo "$modename: error: cannot create temporary directory \`$tmpdir'" 1>&2 + continue + fi + outputname="$tmpdir/$file" + # Replace the output file specification. + relink_command=`$echo "X$relink_command" | $Xsed -e 's%@OUTPUT@%'"$outputname"'%g'` + + $show "$relink_command" + if $run eval "$relink_command"; then : + else + $echo "$modename: error: relink \`$file' with the above command before installing it" 1>&2 + ${rm}r "$tmpdir" + continue + fi + file="$outputname" + else + $echo "$modename: warning: cannot relink \`$file'" 1>&2 + fi + else + # Install the binary that we compiled earlier. + file=`$echo "X$file" | $Xsed -e "s%\([^/]*\)$%$objdir/\1%"` + fi + fi + + $show "$install_prog$stripme $file $destfile" + $run eval "$install_prog\$stripme \$file \$destfile" || exit $? + test -n "$outputname" && ${rm}r "$tmpdir" + ;; + esac + done + + for file in $staticlibs; do + name=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + + # Set up the ranlib parameters. + oldlib="$destdir/$name" + + $show "$install_prog $file $oldlib" + $run eval "$install_prog \$file \$oldlib" || exit $? + + # Do each command in the postinstall commands. + eval cmds=\"$old_postinstall_cmds\" + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || exit $? + done + IFS="$save_ifs" + done + + if test -n "$future_libdirs"; then + $echo "$modename: warning: remember to run \`$progname --finish$future_libdirs'" 1>&2 + fi + + if test -n "$current_libdirs"; then + # Maybe just do a dry run. + test -n "$run" && current_libdirs=" -n$current_libdirs" + exec $SHELL $0 --finish$current_libdirs + exit 1 + fi + + exit 0 + ;; + + # libtool finish mode + finish) + modename="$modename: finish" + libdirs="$nonopt" + admincmds= + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + for dir + do + libdirs="$libdirs $dir" + done + + for libdir in $libdirs; do + if test -n "$finish_cmds"; then + # Do each command in the finish commands. + eval cmds=\"$finish_cmds\" + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" || admincmds="$admincmds + $cmd" + done + IFS="$save_ifs" + fi + if test -n "$finish_eval"; then + # Do the single finish_eval. + eval cmds=\"$finish_eval\" + $run eval "$cmds" || admincmds="$admincmds + $cmds" + fi + done + fi + + # Exit here if they wanted silent mode. + test "$show" = : && exit 0 + + echo "----------------------------------------------------------------------" + echo "Libraries have been installed in:" + for libdir in $libdirs; do + echo " $libdir" + done + echo + echo "If you ever happen to want to link against installed libraries" + echo "in a given directory, LIBDIR, you must either use libtool, and" + echo "specify the full pathname of the library, or use \`-LLIBDIR'" + echo "flag during linking and do at least one of the following:" + if test -n "$shlibpath_var"; then + echo " - add LIBDIR to the \`$shlibpath_var' environment variable" + echo " during execution" + fi + if test -n "$runpath_var"; then + echo " - add LIBDIR to the \`$runpath_var' environment variable" + echo " during linking" + fi + if test -n "$hardcode_libdir_flag_spec"; then + libdir=LIBDIR + eval flag=\"$hardcode_libdir_flag_spec\" + + echo " - use the \`$flag' linker flag" + fi + if test -n "$admincmds"; then + echo " - have your system administrator run these commands:$admincmds" + fi + if test -f /etc/ld.so.conf; then + echo " - have your system administrator add LIBDIR to \`/etc/ld.so.conf'" + fi + echo + echo "See any operating system documentation about shared libraries for" + echo "more information, such as the ld(1) and ld.so(8) manual pages." + echo "----------------------------------------------------------------------" + exit 0 + ;; + + # libtool execute mode + execute) + modename="$modename: execute" + + # The first argument is the command name. + cmd="$nonopt" + if test -z "$cmd"; then + $echo "$modename: you must specify a COMMAND" 1>&2 + $echo "$help" + exit 1 + fi + + # Handle -dlopen flags immediately. + for file in $execute_dlfiles; do + if test ! -f "$file"; then + $echo "$modename: \`$file' is not a file" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + dir= + case "$file" in + *.la) + # Check to see that this really is a libtool archive. + if (sed -e '2q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then : + else + $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + # Read the libtool library. + dlname= + library_names= + + # If there is no directory component, then add one. + case "$file" in + */* | *\\*) . $file ;; + *) . ./$file ;; + esac + + # Skip this library if it cannot be dlopened. + if test -z "$dlname"; then + # Warn if it was a shared library. + test -n "$library_names" && $echo "$modename: warning: \`$file' was not linked with \`-export-dynamic'" + continue + fi + + dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'` + test "X$dir" = "X$file" && dir=. + + if test -f "$dir/$objdir/$dlname"; then + dir="$dir/$objdir" + else + $echo "$modename: cannot find \`$dlname' in \`$dir' or \`$dir/$objdir'" 1>&2 + exit 1 + fi + ;; + + *.lo) + # Just add the directory containing the .lo file. + dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'` + test "X$dir" = "X$file" && dir=. + ;; + + *) + $echo "$modename: warning \`-dlopen' is ignored for non-libtool libraries and objects" 1>&2 + continue + ;; + esac + + # Get the absolute pathname. + absdir=`cd "$dir" && pwd` + test -n "$absdir" && dir="$absdir" + + # Now add the directory to shlibpath_var. + if eval "test -z \"\$$shlibpath_var\""; then + eval "$shlibpath_var=\"\$dir\"" + else + eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\"" + fi + done + + # This variable tells wrapper scripts just to set shlibpath_var + # rather than running their programs. + libtool_execute_magic="$magic" + + # Check if any of the arguments is a wrapper script. + args= + for file + do + case "$file" in + -*) ;; + *) + # Do a test to see if this is really a libtool program. + if (sed -e '4q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + # If there is no directory component, then add one. + case "$file" in + */* | *\\*) . $file ;; + *) . ./$file ;; + esac + + # Transform arg to wrapped name. + file="$progdir/$program" + fi + ;; + esac + # Quote arguments (to preserve shell metacharacters). + file=`$echo "X$file" | $Xsed -e "$sed_quote_subst"` + args="$args \"$file\"" + done + + if test -z "$run"; then + # Export the shlibpath_var. + eval "export $shlibpath_var" + + # Restore saved enviroment variables + if test "${save_LC_ALL+set}" = set; then + LC_ALL="$save_LC_ALL"; export LC_ALL + fi + if test "${save_LANG+set}" = set; then + LANG="$save_LANG"; export LANG + fi + + # Now actually exec the command. + eval "exec \$cmd$args" + + $echo "$modename: cannot exec \$cmd$args" + exit 1 + else + # Display what would be done. + eval "\$echo \"\$shlibpath_var=\$$shlibpath_var\"" + $echo "export $shlibpath_var" + $echo "$cmd$args" + exit 0 + fi + ;; + + # libtool uninstall mode + uninstall) + modename="$modename: uninstall" + rm="$nonopt" + files= + + for arg + do + case "$arg" in + -*) rm="$rm $arg" ;; + *) files="$files $arg" ;; + esac + done + + if test -z "$rm"; then + $echo "$modename: you must specify an RM program" 1>&2 + $echo "$help" 1>&2 + exit 1 + fi + + for file in $files; do + dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'` + test "X$dir" = "X$file" && dir=. + name=`$echo "X$file" | $Xsed -e 's%^.*/%%'` + + rmfiles="$file" + + case "$name" in + *.la) + # Possibly a libtool archive, so verify it. + if (sed -e '2q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then + . $dir/$name + + # Delete the libtool libraries and symlinks. + for n in $library_names; do + rmfiles="$rmfiles $dir/$n" + test "X$n" = "X$dlname" && dlname= + done + test -n "$dlname" && rmfiles="$rmfiles $dir/$dlname" + test -n "$old_library" && rmfiles="$rmfiles $dir/$old_library" + + $show "$rm $rmfiles" + $run $rm $rmfiles + + if test -n "$library_names"; then + # Do each command in the postuninstall commands. + eval cmds=\"$postuninstall_cmds\" + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" + done + IFS="$save_ifs" + fi + + if test -n "$old_library"; then + # Do each command in the old_postuninstall commands. + eval cmds=\"$old_postuninstall_cmds\" + IFS="${IFS= }"; save_ifs="$IFS"; IFS='~' + for cmd in $cmds; do + IFS="$save_ifs" + $show "$cmd" + $run eval "$cmd" + done + IFS="$save_ifs" + fi + + # FIXME: should reinstall the best remaining shared library. + fi + ;; + + *.lo) + if test "$build_old_libs" = yes; then + oldobj=`$echo "X$name" | $Xsed -e "$lo2o"` + rmfiles="$rmfiles $dir/$oldobj" + fi + $show "$rm $rmfiles" + $run $rm $rmfiles + ;; + + *) + $show "$rm $rmfiles" + $run $rm $rmfiles + ;; + esac + done + exit 0 + ;; + + "") + $echo "$modename: you must specify a MODE" 1>&2 + $echo "$generic_help" 1>&2 + exit 1 + ;; + esac + + $echo "$modename: invalid operation mode \`$mode'" 1>&2 + $echo "$generic_help" 1>&2 + exit 1 +fi # test -z "$show_help" + +# We need to display help for each of the modes. +case "$mode" in +"") $echo \ +"Usage: $modename [OPTION]... [MODE-ARG]... + +Provide generalized library-building support services. + + --config show all configuration variables + --debug enable verbose shell tracing +-n, --dry-run display commands without modifying any files + --features display basic configuration information and exit + --finish same as \`--mode=finish' + --help display this help message and exit + --mode=MODE use operation mode MODE [default=inferred from MODE-ARGS] + --quiet same as \`--silent' + --silent don't print informational messages + --version print version information + +MODE must be one of the following: + + compile compile a source file into a libtool object + execute automatically set library path, then run a program + finish complete the installation of libtool libraries + install install libraries or executables + link create a library or an executable + uninstall remove libraries from an installed directory + +MODE-ARGS vary depending on the MODE. Try \`$modename --help --mode=MODE' for +a more detailed description of MODE." + exit 0 + ;; + +compile) + $echo \ +"Usage: $modename [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE + +Compile a source file into a libtool library object. + +This mode accepts the following additional options: + + -o OUTPUT-FILE set the output file name to OUTPUT-FILE + -static always build a \`.o' file suitable for static linking + +COMPILE-COMMAND is a command to be used in creating a \`standard' object file +from the given SOURCEFILE. + +The output file name is determined by removing the directory component from +SOURCEFILE, then substituting the C source code suffix \`.c' with the +library object suffix, \`.lo'." + ;; + +execute) + $echo \ +"Usage: $modename [OPTION]... --mode=execute COMMAND [ARGS]... + +Automatically set library path, then run a program. + +This mode accepts the following additional options: + + -dlopen FILE add the directory containing FILE to the library path + +This mode sets the library path environment variable according to \`-dlopen' +flags. + +If any of the ARGS are libtool executable wrappers, then they are translated +into their corresponding uninstalled binary, and any of their required library +directories are added to the library path. + +Then, COMMAND is executed, with ARGS as arguments." + ;; + +finish) + $echo \ +"Usage: $modename [OPTION]... --mode=finish [LIBDIR]... + +Complete the installation of libtool libraries. + +Each LIBDIR is a directory that contains libtool libraries. + +The commands that this mode executes may require superuser privileges. Use +the \`--dry-run' option if you just want to see what would be executed." + ;; + +install) + $echo \ +"Usage: $modename [OPTION]... --mode=install INSTALL-COMMAND... + +Install executables or libraries. + +INSTALL-COMMAND is the installation command. The first component should be +either the \`install' or \`cp' program. + +The rest of the components are interpreted as arguments to that command (only +BSD-compatible install options are recognized)." + ;; + +link) + $echo \ +"Usage: $modename [OPTION]... --mode=link LINK-COMMAND... + +Link object files or libraries together to form another library, or to +create an executable program. + +LINK-COMMAND is a command using the C compiler that you would use to create +a program from several object files. + +The following components of LINK-COMMAND are treated specially: + + -all-static do not do any dynamic linking at all + -avoid-version do not add a version suffix if possible + -dlopen FILE \`-dlpreopen' FILE if it cannot be dlopened at runtime + -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols + -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3) + -export-symbols SYMFILE + try to export only the symbols listed in SYMFILE + -export-symbols-regex REGEX + try to export only the symbols matching REGEX + -LLIBDIR search LIBDIR for required installed libraries + -lNAME OUTPUT-FILE requires the installed library libNAME + -module build a library that can dlopened + -no-undefined declare that a library does not refer to external symbols + -o OUTPUT-FILE create OUTPUT-FILE from the specified objects + -release RELEASE specify package release information + -rpath LIBDIR the created library will eventually be installed in LIBDIR + -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries + -static do not do any dynamic linking of libtool libraries + -version-info CURRENT[:REVISION[:AGE]] + specify library version info [each variable defaults to 0] + +All other options (arguments beginning with \`-') are ignored. + +Every other argument is treated as a filename. Files ending in \`.la' are +treated as uninstalled libtool libraries, other files are standard or library +object files. + +If the OUTPUT-FILE ends in \`.la', then a libtool library is created, +only library objects (\`.lo' files) may be specified, and \`-rpath' is +required, except when creating a convenience library. + +If OUTPUT-FILE ends in \`.a' or \`.lib', then a standard library is created +using \`ar' and \`ranlib', or on Windows using \`lib'. + +If OUTPUT-FILE ends in \`.lo' or \`.${objext}', then a reloadable object file +is created, otherwise an executable program is created." + ;; + +uninstall) + $echo \ +"Usage: $modename [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE... + +Remove libraries from an installation directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically \`/bin/rm'). RM-OPTIONS are options (such as \`-f') to be passed +to RM. + +If FILE is a libtool library, all the files associated with it are deleted. +Otherwise, only FILE itself is deleted using RM." + ;; + +*) + $echo "$modename: invalid operation mode \`$mode'" 1>&2 + $echo "$help" 1>&2 + exit 1 + ;; +esac + +echo +$echo "Try \`$modename --help' for more information about other modes." + +exit 0 + +# Local Variables: +# mode:shell-script +# sh-indentation:2 +# End: diff --git a/neon/macros/ChangeLog b/neon/macros/ChangeLog new file mode 100644 index 000000000..9a59cc69a --- /dev/null +++ b/neon/macros/ChangeLog @@ -0,0 +1,26 @@ +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/neon/macros/ac_c_bigendian_cross.m4 b/neon/macros/ac_c_bigendian_cross.m4 new file mode 100644 index 000000000..f52c41be9 --- /dev/null +++ b/neon/macros/ac_c_bigendian_cross.m4 @@ -0,0 +1,81 @@ +dnl @synopsis AC_C_BIGENDIAN_CROSS +dnl +dnl Check endianess even when crosscompiling +dnl (partially based on the original AC_C_BIGENDIAN). +dnl +dnl The implementation will create a binary, and instead of running +dnl the binary it will be grep'ed for some symbols that will look +dnl different for different endianess of the binary. +dnl +dnl @version Id: ac_c_bigendian_cross.m4,v 1.1 2001/09/24 16:27:57 joe Exp +dnl @author Guido Draheim <guidod@gmx.de> +dnl +AC_DEFUN([AC_C_BIGENDIAN_CROSS], +[AC_CACHE_CHECK(whether byte ordering is bigendian, ac_cv_c_bigendian, +[ac_cv_c_bigendian=unknown +# See if sys/param.h defines the BYTE_ORDER macro. +AC_TRY_COMPILE([#include <sys/types.h> +#include <sys/param.h>], [ +#if !BYTE_ORDER || !BIG_ENDIAN || !LITTLE_ENDIAN + bogus endian macros +#endif], [# It does; now see whether it defined to BIG_ENDIAN or not. +AC_TRY_COMPILE([#include <sys/types.h> +#include <sys/param.h>], [ +#if BYTE_ORDER != BIG_ENDIAN + not big endian +#endif], ac_cv_c_bigendian=yes, ac_cv_c_bigendian=no)]) +if test $ac_cv_c_bigendian = unknown; then +AC_TRY_RUN([main () { + /* Are we little or big endian? From Harbison&Steele. */ + union + { + long l; + char c[sizeof (long)]; + } u; + u.l = 1; + exit (u.c[sizeof (long) - 1] == 1); +}], ac_cv_c_bigendian=no, ac_cv_c_bigendian=yes, +[ echo $ac_n "cross-compiling... " 2>&AC_FD_MSG ]) +fi]) +if test $ac_cv_c_bigendian = unknown; then +AC_MSG_CHECKING(to probe for byte ordering) +[ +cat >conftest.c <<EOF +short ascii_mm[] = { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 }; +short ascii_ii[] = { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 }; +void _ascii() { char* s = (char*) ascii_mm; s = (char*) ascii_ii; } +short ebcdic_ii[] = { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 }; +short ebcdic_mm[] = { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 }; +void _ebcdic() { char* s = (char*) ebcdic_mm; s = (char*) ebcdic_ii; } +int main() { _ascii (); _ebcdic (); return 0; } +EOF +] if test -f conftest.c ; then + if ${CC-cc} conftest.c -o conftest.o && test -f conftest.o ; then + if test `grep -l BIGenDianSyS conftest.o` ; then + echo $ac_n ' big endian probe OK, ' 1>&AC_FD_MSG + ac_cv_c_bigendian=yes + fi + if test `grep -l LiTTleEnDian conftest.o` ; then + echo $ac_n ' little endian probe OK, ' 1>&AC_FD_MSG + if test $ac_cv_c_bigendian = yes ; then + ac_cv_c_bigendian=unknown; + else + ac_cv_c_bigendian=no + fi + fi + echo $ac_n 'guessing bigendian ... ' >&AC_FD_MSG + fi + fi +AC_MSG_RESULT($ac_cv_c_bigendian) +fi +if test $ac_cv_c_bigendian = yes; then + AC_DEFINE(WORDS_BIGENDIAN, 1, [whether byteorder is bigendian]) + BYTEORDER=4321 +else + BYTEORDER=1234 +fi +AC_DEFINE_UNQUOTED(BYTEORDER, $BYTEORDER, [1234 = LIL_ENDIAN, 4321 = BIGENDIAN]) +if test $ac_cv_c_bigendian = unknown; then + AC_MSG_ERROR(unknown endianess - sorry, please pre-set ac_cv_c_bigendian) +fi +]) diff --git a/neon/macros/acconfig.h b/neon/macros/acconfig.h new file mode 100644 index 000000000..07ee5ad3d --- /dev/null +++ b/neon/macros/acconfig.h @@ -0,0 +1,4 @@ +/* Unconditionally define _GNU_SOURCE */ +#ifndef _GNU_SOURCE +# undef _GNU_SOURCE +#endif diff --git a/neon/macros/aclocal-include.m4 b/neon/macros/aclocal-include.m4 new file mode 100644 index 000000000..abf6533fe --- /dev/null +++ b/neon/macros/aclocal-include.m4 @@ -0,0 +1,16 @@ +# aclocal-include.m4 +# +# This macro adds the name macrodir to the set of directories +# that `aclocal' searches for macros. + +# serial 1 + +dnl AM_ACLOCAL_INCLUDE(macrodir) +AC_DEFUN([AM_ACLOCAL_INCLUDE], +[ + AM_CONDITIONAL(INSIDE_GNOME_COMMON, test x = y) + + test -n "$ACLOCAL_FLAGS" && ACLOCAL="$ACLOCAL $ACLOCAL_FLAGS" + + for k in $1 ; do ACLOCAL="$ACLOCAL -I $k" ; done +]) diff --git a/neon/macros/compiler-flags.m4 b/neon/macros/compiler-flags.m4 new file mode 100644 index 000000000..63f8e2e6a --- /dev/null +++ b/neon/macros/compiler-flags.m4 @@ -0,0 +1,109 @@ +dnl GNOME_COMPILE_WARNINGS +dnl Turn on many useful compiler warnings +dnl For now, only works on GCC +AC_DEFUN([GNOME_COMPILE_WARNINGS],[ + AC_ARG_ENABLE(compile-warnings, + [ --enable-compile-warnings=[no/minimum/yes] Turn on compiler warnings.],,enable_compile_warnings=minimum) + + AC_MSG_CHECKING(what warning flags to pass to the C compiler) + warnCFLAGS= + if test "x$GCC" != xyes; then + enable_compile_warnings=no + fi + + if test "x$enable_compile_warnings" != "xno"; then + if test "x$GCC" = "xyes"; then + case " $CFLAGS " in + *[\ \ ]-Wall[\ \ ]*) ;; + *) warnCFLAGS="-Wall -Wunused" ;; + esac + + ## -W is not all that useful. And it cannot be controlled + ## with individual -Wno-xxx flags, unlike -Wall + if test "x$enable_compile_warnings" = "xyes"; then + warnCFLAGS="$warnCFLAGS -Wmissing-prototypes -Wmissing-declarations" + fi + fi + fi + AC_MSG_RESULT($warnCFLAGS) + + AC_ARG_ENABLE(iso-c, + [ --enable-iso-c Try to warn if code is not ISO C ],, + enable_iso_c=no) + + AC_MSG_CHECKING(what language compliance flags to pass to the C compiler) + complCFLAGS= + if test "x$enable_iso_c" != "xno"; then + if test "x$GCC" = "xyes"; then + case " $CFLAGS " in + *[\ \ ]-ansi[\ \ ]*) ;; + *) complCFLAGS="$complCFLAGS -ansi" ;; + esac + + case " $CFLAGS " in + *[\ \ ]-pedantic[\ \ ]*) ;; + *) complCFLAGS="$complCFLAGS -pedantic" ;; + esac + fi + fi + AC_MSG_RESULT($complCFLAGS) + if test "x$cflags_set" != "xyes"; then + CFLAGS="$CFLAGS $warnCFLAGS $complCFLAGS" + cflags_set=yes + AC_SUBST(cflags_set) + fi +]) + +dnl For C++, do basically the same thing. + +AC_DEFUN([GNOME_CXX_WARNINGS],[ + AC_ARG_ENABLE(cxx-warnings, + [ --enable-cxx-warnings=[no/minimum/yes] Turn on compiler warnings.],,enable_cxx_warnings=minimum) + + AC_MSG_CHECKING(what warning flags to pass to the C++ compiler) + warnCXXFLAGS= + if test "x$GCC" != xyes; then + enable_compile_warnings=no + fi + if test "x$enable_cxx_warnings" != "xno"; then + if test "x$GCC" = "xyes"; then + case " $CXXFLAGS " in + *[\ \ ]-Wall[\ \ ]*) ;; + *) warnCXXFLAGS="-Wall -Wno-unused" ;; + esac + + ## -W is not all that useful. And it cannot be controlled + ## with individual -Wno-xxx flags, unlike -Wall + if test "x$enable_cxx_warnings" = "xyes"; then + warnCXXFLAGS="$warnCXXFLAGS -Wmissing-prototypes -Wmissing-declarations -Wshadow -Woverloaded-virtual" + fi + fi + fi + AC_MSG_RESULT($warnCXXFLAGS) + + AC_ARG_ENABLE(iso-cxx, + [ --enable-iso-cxx Try to warn if code is not ISO C++ ],, + enable_iso_cxx=no) + + AC_MSG_CHECKING(what language compliance flags to pass to the C++ compiler) + complCXXFLAGS= + if test "x$enable_iso_cxx" != "xno"; then + if test "x$GCC" = "xyes"; then + case " $CXXFLAGS " in + *[\ \ ]-ansi[\ \ ]*) ;; + *) complCXXFLAGS="$complCXXFLAGS -ansi" ;; + esac + + case " $CXXFLAGS " in + *[\ \ ]-pedantic[\ \ ]*) ;; + *) complCXXFLAGS="$complCXXFLAGS -pedantic" ;; + esac + fi + fi + AC_MSG_RESULT($complCXXFLAGS) + if test "x$cxxflags_set" != "xyes"; then + CXXFLAGS="$CXXFLAGS $warnCXXFLAGS $complCXXFLAGS" + cxxflags_set=yes + AC_SUBST(cxxflags_set) + fi +]) diff --git a/neon/macros/gnome-common.m4 b/neon/macros/gnome-common.m4 new file mode 100644 index 000000000..b72382970 --- /dev/null +++ b/neon/macros/gnome-common.m4 @@ -0,0 +1,14 @@ +# gnome-common.m4 +# +# This only for packages that are not in the GNOME CVS tree. + +dnl GNOME_COMMON_INIT + +AC_DEFUN([GNOME_COMMON_INIT], +[ + GNOME_ACLOCAL_DIR=`$ACLOCAL --print-ac-dir`/gnome + AC_SUBST(GNOME_ACLOCAL_DIR) + + ACLOCAL="$ACLOCAL -I $GNOME_ACLOCAL_DIR" +]) + diff --git a/neon/macros/gnome-gnorba-check.m4 b/neon/macros/gnome-gnorba-check.m4 new file mode 100644 index 000000000..dbac0a6cf --- /dev/null +++ b/neon/macros/gnome-gnorba-check.m4 @@ -0,0 +1,35 @@ +dnl +dnl GNOME_GNORBA_HOOK (script-if-gnorba-found, failflag) +dnl +dnl if failflag is "failure" it aborts if gnorba is not found. +dnl + +AC_DEFUN([GNOME_GNORBA_HOOK],[ + GNOME_ORBIT_HOOK([],$2) + AC_CACHE_CHECK([for gnorba libraries],gnome_cv_gnorba_found,[ + gnome_cv_gnorba_found=no + if test x$gnome_cv_orbit_found = xyes; then + GNORBA_CFLAGS="`gnome-config --cflags gnorba gnomeui`" + GNORBA_LIBS="`gnome-config --libs gnorba gnomeui`" + if test -n "$GNORBA_LIBS"; then + gnome_cv_gnorba_found=yes + fi + fi + ]) + AM_CONDITIONAL(HAVE_GNORBA, test x$gnome_cv_gnorba_found = xyes) + if test x$gnome_cv_orbit_found = xyes; then + $1 + GNORBA_CFLAGS="`gnome-config --cflags gnorba gnomeui`" + GNORBA_LIBS="`gnome-config --libs gnorba gnomeui`" + AC_SUBST(GNORBA_CFLAGS) + AC_SUBST(GNORBA_LIBS) + else + if test x$2 = xfailure; then + AC_MSG_ERROR(gnorba library not installed or installation problem) + fi + fi +]) + +AC_DEFUN([GNOME_GNORBA_CHECK], [ + GNOME_GNORBA_HOOK([],failure) +]) diff --git a/neon/macros/gnome-orbit-check.m4 b/neon/macros/gnome-orbit-check.m4 new file mode 100644 index 000000000..54bf33aa4 --- /dev/null +++ b/neon/macros/gnome-orbit-check.m4 @@ -0,0 +1,33 @@ +dnl +dnl GNOME_ORBIT_HOOK (script-if-orbit-found, failflag) +dnl +dnl if failflag is "failure" it aborts if orbit is not found. +dnl + +AC_DEFUN([GNOME_ORBIT_HOOK],[ + AC_PATH_PROG(ORBIT_CONFIG,orbit-config,no) + AC_PATH_PROG(ORBIT_IDL,orbit-idl,no) + AC_CACHE_CHECK([for working ORBit environment],gnome_cv_orbit_found,[ + if test x$ORBIT_CONFIG = xno -o x$ORBIT_IDL = xno; then + gnome_cv_orbit_found=no + else + gnome_cv_orbit_found=yes + fi + ]) + AM_CONDITIONAL(HAVE_ORBIT, test x$gnome_cv_orbit_found = xyes) + if test x$gnome_cv_orbit_found = xyes; then + $1 + ORBIT_CFLAGS=`orbit-config --cflags client server` + ORBIT_LIBS=`orbit-config --use-service=name --libs client server` + AC_SUBST(ORBIT_CFLAGS) + AC_SUBST(ORBIT_LIBS) + else + if test x$2 = xfailure; then + AC_MSG_ERROR(ORBit not installed or installation problem) + fi + fi +]) + +AC_DEFUN([GNOME_ORBIT_CHECK], [ + GNOME_ORBIT_HOOK([],failure) +]) diff --git a/neon/macros/gnome-print-check.m4 b/neon/macros/gnome-print-check.m4 new file mode 100644 index 000000000..7d98281d9 --- /dev/null +++ b/neon/macros/gnome-print-check.m4 @@ -0,0 +1,171 @@ +# Configure paths for GNOME-PRINT +# Chris Lahey 99-2-5 +# stolen from Manish Singh again +# stolen back from Frank Belew +# stolen from Manish Singh +# Shamelessly stolen from Owen Taylor + +dnl AM_PATH_GNOME_PRINT([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for GNOME-PRINT, and define GNOME_PRINT_CFLAGS and GNOME_PRINT_LIBS +dnl +AC_DEFUN(AM_PATH_GNOME_PRINT, +[dnl +dnl Get the cflags and libraries from the gnome-config script +dnl +AC_ARG_WITH(gnome-print-prefix,[ --with-gnome-print-prefix=PFX Prefix where GNOME-PRINT is installed (optional)], + gnome_print_prefix="$withval", gnome_print_prefix="") +AC_ARG_WITH(gnome-print-exec-prefix,[ --with-gnome-print-exec-prefix=PFX Exec prefix where GNOME-PRINT is installed (optional)], + gnome_print_exec_prefix="$withval", gnome_print_exec_prefix="") +AC_ARG_ENABLE(gnome-printtest, [ --disable-gnome-printtest Do not try to compile and run a test GNOME-PRINT program], + , enable_gnome_printtest=yes) + + if test x$gnome_print_exec_prefix != x ; then + gnome_print_args="$gnome_print_args --exec-prefix=$gnome_print_exec_prefix" + if test x${GNOME_CONFIG+set} != xset ; then + GNOME_CONFIG=$gnome_print_exec_prefix/bin/gnome-config + fi + fi + if test x$gnome_print_prefix != x ; then + gnome_print_args="$gnome_print_args --prefix=$gnome_print_prefix" + if test x${GNOME_CONFIG+set} != xset ; then + GNOME_CONFIG=$gnome_print_prefix/bin/gnome-config + fi + fi + + AC_PATH_PROG(GNOME_CONFIG, gnome-config, no) + min_gnome_print_version=ifelse([$1], ,0.1.0,$1) + AC_MSG_CHECKING(for GNOME-PRINT - version >= $min_gnome_print_version) + no_gnome_print="" + if test "$GNOME_CONFIG" = "no" ; then + no_gnome_print=yes + else + GNOME_PRINT_CFLAGS=`$GNOME_CONFIG $gnome_printconf_args --cflags print` + GNOME_PRINT_LIBS=`$GNOME_CONFIG $gnome_printconf_args --libs print` + + gnome_print_major_version=`$GNOME_CONFIG $gnome_print_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` + gnome_print_minor_version=`$GNOME_CONFIG $gnome_print_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` + gnome_print_micro_version=`$GNOME_CONFIG $gnome_print_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` + if test "x$enable_gnome_printtest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $GNOME_PRINT_CFLAGS" + LIBS="$LIBS $GNOME_PRINT_LIBS" +dnl +dnl Now check if the installed GNOME-PRINT is sufficiently new. (Also sanity +dnl checks the results of gnome-config to some extent +dnl + rm -f conf.gnome_printtest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libgnomeprint/gnome-print.h> + +static char* +my_strdup (char *str) +{ + char *new_str; + + if (str) + { + new_str = malloc ((strlen (str) + 1) * sizeof(char)); + strcpy (new_str, str); + } + else + new_str = NULL; + + return new_str; +} + +int main () +{ + int major, minor, micro; + char *tmp_version; + + system ("touch conf.gnome_printtest"); + + /* HP/UX 9 (%@#!) writes to sscanf strings */ + tmp_version = my_strdup("$min_gnome_print_version"); + if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { + printf("%s, bad version string\n", "$min_gnome_print_version"); + exit(1); + } + return 0; +#if 0 + if (($gnome_print_major_version > major) || + (($gnome_print_major_version == major) && ($gnome_print_minor_version > minor)) || + (($gnome_print_major_version == major) && ($gnome_print_minor_version == minor) && ($gnome_print_micro_version >= micro))) + { + return 0; + } + else + { + printf("\n*** 'gnome-config print --version' returned %d.%d.%d, but the minimum version\n", $gnome_print_major_version, $gnome_print_minor_version, $gnome_print_micro_version); + printf("*** of GNOME-PRINT required is %d.%d.%d. If gnome-config is correct, then it is\n", major, minor, micro); + printf("*** best to upgrade to the required version.\n"); + printf("*** If gnome-config was wrong, set the environment variable GNOME_CONFIG\n"); + printf("*** to point to the correct copy of gnome-config, and remove the file\n"); + printf("*** config.cache before re-running configure\n"); + return 1; + } +#endif +} + +],, no_gnome_print=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + fi + if test "x$no_gnome_print" = x ; then + AC_MSG_RESULT(yes) + ifelse([$2], , :, [$2]) + else + AC_MSG_RESULT(no) + if test "$GNOME_CONFIG" = "no" ; then + echo "*** The gnome-config script installed by GNOME-LIBS could not be found" + echo "*** If GNOME-PRINT was installed in PREFIX, make sure PREFIX/bin is in" + echo "*** your path, or set the GNOME_CONFIG environment variable to the" + echo "*** full path to gnome-config." + else + if test -f conf.gnome_printtest ; then + : + else + echo "*** Could not run GNOME-PRINT test program, checking why..." + CFLAGS="$CFLAGS $GNOME_PRINT_CFLAGS" + LIBS="$LIBS $GNOME_PRINT_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <libgnomeprint/gnome-print.h> +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding GNOME-PRINT or finding the wrong" + echo "*** version of GNOME-PRINT. If it is not finding GNOME-PRINT, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means GNOME-PRINT was incorrectly installed" + echo "*** or that you have moved GNOME-PRINT since it was installed. In the latter case, you" + echo "*** may want to edit the gnome-config script: $GNOME_CONFIG" ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + fi + GNOME_PRINT_CFLAGS="" + GNOME_PRINT_LIBS="" + ifelse([$3], , :, [$3]) + fi + AC_SUBST(GNOME_PRINT_CFLAGS) + AC_SUBST(GNOME_PRINT_LIBS) + rm -f conf.gnome_printtest +]) + +AC_DEFUN([GNOME_PRINT_CHECK], [ + AM_PATH_GNOME_PRINT(0.1.0,,[AC_MSG_ERROR(GNOME-PRINT not found)]) +]) diff --git a/neon/macros/gnome-pthread-check.m4 b/neon/macros/gnome-pthread-check.m4 new file mode 100644 index 000000000..a4eb3b489 --- /dev/null +++ b/neon/macros/gnome-pthread-check.m4 @@ -0,0 +1,16 @@ +dnl +dnl And better, use gthreads instead... +dnl + +AC_DEFUN([GNOME_PTHREAD_CHECK],[ + PTHREAD_LIB="" + AC_CHECK_LIB(pthread, pthread_create, PTHREAD_LIB="-lpthread", + [AC_CHECK_LIB(pthreads, pthread_create, PTHREAD_LIB="-lpthreads", + [AC_CHECK_LIB(c_r, pthread_create, PTHREAD_LIB="-lc_r", + [AC_CHECK_FUNC(pthread_create)] + )] + )] + ) + AC_SUBST(PTHREAD_LIB) + AC_PROVIDE([GNOME_PTHREAD_CHECK]) +]) diff --git a/neon/macros/gnome-support.m4 b/neon/macros/gnome-support.m4 new file mode 100644 index 000000000..2c1d04984 --- /dev/null +++ b/neon/macros/gnome-support.m4 @@ -0,0 +1,68 @@ +dnl GNOME_SUPPORT_CHECKS +dnl Check for various support functions needed by the standard +dnl Gnome libraries. Sets LIBOBJS, might define some macros. +dnl This should only be used when building the Gnome libs; +dnl Gnome clients should not need this macro. +AC_DEFUN([GNOME_SUPPORT_CHECKS],[ + # we need an `awk' to build `gnomesupport.h' + AC_REQUIRE([AC_PROG_AWK]) + + # this should go away soon + need_gnome_support=yes + + save_LIBOBJS="$LIBOBJS" + LIBOBJS= + + AC_CHECK_FUNCS(getopt_long,,LIBOBJS="$LIBOBJS getopt.o getopt1.o") + + # for `scandir' + AC_HEADER_DIRENT + + # copied from `configure.in' of `libiberty' + vars="program_invocation_short_name program_invocation_name sys_errlist" + for v in $vars; do + AC_MSG_CHECKING([for $v]) + AC_CACHE_VAL(gnome_cv_var_$v, + [AC_TRY_LINK([int *p;], [extern int $v; p = &$v;], + [eval "gnome_cv_var_$v=yes"], + [eval "gnome_cv_var_$v=no"])]) + if eval "test \"`echo '$gnome_cv_var_'$v`\" = yes"; then + AC_MSG_RESULT(yes) + n=HAVE_`echo $v | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + AC_DEFINE_UNQUOTED($n) + else + AC_MSG_RESULT(no) + fi + done + + AC_REPLACE_FUNCS(memmove mkstemp scandir strcasecmp strerror strndup strnlen) + AC_REPLACE_FUNCS(strtok_r strtod strtol strtoul vasprintf vsnprintf) + + AC_CHECK_FUNCS(realpath,,LIBOBJS="$LIBOBJS canonicalize.o") + + # to include `error.c' error.c has some HAVE_* checks + AC_CHECK_FUNCS(vprintf doprnt strerror_r) + AM_FUNC_ERROR_AT_LINE + + # This is required if we declare setreuid () and setregid (). + AC_TYPE_UID_T + + # see if we need to declare some functions. Solaris is notorious for + # putting functions into the `libc' but not listing them in the headers + AC_CHECK_HEADERS(string.h strings.h stdlib.h unistd.h dirent.h) + GCC_NEED_DECLARATIONS(gethostname setreuid setregid getpagesize) + GCC_NEED_DECLARATION(scandir,[ +#ifdef HAVE_DIRENT_H +#include <dirent.h> +#endif +]) + + # Turn our LIBOBJS into libtool objects. This is gross, but it + # requires changes to autoconf before it goes away. + LTLIBOBJS=`echo "$LIBOBJS" | sed 's/\.o/.lo/g'` + AC_SUBST(need_gnome_support) + AC_SUBST(LTLIBOBJS) + + LIBOBJS="$save_LIBOBJS" + AM_CONDITIONAL(BUILD_GNOME_SUPPORT, test "$need_gnome_support" = yes) +]) diff --git a/neon/macros/gnome-x-checks.m4 b/neon/macros/gnome-x-checks.m4 new file mode 100644 index 000000000..e1125cc55 --- /dev/null +++ b/neon/macros/gnome-x-checks.m4 @@ -0,0 +1,80 @@ +dnl GNOME_X_CHECKS +dnl +dnl Basic X11 related checks for X11. At the end, the following will be +dnl defined/changed: +dnl GTK_{CFLAGS,LIBS} From AM_PATH_GTK +dnl CPPFLAGS Will include $X_CFLAGS +dnl GNOME_HAVE_SM `true' or `false' depending on whether session +dnl management is available. It is available if +dnl both -lSM and X11/SM/SMlib.h exist. (Some +dnl Solaris boxes have the library but not the header) +dnl XPM_LIBS -lXpm if Xpm library is present, otherwise "" +dnl +dnl The following configure cache variables are defined (but not used): +dnl gnome_cv_passdown_{x_libs,X_LIBS,X_CFLAGS} +dnl +AC_DEFUN([GNOME_X_CHECKS], +[ + AM_PATH_GTK(1.2.7,,AC_MSG_ERROR(GTK not installed, or gtk-config not in path),gthread) + dnl Hope that GTK_CFLAGS have only -I and -D. Otherwise, we could + dnl test -z "$x_includes" || CPPFLAGS="$CPPFLAGS -I$x_includes" + dnl + dnl Use CPPFLAGS instead of CFLAGS because AC_CHECK_HEADERS uses + dnl CPPFLAGS, not CFLAGS + CPPFLAGS="$CPPFLAGS $GTK_CFLAGS" + + saved_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS $GTK_LIBS" + + gnome_cv_passdown_x_libs="$GTK_LIBS" + gnome_cv_passdown_X_LIBS="$GTK_LIBS" + gnome_cv_passdown_X_CFLAGS="$GTK_CFLAGS" + gnome_cv_passdown_GTK_LIBS="$GTK_LIBS" + + LDFLAGS="$saved_ldflags $GTK_LIBS" + +dnl We are requiring GTK >= 1.1.1, which means this will be fine anyhow. + USE_DEVGTK=true + +dnl AC_MSG_CHECKING([whether to use features from (unstable) GTK+ 1.1.x]) +dnl AC_EGREP_CPP(answer_affirmatively, +dnl [#include <gtk/gtkfeatures.h> +dnl #ifdef GTK_HAVE_FEATURES_1_1_0 +dnl answer_affirmatively +dnl #endif +dnl ], dev_gtk=yes, dev_gtk=no) +dnl if test "$dev_gtk" = "yes"; then +dnl USE_DEVGTK=true +dnl fi +dnl AC_MSG_RESULT("$dev_gtk") + + GNOME_HAVE_SM=true + case "$GTK_LIBS" in + *-lSM*) + dnl Already found it. + ;; + *) + dnl Assume that if we have -lSM then we also have -lICE. + AC_CHECK_LIB(SM, SmcSaveYourselfDone, + [GTK_LIBS="-lSM -lICE $GTK_LIBS"],GNOME_HAVE_SM=false, + $x_libs -lICE) + ;; + esac + + if test "$GNOME_HAVE_SM" = true; then + AC_CHECK_HEADERS(X11/SM/SMlib.h,,GNOME_HAVE_SM=false) + fi + + if test "$GNOME_HAVE_SM" = true; then + AC_DEFINE(HAVE_LIBSM) + fi + + XPM_LIBS="" + AC_CHECK_LIB(Xpm, XpmFreeXpmImage, [XPM_LIBS="-lXpm"], , $x_libs) + AC_SUBST(XPM_LIBS) + + AC_REQUIRE([GNOME_PTHREAD_CHECK]) + LDFLAGS="$saved_ldflags" + + AC_PROVIDE([GNOME_X_CHECKS]) +]) diff --git a/neon/macros/gnome.m4 b/neon/macros/gnome.m4 new file mode 100644 index 000000000..a3a9ca740 --- /dev/null +++ b/neon/macros/gnome.m4 @@ -0,0 +1,124 @@ +dnl +dnl GNOME_INIT_HOOK (script-if-gnome-enabled, [failflag], [additional-inits]) +dnl +dnl if failflag is "fail" then GNOME_INIT_HOOK will abort if gnomeConf.sh +dnl is not found. +dnl + +AC_DEFUN([GNOME_INIT_HOOK],[ + AC_SUBST(GNOME_LIBS) + AC_SUBST(GNOMEUI_LIBS) + AC_SUBST(GNOMEGNORBA_LIBS) + AC_SUBST(GTKXMHTML_LIBS) + AC_SUBST(ZVT_LIBS) + AC_SUBST(GNOME_LIBDIR) + AC_SUBST(GNOME_INCLUDEDIR) + + AC_ARG_WITH(gnome-includes, + [ --with-gnome-includes Specify location of GNOME headers],[ + CFLAGS="$CFLAGS -I$withval" + ]) + + AC_ARG_WITH(gnome-libs, + [ --with-gnome-libs Specify location of GNOME libs],[ + LDFLAGS="$LDFLAGS -L$withval" + gnome_prefix=$withval + ]) + + AC_ARG_WITH(gnome, + [ --with-gnome Specify prefix for GNOME files], + if test x$withval = xyes; then + want_gnome=yes + dnl Note that an empty true branch is not + dnl valid sh syntax. + ifelse([$1], [], :, [$1]) + else + if test "x$withval" = xno; then + want_gnome=no + else + want_gnome=yes + LDFLAGS="$LDFLAGS -L$withval/lib" + CFLAGS="$CFLAGS -I$withval/include" + gnome_prefix=$withval/lib + fi + fi, + want_gnome=yes) + + if test "x$want_gnome" = xyes; then + + AC_PATH_PROG(GNOME_CONFIG,gnome-config,no) + if test "$GNOME_CONFIG" = "no"; then + no_gnome_config="yes" + else + AC_MSG_CHECKING(if $GNOME_CONFIG works) + if $GNOME_CONFIG --libs-only-l gnome >/dev/null 2>&1; then + AC_MSG_RESULT(yes) + GNOME_GNORBA_HOOK([],$2) + GNOME_LIBS="`$GNOME_CONFIG --libs-only-l gnome`" + GNOMEUI_LIBS="`$GNOME_CONFIG --libs-only-l gnomeui`" + GNOMEGNORBA_LIBS="`$GNOME_CONFIG --libs-only-l gnorba gnomeui`" + GTKXMHTML_LIBS="`$GNOME_CONFIG --libs-only-l gtkxmhtml`" + ZVT_LIBS="`$GNOME_CONFIG --libs-only-l zvt`" + GNOME_LIBDIR="`$GNOME_CONFIG --libs-only-L gnorba gnomeui`" + GNOME_INCLUDEDIR="`$GNOME_CONFIG --cflags gnorba gnomeui`" + $1 + else + AC_MSG_RESULT(no) + no_gnome_config="yes" + fi + fi + + if test x$exec_prefix = xNONE; then + if test x$prefix = xNONE; then + gnome_prefix=$ac_default_prefix/lib + else + gnome_prefix=$prefix/lib + fi + else + gnome_prefix=`eval echo \`echo $libdir\`` + fi + + if test "$no_gnome_config" = "yes"; then + AC_MSG_CHECKING(for gnomeConf.sh file in $gnome_prefix) + if test -f $gnome_prefix/gnomeConf.sh; then + AC_MSG_RESULT(found) + echo "loading gnome configuration from" \ + "$gnome_prefix/gnomeConf.sh" + . $gnome_prefix/gnomeConf.sh + $1 + else + AC_MSG_RESULT(not found) + if test x$2 = xfail; then + AC_MSG_ERROR(Could not find the gnomeConf.sh file that is generated by gnome-libs install) + fi + fi + fi + fi + + if test -n "$3"; then + n="$3" + for i in $n; do + AC_MSG_CHECKING(extra library \"$i\") + case $i in + applets) + AC_SUBST(GNOME_APPLETS_LIBS) + GNOME_APPLETS_LIBS=`$GNOME_CONFIG --libs-only-l applets` + AC_MSG_RESULT($GNOME_APPLETS_LIBS);; + capplet) + AC_SUBST(GNOME_CAPPLET_LIBS) + GNOME_CAPPLET_LIBS=`$GNOME_CONFIG --libs-only-l capplet` + AC_MSG_RESULT($GNOME_CAPPLET_LIBS);; + *) + AC_MSG_RESULT(unknown library) + esac + done + fi +]) + +dnl +dnl GNOME_INIT ([additional-inits]) +dnl + +AC_DEFUN([GNOME_INIT],[ + GNOME_INIT_HOOK([],fail,$1) +]) diff --git a/neon/macros/neon-checks.m4 b/neon/macros/neon-checks.m4 new file mode 100644 index 000000000..da5fffe46 --- /dev/null +++ b/neon/macros/neon-checks.m4 @@ -0,0 +1,21 @@ + +dnl Call these checks when compiling the libneon source package. + +AC_DEFUN([LIBNEON_SOURCE_CHECKS],[ + +AC_CHECK_HEADERS(stdarg.h string.h strings.h sys/time.h regex.h \ + stdlib.h unistd.h limits.h sys/select.h) + +AC_REPLACE_FUNCS(strcasecmp) + +dnl Check for snprintf +AC_CHECK_FUNC(snprintf, + AC_DEFINE(HAVE_SNPRINTF, 1, [Define if you have snprintf]), + LIBOBJS="$LIBOBJS lib/snprintf.o" ) + +AC_CHECK_FUNC(gethostbyname, + AC_MSG_RESULT(using libc's gethostbyname), + AC_CHECK_LIB(nsl,gethostbyname) + ) + +]) diff --git a/neon/macros/neon-debug.m4 b/neon/macros/neon-debug.m4 new file mode 100644 index 000000000..b4d5b870e --- /dev/null +++ b/neon/macros/neon-debug.m4 @@ -0,0 +1,41 @@ +# Copyright (C) 1998-2001 Joe Orton <joe@manyfish.co.uk> +# +# 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> + +# Adds an --disable-debug argument to configure to allow disabling +# debugging messages. +# +# Usage: +# NEON_WARNINGS([actions-if-debug-enabled], [actions-if-debug-disable]) +# + +AC_DEFUN([NEON_DEBUG], [ + +AC_ARG_ENABLE(debug, + [ --disable-debug disable runtime debugging messages ], + [with_debug=$enableval], + [with_debug=yes]) dnl Defaults to ENABLED + +if test "$with_debug" = "yes"; then + AC_DEFINE(NE_DEBUGGING, 1, [Define to enable debugging]) + : + $1 +else + : + $2 +fi + +]) diff --git a/neon/macros/neon-socks.m4 b/neon/macros/neon-socks.m4 new file mode 100644 index 000000000..499e3646d --- /dev/null +++ b/neon/macros/neon-socks.m4 @@ -0,0 +1,45 @@ +# Copyright (C) 1998-2001 Joe Orton <joe@manyfish.co.uk> +# +# 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> + +# Following instructions at: +# http://www.socks.nec.com/how2socksify.html + +AC_DEFUN([NEON_SOCKS], [ + +AC_ARG_WITH([--with-socks], +[ --with-socks[=DIR] Use SOCKS library ], +[ +if test "$withval" != "no"; then + + if test "$withval" != "yes"; then + LDFLAGS="$LDFLAGS -L$withval/lib" + CFLAGS="$CFLAGS -I$withval/include" + fi + + AC_CHECK_HEADERS(sock.h) + + CFLAGS="$CFLAGS -DSOCKS" + + AC_CHECK_LIB(socks5, connect, [NEONLIBS="$NEONLIBS -lsocks5"], + AC_MSG_ERROR([could not find libsocks5 for SOCKS support])) + +fi + +]) + +]) + diff --git a/neon/macros/neon-ssl.m4 b/neon/macros/neon-ssl.m4 new file mode 100644 index 000000000..6603a83e7 --- /dev/null +++ b/neon/macros/neon-ssl.m4 @@ -0,0 +1,68 @@ +# SSL macro from fetchmail +# (C) Eric S. Raymond <esr@thyrsus.com> + +AC_DEFUN([NEON_SSL],[ + +### use option --with-ssl to compile in the SSL support +AC_ARG_WITH(ssl, + [ --with-ssl=[DIR] enable SSL support using libraries in DIR], + [with_ssl=$withval], + [with_ssl=no]) +test "$with_ssl" = "yes" && AC_DEFINE(SSL_ENABLE) + +if test "$with_ssl" = "yes" +then +# He didn't specify an SSL location. Let's look at some common +# directories where SSL has been found in the past and try and auto +# configure for SSL. OpenSSL determination will be made later. +# This will screw up if an OpenSSL install is located in a later +# directory than an older SSLeay install, but the user should fix that +# anyways and he can override on the configure line. + + for ac_dir in \ + /usr/local/ssl \ + /usr/ssl \ + /local/ssl \ + /opt/ssl \ + ; \ + do + if test -d "$ac_dir" ; then + with_ssl=$ac_dir + break; + fi + done +fi + +if test -n "$with_ssl" -a "$with_ssl" != "no" +then + # With the autoconfigure above, the only time this is going to be + # true is going to be when we could not find the headers. If they + # are not in system standard locations, we are going to be broken. + if test "$with_ssl" = "yes" + then +# Let's just define the standard location for the SSLeay root + with_ssl="/usr/local/ssl" + fi + if test -r $with_ssl/include/openssl/ssl.h + then +### ssl.h found under openssl. Use openssl configuration preferentially + echo "Enabling OpenSSL support in $with_ssl" + CFLAGS="$CFLAGS -I$with_ssl/include -I$with_ssl/include/openssl" +### OpenBSD comes with ssl headers + elif test -r /usr/include/ssl/ssl.h + then + echo "Enabling SSLeay support in $with_ssl" + CFLAGS="$CFLAGS -I/usr/include/ssl" + else + echo "Enabling SSLeay support in $with_ssl" + CFLAGS="$CFLAGS -I$with_ssl/include" + fi + LDFLAGS="$LDFLAGS -L$with_ssl/lib" + LIBS="$LIBS -lssl -lcrypto" + AC_DEFINE(SSL_ENABLE) +else + echo 'Disabling SSL support...' +fi + +]) + diff --git a/neon/macros/neon-test.m4 b/neon/macros/neon-test.m4 new file mode 100644 index 000000000..e748ee0c6 --- /dev/null +++ b/neon/macros/neon-test.m4 @@ -0,0 +1,24 @@ +# Copyright (C) 2001 Joe Orton <joe@manyfish.co.uk> +# +# 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_CHECK_FUNCS(pipe) + +]) diff --git a/neon/macros/neon-warnings.m4 b/neon/macros/neon-warnings.m4 new file mode 100644 index 000000000..3c6a6454b --- /dev/null +++ b/neon/macros/neon-warnings.m4 @@ -0,0 +1,38 @@ +# Copyright (C) 1998-2000 Joe Orton. +# 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 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> +# Id: neon-warnings.m4,v 1.4 2000/07/27 15:23:24 joe Exp + +# Usage: +# NEON_WARINGS(default) + +AC_DEFUN([NEON_WARNINGS],[ + +AC_ARG_ENABLE(warnings, + [ --enable-warnings enable GCC warnings during build ], + [with_warnings=$enableval], + [with_warnings=no]) dnl Defaults to DISABLED + +if test "$with_warnings" = "yes"; then + CFLAGS="$CFLAGS -Wall -ansi-pedantic -Wmissing-declarations -Winline" + if test -z "$with_ssl" -o "$with_ssl" = "no"; then + # joe: My OpenSSL ssl.h fails strict prototypes checks + # left right and center + CFLAGS="$CFLAGS -Wstrict-prototypes" + fi +fi + +]) + diff --git a/neon/macros/neon-xml-parser.m4 b/neon/macros/neon-xml-parser.m4 new file mode 100644 index 000000000..aca9d07e2 --- /dev/null +++ b/neon/macros/neon-xml-parser.m4 @@ -0,0 +1,84 @@ +dnl Check for XML parser. +dnl Supports: +dnl * libxml (requires version 1.8.3 or later) +dnl * expat in -lexpat +dnl * expat in -lxmlparse and -lxmltok (as packaged by Debian) +dnl * Bundled expat if bundled is passed as arg + +dnl Usage: NEON_XML_PARSER([bundled]) + +AC_DEFUN([NEON_XML_PARSER], +[ + +AC_ARG_ENABLE( libxml, + [ --enable-libxml force use of libxml ], + [neon_force_libxml=$enableval], + [neon_force_libxml=no]) + +if test "$neon_force_libxml" = "no"; then +dnl Look for expat +AC_CHECK_LIB(expat, XML_Parse, + neon_expat_libs="-lexpat" neon_found_parser="expat", + AC_CHECK_LIB(xmlparse, XML_Parse, + neon_expat_libs="-lxmltok -lxmlparse" neon_found_parser="expat", + neon_found_parser="no", + -lxmltok ) + ) +else + neon_found_parser="no" +fi + +if test "$neon_found_parser" = "no"; then + # Have we got libxml 1.8.3 or later? + AC_CHECK_PROG(XML_CONFIG, xml-config, xml-config) + if test "$XML_CONFIG" != ""; then + # Check for recent library + oLIBS="$LIBS" + oCFLAGS="$CFLAGS" + LIBS="$LIBS `$XML_CONFIG --libs`" + CFLAGS="$CFLAGS `$XML_CONFIG --cflags`" + AC_CHECK_LIB(xml, xmlCreatePushParserCtxt, + neon_found_parser="libxml" neon_xml_parser_message="libxml" + AC_DEFINE(HAVE_LIBXML, 1, [Define if you have libxml]), + CFLAGS="$oCFLAGS" + LIBS="$oLIBS" + AC_WARN([cannot use old libxml (1.8.3 or newer required)]) + ) + fi +fi + +if test "$neon_found_parser" = "expat"; then + # This is crap. Maybe just use AC_CHECK_HEADERS and use the + # right file by ifdef'ing is best + AC_CHECK_HEADER(xmlparse.h, + neon_expat_incs="" neon_found_expatincs="yes", + AC_CHECK_HEADER(xmltok/xmlparse.h, + neon_expat_incs="-I/usr/include/xmltok" neon_found_expatincs="yes", + )) + if test "$neon_found_expatincs" = "yes"; then + AC_DEFINE(HAVE_EXPAT, 1, [Define if you have expat]) + if test "$neon_expat_incs"; then + CFLAGS="$CFLAGS $neon_expat_incs" + fi + LIBS="$LIBS $neon_expat_libs" + else + AC_MSG_ERROR(["found expat library but could not find xmlparse.h"]) + fi + neon_xml_parser_message="expat in $neon_expat_libs" +fi + +if test "$neon_found_parser" = "no" ; then + if test "$1" = "bundled"; then + # Use the bundled expat sources + LIBOBJS="$LIBOBJS expat/xmltok/xmltok.o expat/xmltok/xmlrole.o expat/xmlparse/xmlparse.o expat/xmlparse/hashtable.o" + CFLAGS="$CFLAGS -DXML_DTD -Iexpat/xmlparse -Iexpat/xmltok" + AC_MSG_RESULT(using supplied expat XML parser) + AC_DEFINE(HAVE_EXPAT, 1, [Define if you have expat] ) + neon_xml_parser_message="supplied expat" + else + AC_MSG_ERROR([no XML parser was found]) + fi + +fi + +])
\ No newline at end of file diff --git a/neon/macros/neon.m4 b/neon/macros/neon.m4 new file mode 100644 index 000000000..5b4d83920 --- /dev/null +++ b/neon/macros/neon.m4 @@ -0,0 +1,75 @@ + +AC_DEFUN([NEON_LIBRARY],[ + +AC_ARG_WITH( neon, + [ --with-neon Specify location of neon library ], + [neon_loc="$withval"]) + +if test "$1" = "bundle"; then + AC_ARG_WITH( included-neon, + [ --with-included-neon Force use of included neon library ], + [neon_included="$withval"], + [neon_included="no"]) +fi + +AC_MSG_CHECKING(for neon location) + +if test "$neon_included" != "yes"; then + # Look for neon + + if test -z "$neon_loc"; then + # Look in standard places + for d in /usr /usr/local; do + if test -x $d/bin/neon-config; then + neon_loc=$d + fi + done + fi + + if test -x $neon_loc/bin/neon-config; then + # Couldn't find it! + AC_MSG_RESULT(found in $neon_loc) + NEON_CONFIG=$neon_loc/bin/neon-config + CFLAGS="$CFLAGS `$NEON_CONFIG --cflags`" + LIBS="$LIBS `$NEON_CONFIG --libs`" + else + if test "$1" = "bundle"; then + neon_included="yes" + else + AC_MSG_ERROR(could not find neon) + fi + fi +fi + +if test "$neon_included" = "yes"; then + AC_MSG_RESULT(using supplied) + NEON_XML_PARSER + CFLAGS="$CFLAGS -Ilibneon" + LIBNEON_SOURCE_CHECKS + LIBOBJS="$LIBOBJS \$(NEONOBJS)" +fi + +]) + +dnl Call these checks when compiling the libneon source package. + +AC_DEFUN([LIBNEON_SOURCE_CHECKS],[ + +AC_CHECK_HEADERS(stdarg.h string.h strings.h sys/time.h regex.h \ + stdlib.h unistd.h limits.h sys/select.h) + +AC_REPLACE_FUNCS(strcasecmp) + +dnl Check for snprintf +AC_CHECK_FUNC(snprintf, + AC_DEFINE(HAVE_SNPRINTF, 1, [Define if you have snprintf]), + LIBOBJS="$LIBOBJS lib/snprintf.o" ) + +AC_CHECK_FUNC(gethostbyname, + AC_MSG_RESULT(using libc's gethostbyname), + AC_CHECK_LIB(nsl,gethostbyname) + ) + +NEON_SSL + +]) diff --git a/neon/macros/readline.m4 b/neon/macros/readline.m4 new file mode 100644 index 000000000..2769ffd9c --- /dev/null +++ b/neon/macros/readline.m4 @@ -0,0 +1,47 @@ + +dnl CHECK_READLINE checks for presence of readline on the +dnl system. + +AC_DEFUN([CHECK_READLINE], [ + +AC_ARG_ENABLE(readline, + [ --disable-readline disable readline support ], + [use_readline=$enableval], + [use_readline=yes]) dnl Defaults to ON (if found) + +if test "$use_readline" = "yes"; then + AC_CHECK_LIB(curses, tputs, LIBS="$LIBS -lcurses", + AC_CHECK_LIB(ncurses, tputs)) + AC_CHECK_LIB(readline, readline) + + AC_SEARCH_LIBS(add_history, history, + AC_DEFINE(HAVE_ADD_HISTORY, 1, [Define if you have the add_history function]) + ) + + AC_CHECK_HEADERS(history.h readline/history.h readline.h readline/readline.h) + + AC_MSG_CHECKING(whether filename_completion_function is present) + + AC_TRY_LINK([ +/* need stdio.h since readline.h uses FILE * */ +#include <stdio.h> + +#if defined(HAVE_READLINE_H) +#include <readline.h> +#elif defined(HAVE_READLINE_READLINE_H) +#include <readline/readline.h> +#endif +], +[void (*foo)() = filename_completion_function;], + AC_DEFINE(HAVE_FILENAME_COMPLETION_FUNCTION, 1, + [Define if you have filename_completion_function in readline]) + AC_MSG_RESULT(yes), + AC_MSG_RESULT(no)) + + msg_readline="enabled" +else + msg_readline="disabled" +fi + +]) + diff --git a/neon/macros/socklen-arg-type.m4 b/neon/macros/socklen-arg-type.m4 new file mode 100644 index 000000000..2d84ac445 --- /dev/null +++ b/neon/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/macros/strftime.m4 b/neon/macros/strftime.m4 new file mode 100644 index 000000000..1f95d7e77 --- /dev/null +++ b/neon/macros/strftime.m4 @@ -0,0 +1,146 @@ +#serial 6 + +dnl This macro is intended to be used solely in this file. +dnl These are the prerequisite macros for GNU's strftime.c replacement. +dnl FIXME: the list is far from complete +AC_DEFUN(_jm_STRFTIME_PREREQS, +[ + dnl strftime.c uses localtime_r if it exists. Check for it. + AC_CHECK_FUNCS(localtime_r) + dnl FIXME: add tests for everything in strftime.c: e.g., HAVE_BCOPY, + dnl HAVE_TZNAME, HAVE_TZSET, HAVE_TM_ZONE, etc. +]) + +dnl Determine if the strftime function has all the features of the GNU one. +dnl +dnl From Jim Meyering. +dnl +AC_DEFUN(jm_FUNC_GNU_STRFTIME, +[AC_REQUIRE([AC_HEADER_TIME])dnl + + _jm_STRFTIME_PREREQS + + AC_REQUIRE([AC_C_CONST])dnl + AC_REQUIRE([AC_HEADER_STDC])dnl + AC_CHECK_HEADERS(sys/time.h) + AC_CACHE_CHECK([for working GNU strftime], jm_cv_func_working_gnu_strftime, + [AC_TRY_RUN( +changequote(<<, >>)dnl +<< /* Ulrich Drepper provided parts of the test program. */ +#if STDC_HEADERS +# include <stdlib.h> +#endif + +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +static int +compare (const char *fmt, const struct tm *tm, const char *expected) +{ + char buf[99]; + strftime (buf, 99, fmt, tm); + if (strcmp (buf, expected)) + { +#ifdef SHOW_FAILURES + printf ("fmt: \"%s\", expected \"%s\", got \"%s\"\n", + fmt, expected, buf); +#endif + return 1; + } + return 0; +} + +int +main () +{ + int n_fail = 0; + struct tm *tm; + time_t t = 738367; /* Fri Jan 9 13:06:07 1970 */ + tm = gmtime (&t); + + /* This is necessary to make strftime give consistent zone strings and + e.g., seconds since the epoch (%s). */ + putenv ("TZ=GMT0"); + +#undef CMP +#define CMP(Fmt, Expected) n_fail += compare ((Fmt), tm, (Expected)) + + CMP ("%-m", "1"); /* GNU */ + CMP ("%A", "Friday"); + CMP ("%^A", "FRIDAY"); /* The ^ is a GNU extension. */ + CMP ("%B", "January"); + CMP ("%^B", "JANUARY"); + CMP ("%C", "19"); /* POSIX.2 */ + CMP ("%D", "01/09/70"); /* POSIX.2 */ + CMP ("%F", "1970-01-09"); + CMP ("%G", "1970"); /* GNU */ + CMP ("%H", "13"); + CMP ("%I", "01"); + CMP ("%M", "06"); + CMP ("%M", "06"); + CMP ("%R", "13:06"); /* POSIX.2 */ + CMP ("%S", "07"); + CMP ("%T", "13:06:07"); /* POSIX.2 */ + CMP ("%U", "01"); + CMP ("%V", "02"); + CMP ("%W", "01"); + CMP ("%X", "13:06:07"); + CMP ("%Y", "1970"); + CMP ("%Z", "GMT"); + CMP ("%_m", " 1"); /* GNU */ + CMP ("%a", "Fri"); + CMP ("%^a", "FRI"); + CMP ("%b", "Jan"); + CMP ("%^b", "JAN"); + CMP ("%c", "Fri Jan 9 13:06:07 1970"); + CMP ("%^c", "FRI JAN 9 13:06:07 1970"); + CMP ("%d", "09"); + CMP ("%e", " 9"); /* POSIX.2 */ + CMP ("%g", "70"); /* GNU */ + CMP ("%h", "Jan"); /* POSIX.2 */ + CMP ("%^h", "JAN"); + CMP ("%j", "009"); + CMP ("%k", "13"); /* GNU */ + CMP ("%l", " 1"); /* GNU */ + CMP ("%m", "01"); + CMP ("%n", "\n"); /* POSIX.2 */ + CMP ("%p", "PM"); + CMP ("%r", "01:06:07 PM"); /* POSIX.2 */ + CMP ("%s", "738367"); /* GNU */ + CMP ("%t", "\t"); /* POSIX.2 */ + CMP ("%u", "5"); /* POSIX.2 */ + CMP ("%w", "5"); + CMP ("%x", "01/09/70"); + CMP ("%y", "70"); + CMP ("%z", "+0000"); /* GNU */ + + exit (n_fail ? 1 : 0); +} + >>, +changequote([, ])dnl + jm_cv_func_working_gnu_strftime=yes, + jm_cv_func_working_gnu_strftime=no, + dnl When crosscompiling, assume strftime is missing or broken. + jm_cv_func_working_gnu_strftime=no) + ]) + if test $jm_cv_func_working_gnu_strftime = no; then + AC_SUBST(LIBOBJS) + LIBOBJS="$LIBOBJS strftime.$ac_objext" + AC_DEFINE_UNQUOTED(strftime, gnu_strftime, + [Define to gnu_strftime if the replacement function should be used.]) + fi +]) + +AC_DEFUN(jm_FUNC_STRFTIME, +[ + _jm_STRFTIME_PREREQS + AC_REPLACE_FUNCS(strftime) +]) diff --git a/neon/neon-config.in b/neon/neon-config.in new file mode 100644 index 000000000..d332354f5 --- /dev/null +++ b/neon/neon-config.in @@ -0,0 +1,73 @@ +#! /bin/sh +# Originally from libxml, Copyright (C) Daniel Veillard +# Modifications for neon Copyright (C) 2000 Joe Orton. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +includedir=@includedir@ + +usage() +{ + cat <<EOF +Usage: neon-config [OPTION] + +Known values for OPTION are: + + --prefix=DIR change neon prefix [default $prefix] + --libs print library linking information + --cflags print pre-processor and compiler flags + --help display this help and exit + --version output version information +EOF + + exit $1 +} + +if test $# -eq 0; then + usage 1 +fi + +cflags=false +libs=false + +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 @NEON_INCLUDEDIR@ @NEON_CFLAGS@ + ;; + + --libs) + echo -L@libdir@ @NEON_LIBS@ + ;; + + *) + usage + exit 1 + ;; + esac + shift +done + +exit 0 diff --git a/neon/neon.dsp b/neon/neon.dsp new file mode 100644 index 000000000..41c512c7e --- /dev/null +++ b/neon/neon.dsp @@ -0,0 +1,215 @@ +# Microsoft Developer Studio Project File - Name="neon" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=neon - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "neon.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "neon.mak" CFG="neon - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "neon - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "neon - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "neon - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D +"_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "$(NEON_EXPAT_LITE_WIN32)" /D "NDEBUG" +/D "WIN32" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"Release\neons.lib" + +!ELSEIF "$(CFG)" == "neon - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" +/D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "$(NEON_EXPAT_LITE_WIN32)" /D +"_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"Debug\neonsd.lib" + +!ENDIF + +# Begin Target + +# Name "neon - Win32 Release" +# Name "neon - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\src\base64.c +# End Source File +# Begin Source File + +SOURCE=.\src\dates.c +# End Source File +# Begin Source File + +SOURCE=.\src\dav_207.c +# End Source File +# Begin Source File + +SOURCE=.\src\dav_basic.c +# End Source File +# Begin Source File + +SOURCE=.\src\dav_locks.c +# End Source File +# Begin Source File + +SOURCE=.\src\dav_props.c +# End Source File +# Begin Source File + +SOURCE=.\src\hip_xml.c +# End Source File +# Begin Source File + +SOURCE=.\src\http_auth.c +# End Source File +# Begin Source File + +SOURCE=.\src\http_basic.c +# End Source File +# Begin Source File + +SOURCE=.\src\http_cookies.c +# End Source File +# Begin Source File + +SOURCE=.\src\http_redirect.c +# End Source File +# Begin Source File + +SOURCE=.\src\http_request.c +# End Source File +# Begin Source File + +SOURCE=.\src\http_utils.c +# End Source File +# Begin Source File + +SOURCE=.\src\md5.c +# End Source File +# Begin Source File + +SOURCE=.\src\ne_alloc.c +# End Source File +# Begin Source File + +SOURCE=.\src\neon_i18n.c +# End Source File +# Begin Source File + +SOURCE=.\src\socket.c +# End Source File +# Begin Source File + +SOURCE=.\src\sslcerts.c +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=.\src\string_utils.c +# End Source File +# Begin Source File + +SOURCE=.\src\uri.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Generated Header Files" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\config.h +# End Source File +# Begin Source File + +SOURCE=.\config.hw + +!IF "$(CFG)" == "neon - Win32 Release" + +# Begin Custom Build +InputPath=.\config.hw + +".\config.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy .\config.hw .\config.h > nul + echo Created config.h from config.hw + +# End Custom Build + +!ELSEIF "$(CFG)" == "neon - Win32 Debug" + +# Begin Custom Build +InputPath=.\config.hw + +".\src\config.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy .\config.hw .\src\config.h > nul + echo Created config.h from config.hw + +# End Custom Build + +!ENDIF + +# End Source File +# End Group +# End Target +# End Project + diff --git a/neon/neon.dsw b/neon/neon.dsw new file mode 100644 index 000000000..a2305f561 --- /dev/null +++ b/neon/neon.dsw @@ -0,0 +1,30 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "neon"=".\neon.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + + diff --git a/neon/neon.mak b/neon/neon.mak new file mode 100644 index 000000000..76b3c4eaf --- /dev/null +++ b/neon/neon.mak @@ -0,0 +1,404 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on neon.dsp +!IF "$(CFG)" == "" +CFG=neon - Win32 Release +!MESSAGE No configuration specified. Defaulting to neon - Win32 Release +!ENDIF + +!IF "$(CFG)" != "neon - Win32 Release" && "$(CFG)" != "neon - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "neon.mak" CFG="neon - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "neon - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "neon - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(EXPAT_SRC)" == "" +!MESSAGE +!MESSAGE Need expat-lite source directory +!MESSAGE +!MESSAGE Call with +!MESSAGE nmake /f neon.mak EXPAT_SRC=D:\apache_1.3.20\src\lib\expat-lite +!MESSAGE +!ERROR +!ENDIF + + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "neon - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +ALL : ".\config.h" "$(OUTDIR)\neons.lib" + + +CLEAN : + -@erase "$(INTDIR)\base64.obj" + -@erase "$(INTDIR)\ne_207.obj" + -@erase "$(INTDIR)\ne_alloc.obj" + -@erase "$(INTDIR)\ne_auth.obj" + -@erase "$(INTDIR)\ne_basic.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_socket.obj" + -@erase "$(INTDIR)\ne_string.obj" + -@erase "$(INTDIR)\ne_uri.obj" + -@erase "$(INTDIR)\ne_utils.obj" + -@erase "$(INTDIR)\ne_xml.obj" + -@erase "$(OUTDIR)\neons.lib" + -@erase ".\config.h" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/c /nologo /MD /W3 /GX /O2 /I "$(EXPAT_SRC)" /D "NDEBUG" /D "HAVE_CONFIG_H" /Fo"$(INTDIR)\\" + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +RSC=rc.exe +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\neon.bsc" +BSC32_SBRS= \ + +LIB32=link.exe -lib +LIB32_FLAGS=/nologo /out:"$(OUTDIR)\neons.lib" +LIB32_OBJS= \ + "$(INTDIR)\base64.obj" \ + "$(INTDIR)\ne_xml.obj" \ + "$(INTDIR)\ne_alloc.obj" \ + "$(INTDIR)\ne_auth.obj" \ + "$(INTDIR)\ne_basic.obj" \ + "$(INTDIR)\ne_cookies.obj" \ + "$(INTDIR)\ne_dates.obj" \ + "$(INTDIR)\ne_i18n.obj" \ + "$(INTDIR)\ne_locks.obj" \ + "$(INTDIR)\ne_md5.obj" \ + "$(INTDIR)\ne_props.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" \ + "$(INTDIR)\ne_207.obj" + +"$(OUTDIR)\neons.lib" : "$(OUTDIR)" $(DEF_FILE) $(LIB32_OBJS) + $(LIB32) @<< + $(LIB32_FLAGS) $(DEF_FLAGS) $(LIB32_OBJS) +<< + +!ELSEIF "$(CFG)" == "neon - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +ALL : "$(OUTDIR)\neonsd.lib" + + +CLEAN : + -@erase "$(INTDIR)\base64.obj" + -@erase "$(INTDIR)\ne_207.obj" + -@erase "$(INTDIR)\ne_alloc.obj" + -@erase "$(INTDIR)\ne_auth.obj" + -@erase "$(INTDIR)\ne_basic.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_socket.obj" + -@erase "$(INTDIR)\ne_string.obj" + -@erase "$(INTDIR)\ne_uri.obj" + -@erase "$(INTDIR)\ne_utils.obj" + -@erase "$(INTDIR)\ne_xml.obj" + -@erase "$(INTDIR)\vc60.idb" + -@erase "$(INTDIR)\vc60.pdb" + -@erase "$(OUTDIR)\neonsd.lib" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/c /nologo /MDd /W3 /Gm /GX /Zi /Od /I "$(EXPAT_SRC)" /D "HAVE_CONFIG_H" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +RSC=rc.exe +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\neon.bsc" +BSC32_SBRS= \ + +LIB32=link.exe -lib +LIB32_FLAGS=/nologo /out:"$(OUTDIR)\neonsd.lib" +LIB32_OBJS= \ + "$(INTDIR)\base64.obj" \ + "$(INTDIR)\ne_xml.obj" \ + "$(INTDIR)\ne_alloc.obj" \ + "$(INTDIR)\ne_auth.obj" \ + "$(INTDIR)\ne_basic.obj" \ + "$(INTDIR)\ne_cookies.obj" \ + "$(INTDIR)\ne_dates.obj" \ + "$(INTDIR)\ne_i18n.obj" \ + "$(INTDIR)\ne_locks.obj" \ + "$(INTDIR)\ne_md5.obj" \ + "$(INTDIR)\ne_props.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" \ + "$(INTDIR)\ne_207.obj" + +"$(OUTDIR)\neonsd.lib" : "$(OUTDIR)" $(DEF_FILE) $(LIB32_OBJS) + $(LIB32) @<< + $(LIB32_FLAGS) $(DEF_FLAGS) $(LIB32_OBJS) +<< + +!ENDIF + + +#!IF "$(NO_EXTERNAL_DEPS)" != "1" +#!IF EXISTS("neon.dep") +#!INCLUDE "neon.dep" +#!ELSE +#!MESSAGE Warning: cannot find "neon.dep" +#!ENDIF +#!ENDIF + + +!IF "$(CFG)" == "neon - Win32 Release" || "$(CFG)" == "neon - Win32 Debug" +SOURCE=.\src\base64.c + +"$(INTDIR)\base64.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_207.c + +"$(INTDIR)\ne_207.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_alloc.c + +"$(INTDIR)\ne_alloc.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_auth.c + +"$(INTDIR)\ne_auth.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_basic.c + +"$(INTDIR)\ne_basic.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_cookies.c + +"$(INTDIR)\ne_cookies.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_dates.c + +"$(INTDIR)\ne_dates.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_i18n.c + +"$(INTDIR)\ne_i18n.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_locks.c + +"$(INTDIR)\ne_locks.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_md5.c + +"$(INTDIR)\ne_md5.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_props.c + +"$(INTDIR)\ne_props.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_redirect.c + +"$(INTDIR)\ne_redirect.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_request.c + +"$(INTDIR)\ne_request.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_session.c + +"$(INTDIR)\ne_session.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_socket.c + +"$(INTDIR)\ne_socket.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_string.c + +"$(INTDIR)\ne_string.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_uri.c + +"$(INTDIR)\ne_uri.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_utils.c + +"$(INTDIR)\ne_utils.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\ne_xml.c + +"$(INTDIR)\ne_xml.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=.\src\sslcerts.c +SOURCE=.\config.hw + +!IF "$(CFG)" == "neon - Win32 Release" + +InputPath=.\config.hw + +".\config.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + <<tempfile.bat + @echo off + copy .\config.hw .\src\config.h > nul + echo Created config.h from config.hw +<< + + +!ELSEIF "$(CFG)" == "neon - Win32 Debug" + +InputPath=.\config.hw + +".\src\config.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + <<tempfile.bat + @echo off + copy .\config.hw .\src\config.h > nul + echo Created config.h from config.hw +<< + + +!ENDIF + + +!ENDIF + diff --git a/neon/src/.cvsignore b/neon/src/.cvsignore new file mode 100644 index 000000000..70f06c308 --- /dev/null +++ b/neon/src/.cvsignore @@ -0,0 +1,2 @@ +*.lo +.libs diff --git a/neon/src/COPYING.LIB b/neon/src/COPYING.LIB new file mode 100644 index 000000000..161a3d1d4 --- /dev/null +++ b/neon/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/neon/src/ChangeLog b/neon/src/ChangeLog new file mode 100644 index 000000000..aa54969af --- /dev/null +++ b/neon/src/ChangeLog @@ -0,0 +1,351 @@ +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/neon/src/Makefile.in b/neon/src/Makefile.in new file mode 100644 index 000000000..534214a90 --- /dev/null +++ b/neon/src/Makefile.in @@ -0,0 +1,109 @@ +# +# TODO: document properly. +# +# You need to AC_SUBST: +# - NEONOBJS +# - NEON_TARGET (to libneon.la or libneon.a) +# - NEON_INTERFACE_VERSION (if necessary, which it's probably not for you) +# - OBJ_EXT (to .lo or .o) +# + +SHELL = @SHELL@ + +CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ +DEFS = @DEFS@ +INCLUDES = -I.. -I. +top_builddir = @top_builddir@ +CC = @CC@ +CCLD = $(CC) +AR = @AR@ +RANLIB = @RANLIB@ + +LIBS = @LIBS@ + +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +libdir = @libdir@ + +LIBTOOL = @LIBTOOL@ +LINK = $(LIBTOOL) --quiet --mode=link $(CCLD) $(LDFLAGS) +COMPILE = $(CC) $(DEFS) $(INCLUDES) $(CFLAGS) +NEON_INTERFACE_VERSION = @NEON_INTERFACE_VERSION@ + +OBJECTS = @NEONOBJS@ + +obj_ext = @OBJ_EXT@ + +.SUFFIXES: +.SUFFIXES: .c .lo .o + +NEON_TARGET = @NEON_TARGET@ + +all: $(NEON_TARGET) + +.c.lo: + $(LIBTOOL) --quiet --mode=compile $(COMPILE) -c $< -o $@ +.c.o: + $(COMPILE) -c $< -o $@ + +libneon.la: $(OBJECTS) + $(LINK) -rpath $(libdir) -version-info $(NEON_INTERFACE_VERSION) -o $@ $(LDFLAGS) $(OBJECTS) $(LIBS) + +libneon.a: $(OBJECTS) + $(AR) cru $@ $(OBJECTS) + $(RANLIB) $@ + +clean: + rm -f @NEONOBJS@ $(NEON_TARGET) + +neonreq = http_request.h http_utils.h string_utils.h nsocket.h \ + ne_alloc.h $(top_builddir)/config.h + +http_request.$(obj_ext): http_request.c neon_config.h $(neonreq) \ + neon_i18n.h http_private.h + +socket.$(obj_ext): socket.c nsocket.h $(top_builddir)/config.h string_utils.h + +http_auth.$(obj_ext): http_auth.c http_auth.h string_utils.h \ + $(top_builddir)/config.h dates.h base64.h neon_md5.h + +dav_basic.$(obj_ext): dav_basic.c $(neonreq) dav_207.h hip_xml.$(obj_ext) + +http_basic.$(obj_ext): http_basic.c http_basic.h $(neonreq) + +http_utils.$(obj_ext): http_utils.c $(top_builddir)/config.h \ + http_utils.h dates.h neon_config.h + +hip_xml.$(obj_ext): hip_xml.c hip_xml.h string_utils.h http_utils.h \ + $(top_builddir)/config.h + +dav_207.$(obj_ext): dav_207.c dav_207.h hip_xml.h \ + $(top_builddir)/config.h http_utils.h neon_i18n.h + +string_utils.$(obj_ext): string_utils.c string_utils.h ne_alloc.h \ + $(top_builddir)/config.h + +ne_alloc.$(obj_ext): ne_alloc.c ne_alloc.h $(top_builddir)/config.h + +dates.$(obj_ext): dates.c dates.h $(top_builddir)/config.h + +base64.$(obj_ext): base64.c base64.h $(top_builddir)/config.h + +uri.$(obj_ext): uri.c uri.h http_utils.h string_utils.h ne_alloc.h \ + $(top_builddir)/config.h + +md5.$(obj_ext): md5.c neon_md5.h $(top_builddir)/config.h + +dav_props.$(obj_ext): dav_props.c $(top_builddir)/config.h \ + dav_props.h dav_207.h hip_xml.h + +dav_locks.$(obj_ext): dav_locks.c $(neonreq) dav_locks.h dav_207.h hip_xml.h + +http_redirect.$(obj_ext): http_redirect.c $(neonreq) http_redirect.h \ + uri.h http_private.h + +http_cookies.$(obj_ext): http_cookies.c $(neonreq) http_cookies.h uri.h \ + http_private.h diff --git a/neon/src/Makefile.incl b/neon/src/Makefile.incl new file mode 100644 index 000000000..87281b854 --- /dev/null +++ b/neon/src/Makefile.incl @@ -0,0 +1,45 @@ +# Emacs, are you listening? This is a -*- makefile -*-. Thankyou. +# +# This is a Makefile fragment. +# To use it, set: +# neon_dir to the location of the neon source directory +# config_h to the location of the config.h file + +neonreq = $(neon_dir)/http_request.h $(neon_dir)/http_utils.h $(neon_dir)/string_utils.h + +$(neon_dir)/http_request.o: $(neon_dir)/http_request.c \ + $(neonreq) $(config_h) $(neon_dir)/neon.h $(neon_dir)/socket.h \ + $(neon_dir)/neon_i18n.h + +$(neon_dir)/socket.o: $(neon_dir)/socket.c $(neon_dir)/socket.h \ + $(config_h) $(neon_dir)/string_utils.h + +$(neon_dir)/http_auth.o: $(neon_dir)/http_auth.c \ + $(neon_dir)/http_auth.h $(neon_dir)/string_utils.h \ + $(config_h) $(neon_dir)/dates.h $(neon_dir)/base64.h \ + $(neon_dir)/md5.h + +$(neon_dir)/dav_basic.o: $(neon_dir)/dav_basic.c $(neon_dir)/dav_207.h \ + $(neonreq) $(neon_dir)/hip_xml.o + +$(neon_dir)/http_basic.o: $(neon_dir)/http_basic.c $(neon_dir)/http_basic.h \ + $(neonreq) + +$(neon_dir)/http_utils.o: $(neon_dir)/http_utils.c $(neon_dir)/http_utils.h \ + $(neon_dir)/dates.h $(config_h) + +$(neon_dir)/hip_xml.o: $(neon_dir)/hip_xml.c $(neon_dir)/hip_xml.h \ + $(neon_dir)/string_utils.h $(neon_dir)/http_utils.h $(config_h) + +$(neon_dir)/dav_207.o: $(neon_dir)/dav_207.c $(neon_dir)/dav_207.h \ + $(neon_dir)/hip_xml.h $(config_h) $(neon_dir)/http_utils.h \ + $(neon_dir)/neon_i18n.h + +$(neon_dir)/dates.o: $(neon_dir)/dates.c $(neon_dir)/dates.h \ + $(config_h) + +$(neon_dir)/md5.o: $(neon_dir)/md5.c $(neon_dir)/md5.h $(config_h) + +$(neon_dir)/dav_props.o: $(neon_dir)/dav_props.c $(neon_dir)/dav_props.h \ + $(neon_dir)/dav_207.h $(neon_dir)/hip_xml.h + diff --git a/neon/src/README b/neon/src/README new file mode 100644 index 000000000..0cb9a3f45 --- /dev/null +++ b/neon/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/neon/src/TODO b/neon/src/TODO new file mode 100644 index 000000000..125bd331b --- /dev/null +++ b/neon/src/TODO @@ -0,0 +1,98 @@ + +To Do List for neon -*- text -*- +------------------- Id: TODO,v 1.12 2000/05/10 18:15:47 joe Exp + +Please submit feature requests to <mailto:neon@webdav.org> + +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 + +2. Add proper domain support to authentication code. (requires full + URI parsing support). Need to tell the auth layer the server + details. + +3. Add a callback to determine whether a specific request should go + through the proxy server or not... based on URI, maybe method too + since lots of HTTP/1.0 proxies break on DAV requests? + +4. Better cnonce generation for authentication: use /dev/random or + whatever like mod_auth_digest. + +5. Add request hooks to remove all authentication code from + http_request.c. + +6. PUT/GET with ranges... http_get_range + +7. hip_xml/dav_207/dav_prop might need a revamp. + + hip_xml: use an expat-esque interface; i.e. make hip_xml_parser + opaque and add API to set handlers rather than initialize + structures directly. Two problems need to be solved more elegantly: + + 1) Allowing "sub-handler" for allowing parsing property element + contents independantly of the overally 207 response parse. + + 2) Allow "mixed-mode" parsing of XML which mixes cdata + elements. + + Probably, make hip_xml more of a "utility" layer rather than a + "driving" layer; abstract out expat/libxml, namespace lookups, + element name -> id mapping, easy CDATA handling... + +8. WebDAV class 2 locking. Problems: this requires parsing the XML + within a property element. This could be achieved by doing a + completely new XML parse of the property value returned by end_prop + from the 207 code, but this is a bit noddy. Options: + + a) Ideally: extend hip_xml and the 207 layer to allow doing a + proper start/end_element/cdata parse of the XML *within* a + property. This is tricky since it means extending hip_xml to + *dynamically* determine whether to be in "collect" mode or not. Add + another callback for this? + +9. DeltaV support (http://www.webdav.org/deltav/). See also the + inversion project (http://inversion.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). + +12. SSL/TSL support... make it pluggable so we don't have to export + crypto-related code at ALL? + +13. Should we really be abort()'ing on out-of-memory? It makes a lot + of code MUCH simpler (esp. sbuffer_* usage). + +14. Nicer request-header manipulation... some kind of indexed data + structure, so we're sure we don't add the same header to the + request twice (e.g. Cache-Control). Must be at least as flexible + as sbuffer usage, though. + +16. Socket status notification (socket.c:sock_register_*) is awful. + +17. Should we really be i18n'izing the low-level error messages in + http_request.c, dav_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. + +18. PROPFIND/propnames support. + +19. libtool support, for proper shared libraries. + +20. Add full 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 + http_auth_session, so I log into e.g. /foo/ and /bar/ (which + are not in the same authentication domain) + switch between them without having to re-enter passwords all the + time. + +22. Handle PROPFIND property error responses properly. + +23. Mechanism for aborting a request mid-response; e.g., when a GET + fails due to out of disk space, abort the download. + diff --git a/neon/src/base64.c b/neon/src/base64.c new file mode 100644 index 000000000..54bb2243e --- /dev/null +++ b/neon/src/base64.c @@ -0,0 +1,103 @@ +/* + Text->Base64 convertion, from RFC1521 + Copyright (C) 1998,1999,2000 Joe Orton <joe@orton.demon.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 + + Id: base64.c,v 1.3 2000/05/09 18:26:58 joe Exp +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "xalloc.h" +#include "base64.h" + +/* Base64 specification from RFC 1521 */ + +static const char b64_alphabet[65] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/=" }; + +char *base64( const char *text ) +{ + /* 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; + int inlen, outlen; + + /* Use 'buffer' to store the output. Work out how big it should be... + * This must be a multiple of 4 bytes */ + + inlen = strlen( text ); + outlen = (inlen*4)/3; + if( (inlen % 3) > 0 ) /* got to pad */ + outlen += 4 - (inlen % 3); + + buffer = xmalloc( 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 ) { + /* 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; +} + +#ifdef BASE64_TEST + +/* Standalone tester */ + +#include <stdio.h> + +/* Tester for Base64 algorithm */ +int main( int argc, char **argv ) { + char *out; + if( argc != 2 ) { + printf( "Usage: %s plaintext\n", argv[0] ); + exit(-1); + } + out = base64( argv[1] ); + printf( "Plain: [%s], Base64: [%s]\n", argv[1], out ); + return 0; +} + +#endif /* BASE64_TEST */ + diff --git a/neon/src/base64.h b/neon/src/base64.h new file mode 100644 index 000000000..3c4f55a17 --- /dev/null +++ b/neon/src/base64.h @@ -0,0 +1,32 @@ +/* + Text->Base64 convertion, from RFC1521 + Copyright (C) 1998-2000 Joe Orton <joe@orton.demon.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 BASE64_H +#define BASE64_H + +/* Plain->Base64 converter - will malloc the buffer to the correct size, + * and fill it with the Base64 conversion of the parameter. + * Base64 specification from RFC 1521. You must free() it. */ + +char *base64( const char *text ); + +#endif /* BASE64_H */ + diff --git a/neon/src/dates.c b/neon/src/dates.c new file mode 100644 index 000000000..6c5e93d8f --- /dev/null +++ b/neon/src/dates.c @@ -0,0 +1,163 @@ +/* + Date manipulation routines + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: dates.c,v 1.5 2000/05/09 18:25:37 joe Exp +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> + +#include <time.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#include <stdio.h> + +#include "xalloc.h" +#include "dates.h" + +/* Generic date manipulation routines. */ + +/* 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" +}; + +/* Returns the time/date GMT, in RFC1123-type format: eg + * Sun, 06 Nov 1994 08:49:37 GMT. */ +char *rfc1123_date( time_t anytime ) { + struct tm *gmt; + char *ret; + gmt = gmtime( &anytime ); + ret = xmalloc( 29 + 1 ); /* dates are 29 chars long */ +/* it goes: Sun, 06 Nov 1994 08:49:37 GMT */ + 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 RFC1123-formatted date string and returns the time_t. + * Returns (time_t)-1 if the parse fails. */ +time_t 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; + return mktime( &gmt ); +} + +/* Takes a string containing a RFC1036-style date and returns the time_t */ +time_t 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( RFC1036_FORMAT, + wkday, &gmt.tm_mday, mon, &gmt.tm_year, + &gmt.tm_hour, &gmt.tm_min, &gmt.tm_sec ); + /* 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; + return mktime( &gmt ); +} + + +/* (as)ctime dates are like: + * Wed Jun 30 21:49:08 1993 + */ +time_t asctime_parse( const char *date ) { + struct tm gmt = {0}; + int n; + static char wkday[4], mon[4]; + n = sscanf( 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; + return mktime( &gmt ); +} + +#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 = rfc1123_parse( argv[1] ); + printf( "Parsed: %d\n", in ); + out = rfc1123_date( in ); + printf( "Back again: %s\n", out ); + } else { + now = time(NULL); + out = rfc1123_date(now); + in = rfc1123_parse(out); + printf( "time(NULL) = %d\n", now ); + printf( "RFC1123 Time: [%s]\n", out ); + printf( "Parsed = %d\n", in ); + out = rfc1123_date( in ); + printf( "Back again: [%s]\n", out ); + } + return 0; +} + +#endif + + diff --git a/neon/src/dates.h b/neon/src/dates.h new file mode 100644 index 000000000..9e6a99579 --- /dev/null +++ b/neon/src/dates.h @@ -0,0 +1,42 @@ +/* + Date manipulation routines + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 "config.h" + +#include <sys/types.h> + +/* Date manipulation routines as per RFC1123 and RFC1036 */ + +/* Return current date/time in RFC1123 format */ +char *rfc1123_date( time_t anytime ); + +/* Returns time from date/time in RFC1123 format */ +time_t rfc1123_parse( const char *date ); + +time_t rfc1036_parse( const char *date ); + +/* Parses asctime date string */ +time_t asctime_parse( const char *date ); + +#endif /* DATES_H */ diff --git a/neon/src/dav_207.c b/neon/src/dav_207.c new file mode 100644 index 000000000..6ee7807f3 --- /dev/null +++ b/neon/src/dav_207.c @@ -0,0 +1,262 @@ +/* + WebDAV 207 multi-status response handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: dav_207.c,v 1.17 2000/05/10 12:02:23 joe Exp +*/ + +/* Generic handling for WebDAV 207 Multi-Status responses. */ + +#include <config.h> + +#include "http_utils.h" +#include "hip_xml.h" +#include "dav_207.h" +#include "uri.h" +#include "xalloc.h" + +#include "neon_i18n.h" + +struct dav_207_parser_s { + dav_207_start_response start_response; + dav_207_end_response end_response; + dav_207_start_propstat start_propstat; + dav_207_end_propstat end_propstat; + struct hip_xml_parser parser; + struct hip_xml_handler handler; + void *userdata; + /* current position */ + void *response, *propstat; + /* caching */ + http_status status; + char *description, *href, *status_line; +}; + +const static struct hip_xml_elm elements[] = { + { "DAV:", "multistatus", DAV_ELM_multistatus, 0 }, + { "DAV:", "response", DAV_ELM_response, 0 }, + { "DAV:", "responsedescription", DAV_ELM_responsedescription, 0 }, + { "DAV:", "href", DAV_ELM_href, HIP_XML_CDATA }, + { "DAV:", "propstat", DAV_ELM_propstat, 0 }, + { "DAV:", "prop", DAV_ELM_prop, 0 }, + { "DAV:", "status", DAV_ELM_status, HIP_XML_CDATA }, +#if 0 + { "", "", HIP_ELM_unknown, HIP_XML_COLLECT }, +#endif + { NULL } +}; + +/* Set the callbacks for the parser */ +void dav_207_set_response_handlers( dav_207_parser *p, + dav_207_start_response start, + dav_207_end_response end ) +{ + p->start_response = start; + p->end_response = end; +} + +void dav_207_set_propstat_handlers( dav_207_parser *p, + dav_207_start_propstat start, + dav_207_end_propstat end ) +{ + p->start_propstat = start; + p->end_propstat = end; +} + +/* Parse an input block, as hip_xml_parse */ +void dav_207_parse( void *parser, const char *block, size_t len ) +{ + dav_207_parser *p = parser; + hip_xml_parse( &p->parser, block, len ); +} + +static int +start_element( void *userdata, const struct hip_xml_state *s, const hip_xml_char **atts ) +{ + dav_207_parser *p = userdata; + + switch( s->elm->id ) { + case DAV_ELM_response: + /* Create new response delayed until we get HREF */ + break; + case DAV_ELM_propstat: + if( p->start_propstat ) { + p->propstat = (*p->start_propstat)( p->userdata, p->response ); + } + break; + } + return 0; +} + +static int +end_element( void *userdata, const struct hip_xml_state *s, const char *cdata ) +{ + dav_207_parser *p = userdata; + + switch( s->elm->id ) { + case DAV_ELM_responsedescription: + p->description = xstrdup(cdata); + break; + case DAV_ELM_href: + /* Now we have the href, begin the response */ + if( p->start_response && cdata != NULL ) { + char *uri = uri_unescape( cdata ); + p->response = (*p->start_response)( p->userdata, uri ); + free( uri ); + } + break; + case DAV_ELM_status: + switch( s->parent->elm->id ) { + case DAV_ELM_propstat: + case DAV_ELM_response: + p->status_line = xstrdup(cdata); + if( http_parse_statusline( p->status_line, &p->status ) ) { + snprintf( p->parser.error, BUFSIZ, + _("Invalid HTTP status line in status element at line %d of response"), + hip_xml_currentline(&p->parser) ); + HTTP_FREE( p->status_line ); + return -1; + } + break; + } + break; + case DAV_ELM_propstat: + if( p->end_propstat ) { + (*p->end_propstat)( p->userdata, p->propstat, p->status_line, + p->status_line?&p->status:NULL, p->description ); + } + HTTP_FREE( p->status_line ); + break; + case DAV_ELM_response: + if( p->end_response ) { + (*p->end_response)( p->userdata, p->response, p->status_line, + p->status_line?&p->status:NULL ); + } + HTTP_FREE( p->status_line ); + break; + } + return 0; +} + +/* This should map directly from the DTD... with the addition of + * ignoring anything we don't understand, being liberal in what we + * accept. */ +static int check_context( hip_xml_elmid parent, hip_xml_elmid child ) +{ + switch( parent ) { + case HIP_ELM_root: + switch( child ) { + case DAV_ELM_multistatus: + case DAV_ELM_response: /* not sure why this is here... */ + return 0; + default: + } + break; + case DAV_ELM_multistatus: + /* <!ELEMENT multistatus (response+, responsedescription?) > */ + switch( child ) { + case DAV_ELM_response: + case DAV_ELM_responsedescription: + case HIP_ELM_unknown: + return 0; + default: + } + break; + case DAV_ELM_response: + /* <!ELEMENT response (href, ((href*, status)|(propstat+)), + responsedescription?) > */ + switch( child ) { + case DAV_ELM_href: + case DAV_ELM_status: + case DAV_ELM_propstat: + case DAV_ELM_responsedescription: + case HIP_ELM_unknown: + return 0; + default: + } + break; + case DAV_ELM_propstat: + /* <!ELEMENT propstat (prop, status, responsedescription?) > */ + switch( child ) { + case DAV_ELM_prop: + case DAV_ELM_status: + case DAV_ELM_responsedescription: + case HIP_ELM_unknown: + return 0; + default: + } + break; + case DAV_ELM_prop: + /* <!ELEMENT prop ANY > */ + switch( child ) { + case HIP_ELM_unknown: + return 0; + default: + break; + } + break; + case HIP_ELM_unknown: + return 0; + default: + break; + } + return -1; +} + +static dav_207_parser *init_parser( void *userdata, struct hip_xml_handler *extra ) +{ + dav_207_parser *p = xmalloc( sizeof(dav_207_parser) ); + memset( p, 0, sizeof(dav_207_parser) ); + p->handler.validate_cb = check_context; + p->handler.startelm_cb = start_element; + p->handler.endelm_cb = end_element; + p->handler.userdata = p; + p->handler.elements = elements; + p->handler.next = extra; + p->userdata = userdata; + hip_xml_init( &p->parser, &p->handler ); + return p; +} + +dav_207_parser *dav_207_init_with_handler( void *userdata, + struct hip_xml_handler *extra ) +{ + return init_parser( userdata, extra ); +} + +dav_207_parser *dav_207_init( void *userdata ) +{ + return init_parser( userdata, NULL ); +} + +int dav_207_finish( dav_207_parser *p ) { + int ret = hip_xml_finish( &p->parser ); + HTTP_FREE( p->response ); + free( p ); + return ret; +} + +const char *dav_207_error( dav_207_parser *p ) +{ + return p->parser.error; +} + +int dav_accept_207( void *userdata, http_req *req, http_status *status ) +{ + return (status->code == 207); +} diff --git a/neon/src/dav_207.h b/neon/src/dav_207.h new file mode 100644 index 000000000..2881d62a7 --- /dev/null +++ b/neon/src/dav_207.h @@ -0,0 +1,91 @@ +/* + WebDAV 207 multi-status response handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 "hip_xml.h" +#include "http_utils.h" /* for struct http_status */ +#include "http_request.h" /* for http_req */ + +#define DAV_ELM_multistatus (101) +#define DAV_ELM_response (102) +#define DAV_ELM_responsedescription (103) +#define DAV_ELM_href (104) +#define DAV_ELM_propstat (105) +#define DAV_ELM_prop (106) +#define DAV_ELM_status (107) + +struct dav_207_parser_s; +typedef struct dav_207_parser_s dav_207_parser; + +/* The name of a WebDAV property. */ +typedef struct { + const char *nspace, *name; +} dav_propname; + +/* The handler structure: you provide a set of callbacks. + * They are called in the order they are listed... start/end_prop + * multiple times before end_prop, start/end_propstat multiple times + * before an end_response, start/end_response multiple times. + */ + +/* TODO: do we need to pass userdata to ALL of these? We could get away with + * only passing the userdata to the start_'s and relying on the caller + * to send it through as the _start return value if they need it. */ + +typedef void *(*dav_207_start_response)( void *userdata, const char *href ); +typedef void (*dav_207_end_response)( + void *userdata, void *response, const char *status_line, const http_status *status ); + +typedef void *(*dav_207_start_propstat)( void *userdata, void *response ); +typedef void (*dav_207_end_propstat)( + void *userdata, void *propstat, const char *status_line, const http_status *status, + const char *description ); + +/* Create a 207 parser */ + +dav_207_parser *dav_207_init( void *userdata ); + +dav_207_parser *dav_207_init_with_handler( void *userdata, + struct hip_xml_handler *extra ); + +/* Set the callbacks for the parser */ + +void dav_207_set_response_handlers( + dav_207_parser *p, dav_207_start_response start, dav_207_end_response end ); + +void dav_207_set_propstat_handlers( + dav_207_parser *p, dav_207_start_propstat start, dav_207_end_propstat end ); + +/* Parse an input block, as hip_xml_parse */ +void dav_207_parse( void *parser, const char *block, size_t len ); + +/* Finish parsing... returns 0 if the parse was successful, else + * non-zero. */ +int dav_207_finish( dav_207_parser *p ); + +const char *dav_207_error( dav_207_parser *p ); + +/* An acceptance function which only accepts 207 responses */ +int dav_accept_207( void *userdata, http_req *req, http_status *status ); + +#endif /* DAV207_H */ diff --git a/neon/src/dav_basic.c b/neon/src/dav_basic.c new file mode 100644 index 000000000..eccc2acb3 --- /dev/null +++ b/neon/src/dav_basic.c @@ -0,0 +1,232 @@ +/* + WebDAV Class 1 namespace operations and 207 error handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: dav_basic.c,v 1.12 2000/05/09 18:28:00 joe Exp +*/ + +#include "config.h" + +#include "http_request.h" + +#include "dav_basic.h" +#include "uri.h" /* for uri_has_trailing_slash */ +#include "http_basic.h" /* for http_content_type */ +#include "string_utils.h" /* for sbuffer */ +#include "dav_207.h" + +/* 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. TODO: should display 'description' too, + * but find out whether servers actually use this first. */ + +/* This is passed as userdata to the 207 code. */ +struct context { + char *href; + sbuffer buf; + unsigned int is_error; +}; + +static void *start_response( void *userdata, const char *href ) +{ + struct context *ctx = userdata; + HTTP_FREE( ctx->href ); + ctx->href = xstrdup(href); + return NULL; +} + +static void handle_error( struct context *ctx, + const char *status_line, const http_status *status ) +{ + if( status && status->class != 2 ) { + ctx->is_error = 1; + if( status->code != 424 ) { + sbuffer_concat( ctx->buf, ctx->href, ": ", status_line, "\n", NULL ); + } + } +} + +static void end_response( void *userdata, void *response, const char *status_line, + const http_status *status ) +{ + struct context *ctx = userdata; + handle_error( ctx, status_line, status ); +} + +static void +end_propstat( void *userdata, void *propstat, const char *status_line, + const http_status *status, const char *description ) +{ + struct context *ctx = userdata; + handle_error( ctx, status_line, status ); +} + +void dav_add_depth_header( http_req *req, int depth ) +{ + sbuffer hdrs = http_get_request_header( req ); + switch( depth ) { + case DAV_DEPTH_ZERO: + sbuffer_zappend( hdrs, "Depth: 0" EOL ); + break; + case DAV_DEPTH_ONE: + sbuffer_zappend( hdrs, "Depth: 1" EOL ); + break; + default: + sbuffer_zappend( hdrs, "Depth: infinity" EOL ); + break; + } +} + +/* Dispatch a DAV request and handle a 207 error response appropriately */ +int dav_simple_request( http_session *sess, http_req *req ) +{ + int ret, invalid; + http_status status; + http_content_type ctype = {0}; + struct context ctx = {0}; + dav_207_parser *p; + + p = dav_207_init( &ctx ); + ctx.buf = sbuffer_create(); + + dav_207_set_response_handlers( p, start_response, end_response ); + dav_207_set_propstat_handlers( p, NULL, end_propstat ); + + http_add_response_body_reader( req, dav_accept_207, dav_207_parse, p ); + http_add_response_header_handler( req, "Content-Type", + http_content_type_handler, &ctype ); + + ret = http_request_dispatch( req, &status ); + + invalid = dav_207_finish( p ); + + if( ret == HTTP_OK ) { + if( status.code == 207 ) { + if( invalid ) { + http_set_error( sess, dav_207_error(p) ); + ret = HTTP_ERROR; + } else if( ctx.is_error ) { + http_set_error( sess, sbuffer_data(ctx.buf) ); + ret = HTTP_ERROR; + } + } else if( status.class != 2 ) { + ret = HTTP_ERROR; + } + } + + sbuffer_destroy(ctx.buf); + HTTP_FREE(ctx.href); + + http_request_destroy( req ); + + return ret; +} + +static int copy_or_move( http_session *sess, int is_move, int no_overwrite, + const char *src, const char *dest ) { + http_req *req = http_request_create( sess, is_move?"MOVE":"COPY", src ); + const char *str; + sbuffer hdrs; + +#ifdef USE_DAV_LOCKS + /* Copy needs to read/write the source, and write to the destination */ + dav_lock_using_resource( req, src, + is_move?dav_lockusage_write:dav_lockusage_read, + DAV_DEPTH_INFINITE ); + dav_lock_using_resource( req, dest, dav_lockusage_write, + DAV_DEPTH_INFINITE ); + /* And we need to be able to add members to the destination's parent */ + dav_lock_using_parent( req, dest ); +#endif + + hdrs = http_get_request_header( req ); + str = http_get_server_hostport( sess ); + sbuffer_concat( hdrs, "Destination: http://", str, dest, EOL, NULL ); + + if( no_overwrite ) + sbuffer_zappend( hdrs, "Overwrite: F" EOL ); + +#if 0 + /* FIXME */ + if( ! http_webdav_server ) { + /* For non-WebDAV servers */ + strcat( req.headers, "New-URI: " ); + strcat( req.headers, to ); + strcat( req.headers, EOL ); + } +#endif + + return dav_simple_request( sess, req ); +} + +int dav_copy( http_session *sess, const char *src, const char *dest ) { + return copy_or_move( sess, 0, 1, src, dest ); +} + +int dav_move( http_session *sess, const char *src, const char *dest ) { + return copy_or_move( sess, 1, 1, src, dest ); +} + +/* Deletes the specified resource. (and in only two lines of code!) */ +int dav_delete( http_session *sess, const char *uri ) { + http_req *req = http_request_create( sess, "DELETE", uri ); + +#ifdef USE_DAV_LOCKS + dav_lock_using_resource( req, uri, dav_lockusage_write, DAV_DEPTH_INFINITE ); + dav_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 dav_simple_request( sess, req ); +} + +int dav_mkcol( http_session *sess, const char *uri ) +{ + http_req *req; + char *real_uri; + int ret; + + if( uri_has_trailing_slash( uri ) ) { + real_uri = xstrdup( uri ); + } else { + CONCAT2( real_uri, uri, "/" ); + } + + req = http_request_create( sess, "MKCOL", real_uri ); + +#ifdef USE_DAV_LOCKS + dav_lock_using_resource( req, real_uri, dav_lockusage_write, 0 ); + dav_lock_using_parent( req, real_uri ); +#endif + + ret = dav_simple_request( sess, req ); + + free( real_uri ); + + return ret; +} diff --git a/neon/src/dav_basic.h b/neon/src/dav_basic.h new file mode 100644 index 000000000..a2363e52f --- /dev/null +++ b/neon/src/dav_basic.h @@ -0,0 +1,64 @@ +/* + Basic WebDAV support + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 DAV_BASIC_H +#define DAV_BASIC_H + +#include "http_request.h" + +#include "dav_207.h" + +#define DAV_DEPTH_ZERO (0) +#define DAV_DEPTH_ONE (1) +#define DAV_DEPTH_INFINITE (2) + +/* Adds a Depth: header to a request */ +void dav_add_depth_header( http_req *req, int depth ); + +/* Handle a simple WebDAV request. + * + * Usage: + * 1. Create the request using http_request_create() + * 2. Set any headers, the request body, whatever. + * 3. Call dav_simple_request to dispatch and destroy the request. + * + * (note the request IS destroyed by this function, don't do it + * yourself). + * + * Returns HTTP_* as http_request_dispatch() would. If the response is + * a 207, a user-friendly error message is written to the session + * error buffer; e.g. DELETE /foo/ might give the error: + * /foo/bar: HTTP/1.1 423 Locked + */ +int dav_simple_request( http_session *sess, http_req *req ); + +/* Basic WebDAV methods: + * dav_copy: copy resoure from src to dest + * dav_move: move resource from src to dest + * dav_delete: delete resource at uri + * dav_mkcol: create a collection at uri (uri MUST have a trailing slash). + */ +int dav_copy( http_session *sess, const char *src, const char *dest ); +int dav_move( http_session *sess, const char *src, const char *dest ); +int dav_delete( http_session *sess, const char *uri ); +int dav_mkcol( http_session *sess, const char *uri ); + +#endif diff --git a/neon/src/dav_locks.c b/neon/src/dav_locks.c new file mode 100644 index 000000000..65acd3171 --- /dev/null +++ b/neon/src/dav_locks.c @@ -0,0 +1,622 @@ +/* + WebDAV Class 2 locking operations + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: dav_locks.c,v 1.3 2000/07/16 15:39:25 joe Exp +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "http_request.h" +#include "dav_locks.h" +#include "dav_basic.h" +#include "dav_props.h" +#include "uri.h" +#include "dav_207.h" +#include "neon_i18n.h" +#include "hip_xml.h" + +#include "xalloc.h" + +#define HOOK_ID "http://webdav.org/neon/hooks/webdav-locking" + +/* The list of locks to submit in an If header + * for the current requests */ +struct submit_locks { + const struct dav_lock *lock; + const char *uri; + struct submit_locks *next; +}; + +struct dav_lock_session_s { + struct dav_lock *locks; +}; + +/* Per-request lock structure */ +struct request_locks { + struct submit_locks *locks; /* for the If header */ + dav_lock_session *session; +}; + +/* Context for PROPFIND/lockdiscovery callbacks */ +struct lock_discovery { + struct dav_lock_result *list; +}; + +/* Hook callbacks */ +static void *create(void *session, http_req *req, + const char *method, const char *uri); +static void pre_send(void *private, sbuffer req); +static void destroy(void *private); + +/* The hooks are needed for construction of the If header */ +static http_request_hooks lock_hooks = { + HOOK_ID, /* unique id for the locking hooks */ + create, + NULL, /* use_body not needed */ + pre_send, + NULL, /* post_send not needed */ + destroy +}; + +/* bit random, these */ +#define DAV_ELM_lockdiscovery 2 +#define DAV_ELM_activelock 3 +#define DAV_ELM_lockscope 4 +#define DAV_ELM_locktype 5 +#define DAV_ELM_depth 6 +#define DAV_ELM_owner 7 +#define DAV_ELM_timeout 9 +#define DAV_ELM_locktoken 10 +#define DAV_ELM_lockinfo 11 +#define DAV_ELM_write 14 +#define DAV_ELM_exclusive 15 +#define DAV_ELM_shared 16 + +static const struct hip_xml_elm lock_elms[] = { +#define A(x) { "DAV:", #x, DAV_ELM_ ## x, HIP_XML_COLLECT } /* ANY */ +#define D(x) { "DAV:", #x, DAV_ELM_ ## x, 0 } /* normal */ +#define C(x) { "DAV:", #x, DAV_ELM_ ## x, HIP_XML_CDATA } /* (#PCDATA) */ +#define E(x) { "DAV:", #x, DAV_ELM_ ## x, 0 /* LEAF */ } /* EMPTY */ + D(lockdiscovery), D(activelock), + D(prop), + D(lockscope), D(locktype), C(depth), A(owner), C(timeout), D(locktoken), + /* no lockentry */ + D(lockinfo), D(lockscope), D(locktype), + E(write), E(exclusive), E(shared), + C(href), +#undef A +#undef D +#undef C +#undef E + { NULL, 0, 0 } +}; + +static void *create(void *session, http_req *req, + const char *method, const char *uri) +{ + struct request_locks *rl = xcalloc(sizeof *rl); + rl->session = session; + return rl; +} + +static void pre_send(void *private, sbuffer req) +{ + struct request_locks *rl = private; + + if (rl->locks != NULL) { + struct submit_locks *item; + + /* Add in the If header */ + sbuffer_zappend(req, "If:"); + for (item = rl->locks; item != NULL; item = item->next) { + sbuffer_concat(req, " <", item->lock->uri, "> (<", + item->lock->token, ">)", NULL); + } + sbuffer_zappend(req, EOL); + } +} + +static void destroy(void *priv) +{ + struct request_locks *rl = priv; + struct submit_locks *lock, *next; + + for (lock = rl->locks; lock != NULL; lock = next) { + next = lock->next; + free(lock); + } + + free(rl); +} + +dav_lock_session *dav_lock_register(http_session *sess) +{ + dav_lock_session *locksess = xcalloc(sizeof *locksess); + + /* Register the hooks */ + http_add_hooks(sess, &lock_hooks, locksess); + + return locksess; +} + +void dav_lock_unregister(dav_lock_session *sess) +{ + /* FIXME: free the lock list */ + free(sess); +} + +/* Submit the given lock for the given URI */ +static void submit_lock(struct request_locks *rl, struct dav_lock *lock, + const char *uri) +{ + struct submit_locks *slock; + + /* Check for dups */ + for (slock = rl->locks; slock != NULL; slock = slock->next) { + if (strcasecmp(slock->lock->token, lock->token) == 0) + return; + } + + slock = xcalloc(sizeof *slock); + slock->lock = lock; + slock->uri = uri; + slock->next = rl->locks; + rl->locks = slock; +} + +struct dav_lock *dav_lock_find(dav_lock_session *sess, const char *uri) +{ + struct dav_lock *cur; + for (cur = sess->locks; cur != NULL; cur = cur->next) { + if (uri_compare(uri, cur->uri) == 0) + return cur; + } + return NULL; +} + +void dav_lock_using_parent(http_req *req, const char *uri) +{ + struct request_locks *rl = http_get_hook_private(req, HOOK_ID); + char *parent = uri_parent(uri); + + if (parent != NULL) { + struct dav_lock *lock; + /* Find any locks on the parent resource. + * FIXME: should check for depth-infinity locks too. */ + lock = dav_lock_find(rl->session, parent); + if (lock) { + DEBUG(DEBUG_LOCKS, "Locked parent, %s on %s\n", lock->token, + lock->uri); + submit_lock(rl, lock, uri); + } + free(parent); + } +} + +int dav_lock_iterate(dav_lock_session *sess, + dav_lock_walkfunc func, void *userdata) +{ + struct dav_lock *lock; + int count = 0; + + for (lock = sess->locks; lock != NULL; lock = lock->next) { + (*func)(lock, userdata); + count++; + } + + return count; +} + +void dav_lock_using_resource(http_req *req, const char *uri, int depth) +{ + /* Grab the private cookie for this request */ + struct request_locks *rl = http_get_hook_private(req, HOOK_ID); + struct dav_lock *lock; /* all the known locks */ + int match; + + /* Iterate over the list of session locks to see if any of + * them apply to this resource */ + for (lock = rl->session->locks; lock != NULL; lock = lock->next) { + + match = 0; + + if (depth == DAV_DEPTH_INFINITE && uri_childof(uri, lock->uri)) { + /* Case 1: this is a depth-infinity request which will + * modify a lock somewhere inside the collection. */ + DEBUG(DEBUG_LOCKS, "Has child: %s\n", lock->token); + match = 1; + } + else if (uri_compare(uri, lock->uri) == 0) { + /* Case 2: this request is directly on a locked resource */ + DEBUG(DEBUG_LOCKS, "Has direct lock: %s\n", lock->token); + match = 1; + } + else if (lock->depth == DAV_DEPTH_INFINITE && + uri_childof(lock->uri, uri)) { + /* Case 3: there is a higher-up infinite-depth lock which + * covers the resource that this request will modify. */ + DEBUG(DEBUG_LOCKS, "Is child of: %s\n", lock->token); + match = 1; + } + + if (match) { + submit_lock(rl, lock, uri); + } + } + +} + +void dav_lock_add(dav_lock_session *sess, struct dav_lock *lock) +{ + if (sess->locks != NULL) { + sess->locks->prev = lock; + } + lock->prev = NULL; + lock->next = sess->locks; + sess->locks = lock; +} + +void dav_lock_remove(dav_lock_session *sess, struct dav_lock *lock) +{ + if (lock->prev != NULL) { + lock->prev->next = lock->next; + } else { + sess->locks = lock->next; + } + if (lock->next != NULL) { + lock->next->prev = lock->prev; + } +} + +void dav_lock_free(struct dav_lock *lock) +{ + HTTP_FREE(lock->uri); + HTTP_FREE(lock->owner); + HTTP_FREE(lock->token); + free(lock); +} + +int dav_unlock(http_session *sess, struct dav_lock *lock) +{ + http_req *req = http_request_create(sess, "UNLOCK", lock->uri); + http_status status; + int ret; + + http_print_request_header(req, "Lock-Token", "<%s>", lock->token); + + /* TODO: need this or not? + * it definitely goes away when lock-null resources go away */ + dav_lock_using_parent(req, lock->uri); + + ret = http_request_dispatch(req, &status); + + if (ret == HTTP_OK && status._class == 2) { + ret = HTTP_OK; + } else { + ret = HTTP_ERROR; + } + + http_request_destroy(req); + + return ret; +} + +static int check_context(hip_xml_elmid parent, hip_xml_elmid child) { + DEBUG(DEBUG_XML, "dav_locks: check_context %d in %d\n", child, parent); + switch (parent) { + case HIP_ELM_root: + /* TODO: for LOCK requests only... + * shouldn't allow this for PROPFIND really */ + if (child == DAV_ELM_prop) + return HIP_XML_VALID; + break; + case DAV_ELM_prop: + if (child == DAV_ELM_lockdiscovery) + return HIP_XML_VALID; + break; + case DAV_ELM_lockdiscovery: + if (child == DAV_ELM_activelock) + return HIP_XML_VALID; + break; + case DAV_ELM_activelock: + switch (child) { + case DAV_ELM_lockscope: + case DAV_ELM_locktype: + case DAV_ELM_depth: + case DAV_ELM_owner: + case DAV_ELM_timeout: + case DAV_ELM_locktoken: + return HIP_XML_VALID; + default: + break; + } + break; + case DAV_ELM_lockscope: + switch (child) { + case DAV_ELM_exclusive: + case DAV_ELM_shared: + return HIP_XML_VALID; + default: + break; + } + case DAV_ELM_locktype: + if (child == DAV_ELM_write) + return HIP_XML_VALID; + break; + /* ... depth is PCDATA, owner is COLLECT, timeout is PCDATA */ + case DAV_ELM_locktoken: + if (child == DAV_ELM_href) + return HIP_XML_VALID; + break; + } + return HIP_XML_DECLINE; +} + +static int parse_depth(const char *depth) { + if (strcasecmp(depth, "infinity") == 0) { + return DAV_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 DAV_TIMEOUT_INFINITE; + } else if (strncasecmp(timeout, "Second-", 7) == 0) { + long to = strtol(timeout, NULL, 10); + if (to == LONG_MIN || to == LONG_MAX) + return DAV_TIMEOUT_INVALID; + return to; + } else { + return DAV_TIMEOUT_INVALID; + } +} + +static void *start_resource(void *userdata, const char *href) +{ + struct dav_lock_result *res = xcalloc(sizeof *res); + res->href = xstrdup(href); + return res; +} + +static void end_resource(void *userdata, void *response, + const char *status_line, const http_status *status, + const char *description) +{ + struct lock_discovery *ctx = userdata; + struct dav_lock_result *res = response; + + if (response == NULL) + return; + + res->next = ctx->list; + if (status_line != NULL && status != NULL) { + res->status_line = xstrdup(status_line); + res->status = *status; + res->status.reason_phrase = /* eeeeekkk.... HACKACKACKACK */ + res->status_line + (status->reason_phrase - res->status_line); + } + ctx->list = res; + DEBUG(DEBUG_LOCKS, "End of response for %s\n", res->href); +} + +static int end_element_common(struct dav_lock *l, const struct hip_xml_elm *elm, + const char *cdata) +{ + switch (elm->id){ + case DAV_ELM_write: + l->type = dav_locktype_write; + break; + case DAV_ELM_exclusive: + l->scope = dav_lockscope_exclusive; + break; + case DAV_ELM_shared: + l->scope = dav_lockscope_shared; + break; + case DAV_ELM_depth: + DEBUG(DEBUG_LOCKS, "Got depth: %s\n", cdata); + l->depth = parse_depth(cdata); + if (l->depth == -1) { + return -1; + } + break; + case DAV_ELM_timeout: + DEBUG(DEBUG_LOCKS, "Got timeout: %s\n", cdata); + l->timeout = parse_timeout(cdata); + if (l->timeout == DAV_TIMEOUT_INVALID) { + return -1; + } + break; + case DAV_ELM_owner: + l->owner = strdup(cdata); + break; + case DAV_ELM_href: + l->token = strdup(cdata); + break; + } + return 0; +} + +static int +start_element_ldisc(void *userdata, const struct hip_xml_elm *elm, + const char **atts) +{ + struct dav_lock_result *l = dav_propfind_get_current_resource(userdata); + struct dav_lock *lck; + + if (l == NULL) + return -1; + + switch (elm->id) { + case DAV_ELM_activelock: + lck = xcalloc(sizeof *lck); + lck->next = l->lock; + if (l->lock != NULL) + l->lock->prev = l->lock; + l->lock = lck; + lck->uri = xstrdup(l->href); + break; + default: + break; + } + + return 0; +} + +/* End-element handler for lock discovery PROPFIND response */ +static int +end_element_ldisc(void *userdata, const struct hip_xml_elm *elm, + const char *cdata) +{ + struct dav_lock_result *l = dav_propfind_get_current_resource(userdata); + + return end_element_common(l->lock, elm, cdata); +} + +/* End-element handler for LOCK response */ +static int +end_element_lock(void *userdata, const struct hip_xml_elm *elm, + const char *cdata) +{ + struct dav_lock *lock = userdata; + return end_element_common(lock, elm, cdata); +} + +/* Discover all locks on URI */ +int dav_lock_discover(http_session *sess, + const char *uri, struct dav_lock_result **locks) +{ + dav_propfind_handler *handler = dav_propfind_create(sess, uri, 0); + struct lock_discovery ctx = {0}; + const dav_propname props[] = { + { "DAV:", "lockdiscovery" }, + { NULL } + }; + int ret; + + dav_propfind_set_resource_handlers(handler, start_resource, end_resource); + hip_xml_add_handler(dav_propfind_get_parser(handler), lock_elms, + check_context, start_element_ldisc, end_element_ldisc, + handler); + + ret = dav_propfind_named(handler, props, &ctx); + + if (ret == HTTP_OK) { + if (ctx.list != NULL) { + *locks = ctx.list; + ret = HTTP_OK; + } else { + http_set_error(sess, _("No locks found.\n")); + ret = HTTP_ERROR; + } + } else { + /* FIXME: free the list */ + } + + return ret; +} + +int dav_lock(http_session *sess, struct dav_lock *lock) +{ + http_req *req = http_request_create(sess, "LOCK", lock->uri); + http_status status; + sbuffer body = sbuffer_create(); + hip_xml_parser *parser = hip_xml_create(); + int ret, parse_failed; + char *locktoken = NULL; + + hip_xml_add_handler(parser, lock_elms, check_context, + NULL, end_element_lock, lock); + + /* Create the body */ + sbuffer_concat(body, "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL + "<lockinfo xmlns='DAV:'>" EOL " <lockscope>", + lock->scope==dav_lockscope_exclusive? + "<exclusive/>":"<shared/>", + "</lockscope>" EOL + "<locktype><write/></locktype>", NULL); + + if (lock->owner) { + sbuffer_concat(body, "<owner>", lock->owner, "</owner>" EOL, NULL); + } + sbuffer_zappend(body, "</lockinfo>" EOL); + + http_set_request_body_buffer(req, sbuffer_data(body)); + http_add_response_body_reader(req, http_accept_2xx, + hip_xml_parse_v, parser); + http_add_response_header_handler(req, "Lock-Token", + http_duplicate_header, &locktoken); + http_add_request_header(req, "Content-Type", "text/xml"); + dav_add_depth_header(req, lock->depth); + + /* 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. + */ + dav_lock_using_parent(req, lock->uri); + /* This one is clearer from 2518 sec 8.10.4. */ + dav_lock_using_resource(req, lock->uri, lock->depth); + + ret = http_request_dispatch(req, &status); + + sbuffer_destroy(body); + parse_failed = !hip_xml_valid(parser); + + if (ret == HTTP_OK && status._class == 2) { + if (parse_failed) { + ret = HTTP_ERROR; + http_set_error(sess, hip_xml_get_error(parser)); + } + else if (status.code == 207) { + ret = HTTP_ERROR; + /* TODO: set the error string appropriately */ + } + else if (locktoken == NULL || strlen(locktoken) < 3) { + /* No valid Lock-Token header: IIS5 does this */ + /* FIXME */ + } else { + /* We have a locktoken header: strip of the angle brackets */ + if (locktoken[0] == '<') + locktoken++; + if (locktoken[strlen(locktoken)-1] == '>') + locktoken[strlen(locktoken)-1] = '\0'; + + /* FIXME */ + } + } else { + ret = HTTP_ERROR; + } + + http_request_destroy(req); + + /* TODO: free the list */ + return ret; +} diff --git a/neon/src/dav_locks.h b/neon/src/dav_locks.h new file mode 100644 index 000000000..3c2371746 --- /dev/null +++ b/neon/src/dav_locks.h @@ -0,0 +1,86 @@ +/* + WebDAV Class 2 locking operations + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 DAV_LOCKS_H +#define DAV_LOCKS_H + +enum dav_lock_scope { + dav_lockscope_exclusive, + dav_lockscope_shared +}; + +enum dav_lock_type { + dav_locktype_write +}; + +/* Would be typedef'ed to dav_lock except lock is a verb and a noun. + * Damn the English language. */ +struct dav_lock { + char *uri; + int depth; + enum dav_lock_type type; + enum dav_lock_scope scope; + char *token; + char *owner; + long timeout; + struct dav_lock *next; + struct dav_lock *prev; +}; + +struct dav_lock_result { + struct dav_lock *lock; + char *href; + http_status status; + char *status_line; + struct dav_lock_result *next; +}; + +#define DAV_TIMEOUT_INFINITE -1 +#define DAV_TIMEOUT_INVALID -2 + +typedef struct dav_lock_session_s dav_lock_session; + +dav_lock_session *dav_lock_register(http_session *sess); +void dav_lock_unregister(dav_lock_session *sess); + +void dav_lock_add(dav_lock_session *sess, struct dav_lock *lock); +void dav_lock_remove(dav_lock_session *sess, struct dav_lock *lock); + +typedef void (*dav_lock_walkfunc)(struct dav_lock *lock, void *userdata); + +int dav_lock_iterate(dav_lock_session *sess, + dav_lock_walkfunc func, void *userdata); + +/* Indicate that this request is of depth n on given uri */ +void dav_lock_using_resource(http_req *req, const char *uri, int depth); +/* Indicate that this request will modify parent collection of given URI */ +void dav_lock_using_parent(http_req *req, const char *uri); + +int dav_lock(http_session *sess, struct dav_lock *lock); +int dav_lock_discover(http_session *sess, + const char *uri, struct dav_lock_result **locks); + +struct dav_lock *dav_lock_find(dav_lock_session *sess, const char *uri); +int dav_unlock(http_session *sess, struct dav_lock *lock); + +void dav_lock_free(struct dav_lock *lock); + +#endif /* DAV_LOCKS_H */ diff --git a/neon/src/dav_props.c b/neon/src/dav_props.c new file mode 100644 index 000000000..1b86a1204 --- /dev/null +++ b/neon/src/dav_props.c @@ -0,0 +1,192 @@ +/* + WebDAV Properties manipulation + Copyright (C) 2000, Joe Orton <joe@orton.demon.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 + + Id: dav_props.c,v 1.12 2000/05/09 22:36:55 joe Exp +*/ + +#include "config.h" + +#include "xalloc.h" +#include "dav_props.h" +#include "dav_basic.h" +#include "hip_xml.h" + +struct dav_propfind_handler_s { + dav_pf_start_resource start_resource; + dav_pf_end_resource end_resource; + struct hip_xml_handler *handler; + http_session *sess; + const char *uri; + int depth; + char *href; + http_status status; + void *userdata, *resource; +}; + +void *dav_propfind_get_current_resource( dav_propfind_handler *handler ) +{ + return handler->resource; +} + +static void *start_response( void *userdata, const char *href ) +{ + dav_propfind_handler *ctx = userdata; + ctx->resource = NULL; + if( ctx->start_resource ) { + ctx->resource = (*ctx->start_resource)( ctx->userdata, href ); + } + return ctx->resource; +} + +static void end_response( void *userdata, void *response, + const char *status_line, const http_status *status ) +{ + dav_propfind_handler *ctx = userdata; + if( ctx->end_resource ) { + (*ctx->end_resource)( ctx->userdata, response, status_line, status ); + } +} + +dav_propfind_handler * +dav_propfind_create( http_session *sess, const char *uri, int depth ) +{ + dav_propfind_handler *ret = xmalloc(sizeof(dav_propfind_handler)); + memset( ret, 0, sizeof(dav_propfind_handler)); + ret->uri = uri; + ret->depth = depth; + ret->sess = sess; + return ret; +} + +void dav_propfind_set_resource_handlers( dav_propfind_handler *handler, + dav_pf_start_resource start_res, + dav_pf_end_resource end_res ) +{ + handler->start_resource = start_res; + handler->end_resource = end_res; +} + +void dav_propfind_set_element_handler( dav_propfind_handler *handler, + struct hip_xml_handler *elm_handler ) +{ + handler->handler = elm_handler; +} + +static int propfind( dav_propfind_handler *handler, const dav_propname *names, + void *userdata ) +{ + int ret, is_allprop = (names==NULL); + http_req *req = http_request_create( handler->sess, "PROPFIND", handler->uri ); + http_status status; + dav_207_parser *parser; + sbuffer body, hdrs; + + body = sbuffer_create(); + + sbuffer_concat( body, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL + "<propfind xmlns=\"DAV:\">", NULL ); + + if( !is_allprop ) { + int n; + sbuffer_zappend( body, "<prop>" EOL ); + for( n = 0; names[n].name != NULL; n++ ) { + sbuffer_concat( body, "<", names[n].name, " xmlns=\"", + names[n].nspace, "\"/>" EOL, NULL ); + } + sbuffer_zappend( body, "</prop></propfind>" EOL ); + } else { + sbuffer_zappend( body, "</allprop></propfind>" EOL ); + } + + parser = dav_207_init_with_handler( handler, handler->handler ); + dav_207_set_response_handlers( parser, start_response, end_response ); + handler->userdata = userdata; + + http_set_request_body_buffer( req, sbuffer_data(body) ); + + hdrs = http_get_request_header( req ); + sbuffer_zappend( hdrs, "Content-Type: text/xml" EOL ); /* TODO: UTF-8? */ + dav_add_depth_header( req, handler->depth ); + + http_add_response_body_reader( req, dav_accept_207, dav_207_parse, parser ); + + ret = http_request_dispatch( req, &status ); + + http_request_destroy( req ); + + return ret; +} + +int dav_propfind_allprop( dav_propfind_handler *handler, void *userdata ) +{ + return propfind( handler, NULL, userdata ); +} + +int dav_propfind_named( dav_propfind_handler *handler, + const dav_propname *names, void *userdata ) +{ + return propfind( handler, names, userdata ); +} + + +/* The easy one... PROPPATCH */ +int dav_proppatch( http_session *sess, const char *uri, + const dav_proppatch_operation *items ) +{ + http_req *req = http_request_create( sess, "PROPPATCH", uri ); + sbuffer body = sbuffer_create(), hdrs; + int n, ret; + + /* Create the request body */ + sbuffer_zappend( body, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" EOL + "<propertyupdate xmlns=\"DAV:\">" ); + + for( n = 0; items[n].name != NULL; n++ ) { + switch( items[n].type ) { + case dav_propset: + /* <set><prop><prop-name>value</prop-name></prop></set> */ + sbuffer_concat( body, "<set><prop>" + "<", items[n].name->name, " xmlns=\"", + items[n].name->nspace, "\">", items[n].value, + "</", items[n].name->name, "></prop></set>" EOL, NULL ); + break; + + case dav_propremove: + /* <remove><prop><prop-name/></prop></remove> */ + sbuffer_concat( body, "<remove><prop><", items[n].name->name, " xmlns=\"", + items[n].name->nspace, "\"/></prop></remove>" EOL, NULL ); + break; + } + } + + sbuffer_zappend( body, "</propertyupdate>" EOL ); + + http_set_request_body_buffer( req, sbuffer_data(body) ); + + hdrs = http_get_request_header( req ); + sbuffer_zappend( hdrs, "Content-Type: text/xml" EOL ); /* TODO: UTF-8? */ + + ret = dav_simple_request( sess, req ); + + sbuffer_destroy( body ); + + return ret; +} + diff --git a/neon/src/dav_props.h b/neon/src/dav_props.h new file mode 100644 index 000000000..9f0ac0fe2 --- /dev/null +++ b/neon/src/dav_props.h @@ -0,0 +1,89 @@ +/* + WebDAV Properties manipulation + Copyright (C) 2000, Joe Orton <joe@orton.demon.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 DAV_PROPS_H +#define DAV_PROPS_H + +#include "http_request.h" +#include "dav_207.h" + +/* PROPFIND results via three callbacks: */ + +struct dav_propfind_handler_s; +typedef struct dav_propfind_handler_s dav_propfind_handler; + +/* Callback types */ +typedef void *(*dav_pf_start_resource)( void *userdata, const char *href ); +typedef void (*dav_pf_got_property)( void *userdata, void *resource, + const dav_propname *name, const hip_xml_char **atts, + const char *value ); +typedef void (*dav_pf_end_resource)( void *userdata, void *resource, + const char *status_line, const http_status *status ); + +/* Find properties with names in set *props on resource at URI + * in set *props, with given depth. If props == NULL all properties + * are returned. + * Returns: + * HTTP_OK on success. + * else other HTTP_* code + */ +dav_propfind_handler * +dav_propfind_create( http_session *sess, const char *uri, int depth ); + +void dav_propfind_register( dav_propfind_handler *handler, + dav_pf_start_resource start_res, + dav_pf_got_property got_prop, + dav_pf_end_resource end_res ); + +void dav_propfind_set_resource_handlers( dav_propfind_handler *handler, + dav_pf_start_resource start_res, + dav_pf_end_resource end_res ); + +void dav_propfind_set_element_handler( dav_propfind_handler *handler, + struct hip_xml_handler *elm_handler ); + +int dav_propfind_allprop( dav_propfind_handler *handler, void *userdata ); + +int dav_propfind_named( dav_propfind_handler *handler, + const dav_propname *names, void *userdata ); + +/* A PROPPATCH request may include any number of operations. Pass an + * array of these operations to dav_proppatch, with the last item + * having the name element being NULL. If the type is propset, the + * property of the given name is set to the new value. If the type is + * propremove, the property of the given name is deleted, and the + * value is ignored. */ +typedef struct { + const dav_propname *name; + enum { + dav_propset, + dav_propremove + } type; + const char *value; +} dav_proppatch_operation; + +/* FIXME: shouldn't need this. */ +void *dav_propfind_get_current_resource( dav_propfind_handler *handler ); + +int dav_proppatch( http_session *sess, const char *uri, + const dav_proppatch_operation *items ); + +#endif /* DAV_PROPS_H */ diff --git a/neon/src/hip_xml.c b/neon/src/hip_xml.c new file mode 100644 index 000000000..1761504ce --- /dev/null +++ b/neon/src/hip_xml.c @@ -0,0 +1,605 @@ +/* + Higher Level Interface to XML Parsers. + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: hip_xml.c,v 1.14 2000/05/09 22:33:54 joe Exp +*/ + +#include <config.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "neon_i18n.h" + +#include "xalloc.h" +#include "http_utils.h" +#include "string_utils.h" +#include "hip_xml.h" + +static const char *friendly_name( const struct hip_xml_elm *elm ) +{ + switch( elm->id ) { + case HIP_ELM_root: + return _("document root"); + case HIP_ELM_unknown: + return _("unknown element"); + default: + if( elm->name ) { + return elm->name; + } else { + return _("unspecified"); + } + } +} + +/* TODO: + * could move 'valid' into state, maybe allow optional + * continuation past an invalid branch. + */ + +const static struct hip_xml_elm root_element = +{ "@<root>@", HIP_ELM_root, 0 }; + +/* The callback handlers */ +static void start_element( void *userdata, const hip_xml_char *name, const hip_xml_char **atts ); +static void end_element( void *userdata, const hip_xml_char *name ); +static void char_data( void *userdata, const hip_xml_char *cdata, int len ); + +#ifdef HAVE_EXPAT +/* libxml doesn't seem to pass stuff back in UTF-8. + * maybe libxml-2.0 will do. */ +#define HIP_XML_DECODE_UTF8 +#endif + +#ifdef HIP_XML_DECODE_UTF8 + +/* UTF-8 decoding */ + +/* Single byte range 0x00 -> 0x7F */ +#define SINGLEBYTE_UTF8( ch ) ( ((unsigned char) (ch)) < 0x80 ) + +/* Decode a double byte UTF8 string. + * Returns 0 on success or non-zero on error. */ +static inline int decode_utf8_double( char *dest, const char *src ); + +#endif + +/* Linked list of namespace scopes */ +struct hip_xml_nspace { + hip_xml_char *name; + hip_xml_char *uri; + struct hip_xml_nspace *next; +}; + +/* And an auxiliary */ +static int parse_element( struct hip_xml_parser *p, struct hip_xml_state *state, + const hip_xml_char *name, const hip_xml_char **atts ); + + +#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 */ + NULL, /* xmlParserError */ + NULL, /* xmlParserError */ + NULL, /* getParameterEntity */ +}; + +#endif /* HAVE_LIBXML */ + +#ifdef HIP_XML_DECODE_UTF8 + +static inline int +decode_utf8_double( char *dest, const char *src ) +{ + /* From utf-8 man page; two-byte encoding is: + * 0x00000080 - 0x000007FF: + * 110xxxxx 10xxxxxx + * If more than 8-bits of those x's are set, we fail. + * So, we check that the first 6 bits of the first byte are: + * 110000. + * Then decode like: + * 110000xx 10yyyyyy -> xxyyyyyy + * Do this with a mask and a compare: + * zzzzzzzz + * & 11111100 <=> 0xFC + * == 11000000 <=> 0XC0 + * + * joe: A real C hacker would probably do some funky bit + * inversion, and turn this into an is-not-zero test, + * but I'm a fake, so... + */ + if( (src[0] & 0xFC) == 0xC0 ) { + dest[0] = ((src[0] & 0x03) << 6) | (src[1] & 0x3F); + return 0; + } else { + return -1; + } +} + +#endif + +int hip_xml_currentline( struct hip_xml_parser *p ) { +#ifdef HAVE_EXPAT + return XML_GetCurrentLineNumber(p->parser); +#else + return p->parser->input->line; +#endif +} + +static int find_handler( struct hip_xml_handler *list, struct hip_xml_state *state ) { + struct hip_xml_handler *cur, *unk_handler = NULL; + const struct hip_xml_elm *unk_elm = NULL; + int n; + for( cur = list; cur != NULL; cur = cur->next ) { + for( n = 0; cur->elements[n].nspace != NULL; n++ ) { + if( strcasecmp( cur->elements[n].name, state->name ) == 0 && + strcasecmp( cur->elements[n].nspace, state->nspace ) == 0 ) { + state->handler = cur; + state->elm = &cur->elements[n]; + return 0; + } + if( !unk_elm && cur->elements[n].id == HIP_ELM_unknown ) { + unk_elm = &cur->elements[n]; + unk_handler = cur; + } + } + } + if( !cur && unk_elm ) { + /* Give them the unknown handler */ + state->handler = unk_handler; + state->elm = unk_elm; + return 0; + } else { + return -1; + } +} + +char *hip_xml_escape( const char *text ) +{ +#if 0 + sbuffer buf = sbuffer_create(); + const char *pnt; + if( !buf ) return NULL; + /* FIXME: implement */ +#endif + return NULL; +} + +/* Called with the start of a new element. */ +static void +start_element( void *userdata, const hip_xml_char *name, const hip_xml_char **atts ) +{ + struct hip_xml_parser *p = (struct hip_xml_parser *)userdata; + struct hip_xml_state *s; + + if( !p->valid ) { + /* We've stopped parsing */ + DEBUG( DEBUG_XML, "Parse died. Ignoring start of element: %s\n", name ); + return; + } + if( p->collect ) { + /* In Collect Mode. */ + sbuffer_concat( p->buffer, "<", name, NULL ); + if( atts != NULL ) { + int n; + for( n = 0; atts[n] != NULL; n+=2 ) { + sbuffer_concat( p->buffer, " ", atts[n], "=", atts[n+1], + NULL ); + } + } + sbuffer_zappend( p->buffer, ">" ); + /* One deeper */ + p->collect++; + return; + } + /* Set the new state */ + s = xmalloc( sizeof(struct hip_xml_state) ); + memset( s, 0, sizeof(struct hip_xml_state) ); + s->parent = p->current; + p->current = s; + /* We need to handle namespaces ourselves */ + if( parse_element( p, s, name, atts ) ) { + /* it bombed. */ + p->valid = 0; + return; + } + /* Map the element name to an id */ + DEBUG( DEBUG_XMLPARSE, "Mapping element name %s@@%s... ", s->nspace, s->name ); + if( find_handler( p->handlers, s ) ) { + DEBUG( DEBUG_XMLPARSE, "Unexpected element\n" ); + snprintf( p->error, BUFSIZ, "Unknown XML element `%s'", s->name ); + p->valid = 0; + return; + } + + DEBUG( DEBUG_XMLPARSE, "mapped to id %d\n", s->elm->id ); + + /* Do we want cdata? */ + p->want_cdata = ((p->current->elm->flags & HIP_XML_CDATA) + == HIP_XML_CDATA); + p->collect = ((p->current->elm->flags & HIP_XML_COLLECT) + == HIP_XML_COLLECT); + + /* expat is not a validating parser - check the new element + * is valid in the current context. + */ + DEBUG( DEBUG_XML, "Checking context of %s@@%s: element %s (parent: %s)\n", + s->nspace, s->name, friendly_name(s->elm), friendly_name(s->parent->elm) ); + if( (*s->handler->validate_cb)( s->parent->elm->id, s->elm->id ) ) { + DEBUG( DEBUG_XML, "Invalid context.\n" ); + snprintf( p->error, BUFSIZ, + _("XML is not valid (%s found in parent %s)"), + friendly_name(s->elm), friendly_name(s->parent->elm) ); + p->valid = 0; + } else { + DEBUG( DEBUG_XML, "Valid context.\n" ); + if( s->handler->startelm_cb ) { + if( (*s->handler->startelm_cb)( s->handler->userdata, s, atts ) ) { + DEBUG( DEBUG_XML, "Startelm callback failed.\n" ); + p->valid = 0; + } + } else { + DEBUG( DEBUG_XML, "No startelm handler.\n" ); + } + } + +} + +/* Destroys given state */ +static void destroy_state( struct hip_xml_state *s ) { + struct hip_xml_nspace *this_ns, *next_ns; + DEBUG( DEBUG_XMLPARSE, "Freeing namespaces...\n" ); + HTTP_FREE( s->default_ns ); + HTTP_FREE( s->name ); + /* Free the namespaces */ + this_ns = s->nspaces; + while( this_ns != NULL ) { + next_ns = this_ns->next; + free( this_ns->name ); + free( this_ns->uri ); + free( this_ns ); + this_ns = next_ns; + }; + DEBUG( DEBUG_XMLPARSE, "Finished freeing namespaces.\n" ); + free( s ); +} + +static void char_data( void *userdata, const hip_xml_char *data, int len ) { + struct hip_xml_parser *p = userdata; + + if( !p->want_cdata || !p->valid ) return; + /* First, if this is the beginning of the CDATA, skip all + * leading whitespace, we don't want it. */ + DEBUG( DEBUG_XMLPARSE, "Given %d bytes of cdata.\n", len ); + if( sbuffer_size(p->buffer) == 0 ) { + size_t wslen = 0; + /* Ignore any leading whitespace */ + while( wslen < len && + ( data[wslen] == ' ' || data[wslen] == '\r' || + data[wslen] == '\n' || data[wslen] == '\t' ) ) { + wslen++; + } + data += wslen; + len -= wslen; + DEBUG( DEBUG_XMLPARSE, "Skipped %d bytes of leading whitespace.\n", + wslen ); + if( len == 0 ) { + DEBUG( DEBUG_XMLPARSE, "Zero bytes of content.\n" ); + return; + } + } + +#ifdef HIP_XML_DECODE_UTF8 + + if( (p->current->elm->flags & HIP_XML_UTF8DECODE) == HIP_XML_UTF8DECODE ) { + int n, m, clen; + char *dest; + + clen = sbuffer_size(p->buffer); + sbuffer_grow( p->buffer, clen + len + 1 ); + dest = sbuffer_data( p->buffer ) + clen; + + for( n = 0, m = 0; n < len; n++, m++ ) { + if( SINGLEBYTE_UTF8( data[n] ) ) { + dest[m] = data[n]; + } else { + /* An optimisation here: we only deal with 8-bit + * data, which will be encoded as two bytes of UTF-8 */ + if( (len - n < 2) || + decode_utf8_double( &dest[m], &data[n] ) ) { + /* Failed to decode! */ + DEBUG( DEBUG_XML, "Could not decode UTF-8 data.\n" ); + strcpy( p->error, "XML parser received non-8-bit data" ); + p->valid = 0; + return; + } else { +#if 0 + DEBUG( DEBUG_XML, "UTF-8 two-bytes decode: " + "0x%02hx 0x%02hx -> 0x%02hx!\n", + data[n] & 0xFF, data[n+1] & 0xFF, dest[m] & 0xFF ); +#endif + /* Skip the second byte */ + n += 2; + } + } + } + sbuffer_altered( p->buffer ); + } else { + sbuffer_append( p->buffer, data, len ); + } + +#else /* !HIP_XML_DECODE_UTF8 */ + + sbuffer_append( p->buffer, data, len ); + +#endif + +} + +/* Called with the end of an element */ +static void end_element( void *userdata, const hip_xml_char *name ) { + struct hip_xml_parser *p = userdata; + struct hip_xml_state *s = p->current; + if( !p->valid ) { + /* We've stopped parsing */ + DEBUG( DEBUG_XML, "Parse died. Ignoring end of element: %s\n", name ); + return; + } + if( p->collect > 0 ) { + if( --p->collect ) { + sbuffer_concat( p->buffer, "</", name, ">", NULL ); + return; + } + } + + /* process it */ + if( s->handler->endelm_cb ) { + DEBUG( DEBUG_XMLPARSE, "Calling endelm callback for %s.\n", s->elm->name ); + if( (*s->handler->endelm_cb)( s->handler->userdata, s, + p->want_cdata?sbuffer_data(p->buffer): + NULL ) ) { + DEBUG( DEBUG_XML, "Endelm callback failed.\n" ); + p->valid = 0; + } + } + p->current = s->parent; + /* Move the current pointer up the branch */ + DEBUG( DEBUG_XML, "Back in element: %s\n", friendly_name(p->current->elm) ); + if( p->want_cdata ) { + sbuffer_clear( p->buffer ); + } + destroy_state( s ); +} + +/* Parses the attributes, and handles XML namespaces. + * With a little bit of luck. + * Returns: + * the element name on success + * or NULL on error. + */ +static int parse_element( struct hip_xml_parser *p, struct hip_xml_state *state, + const hip_xml_char *name, const hip_xml_char **atts ) +{ + struct hip_xml_nspace *ns; + const hip_xml_char *pnt; + struct hip_xml_state *xmlt; + + DEBUG( DEBUG_XMLPARSE, "Parsing elm of name: [%s]\n", name ); + /* Parse the atts for namespace declarations... if we have any atts. + * expat will never pass us atts == NULL, but libxml will. */ + if( atts != NULL ) { + int attn; + for( attn = 0; atts[attn]!=NULL; attn+=2 ) { + DEBUG( DEBUG_XMLPARSE, "Got attribute: [%s] = [%s]\n", atts[attn], atts[attn+1] ); + if( strcasecmp( atts[attn], "xmlns" ) == 0 ) { + /* New default namespace */ + state->default_ns = xstrdup( atts[attn+1] ); + DEBUG( DEBUG_XMLPARSE, "New default namespace: %s\n", + state->default_ns ); + } else if( strncasecmp( atts[attn], "xmlns:", 6 ) == 0 ) { + /* New namespace scope */ + ns = xmalloc( sizeof( struct hip_xml_nspace ) ); + ns->next = state->nspaces; + state->nspaces = ns; + ns->name = xstrdup( atts[attn]+6 ); /* skip the xmlns= */ + ns->uri = xstrdup( atts[attn+1] ); + DEBUG( DEBUG_XMLPARSE, "New namespace scope: %s -> %s\n", + ns->name, ns->uri ); + } + } + } + /* Now check the elm name for a namespace scope */ + pnt = strchr( name, ':' ); + if( pnt == NULL ) { + /* No namespace prefix - have we got a default? */ + state->name = xstrdup(name); + DEBUG( DEBUG_XMLPARSE, "No prefix found, searching for default.\n" ); + for( xmlt = state; xmlt!=NULL; xmlt=xmlt->parent ) { + if( xmlt->default_ns != NULL ) { + state->nspace = xmlt->default_ns; + break; + } + } + if( state->nspace == NULL ) { + DEBUG( DEBUG_XMLPARSE, "No default namespace, using empty.\n" ); + state->nspace = ""; + } + } else { + DEBUG( DEBUG_XMLPARSE, "Got namespace scope. Trying to resolve..." ); + /* Have a scope - resolve it */ + for( xmlt = state; state->nspace==NULL && xmlt!=NULL; xmlt=xmlt->parent ) { + for( ns = xmlt->nspaces; ns!=NULL; ns=ns->next ) { + /* Just compare against the bit before the : + * pnt points to the colon. */ + if( strncasecmp( ns->name, name, pnt-name ) == 0 ) { + /* Scope matched! Hoorah */ + state->nspace = ns->uri; + /* end the search */ + break; + } + } + } + if( state->nspace != NULL ) { + DEBUG( DEBUG_XMLPARSE, "Resolved prefix to [%s]\n", state->nspace ); + /* The name is everything after the ':' */ + if( pnt[1] == '\0' ) { + snprintf( p->error, BUFSIZ, + "Element name missing in '%s' at line %d.", + name, hip_xml_currentline(p) ); + DEBUG( DEBUG_XMLPARSE, "No element name after ':'. Failed.\n" ); + return -1; + } + state->name = xstrdup(pnt+1); + } else { + DEBUG( DEBUG_XMLPARSE, "Undeclared namespace.\n" ); + snprintf( p->error, BUFSIZ, + "Undeclared namespace in '%s' at line %d.", + name, hip_xml_currentline(p) ); + return -1; + } + } + return 0; +} + +void hip_xml_init( struct hip_xml_parser *p, struct hip_xml_handler *handlers ) +{ + /* Initialize the expat stuff */ + memset( p, 0, sizeof( struct hip_xml_parser ) ); + /* Initialize other stuff */ + p->valid = 1; + /* Placeholder for the root element */ + p->current = p->root = xmalloc( sizeof(struct hip_xml_state) ); + memset( p->root, 0, sizeof(struct hip_xml_state) ); + p->root->elm = &root_element; + p->handlers = handlers; + /* Initialize the cdata buffer */ + p->buffer = sbuffer_create(); +#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 ); +#else + p->parser = xmlCreatePushParserCtxt( &sax_handler, (void *)p, NULL, 0, NULL ); + if( p->parser == NULL ) { + abort(); + } +#endif +} + +void hip_xml_parse_v( void *userdata, const char *block, size_t len ) +{ + struct hip_xml_parser *p = userdata; + /* FIXME: The two XML parsers break all our nice abstraction by + * choosing different char *'s. The swine. This may kill us some + * day. */ + hip_xml_parse( p, (const hip_xml_char *) block, len ); +} + +/* Parse the given block of input of length len */ +void hip_xml_parse( struct hip_xml_parser *p, const hip_xml_char *block, size_t len ) +{ + int ret, flag; + /* duck out if it's broken */ + if( !p->valid ) { + DEBUG( DEBUG_XML, "Not parsing %d bytes.\n", len ); + return; + } + if( len == 0 ) { + flag = -1; + block = ""; + DEBUG( DEBUG_XML, "Got 0-length buffer, end of document.\n" ); + } else { + DEBUG( DEBUG_XML, "Parsing %d 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 ); + DEBUG( DEBUG_XMLPARSE, "XML_Parse returned %d\n", ret ); + if( ret == 0 && p->valid ) { + snprintf( p->error, BUFSIZ, + "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 ); + DEBUG( DEBUG_XMLPARSE, "xmlParseChunk returned %d\n", ret ); + if( p->parser->errNo && p->valid ) { + /* FIXME: error handling */ + snprintf( p->error, BUFSIZ, "XML parse error at line %d.", + hip_xml_currentline(p) ); + p->valid = 0; + } +#endif + +} + +int hip_xml_finish( struct hip_xml_parser *p ) +{ + struct hip_xml_state *s, *parent; + sbuffer_destroy( p->buffer ); + /* Clean up any states which may remain. + * If p.valid, then this should be only the root element. */ + for( s = p->current; s!=NULL; s=parent ) { + parent = s->parent; + destroy_state( s ); + } +#ifdef HAVE_EXPAT + XML_ParserFree( p->parser ); +#else + xmlFreeParserCtxt( p->parser ); +#endif + return !p->valid; +} + diff --git a/neon/src/hip_xml.h b/neon/src/hip_xml.h new file mode 100644 index 000000000..eeadff637 --- /dev/null +++ b/neon/src/hip_xml.h @@ -0,0 +1,254 @@ +/* + Higher Level Interface to XML Parsers. + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 HIP_XML_H +#define HIP_XML_H + +/* + TODO: + + There is a whopping great whole in this design. It doesn't + correctly handle XML with elements interspersed with CDATA, e.g. + <foo>this is cdata<bar>still is</bar>still cdata here</foo> + To handle this, we probably need an extra flag, HIP_XML_MIXED, + and a cdata callback. Then, on start_element, check if we're in + mixed mode, and call the cdata callback with everything-up-to-now, + if we are, then empty the cdata buffer. Note that even with + this flaw the code is useful, since DAV XML responses don't + use this kind of XML. + + To Consider: + + The sitecopy XML parsing code started off as a fairly nasty + hack. This, then, is the abstraction of that hack. + + Need to evaluate whether simply building the entire XML tree + up is actually better. + + My intuition says it's not, but, this is still quite a laborious + method of handling XML. + +*/ + +#include <config.h> + +/* for BUFSIZ */ +#include <stdio.h> + +/* for sbuffer */ +#include "string_utils.h" + +#ifdef HAVE_EXPAT +/******** Expat ***********/ +# include <xmlparse.h> +typedef XML_Char hip_xml_char; + +#else /* not HAVE_EXPAT */ +# ifdef HAVE_LIBXML +/******** libxml **********/ +# include <parser.h> +typedef xmlChar hip_xml_char; + +# else /* not HAVE_LIBXML */ +# error need an XML parser +# endif /* not HAVE_LIBXML */ +#endif /* not HAVE_EXPAT */ + +/* Generic XML response handling... + + Basic principle is that you provide these things: + 1) a list of elements which you wish to handle + 2) a context checking function which will validate whether a + given element is valid in the given parent. + 3) a callback function which is called when a complete element + has been parsed. + + This code then deals with the boring stuff like: + - Dealing with XML namespaces + - Collecting CDATA + + The element list is stored in a 'struct hip_xml_elm' array. + For each element, you specify the element name, an element id, + and some flags for that element: whether it can have children, + and whether it can have cdata. + + Lists of element lists can be chained together, so the elements + from another module don't have to be defined twice. + + Say we have XML docs like: + + <foo attr="yes"> + <bar>norman</bar> + <bee>yesterday</bee> + <bar>fishing</bar> + </foo> + + and we have one module, which handles <foo> elements, and + another module, which handles bar+bee elements. + + The element lists are: + +const static struct hip_xml_elm barbee_elms[] = { + {"bar", HIP_ELM_bar, HIP_XML_CDATA }, + {"bee", HIP_ELM_bee, HIP_XML_CDATA }, + { NULL } +}; + + Note that foo doesn't take CDATA: + +const static struct hip_xml_elm foo_elms[] = { + {"foo", HIP_ELM_foo, 0 }, + { NULL } +}; + + The context validation functions are: + + int check_barbee_context( parent, child ) { + return ((child == HIP_ELM_bar || + child == HIP_ELM_bee) && parent == HIP_ELM_foo); + } + + int check_foo_context( a, parent, child ) { + return (child == HIP_ELM_foo && parent = HIP_ELM_root); + } + + The list-of-lists are declared: + +struct hip_xml_elmlist listfoo = { foo_elms, NULL }, + listbarbee = { barbee_elms, &listfoo }; + + Note that listbarbee chains listfoo on the end. + +*/ + +/* Reserved element id's */ +#define HIP_ELM_unknown -1 +#define HIP_ELM_root 0 + +typedef int hip_xml_elmid; + +struct hip_xml_state; +struct hip_xml_elm; +struct hip_xml_handler; + +/* An element */ +struct hip_xml_elm { + const char *nspace, *name; + hip_xml_elmid id; + unsigned int flags; +}; + +/* Function to check element context... returns 0 if child is a valid + * child tag of parent, else non-zero. */ +typedef int (*hip_xml_validate_cb) + ( hip_xml_elmid child, hip_xml_elmid parent ); + +typedef int (*hip_xml_startelm_cb) + ( void *userdata, const struct hip_xml_state *s, const hip_xml_char **atts ); + +/* Called when a complete element is parsed */ +typedef int (*hip_xml_endelm_cb) + ( void *userdata, const struct hip_xml_state *s, const char *cdata ); + +/* A list of elements */ +struct hip_xml_handler { + const struct hip_xml_elm *elements; /* put it in static memory */ + hip_xml_validate_cb validate_cb; /* validation function */ + hip_xml_startelm_cb startelm_cb; /* on-complete element function */ + hip_xml_endelm_cb endelm_cb; /* on-complete element function */ + void *userdata; + struct hip_xml_handler *next; +}; + +struct hip_xml_state { + /* The element details */ + const struct hip_xml_elm *elm; + hip_xml_char *name; + const hip_xml_char *nspace; + /* Namespaces declared in this element */ + hip_xml_char *default_ns; /* A default namespace */ + struct hip_xml_nspace *nspaces; /* List of other namespace scopes */ + /* Extras */ + struct hip_xml_handler *handler; /* Where the element was declared */ + struct hip_xml_state *parent; /* The parent in the tree */ +}; + +/* We pass around a hip_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 hip_xml_parser { + struct hip_xml_state *root; /* the root of the document */ + struct hip_xml_state *current; /* current element in the branch */ + sbuffer buffer; /* the CDATA/collect buffer */ + unsigned int valid:1; /* currently valid? */ + unsigned int want_cdata:1; /* currently collecting CDATA? */ + unsigned int collect; /* currently collecting all children? */ + struct hip_xml_handler *handlers; /* list of handlers */ +#ifdef HAVE_EXPAT + XML_Parser parser; +#else + xmlParserCtxtPtr parser; +#endif + char error[BUFSIZ]; +}; + +/* Flags */ +/* This element has no children */ +#define HIP_XML_CDATA (1<<1) +/* Collect complete contents of this node as cdata */ +#define HIP_XML_COLLECT ((1<<2) | HIP_XML_CDATA) +/* Decode UTF-8 data in cdata. */ +#define HIP_XML_UTF8DECODE (1<<3) + +/* Initialise the parser p, with the list of elements we accept, + * the context checking function, the element callback, and the userdata + * for the element callback. + */ +void hip_xml_init( struct hip_xml_parser *p, struct hip_xml_handler *elms ); + +/* Cleans up the parser's internal structures allocated by hip_xml_init. + * Returns: + * 0 if the parse was successful + * non-zero if the parse failed. + */ +int hip_xml_finish( struct hip_xml_parser *p ); + +/* Parse the given block of input of length len. Block does + * not need to be NULL-terminated. Note that signed-ness of + * the block characters depends upon whether libxml or expat + * is being used as the underlying parser. */ +void hip_xml_parse( struct hip_xml_parser *p, + const hip_xml_char *block, size_t len ); + +/* As above taking a void * userdata, and a const char * data block, + * both of which are casted appropriately and passed to + * hip_xml_parse. */ +void hip_xml_parse_v( void *userdata, const char *block, size_t len ); + +/* Return current parse line for errors */ +int hip_xml_currentline( struct hip_xml_parser *p ); + +/* Returns XML-escaped text, allocated with malloc() */ +char *hip_xml_escape( const char *text ); + +#endif /* HIP_XML_H */ diff --git a/neon/src/http_auth.c b/neon/src/http_auth.c new file mode 100644 index 000000000..37eb218e9 --- /dev/null +++ b/neon/src/http_auth.c @@ -0,0 +1,887 @@ +/* + HTTP Authentication routines + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: http_auth.c,v 1.8 2000/05/09 18:33:37 joe Exp +*/ + + +/* HTTP Authentication, as per RFC2617. + * + * TODO: + * - Improve cnonce? Make it really random using /dev/random or whatever? + * - Test auth-int support + */ + +#include <config.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#include <stdio.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <time.h> + +#ifndef HAVE_SNPRINTF +#include "snprintf.h" +#endif + +#include "base64.h" +#include "md5.h" +#include "dates.h" + +#include "http_auth.h" +#include "string_utils.h" +#include "uri.h" +#include "http_utils.h" + +/* The MD5 digest of a zero-length entity-body */ +#define DIGEST_MD5_EMPTY "d41d8cd98f00b204e9800998ecf8427e" + +/* A challenge */ +struct http_auth_chall { + http_auth_scheme scheme; + char *realm; + char *domain; + char *nonce; + 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 */ + http_auth_algorithm alg; + struct http_auth_chall *next; +}; + +static const char *qop_values[] = { + NULL, + "auth", + "auth-int" +}; +static const char *algorithm_names[] = { + "MD5", + "MD5-sess", + NULL +}; + +/* Private prototypes */ +static char *get_cnonce(void); +static void clean_session( http_auth_session *sess ); +static int digest_challenge( http_auth_session *, struct http_auth_chall * ); +static int basic_challenge( http_auth_session *, struct http_auth_chall * ); +static char *request_digest( http_auth_session * ); +static char *request_basic( http_auth_session * ); + +/* Domain handling */ +static int is_in_domain( http_auth_session *sess, const char *uri ); +static int parse_domain( http_auth_session *sess, const char *domain ); + +/* Get the credentials, passing a temporary store for the password value */ +static int get_credentials( http_auth_session *sess, char **password ); + +/* Initialize an auth session */ +void http_auth_init( http_auth_session *sess ) +{ + memset( sess, 0, sizeof( http_auth_session ) ); +} + +http_auth_session *http_auth_create( void ) +{ + http_auth_session *sess = xmalloc(sizeof(http_auth_session)); + http_auth_init( sess ); + return sess; +} + +void http_auth_destroy( http_auth_session *sess ) +{ + http_auth_finish( sess ); + free( sess ); +} + +void http_auth_set_creds_cb( http_auth_session *sess, + http_auth_request_creds callback, void *userdata ) +{ + sess->reqcreds = callback; + sess->reqcreds_udata = userdata; +} + +#if 0 +void http_auth_set_server( http_auth_session *sess, + const char *host, unsigned int port, const char *scheme ) +{ + sess->host = host; + sess->port = port; + sess->req_scheme = scheme; +} +#endif + +/* Start a new request. */ +void http_auth_new_request( http_auth_session *sess, + const char *method, const char *uri, + const char *body_buffer, FILE *body_stream ) +{ + if( !sess->can_handle ) { + DEBUG( DEBUG_HTTPAUTH, "Not handling session.\n" ); + } else if( !is_in_domain( sess, uri ) ) { + /* We have moved out of the authentication domain */ + DEBUG( DEBUG_HTTPAUTH, "URI %s outside session domain, not handling.\n", uri ); + sess->will_handle = 0; + } else + + { + DEBUG( DEBUG_HTTPAUTH, "URI %s inside session domain, will handle.\n", uri ); + + sess->will_handle = 1; + sess->uri = uri; + sess->method = method; + sess->got_body = (body_buffer!=NULL) || (body_stream!=NULL); + sess->body_buffer = body_buffer; + sess->body_stream = body_stream; + md5_init_ctx( &sess->response_body ); + } +} + +static void clean_session( http_auth_session *sess ) +{ + sess->can_handle = 0; + HTTP_FREE( sess->basic ); + HTTP_FREE( sess->unq_realm ); + HTTP_FREE( sess->unq_nonce ); + HTTP_FREE( sess->unq_cnonce ); + HTTP_FREE( sess->opaque ); + HTTP_FREE( sess->username ); + if( sess->domain_count > 0 ) { + split_string_free( sess->domain ); + sess->domain_count = 0; + } +} + +void http_auth_finish( http_auth_session *sess ) { + clean_session( sess ); +} + +/* Returns cnonce-value. We just use base64( time ). + * TODO: Could improve this? */ +static char *get_cnonce(void) +{ + char *ret, *tmp; + tmp = rfc1123_date( time(NULL) ); + ret = base64( tmp ); + free( tmp ); + return ret; +} + +static int +get_credentials( http_auth_session *sess, char **password) +{ + return (*sess->reqcreds)( sess->reqcreds_udata, sess->unq_realm, + &sess->username, password ); +} + +static int parse_domain( http_auth_session *sess, const char *domain ) { + char *unq, **doms; + int count, ret; + + unq = shave_string(domain, '"' ); + doms = split_string_c( unq, ' ', NULL, HTTP_WHITESPACE, &count ); + if( doms != NULL ) { + if( count > 0 ) { + sess->domain = doms; + sess->domain_count = count; + ret = 0; + } else { + free( doms ); + ret = -1; + } + } else { + ret = -1; + } + free( unq ); + return ret; +} + +/* Returns: + * 0: if uri is in NOT in domain of session + * else: uri IS in domain of session (or no domain known) + */ +static int is_in_domain( http_auth_session *sess, const char *uri ) +{ +#if 1 + return 1; +#else + /* TODO: Need proper URI comparison for this to work. */ + int ret, dom; + const char *abs_path; + if( sess->domain_count == 0 ) { + DEBUG( DEBUG_HTTPAUTH, "No domain, presuming okay.\n" ); + return 1; + } + ret = 0; + abs_path = uri_abspath( uri ); + for( dom = 0; dom < sess->domain_count; dom++ ) { + DEBUG( DEBUG_HTTPAUTH, "Checking domain: %s\n", sess->domain[dom] ); + if( uri_childof( sess->domain[dom], abs_path ) ) { + ret = 1; + break; + } + } + return ret; +#endif +} + +/* Add authentication creditials to a request */ +char *http_auth_request_header( http_auth_session *sess ) +{ + if( sess->will_handle ) { + switch( sess->scheme ) { + case http_auth_scheme_basic: + return request_basic( sess ); + break; + case http_auth_scheme_digest: + return request_digest( sess ); + break; + default: + break; + } + } + return NULL; +} + +/* Examine a Basic auth challenge. + * Returns 0 if an valid challenge, else non-zero. */ +static int +basic_challenge( http_auth_session *sess, struct http_auth_chall *parms ) +{ + char *tmp, *password; + + /* Verify challenge... must have a realm */ + if( parms->realm == NULL ) { + return -1; + } + + DEBUG( DEBUG_HTTPAUTH, "Got Basic challenge with realm [%s]\n", + parms->realm ); + + clean_session( sess ); + + sess->unq_realm = shave_string(parms->realm, '"' ); + + if( get_credentials( sess, &password ) ) { + /* Failed to get credentials */ + HTTP_FREE( sess->unq_realm ); + return -1; + } + + sess->scheme = http_auth_scheme_basic; + + CONCAT3( tmp, sess->username, ":", password?password:"" ); + sess->basic = base64( tmp ); + free( tmp ); + + HTTP_FREE( password ); + + return 0; +} + +/* Add Basic authentication credentials to a request */ +static char *request_basic( http_auth_session *sess ) +{ + char *buf; + CONCAT3( buf, "Basic ", sess->basic, "\r\n" ); + return buf; +} + +/* Examine a digest challenge: return 0 if it is a valid Digest challenge, + * else non-zero. */ +static int digest_challenge( http_auth_session *sess, + struct http_auth_chall *parms ) +{ + struct md5_ctx tmp; + unsigned char tmp_md5[16]; + char *password; + + /* Do we understand this challenge? */ + if( parms->alg == http_auth_alg_unknown ) { + DEBUG( DEBUG_HTTPAUTH, "Unknown algorithm.\n" ); + return -1; + } + if( (parms->alg == http_auth_alg_md5_sess) && + !( parms->qop_auth || parms->qop_auth_int ) ) { + DEBUG( DEBUG_HTTPAUTH, "Server did not give qop with MD5-session alg.\n" ); + return -1; + } + if( (parms->realm==NULL) || (parms->nonce==NULL) ) { + DEBUG( DEBUG_HTTPAUTH, "Challenge missing nonce or realm.\n" ); + return -1; + } + + if( parms->stale ) { + /* Just a stale response, don't need to get a new username/password */ + DEBUG( DEBUG_HTTPAUTH, "Stale digest challenge.\n" ); + } else { + /* Forget the old session details */ + DEBUG( DEBUG_HTTPAUTH, "In digest challenge.\n" ); + + clean_session( sess ); + sess->unq_realm = shave_string(parms->realm, '"' ); + + /* Not a stale response: really need user authentication */ + if( get_credentials( sess, &password ) ) { + /* Failed to get credentials */ + HTTP_FREE( sess->unq_realm ); + return -1; + } + } + sess->alg = parms->alg; + sess->scheme = http_auth_scheme_digest; + sess->unq_nonce = shave_string(parms->nonce, '"' ); + sess->unq_cnonce = get_cnonce(); + if( parms->domain ) { + if( parse_domain( sess, parms->domain ) ) { + /* TODO: Handle the error? */ + } + } else { + sess->domain = NULL; + sess->domain_count = 0; + } + if( parms->opaque != NULL ) { + sess->opaque = xstrdup( parms->opaque ); /* don't strip the quotes */ + } + + if( parms->got_qop ) { + /* What type of qop are we to apply to the message? */ + DEBUG( DEBUG_HTTPAUTH, "Got qop directive.\n" ); + sess->nonce_count = 0; + if( parms->qop_auth_int ) { + sess->qop = http_auth_qop_auth_int; + } else { + sess->qop = http_auth_qop_auth; + } + } else { + /* No qop at all/ */ + sess->qop = http_auth_qop_none; + } + + if( !parms->stale ) { + /* Calculate H(A1). + * tmp = H( unq(username-value) ":" unq(realm-value) ":" passwd + */ + DEBUG( DEBUG_HTTPAUTH, "Calculating H(A1).\n" ); + md5_init_ctx( &tmp ); + md5_process_bytes( sess->username, strlen(sess->username), &tmp); + md5_process_bytes( ":", 1, &tmp ); + md5_process_bytes( sess->unq_realm, strlen(sess->unq_realm), &tmp ); + md5_process_bytes( ":", 1, &tmp ); + if( password != NULL ) + md5_process_bytes( password, strlen(password), &tmp); + md5_finish_ctx( &tmp, tmp_md5 ); + if( sess->alg == http_auth_alg_md5_sess ) { + unsigned char a1_md5[16]; + struct md5_ctx a1; + char tmp_md5_ascii[33]; + /* Now we calculate the SESSION H(A1) + * A1 = H( ...above...) ":" unq(nonce-value) ":" unq(cnonce-value) + */ + md5_to_ascii( tmp_md5, tmp_md5_ascii ); + md5_init_ctx( &a1 ); + md5_process_bytes( tmp_md5_ascii, 32, &a1 ); + md5_process_bytes( ":", 1, &a1 ); + md5_process_bytes( sess->unq_nonce, strlen(sess->unq_nonce), &a1 ); + md5_process_bytes( ":", 1, &a1 ); + md5_process_bytes( sess->unq_cnonce, strlen(sess->unq_cnonce), &a1 ); + md5_finish_ctx( &a1, a1_md5 ); + md5_to_ascii( a1_md5, sess->h_a1 ); + DEBUG( DEBUG_HTTPAUTH, "Session H(A1) is [%s]\n", sess->h_a1 ); + } else { + md5_to_ascii( tmp_md5, sess->h_a1 ); + DEBUG( DEBUG_HTTPAUTH, "H(A1) is [%s]\n", sess->h_a1 ); + } + + HTTP_FREE( password ); + + } + + DEBUG( DEBUG_HTTPAUTH, "I like this Digest challenge.\n" ); + + return 0; +} + +/* Return Digest authentication credentials header value for the given + * session. */ +static char *request_digest( http_auth_session *sess ) +{ + struct 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}, *ret; + const char *qop_value; /* qop-value */ + size_t retlen; + + /* Increase the nonce-count */ + if( sess->qop != http_auth_qop_none ) { + sess->nonce_count++; + snprintf( nc_value, 9, "%08x", sess->nonce_count ); + DEBUG( DEBUG_HTTPAUTH, "Nonce count is %d, nc is [%s]\n", + sess->nonce_count, nc_value ); + } + qop_value = qop_values[sess->qop]; + + /* Calculate H(A2). */ + md5_init_ctx( &a2 ); + md5_process_bytes( sess->method, strlen(sess->method), &a2 ); + md5_process_bytes( ":", 1, &a2 ); + md5_process_bytes( sess->uri, strlen(sess->uri), &a2 ); + if( sess->qop == http_auth_qop_auth_int ) { + /* Calculate H(entity-body) */ + if( sess->got_body ) { + char tmp_md5_ascii[33], tmp_md5[16]; + if( sess->body_stream != NULL ) { + DEBUG( DEBUG_HTTPAUTH, "Digesting body stream.\n" ); + md5_stream( sess->body_stream, tmp_md5 ); + rewind( sess->body_stream ); /* leave it at the beginning */ + } else if( sess->body_buffer ) { + DEBUG( DEBUG_HTTPAUTH, "Digesting body buffer.\n" ); + md5_buffer( sess->body_buffer, strlen(sess->body_buffer), + tmp_md5 ); + } + md5_to_ascii( tmp_md5, tmp_md5_ascii ); + DEBUG( DEBUG_HTTPAUTH, "H(entity-body) is [%s]\n", tmp_md5_ascii ); + /* Append to A2 */ + md5_process_bytes( ":", 1, &a2 ); + md5_process_bytes( tmp_md5_ascii, 32, &a2 ); + } else { + /* No entity-body. */ + DEBUG( DEBUG_HTTPAUTH, "Digesting empty entity-body.\n" ); + md5_process_bytes( ":" DIGEST_MD5_EMPTY, 33, &a2 ); + } + } + md5_finish_ctx( &a2, a2_md5 ); + md5_to_ascii( a2_md5, a2_md5_ascii ); + DEBUG( DEBUG_HTTPAUTH, "H(A2): %s\n", a2_md5_ascii ); + + DEBUG( DEBUG_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) ":" */ + md5_init_ctx( &rdig ); + + /* Use the calculated H(A1) */ + md5_process_bytes( sess->h_a1, 32, &rdig ); + + md5_process_bytes( ":", 1, &rdig ); + md5_process_bytes( sess->unq_nonce, strlen(sess->unq_nonce), &rdig ); + md5_process_bytes( ":", 1, &rdig ); + if( sess->qop != http_auth_qop_none ) { + /* Add on: + * nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" + */ + DEBUG( DEBUG_HTTPAUTH, "Have qop directive, digesting: [%s:%s:%s]\n", + nc_value, sess->unq_cnonce, qop_value ); + md5_process_bytes( nc_value, 8, &rdig ); + md5_process_bytes( ":", 1, &rdig ); + md5_process_bytes( sess->unq_cnonce, strlen(sess->unq_cnonce), &rdig ); + md5_process_bytes( ":", 1, &rdig ); + /* Store a copy of this structure (see note below) */ + sess->stored_rdig = rdig; + md5_process_bytes( qop_value, strlen(qop_value), &rdig ); + 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) */ + md5_process_bytes( a2_md5_ascii, 32, &rdig ); + md5_finish_ctx( &rdig, rdig_md5 ); + md5_to_ascii( rdig_md5, rdig_md5_ascii ); + + /* Buffer size calculation. */ + + retlen = + 6 /* Digest */ + + 1 + 8 + 1 + 2 + strlen(sess->username) /* username="..." */ + + 2 + 5 + 1 + 2 + strlen(sess->unq_realm) /* , realm="..." */ + + 2 + 5 + 1 + 2 + strlen(sess->unq_nonce) /* , nonce="..." */ + + 2 + 3 + 1 + 2 + strlen(sess->uri) /* , uri="..." */ + + 2 + 8 + 1 + 2 + 32 /* , response="..." */ + + 2 + 9 + 1 + strlen(algorithm_names[sess->alg]) /* , algorithm= */ + ; + + if( sess->opaque != NULL ) + retlen += 2 + 6 + 1 + strlen(sess->opaque); /* , opaque=... */ + + if( sess->qop != http_auth_qop_none ) + retlen += + 2 + 6 + 2 + 1 + strlen(sess->unq_cnonce) + /* , cnonce="..." */ + 2 + 2 + 1 + 8 + /* , nc=... */ + 2 + 3 + 1 + strlen(qop_values[sess->qop]) /* , qop=... */ + ; + + retlen += 2; /* \r\n */ + + DEBUG( DEBUG_HTTPAUTH, "Calculated length of buffer: %d\n", retlen ); + + ret = xmalloc( retlen + 1 ); + + sprintf( ret, + "Digest username=\"%s\", realm=\"%s\"" + ", nonce=\"%s\", uri=\"%s\", response=\"%s\"" + ", algorithm=%s", + sess->username, sess->unq_realm, + sess->unq_nonce, sess->uri, rdig_md5_ascii, + algorithm_names[sess->alg]); + + if( sess->opaque != NULL ) { + /* We never unquote it, so it's still quoted here */ + strcat( ret, ", opaque=" ); + strcat( ret, sess->opaque ); + } + + if( sess->qop != http_auth_qop_none ) { + /* Add in cnonce and nc-value fields */ + strcat( ret, ", cnonce=\"" ); + strcat( ret, sess->unq_cnonce ); + strcat( ret, "\", nc=" ); + strcat( ret, nc_value ); + strcat( ret, ", qop=" ); + strcat( ret, qop_values[sess->qop] ); + } + + DEBUG( DEBUG_HTTPAUTH, "Digest header field value:\n%s\n", ret ); + + strcat( ret, "\r\n" ); + + DEBUG( DEBUG_HTTPAUTH, "Calculated length: %d, actual length: %d\n", + retlen, strlen( ret ) ); + + return ret; +} + +inline void http_auth_response_body( http_auth_session *sess, + const char *buffer, size_t buffer_len ) +{ + if( !sess->will_handle || + sess->scheme != http_auth_scheme_digest ) return; + DEBUG( DEBUG_HTTPAUTH, "Digesting %d bytes of response body.\n", + buffer_len ); + md5_process_bytes( buffer, buffer_len, &sess->response_body ); +} + +/* 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!). + */ +int http_auth_verify_response( http_auth_session *sess, const char *value ) +{ + char **pairs; + http_auth_qop qop = http_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 */ + *unquoted, *qop_value = NULL; + int n, nonce_count, okay; + + if( !sess->will_handle ) { + /* Ignore it */ + return 0; + } + + if( sess->scheme != http_auth_scheme_digest ) { + DEBUG( DEBUG_HTTPAUTH, "Found Auth-Info header not in response to Digest credentials - dodgy.\n" ); + return -1; + } + + DEBUG (DEBUG_HTTPAUTH, "Auth-Info header: %s\n", value ); + + pairs = pair_string( value, ',', '=', HTTP_QUOTES, HTTP_WHITESPACE ); + + for( n = 0; pairs[n]!=NULL; n+=2 ) { + unquoted = shave_string( pairs[n+1], '"' ); + if( strcasecmp( pairs[n], "qop" ) == 0 ) { + qop_value = xstrdup( pairs[n+1] ); + if( strcasecmp( pairs[n+1], "auth-int" ) == 0 ) { + qop = http_auth_qop_auth_int; + } else if( strcasecmp( pairs[n+1], "auth" ) == 0 ) { + qop = http_auth_qop_auth; + } else { + qop = http_auth_qop_none; + } + } else if( strcasecmp( pairs[n], "nextnonce" ) == 0 ) { + nextnonce = xstrdup( unquoted ); + } else if( strcasecmp( pairs[n], "rspauth" ) == 0 ) { + rspauth = xstrdup( unquoted ); + } else if( strcasecmp( pairs[n], "cnonce" ) == 0 ) { + cnonce = xstrdup( unquoted ); + } else if( strcasecmp( pairs[n], "nc" ) == 0 ) { + nc = xstrdup( pairs[n] ); + if( sscanf( pairs[n+1], "%x", &nonce_count ) != 1 ) { + DEBUG( DEBUG_HTTPAUTH, "Couldn't scan [%s] for nonce count.\n", + pairs[n+1] ); + } else { + DEBUG( DEBUG_HTTPAUTH, "Got nonce_count: %d\n", nonce_count ); + } + } + free( unquoted ); + } + pair_string_free( pairs ); + + /* Presume the worst */ + okay = -1; + + if( (qop != http_auth_qop_none) && (qop_value != NULL) ) { + if( (rspauth == NULL) || (cnonce == NULL) || (nc == NULL) ) { + DEBUG( DEBUG_HTTPAUTH, "Missing rspauth, cnonce or nc with qop.\n" ); + } else { /* Have got rspauth, cnonce and nc */ + if( strcmp( cnonce, sess->unq_cnonce ) != 0 ) { + DEBUG( DEBUG_HTTPAUTH, "Response cnonce doesn't match.\n" ); + } else if( nonce_count != sess->nonce_count ) { + DEBUG( DEBUG_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 md5_ctx a2; + unsigned char a2_md5[16], rdig_md5[16]; + char a2_md5_ascii[33], rdig_md5_ascii[33]; + + DEBUG( DEBUG_HTTPAUTH, "Calculating response-digest.\n" ); + + /* First off, H(A2) again. */ + md5_init_ctx( &a2 ); + md5_process_bytes( ":", 1, &a2 ); + md5_process_bytes( sess->uri, strlen(sess->uri), &a2 ); + if( qop == http_auth_qop_auth_int ) { + unsigned char heb_md5[16]; + char heb_md5_ascii[33]; + /* Add on ":" H(entity-body) */ + md5_finish_ctx( &sess->response_body, heb_md5 ); + md5_to_ascii( heb_md5, heb_md5_ascii ); + md5_process_bytes( ":", 1, &a2 ); + md5_process_bytes( heb_md5_ascii, 32, &a2 ); + DEBUG( DEBUG_HTTPAUTH, "Digested [:%s]\n", heb_md5_ascii ); + } + md5_finish_ctx( &a2, a2_md5 ); + 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 != http_auth_qop_none ) { + /* Add in qop-value */ + DEBUG( DEBUG_HTTPAUTH, "Digesting qop-value [%s:].\n", + qop_value ); + md5_process_bytes( qop_value, strlen(qop_value), + &sess->stored_rdig ); + md5_process_bytes( ":", 1, &sess->stored_rdig ); + } + /* Digest ":" H(A2) */ + md5_process_bytes( a2_md5_ascii, 32, &sess->stored_rdig ); + /* All done */ + md5_finish_ctx( &sess->stored_rdig, rdig_md5 ); + md5_to_ascii( rdig_md5, rdig_md5_ascii ); + + DEBUG( DEBUG_HTTPAUTH, "Calculated response-digest of: [%s]\n", + rdig_md5_ascii ); + DEBUG( DEBUG_HTTPAUTH, "Given response-digest of: [%s]\n", + rspauth ); + + /* And... do they match? */ + okay = (strcasecmp( rdig_md5_ascii, rspauth ) == 0)?0:-1; + DEBUG( DEBUG_HTTPAUTH, "Matched: %s\n", okay?"nope":"YES!" ); + } + free( rspauth ); + free( cnonce ); + free( nc ); + } + free( qop_value ); + } else { + DEBUG( DEBUG_HTTPAUTH, "No qop directive, auth okay.\n" ); + okay = 0; + } + + /* Check for a nextnonce */ + if( nextnonce != NULL ) { + DEBUG( DEBUG_HTTPAUTH, "Found nextnonce of [%s].\n", nextnonce ); + if( sess->unq_nonce != NULL ) + free( sess->unq_nonce ); + sess->unq_nonce = nextnonce; + } + + return okay; +} + +/* A new challenge presented by the server */ +int http_auth_challenge( http_auth_session *sess, const char *value ) +{ + char **pairs, *pnt, *unquoted, *key; + struct http_auth_chall *chall = NULL, *challenges = NULL; + int n, success; + + DEBUG( DEBUG_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. + */ + pairs = pair_string( value, ',', '=', HTTP_QUOTES, HTTP_WHITESPACE ); + + for( n = 0; pairs[n]!=NULL; n+=2 ) { + /* Look for an auth-scheme in the key */ + pnt = strchr( pairs[n], ' ' ); + if( pnt != NULL ) { + /* We have a new challenge */ + DEBUG( DEBUG_HTTPAUTH, "New challenge.\n" ); + chall = xmalloc( sizeof(struct http_auth_chall) ); + memset( chall, 0, sizeof(struct http_auth_chall) ); + chall->next = challenges; + challenges = chall; + /* Initialize the challenge parameters */ + /* Which auth-scheme is it (case-insensitive matching) */ + if( strncasecmp( pairs[n], "basic ", 6 ) == 0 ) { + DEBUG( DEBUG_HTTPAUTH, "Basic scheme.\n" ); + chall->scheme = http_auth_scheme_basic; + } else if( strncasecmp( pairs[n], "digest ", 7 ) == 0 ) { + DEBUG( DEBUG_HTTPAUTH, "Digest scheme.\n" ); + chall->scheme = http_auth_scheme_digest; + } else { + DEBUG( DEBUG_HTTPAUTH, "Unknown scheme.\n" ); + free( chall ); + challenges = NULL; + break; + } + /* Now, the real key for this pair starts after the + * auth-scheme... skipping whitespace */ + while( strchr( HTTP_WHITESPACE, *(++pnt) ) != NULL ) + /* nullop */; + key = pnt; + } 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; + } else { + key = pairs[n]; + } + DEBUG( DEBUG_HTTPAUTH, "Got pair: [%s] = [%s]\n", key, pairs[n+1] ); + /* Most values are quoted, so unquote them here */ + unquoted = shave_string( pairs[n+1], '"' ); + /* Now parse the attribute */ + DEBUG( DEBUG_HTTPAUTH, "Unquoted pair is: [%s]\n", unquoted ); + if( strcasecmp( key, "realm" ) == 0 ) { + chall->realm = pairs[n+1]; + } else if( strcasecmp( key, "nonce" ) == 0 ) { + chall->nonce = pairs[n+1]; + } else if( strcasecmp( key, "opaque" ) == 0 ) { + chall->opaque = pairs[n+1]; + } else if( strcasecmp( key, "domain" ) == 0 ) { + chall->domain = pairs[n+1]; + } else if( strcasecmp( key, "stale" ) == 0 ) { + /* Truth value */ + chall->stale = + ( strcasecmp( unquoted, "true" ) == 0 ); + } else if( strcasecmp( key, "algorithm" ) == 0 ) { + if( strcasecmp( unquoted, "md5" ) == 0 ) { + chall->alg = http_auth_alg_md5; + } else if( strcasecmp( unquoted, "md5-sess" ) == 0 ) { + chall->alg = http_auth_alg_md5_sess; + } else { + chall->alg = http_auth_alg_unknown; + } + } else if( strcasecmp( key, "qop" ) == 0 ) { + char **qops; + int qop; + qops = split_string( unquoted, ',', NULL, HTTP_WHITESPACE ); + 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 ); + } + free( unquoted ); + } + + DEBUG( DEBUG_HTTPAUTH, "Finished parsing parameters.\n" ); + + /* Did we find any challenges */ + if( challenges == NULL ) { + pair_string_free( pairs ); + return -1; + } + + success = 0; + + DEBUG( DEBUG_HTTPAUTH, "Looking for Digest challenges.\n" ); + + /* Try a digest challenge */ + for( chall = challenges; chall != NULL; chall = chall->next ) { + if( chall->scheme == http_auth_scheme_digest ) { + if( !digest_challenge( sess, chall ) ) { + success = 1; + break; + } + } + } + + if( !success ) { + DEBUG( DEBUG_HTTPAUTH, "No good Digest challenges, looking for Basic.\n" ); + for( chall = challenges; chall != NULL; chall = chall->next ) { + if( chall->scheme == http_auth_scheme_basic ) { + if( !basic_challenge( sess, chall ) ) { + success = 1; + break; + } + } + } + + if( !success ) { + /* No good challenges - record this in the session state */ + DEBUG( DEBUG_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; + free( challenges ); + challenges = chall; + } + + /* Free up the parsed header values */ + pair_string_free( pairs ); + + return !success; +} diff --git a/neon/src/http_auth.h b/neon/src/http_auth.h new file mode 100644 index 000000000..8b0a430b7 --- /dev/null +++ b/neon/src/http_auth.h @@ -0,0 +1,206 @@ +/* + HTTP authentication routines + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 HTTPAUTH_H +#define HTTPAUTH_H + +#include <sys/types.h> + +#include "md5.h" + +/* HTTP Authentication - a pretty complete client implementation of RFC2617. + */ + +/* + To use: + + 1. A session state variable (http_auth_session) is needed for each of + server state and proxy state. These may be statically declared (use + _init/_finish), or dynamically (use _create/_destroy). + + 2. To begin a new session, call http_auth_init() or http_auth_create(). + Set up a callback function with http_auth_set_creds_cb() for + supplying the username and password on demand. See below for details. + + 3. Before sending a request, pass http_auth_new_request its details, + on BOTH auth session variables if you are using a proxy server too. + + 4. Call http_auth_request_header() and add your 'Authentication:' + header to the request if returns non-NULL. Similarly for + Proxy-Authentication. + + 5. Send the request. + + 6. Read the response: + - Pass the value of the '(Proxy|WWW)-Authenticate' header to + http_auth_challenge. + - If there is 'Authentication-Info', save its value for later. + - Pass each block of the response entity-body to http_auth_response_body. + + 7. After reading the complete response, if an Auth-Info header was + received, pass its value to http_auth_verify_response to check + whether the SERVER was authenticated okay, passing the saved value. + + 8. If a 401 or a 407 response is received, retry once for each, by + going back to step 3. Note that http_auth_new_request MUST be called + again if the SAME request is being retried. + +*/ + +/* The authentication scheme we are using */ +typedef enum { + http_auth_scheme_basic, + http_auth_scheme_digest +} http_auth_scheme; + +typedef enum { + http_auth_alg_md5, + http_auth_alg_md5_sess, + http_auth_alg_unknown +} http_auth_algorithm; + +/* Selected method of qop which the client is using */ +typedef enum { + http_auth_qop_none, + http_auth_qop_auth, + http_auth_qop_auth_int +} http_auth_qop; + +/* The callback used to request the username and password in the given + * realm. The username and password must be placed in malloc()-allocate + * memory. + * Must return: + * 0 on success, + * -1 to cancel. + */ +typedef int (*http_auth_request_creds)( + void *userdata, const char *realm, + char **username, char **password ); + +/* Authentication session state. */ +typedef struct { + /* The scheme used for this authentication session */ + http_auth_scheme scheme; + /* The callback used to request new username+password */ + http_auth_request_creds reqcreds; + void *reqcreds_udata; + + /*** Session details ***/ + + /* The username and password we are using to authenticate with */ + char *username; + /* 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 *unq_realm; + char *unq_nonce; + char *unq_cnonce; + char *opaque; + /* A list of domain strings */ + unsigned int domain_count; + char **domain; + http_auth_qop qop; + http_auth_algorithm alg; + int nonce_count; + /* The ASCII representation of the session's H(A1) value */ + char h_a1[33]; + /* Used for calculation of H(entity-body) of the response */ + struct md5_ctx response_body; + /* Temporary store for half of the Request-Digest + * (an optimisation - used in the response-digest calculation) */ + struct md5_ctx stored_rdig; + + /* Details of server... needed to reconstruct absoluteURI's when + * necessary */ + const char *host; + const char *uri_scheme; + unsigned int port; + + /*** Details of current request ***/ + + /* 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; + /* Whether we have a request body for the current request */ + unsigned int got_body:1; + /* And what the body is - stream or buffer */ + FILE *body_stream; + const char *body_buffer; + +} http_auth_session; + +/* Initializes the authentication state for the given session, + * which will use the given username and password. */ +void http_auth_init( http_auth_session *sess ); + +void http_auth_set_creds_cb( http_auth_session *sess, + http_auth_request_creds callback, void *userdata ); + +/* Finishes off the given authentication session, freeing + * any memory used. */ +void http_auth_finish( http_auth_session *sess ); + +/* Creates a new authentication session. + * Returns non-NULL on success */ +http_auth_session * http_auth_create( void ); + +/* Destroys an authentication session, freeing the session state + * itself too. */ +void http_auth_destroy( http_auth_session *sess ); + +/* Call this before sending a request. Pass ONE OF body_buffer or + * body_stream as non-NULL if the request will include an + * entity-body. If body_buffer is non-NULL, it MUST be + * \0-terminated. If body_stream is non-NULL, it may be read once + * during http_auth_challenge, then rewound. uri must identical to + * Request-URI, EXCEPT for server auth state, where if the request is + * passing through a proxy, then uri should be the same as abs_path. */ +void http_auth_new_request( http_auth_session *sess, + const char *method, const char *uri, + const char *body_buffer, FILE *body_stream ); + +/* Returns the value of the authentication field if one is to be sent, + * else NULL. The return value will be taken from malloc()'ed memory, + * so should be free()'ed after use. */ +char *http_auth_request_header( http_auth_session *sess ); + +/* Pass this the value of the "(Proxy,WWW)-Authenticate: " header field. + * Returns: + * 0 if we can now authenticate ourselves with the server. + * non-zero if we can't + */ +int http_auth_challenge( http_auth_session *sess, const char *value ); + +/* As you receive sections of the response entity-body, pass them to + * this function. */ +void http_auth_response_body( http_auth_session *sess, + const char *buffer, size_t buffer_len ); + +/* If you receive a "(Proxy-)Authentication-Info:" header, pass its value to + * this function. Returns zero if this successfully authenticates + * the response as coming from the server, and false if it hasn't. */ +int http_auth_verify_response( http_auth_session *sess, const char *value ); + +#endif /* HTTPAUTH_H */ diff --git a/neon/src/http_basic.c b/neon/src/http_basic.c new file mode 100644 index 000000000..ac8682d55 --- /dev/null +++ b/neon/src/http_basic.c @@ -0,0 +1,357 @@ +/* + HTTP/1.1 methods + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: http_basic.c,v 1.12 2000/05/10 16:45:58 joe Exp +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <errno.h> + +#include "http_request.h" +#include "http_basic.h" +#include "dates.h" +#include "socket.h" +#include "neon_i18n.h" + +/* Header parser to retrieve Last-Modified date */ +static void get_lastmodified( void *userdata, const char *value ) { + time_t *modtime = userdata; + *modtime = http_dateparse( value ); +} + +int http_getmodtime( http_session *sess, const char *uri, time_t *modtime ) +{ + http_req *req = http_request_create( sess, "HEAD", uri ); + http_status st; + int ret; + +#ifdef USE_DAV_LOCKS + dav_lock_using_resource( req, uri, dav_lockusage_read, 0 ); +#endif + + http_add_response_header_handler( req, "Last-Modified", get_lastmodified, + modtime ); + + *modtime = -1; + + ret = http_request_dispatch( req, &st ); + + if( ret == HTTP_OK && st.class != 2 ) { + *modtime = -1; + ret = HTTP_ERROR; + } + + http_request_destroy( req ); + + return ret; +} + +/* PUT's stream to URI */ +int http_put( http_session *sess, const char *uri, FILE *stream ) +{ + http_req *req = http_request_create( sess, "PUT", uri ); + http_status status; + int ret; + +#ifdef USE_DAV_LOCKS + dav_lock_using_resource( req, uri, dav_lockusage_write, 0 ); + dav_lock_using_parent( req, uri ); +#endif + + http_set_request_body_stream( req, stream ); + + ret = http_request_dispatch( req, &status ); + + if( ret == HTTP_OK && status.class != 2 ) + ret = HTTP_ERROR; + + http_request_destroy( req ); + + return ret; +} + +/* Conditional HTTP put. + * PUTs stream to uri, returning HTTP_FAILED if resource as URI has + * been modified more recently than 'since'. + */ +int +http_put_if_unmodified( http_session *sess, const char *uri, + FILE *stream, time_t since ) { + http_req *req; + http_status status; + sbuffer hdrs; + char *date; + int ret; + + if( http_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 = http_getmodtime( sess, uri, &modtime ); + if( ret != HTTP_OK ) return ret; + if( modtime != since ) + return HTTP_FAILED; + } + + req = http_request_create( sess, "PUT", uri ); + + date = rfc1123_date( since ); + /* Add in the conditionals */ + hdrs = http_get_request_header( req ); + sbuffer_concat( hdrs, "If-Unmodified-Since: ", date, EOL, NULL ); + free( date ); + +#ifdef USE_DAV_LOCKS + dav_lock_using_resource( &req, uri, dav_lockusage_write, 0 ); + /* NB: this will give 412 if the resource doesn't exist, since PUT + * may modify the parent, but thats okay, since that will give + * a HTTP_FAILED response too. */ +#endif + + http_set_request_body_stream( req, stream ); + + ret = http_request_dispatch( req, &status ); + + if( ret == HTTP_OK ) { + if( status.code == 412 ) { + ret = HTTP_FAILED; + } else if( status.class != 2 ) { + ret = HTTP_ERROR; + } + } + + http_request_destroy( req ); + + return ret; +} + +struct get_context { + int error; + size_t total, progress; + http_block_reader callback; /* used in read_file */ + FILE *file; /* used in get_to_fd */ + void *userdata; +}; + +static void get_callback( void *userdata, const char *block, size_t length ) +{ + struct get_context *ctx = userdata; + + DEBUG( DEBUG_HTTP, "Got progress: %d out of %d\n", + ctx->progress, ctx->total ); + + (*ctx->callback)( ctx->userdata, block, length ); + + /* Increase progress */ + ctx->progress += length; + if( ctx->progress > ctx->total ) { + /* Reset the counter if we're uploading it again */ + ctx->progress -= ctx->total; + } + sock_call_progress( ctx->progress, ctx->total ); +} + +int http_read_file( http_session *sess, const char *uri, + http_block_reader reader, void *userdata ) { + struct get_context ctx; + http_req *req = http_request_create( sess, "GET", uri ); + http_status st; + int ret; + + ctx.total = -1; + ctx.progress = 0; + ctx.callback = reader; + ctx.userdata = userdata; + + /* Read the value of the Content-Length header into ctx.total */ + http_add_response_header_handler( req, "Content-Length", + http_handle_numeric_header, + &ctx.total ); + + http_add_response_body_reader( req, http_accept_2xx, get_callback, &ctx ); + +#ifdef USE_DAV_LOCKS + dav_lock_using_resource( req, uri, dav_lockusage_read, 0 ); +#endif + + ret = http_request_dispatch( req, &st ); + + if( ret == HTTP_OK && st.class != 2 ) + ret = HTTP_ERROR; + + http_request_destroy( req ); + + return ret; +} + +static void get_to_fd( void *userdata, const char *block, size_t length ) +{ + struct get_context *ctx = userdata; + FILE *f = ctx->file; + if( !ctx->error ) { + if( fwrite( block, length, 1, f ) < length ) { + ctx->error = errno; + } + } +} + +/* Get to given stream */ +int http_get( http_session *sess, const char *uri, FILE *f ) +{ + http_req *req = http_request_create( sess, "GET", uri ); + http_status status; + struct get_context ctx; + int ret; + + ctx.total = -1; + ctx.progress = 0; + ctx.callback = get_to_fd; + ctx.userdata = &ctx; + ctx.file = f; + ctx.error = 0; + + /* Read the value of the Content-Length header into ctx.total */ + http_add_response_header_handler( req, "Content-Length", + http_handle_numeric_header, + &ctx.total ); + + http_add_response_body_reader( req, http_accept_2xx, get_callback, &ctx ); + +#ifdef USE_DAV_LOCKS + dav_lock_using_resource( req, uri, dav_lockusage_read, 0 ); +#endif + + ret = http_request_dispatch( req, &status ); + + if( ctx.error ) { + char buf[BUFSIZ]; + snprintf( buf, BUFSIZ, + _("Could not write to file: %s"), strerror(ctx.error) ); + http_set_error( sess, buf ); + ret = HTTP_ERROR; + } + + if( ret == HTTP_OK && status.class != 2 ) { + ret = HTTP_ERROR; + } + + http_request_destroy( req ); + + return ret; +} + +static void server_hdr_handler( void *userdata, const char *value ) +{ + char **tokens = split_string( value, ' ', HTTP_QUOTES, NULL ); + http_server_capabilities *caps = userdata; + int n; + + for( n = 0; tokens[n] != NULL; n++ ) { + if( strncasecmp( tokens[n], "Apache/", 7 ) == 0 && + strlen(tokens[n]) > 11 ) { /* 12 == "Apache/1.3.0" */ + const char *ver = tokens[n] + 7; + int count; + char **vers; + vers = split_string_c( ver, '.', NULL, NULL, &count ); + /* Apache/1.3.6 and before have broken Expect: 100 support */ + if( count > 1 && atoi(vers[0]) < 2 && atoi(vers[1]) < 4 && atoi(vers[2]) < 7 ) { + caps->broken_expect100 = 1; + } + split_string_free( vers ); + } + } + + split_string_free( tokens ); +} + +void http_content_type_handler( void *userdata, const char *value ) +{ + http_content_type *ct = userdata; + char *sep, *parms; + + ct->value = xstrdup(value); + + sep = strchr( ct->value, '/' ); + if( !sep ) { + HTTP_FREE( ct->value ); + return; + } + + *++sep = '\0'; + ct->type = ct->value; + ct->subtype = sep; + + parms = strchr( ct->value, ';' ); + + if( parms ) { + *parms = '\0'; + /* TODO: handle charset. */ + } +} + +static void dav_hdr_handler( void *userdata, const char *value ) +{ + char **classes, **class; + http_server_capabilities *caps = userdata; + + classes = split_string( value, ',', HTTP_QUOTES, HTTP_WHITESPACE ); + for( class = classes; *class!=NULL; class++ ) { + + if( strcmp( *class, "1" ) == 0 ) { + caps->dav_class1 = 1; + } else if( strcmp( *class, "2" ) == 0 ) { + caps->dav_class2 = 1; + } else if( strcmp( *class, "<http://apache.org/dav/propset/fs/1>" ) == 0 ) { + caps->dav_executable = 1; + } + } + + split_string_free( classes ); + +} + +int http_options( http_session *sess, const char *uri, + http_server_capabilities *caps ) +{ + http_req *req = http_request_create( sess, "OPTIONS", uri ); + http_status status; + + int ret; + + http_add_response_header_handler( req, "Server", server_hdr_handler, caps ); + http_add_response_header_handler( req, "DAV", dav_hdr_handler, caps ); + + ret = http_request_dispatch( req, &status ); + + if( ret == HTTP_OK && status.class != 2 ) { + ret = HTTP_ERROR; + } + + http_request_destroy( req ); + + return ret; +} diff --git a/neon/src/http_basic.h b/neon/src/http_basic.h new file mode 100644 index 000000000..365b84e8c --- /dev/null +++ b/neon/src/http_basic.h @@ -0,0 +1,103 @@ +/* + HTTP/1.1 methods + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 HTTP_BASIC_H +#define HTTP_BASIC_H + +#include "config.h" + +#include <sys/types.h> /* for time_t */ + +#include <stdio.h> /* for FILE * */ + +/* PUT resource at uri, reading request body from f */ +int http_put( http_session *sess, const char *uri, FILE *f ); + +/* PUT resource at uri 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 http_put_if_unmodified( http_session *sess, + const char *uri, FILE *stream, time_t modtime ); + +/* GET resource at uri, writing response body into f */ +int http_get( http_session *sess, const char *uri, FILE *f ); + +/* GET resource at uri, passing response body blocks to 'reader' */ +int http_read_file( http_session *sess, const char *uri, + http_block_reader reader, void *userdata ); + +/* Retrieve modification time of resource at uri, place in *modtime. + * (uses HEAD) */ +int http_getmodtime( http_session *sess, const char *uri, time_t *modtime ); + +typedef struct { + const char *type, *subtype; + const char *charset; + char *value; +} http_content_type; + +/* Sets (*http_content_type)userdata appropriately. + * Caller must free ->value after use */ +void http_content_type_handler( void *userdata, const char *value ); + +/* Server capabilities: */ +typedef struct { + unsigned int broken_expect100:1; /* True if the server is known to + * have broken Expect: + * 100-continue support; Apache + * 1.3.6 and earlier. */ + + 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 */ +} http_server_capabilities; + +/* Determines server capabilities (using OPTIONS). + * Pass uri="*" to determine proxy server capabilities if using + * a proxy server. */ +int http_options( http_session *sess, const char *uri, + http_server_capabilities *caps ); + +#if 0 /* TODO: unimplemented */ + +typedef http_content_range { + long start, end, total; +} http_content_range; + +/* This will write to the CURRENT position of f; so if you want + * to do a resume download, use: + * struct http_content_range range; + * range.start = resume_from; + * range.end = range.total = 1000; + * fseek( myfile, resume_from, SEEK_SET ); + * http_get_range( sess, uri, &range, myfile ); + */ +int http_get_range( http_session *sess, const char *uri, + http_content_range *range, FILE *f ); + +#endif + + +#endif diff --git a/neon/src/http_cookies.c b/neon/src/http_cookies.c new file mode 100644 index 000000000..6a8562895 --- /dev/null +++ b/neon/src/http_cookies.c @@ -0,0 +1,143 @@ +/* + Basic cookie support for neon + Copyright (C) 2000, Joe Orton <joe@orton.demon.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 + + Id: http_cookies.c,v 1.5 2000/07/16 16:26:46 joe Exp +*/ + +/* 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 "http_request.h" +#include "string_utils.h" +#include "http_cookies.h" +#include "xalloc.h" + +static void set_cookie_hdl(void *userdata, const char *value) +{ + char **pairs = pair_string(value, ';', '=', HTTP_QUOTES, HTTP_WHITESPACE); + http_cookie *cook; + http_cookie_cache *cache = userdata; + int n; + + /* Check sanity */ + if (pairs[0] == NULL || pairs[1] == NULL) { + /* yagaboo */ + return; + } + + DEBUG(DEBUG_HTTP, "Got cookie name=%s\n", pairs[0]); + + /* Search for a cookie of this name */ + DEBUG(DEBUG_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) { + DEBUG(DEBUG_HTTP, "New cookie.\n"); + cook = xmalloc(sizeof(http_cookie)); + memset(cook, 0, sizeof(http_cookie)); + cook->name = pairs[0]; + cook->next = cache->cookies; + cache->cookies = cook; + } else { + /* Free the old value */ + free(cook->value); + } + + cook->value = pairs[1]; + + for (n = 2; pairs[n] != NULL; n+=2) { + DEBUG(DEBUG_HTTP, "Cookie parm %s=%s\n", pairs[n], pairs[n+1]); + if (strcasecmp(pairs[n], "path") == 0) { + cook->path = pairs[n+1]; + pairs[n+1] = NULL; + } 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 = pairs[n+1]; + pairs[n+1] = NULL; + } + } + + DEBUG(DEBUG_HTTP, "End of parms.\n"); + + pair_string_free(pairs); +} + +static void *create(void *session, http_req *req, const char *method, const char *uri) +{ + http_cookie_cache *cache = session; + http_add_response_header_handler(req, "Set-Cookie", set_cookie_hdl, cache); + return cache; +} + +/* Just before sending the request: let them add headers if they want */ +static void pre_send(void *private, sbuffer request) +{ + http_cookie_cache *cache = private; + http_cookie *cook; + + if (cache->cookies == NULL) { + return; + } + + sbuffer_zappend(request, "Cookie: "); + + for (cook = cache->cookies; cook != NULL; cook=cook->next) { + sbuffer_concat(request, cook->name, "=", cook->value, NULL); + if (cook->next != NULL) { + sbuffer_zappend(request, "; "); + } + } + + sbuffer_zappend(request, EOL); + +} + +static void destroy(void *private) +{ + /* FIXME */ + return; +} + +http_request_hooks http_cookie_hooks = { + "http://www.webdav.org/neon/hooks/cookies", + create, + NULL, + pre_send, + NULL, + destroy +}; diff --git a/neon/src/http_cookies.h b/neon/src/http_cookies.h new file mode 100644 index 000000000..eb9274601 --- /dev/null +++ b/neon/src/http_cookies.h @@ -0,0 +1,43 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 COOKIES_H +#define COOKIES_H + +struct http_cookie_s; +typedef struct http_cookie_s http_cookie; + +struct http_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 */ + http_cookie *next; +}; + +typedef struct { + http_cookie *cookies; +} http_cookie_cache; + +extern http_request_hooks http_cookie_hooks; + +#endif /* COOKIES_H */ diff --git a/neon/src/http_private.h b/neon/src/http_private.h new file mode 100644 index 000000000..bea8e51eb --- /dev/null +++ b/neon/src/http_private.h @@ -0,0 +1,176 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 HTTP_PRIVATE_H +#define HTTP_PRIVATE_H + +#include "http_auth.h" + +struct host_info { + const char *hostname; + int port; + struct in_addr addr; + char *hostport; /* URI hostport segment */ + http_auth_session auth; + http_request_auth auth_callback; + void *auth_userdata; +}; + +typedef enum { + body_buffer, + body_stream, + body_none +} request_body; + +/* This is called with each of the headers in the response */ +struct header_handler { + const char *name; + http_header_handler handler; + void *userdata; + struct header_handler *next; +}; + +/* TODO: could unify these all into a generic callback list */ + +struct body_reader { + http_block_reader handler; + http_accept_response accept_response; + unsigned int use:1; + void *userdata; + struct body_reader *next; +}; + +/* Store hook information */ +struct hook { + http_request_hooks *hooks; + void *private; + struct hook *next; +}; + +/* Per-request store for hooks. + * This is a bit noddy really. */ +struct hook_request { + struct hook *hook; + void *cookie; + struct hook_request *next; +}; + +#define HAVE_HOOK(st,func) (st->hook->hooks->func != NULL) +#define HOOK_FUNC(st, func) (*st->hook->hooks->func) + +/* Session support. */ +struct http_session_s { + /* Connection information */ + nsocket *socket; + + struct host_info server, proxy; + + /* Connection states: + * 0: Not connected at all. + * 1: We have a TCP connection to the next-hop server. + * 2: We have a negotiated an SSL connection over the proxy's + * TCP tunnel. + * + * Note, 1 is all we need if we don't have a proxy server, or + * if we do have a proxy server and we're not using SSL. + */ + unsigned int connected:2; + + /* Settings */ + unsigned int have_proxy:1; /* do we have a proxy server? */ + unsigned int no_persist:1; /* set to disable persistent connections */ + unsigned int use_secure:1; /* whether a secure connection is required */ + int expect100_works:2; /* known state of 100-continue support */ + unsigned int in_connect:1; /* doing a proxy CONNECT */ + unsigned int request_secure_upgrade:1; + unsigned int accept_secure_upgrade:1; + + http_use_proxy proxy_decider; + void *proxy_decider_udata; + + nssl_context *ssl_context; + + struct hook *hooks; + + char *location; /* 302 redirect location of last request */ + + char *user_agent; /* full User-Agent string */ + + /* The last HTTP-Version returned by the server */ + int version_major; + int version_minor; + + /* Error string */ + char error[BUFSIZ]; +}; + +struct http_req_s { + const char *method; + char *uri, *abs_path; + + /*** Request ***/ + + sbuffer headers; + request_body body; + FILE *body_stream; + const char *body_buffer; + size_t body_size; + + /**** Response ***/ + + /* The transfer encoding types */ + struct http_response { + unsigned int is_chunked; /* Are we using chunked TE? */ + int length; /* Response entity-body content-length */ + int left; /* Bytes left to read */ + long int chunk_left; /* Bytes of chunk left to read */ + } resp; + + /* List of callbacks which are passed response headers */ + struct header_handler *header_handlers; + /* List of callbacks which are passed response body blocks */ + struct body_reader *body_readers; + + /*** Miscellaneous ***/ + unsigned int method_is_head:1; + unsigned int use_proxy:1; + unsigned int use_expect100:1; + unsigned int can_persist:1; + unsigned int forced_close:1; + unsigned int upgrade_to_tls:1; + + http_session *session; + http_status *status; /* TODO: get rid of this */ + + /* stores request-private hook info */ + struct hook_request *hook_store; + +#ifdef USE_DAV_LOCKS + /* TODO: move this to hooks... list of locks to submit */ + struct dav_submit_locks *if_locks; +#endif + +}; + +#endif /* HTTP_PRIVATE_H */ diff --git a/neon/src/http_redirect.c b/neon/src/http_redirect.c new file mode 100644 index 000000000..97745da69 --- /dev/null +++ b/neon/src/http_redirect.c @@ -0,0 +1,147 @@ +/* + HTTP-redirect support + Copyright (C) 2000, Joe Orton <joe@orton.demon.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 + + Id: http_redirect.c,v 1.4 2000/08/12 15:34:40 joe Exp +*/ + +#include <config.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "http_request.h" +#include "ne_alloc.h" +#include "http_private.h" +#include "http_redirect.h" +#include "uri.h" +#include "neon_i18n.h" + +struct redirect { + char *location; + http_req *req; + http_redirect_confirm confirm; + http_redirect_notify notify; + void *userdata; +}; + +static void *create(void *session, http_req *req, + const char *method, const char *uri); +static int post_send(void *private, http_status *status); +static void destroy(void *private); + +http_request_hooks redirect_hooks = { + "http://www.webdav.org/neon/hooks/http-redirect", + create, + NULL, + NULL, + post_send, + destroy +}; + +static void * +create(void *session, http_req *req, const char *method, const char *uri) +{ + struct redirect *red = session; + + /* for handling 3xx redirects */ + http_add_response_header_handler(req, "Location", + http_duplicate_header, &red->location); + + red->req = req; + + return red; +} + +/* 2616 says we can't auto-redirect if the method is not GET or HEAD. + * We extend this to PROPFIND too, which violates a 2616 MUST, but + * is following the spirit of the spec, I think. */ +static int auto_redirect(struct redirect *red) +{ + return (red->req->method_is_head || + strcasecmp(red->req->method, "GET") == 0 || + strcasecmp(red->req->method, "PROPFIND") == 0); +} + +static int post_send(void *private, http_status *status) +{ + struct redirect *red = private; + struct uri uri; + + if ((status->code != 302 && status->code != 301) || + red->location == NULL) { + /* Nothing to do. */ + return HTTP_OK; + } + + if (uri_parse(red->location, &uri, NULL)) { + /* Couldn't parse the URI */ + http_set_error(red->req->session, + _("Could not parse redirect location.")); + return HTTP_ERROR; + } + + DEBUG(DEBUG_HTTP, "Redirect to hostname: %s, absPath: %s\n", + uri.host, uri.path); + + if (strcasecmp(uri.host, red->req->session->server.hostname) != 0) { + /* Don't allow redirects to a different server */ + return HTTP_OK; + } + + if (auto_redirect(red)) { + if (red->notify != NULL) { + (*red->notify)(red->userdata, red->req->abs_path, uri.path); + } + } else { + /* Need user-confirmation to follow the redirect */ + if (red->confirm == NULL || + !(*red->confirm)(red->userdata, red->req->abs_path, uri.path)) { + return HTTP_OK; + } + } + + /* FIXME FIXME: handle red->req->uri too */ + red->req->abs_path = ne_strdup(uri.path); + return HTTP_RETRY; +} + +static void destroy(void *private) +{ + struct redirect *red = private; + HTTP_FREE(red->location); + free(red); +} + +void http_redirect_register(http_session *sess, + http_redirect_confirm confirm, + http_redirect_notify notify, + void *userdata) +{ + struct redirect *red = ne_calloc(sizeof *red); + + red->confirm = confirm; + red->notify = notify; + red->userdata = userdata; + + http_add_hooks(sess, &redirect_hooks, red); +} diff --git a/neon/src/http_redirect.h b/neon/src/http_redirect.h new file mode 100644 index 000000000..688d2753e --- /dev/null +++ b/neon/src/http_redirect.h @@ -0,0 +1,60 @@ +/* + HTTP-redirect support + Copyright (C) 2000, Joe Orton <joe@orton.demon.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 + + Id: http_redirect.h,v 1.4 2000/08/12 16:04:10 joe Exp +*/ + +#ifndef HTTP_REDIRECT_H +#define HTTP_REDIRECT_H + +#include <http_request.h> + +/* Get confirmation from the user that a redirect from + * URI 'src' to URI 'dest' is acceptable. Should return: + * Non-Zero to FOLLOW the redirect + * Zero to NOT follow the redirect + */ +typedef int (*http_redirect_confirm)(void *userdata, + const char *src, const char *dest); + +/* Notify the user that a redirect has been automatically + * followed from URI 'src' to URI 'dest' */ +typedef void (*http_redirect_notify)(void *userdata, + const char *src, const char *dest); + +/* Register redirect handling for the given session. + * Some redirect responses will be automatically followed. + * If the redirect is automatically followed, the 'notify' callback + * is called. + * For redirects which are NOT automatically followed, the + * 'confirm' callback is called: if this returns zero, the redirect + * is ignored. + * + * 'confirm' may be passed as NULL: in this case, only automatic + * redirects are followed. 'notify' may also be passed as NULL, + * automatic redirects are still followed. + * + * 'userdata' is passed as the first argument to the confirm and + * notify callbacks. */ +void http_redirect_register(http_session *sess, + http_redirect_confirm confirm, + http_redirect_notify notify, + void *userdata); + +#endif /* HTTP_REDIRECT_H */ diff --git a/neon/src/http_request.c b/neon/src/http_request.c new file mode 100644 index 000000000..b14c6cde0 --- /dev/null +++ b/neon/src/http_request.c @@ -0,0 +1,1306 @@ +/* + HTTP request/response handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: http_request.c,v 1.24 2000/05/10 13:24:08 joe Exp +*/ + +/* This is the HTTP client request/response implementation. + * The goal of this code is to be modular and simple. + */ + +/* TODO: + * - Implement request hooks + * - Move DAV locks into a hook + * - Move authentication into a hook + */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef __EMX__ +#include <sys/select.h> +#endif + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.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 /* HAVE_STDLIB_H */ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#ifndef HAVE_SNPRINTF +#include <snprintf.h> +#endif + +#include <netinet/in.h> + +#include "neon_i18n.h" + +#include "xalloc.h" +#include "http_request.h" +#include "http_auth.h" +#include "socket.h" +#include "string_utils.h" /* for sbuffer */ +#include "http_utils.h" +#include "uri.h" +#include "neon.h" /* for NEON_VERSION */ + +struct host_info { + const char *hostname; + int port; + struct in_addr addr; + char *hostport; /* URI hostport segment */ + http_auth_session auth; + http_request_auth auth_callback; + void *auth_userdata; +}; + +typedef enum { + body_buffer, + body_stream, + body_none +} request_body; + +/* This is called with each of the headers in the response */ +struct header_handler { + const char *name; + http_header_handler handler; + void *userdata; + struct header_handler *next; +}; + +/* TODO: could unify these all into a generic callback list */ + +struct body_reader { + http_block_reader handler; + http_accept_response accept_response; + unsigned int use:1; + void *userdata; + struct body_reader *next; +}; + +/* Session support. */ +struct http_session_s { + /* Connection information */ + int socket; + + struct host_info server, proxy; + + unsigned int connected:1; + + /* Settings */ + unsigned int have_proxy:1; /* do we have a proxy server? */ + unsigned int no_persist:1; /* set to disable persistent connections */ + int expect100_works:2; /* known state of 100-continue support */ + + char *user_agent; /* full User-Agent string */ + + /* The last HTTP-Version returned by the server */ + int version_major; + int version_minor; + + /* Error string */ + char error[BUFSIZ]; +}; + +struct hook { + http_request_hooks *handler; + void *private; + struct hook *next; +}; + +struct http_req_s { + /* Fill in these with http_req_init */ + const char *method; + char *uri; + char *abs_path; + + /*** Request ***/ + + sbuffer headers; + /* Set these if you want to send a request body */ + request_body body; + FILE *body_stream; + const char *body_buffer; + size_t body_size; + + /**** Response ***/ + + /* The transfer encoding types */ + struct http_response { + enum { + te_none, + te_chunked, + te_unknown + } te; + int length; /* Response entity-body content-length */ + int left; /* Bytes left to read */ + long int chunk_left; /* Bytes of chunk left to read */ + } resp; + + struct header_handler *header_handlers; + struct body_reader *body_readers; + + /*** Miscellaneous ***/ + unsigned int method_is_head:1; + unsigned int use_proxy:1; + unsigned int use_expect100:1; + unsigned int can_persist:1; + unsigned int forced_close:1; + + http_session *session; + http_status *status; /* TODO: get rid of this */ + +#ifdef USE_DAV_LOCKS + /* TODO: move this to hooks... list of locks to submit */ + struct dav_submit_locks *if_locks; +#endif + +}; +#define HTTP_PORT 80 + +#define HTTP_EXPECT_TIMEOUT 15 +/* 100-continue only used if size > HTTP_EXPECT_MINSIZ */ +#define HTTP_EXPECT_MINSIZE 1024 + +#define HTTP_VERSION_PRE11(s) \ +((s)->version_major<1 || ((s)->version_major==1 && (s)->version_minor<1)) + +#define HTTP_MAXIMUM_HEADER_LENGTH 8192 + +#define NEON_USERAGENT "neon/" NEON_VERSION; + +static void te_hdr_handler( void *userdata, const char *value ); +static void connection_hdr_handler( void *userdata, const char *value ); + +static void set_hostinfo( struct host_info *info, const char *hostname, int port ); +static int lookup_host( struct host_info *info ); + +static int open_connection( http_req *req ); +static int close_connection( http_session *req ); + +static int set_sockerr( http_req *req, const char *doing, int sockerr ); + +static char *get_hostport( struct host_info *host); +static void add_fixed_headers( http_req *req ); +static int get_request_bodysize( http_req *req ); + +static int send_request_body( http_req *req ); +static void build_request( http_req *req, sbuffer buf ); +static int read_message_header( http_req *req, sbuffer buf ); +static int read_response_block( http_req *req, struct http_response *resp, + char *buffer, size_t *buflen ); +static int read_response_body( http_req *req, http_status *status ); + +/* Initializes an HTTP session */ +http_session *http_session_init( void ) +{ + http_session *sess = xmalloc(sizeof(http_session)); + DEBUG( DEBUG_HTTP, "HTTP session begins.\n" ); + strcpy( sess->error, "Unknown error." ); + memset( sess, 0, sizeof(struct http_session_s) ); + sess->version_major = -1; + sess->version_minor = -1; + return sess; +} + +static void +set_hostinfo( struct host_info *info, const char *hostname, int port ) +{ + HTTP_FREE( info->hostport ); + info->hostname = hostname; + info->port = port; + info->hostport = get_hostport( info ); + http_auth_init( &info->auth ); +} + +static int lookup_host( struct host_info *info ) +{ + if( host_lookup( info->hostname, &info->addr ) ) { + return HTTP_LOOKUP; + } else { + return HTTP_OK; + } +} + +int http_version_pre_http11( http_session *sess ) +{ + return HTTP_VERSION_PRE11(sess); +} + +int http_session_server( http_session *sess, const char *hostname, int port ) +{ + set_hostinfo( &sess->server, hostname, port ); + if( !sess->have_proxy ) { + return lookup_host( &sess->server ); + } else { + return HTTP_OK; + } +} + +int http_session_proxy( http_session *sess, const char *hostname, int port ) +{ + sess->have_proxy = 1; + set_hostinfo( &sess->proxy, hostname, port ); + return lookup_host( &sess->proxy ); +} + +void http_set_server_auth( http_session *sess, + http_request_auth callback, void *userdata ) +{ + sess->server.auth_callback = callback; + sess->server.auth_userdata = userdata; +} + +/* Set callback to handle proxy authentication */ +void http_set_proxy_auth( http_session *sess, + http_request_auth callback, void *userdata ) +{ + sess->proxy.auth_callback = callback; + sess->proxy.auth_userdata = userdata; +} + +void http_set_error( http_session *sess, const char *errstring ) +{ + strncpy( sess->error, errstring, BUFSIZ ); + sess->error[BUFSIZ-1] = '\0'; + STRIP_EOL( sess->error ); +} + +const char *http_get_error( http_session *sess ) { + return sess->error; +} + +/* Give authentication credentials */ +static int give_creds( void *udata, const char *realm, + char **username, char **password ) +{ + http_req *req = udata; + http_session *sess = req->session; + if( req->status->code == 407 && req->use_proxy && sess->proxy.auth_callback ) { + return (*sess->proxy.auth_callback)( + sess->proxy.auth_userdata, realm, sess->proxy.hostname, + username, password ); + } else if( req->status->code == 401 && sess->server.auth_callback ) { + return (*sess->server.auth_callback)( + sess->server.auth_userdata, realm, sess->server.hostname, + username, password ); + } + return -1; +} + +void http_duplicate_header( void *userdata, const char *value ) +{ + char **location = userdata; + *location = xstrdup( value ); +} + +void http_handle_numeric_header( void *userdata, const char *value ) +{ + int *location = userdata; + *location = atoi( value ); +} + +/* The body reader callback */ +static void auth_body_reader( void *userdata, const char *block, size_t length ) +{ + http_auth_session *sess = userdata; + http_auth_response_body( sess, block, length ); +} + +int http_session_finish( http_session *sess ) { + DEBUG( DEBUG_HTTP, "http_session_finish called.\n" ); + http_auth_finish( &sess->server.auth ); + if( sess->have_proxy ) { + http_auth_finish( &sess->proxy.auth ); + } + HTTP_FREE( sess->server.hostport ); + HTTP_FREE( sess->proxy.hostport ); + HTTP_FREE( sess->user_agent ); + if( sess->connected ) close_connection(sess); + free( sess ); + return HTTP_OK; +} + +/* Sends the body down the socket. + * Returns HTTP_* code */ +int send_request_body( http_req *req ) { + int ret; + switch( req->body ) { + case body_stream: + ret = sock_transfer( fileno(req->body_stream), req->session->socket, + req->body_size ); + DEBUG( DEBUG_HTTP, "Sent %d bytes.\n", ret ); + rewind( req->body_stream ); /* since we may have to send it again */ + break; + case body_buffer: + DEBUG( DEBUG_HTTP, "Sending body:\n%s\n", req->body_buffer ); + ret = sock_send_string( req->session->socket, req->body_buffer ); + DEBUG( DEBUG_HTTP, "sock_send_string returns: %d\n", ret ); + break; + default: + ret = 0; + break; + } + if( ret < 0 ) { + /* transfer failed */ + req->forced_close = 1; + return set_sockerr( req, _("Could not send request body"), ret ); + } else { + return HTTP_OK; + } +} + +/* Deal with the body size. + * Returns 0 on success or non-zero on error. */ +static int get_request_bodysize( http_req *req ) +{ + struct stat bodyst; + /* Do extra stuff if we have a body */ + switch( req->body ) { + case body_stream: + /* Get file length */ + if( fstat( fileno(req->body_stream), &bodyst ) < 0 ) { + /* Stat failed */ + DEBUG( DEBUG_HTTP, "Stat failed: %s\n", strerror( errno ) ); + return -1; + } + req->body_size = bodyst.st_size; + break; + case body_buffer: + req->body_size = strlen( req->body_buffer ); + break; + default: + /* No body, so no size. */ + break; + } + if( req->body != body_none) { + char tmp[BUFSIZ]; + /* Add the body length header */ + snprintf( tmp, BUFSIZ, "Content-Length: %d" EOL, req->body_size ); + sbuffer_zappend( req->headers, tmp ); + } else { + sbuffer_zappend( req->headers, "Content-Length: 0" EOL ); + } + return 0; +} + +static char *get_hostport( struct host_info *host) +{ + size_t len = strlen( host->hostname ); + char *ret = xmalloc( len + 10 ); + strcpy( ret, host->hostname ); + if( host->port != HTTP_PORT ) { + snprintf( ret + len, 9, ":%d", host->port ); + } + return ret; +} + +const char *http_get_server_hostport( http_session *sess ) { + return sess->server.hostport; +} + + +/* Lob the User-Agent, connection and host headers in to the request + * headers */ +static void add_fixed_headers( http_req *req ) { + if( req->session->user_agent ) { + sbuffer_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. */ + if( HTTP_VERSION_PRE11(req->session) && !req->use_proxy ) { + sbuffer_zappend( req->headers, "Connection: Keep-Alive, TE" EOL ); + sbuffer_zappend( req->headers, "Keep-Alive: " EOL ); + } else { + sbuffer_zappend( req->headers, "Connection: TE" EOL ); + } + /* We send TE: trailers since we understand trailers in the chunked + * response. */ + sbuffer_concat( req->headers, "TE: trailers" EOL + "Host: ", req->session->server.hostport, EOL, NULL ); +} + +static int always_accept_response( void *userdata, http_req *req, http_status *st ) +{ + return 1; +} + +int http_accept_2xx( void *userdata, http_req *req, http_status *st ) +{ + return (st->class == 2); +} + +/* Initializes the request with given method and URI. + * URI must be abs_path - i.e., NO scheme+hostname. It will BREAK + * otherwise. */ +http_req *http_request_create( http_session *sess, + const char *method, const char *uri ) { + sbuffer real_uri; + http_req *req = xmalloc( sizeof(http_req) ); + + /* Clear it out */ + memset( req, 0, sizeof( http_req ) ); + + DEBUG( DEBUG_HTTP, "Creating request...\n" ); + + req->session = sess; + req->headers = sbuffer_create(); + + /* Add in the fixed headers */ + add_fixed_headers( req ); + + /* Set the standard stuff */ + req->method = method; + req->method_is_head = (strcmp( req->method, "HEAD" ) == 0); + req->body = body_none; + + + /* TODO: Can do clever "should we use the proxy" discrimination + * here, e.g. by server hostname used. TODO: when we do that, + * then we might need to do a name lookup on the server here, + * since we omit that if we are using a proxy, normally. */ + req->use_proxy = sess->have_proxy; + + /* Add in standard callbacks */ + + http_auth_set_creds_cb( &sess->server.auth, give_creds, req ); + http_add_response_body_reader( req, always_accept_response, + auth_body_reader, &req->session->server.auth ); + if( req->use_proxy ) { + http_auth_set_creds_cb( &sess->proxy.auth, give_creds, req ); + http_add_response_body_reader( req, always_accept_response, + auth_body_reader, &req->session->proxy.auth ); + } + + /* Add in handlers for all the standard HTTP headers. */ + + http_add_response_header_handler( req, "Content-Length", + http_handle_numeric_header, &req->resp.length ); + http_add_response_header_handler( req, "Transfer-Encoding", + te_hdr_handler, &req->resp ); + http_add_response_header_handler( req, "Connection", + connection_hdr_handler, req ); + + req->abs_path = uri_abspath_escape( uri ); + + real_uri = sbuffer_create(); + if( req->use_proxy ) + sbuffer_concat( real_uri, "http://", + req->session->server.hostport, NULL ); + sbuffer_zappend( real_uri, req->abs_path ); + req->uri = sbuffer_finish(real_uri); + + DEBUG( DEBUG_HTTP, "Request created.\n" ); + + return req; +} + +void http_set_request_body_buffer( http_req *req, const char *buffer ) +{ + req->body = body_buffer; + req->body_buffer = buffer; + req->body_stream = NULL; +} + +void http_set_request_body_stream( http_req *req, FILE *stream ) +{ + req->body = body_stream; + req->body_stream = stream; + req->body_buffer = NULL; +} + +void http_set_expect100( http_session *sess, int use_expect100 ) +{ + if( use_expect100 ) { + sess->expect100_works = 1; + } else { + sess->expect100_works = -1; + } +} + +void http_set_persist( http_session *sess, int persist ) +{ + sess->no_persist = !persist; +} + +void http_set_useragent( http_session *sess, const char *token ) +{ + static const char *fixed = " " NEON_USERAGENT; + HTTP_FREE( sess->user_agent ); + CONCAT2( sess->user_agent, token, fixed ); +} + +sbuffer http_get_request_header( http_req *req ) +{ + return req->headers; +} + +void +http_add_response_header_handler( http_req *req, const char *name, + http_header_handler hdl, void *userdata ) +{ + struct header_handler *new = xmalloc( sizeof(struct header_handler) ); + new->name = name; + new->handler = hdl; + new->userdata = userdata; + new->next = req->header_handlers; + req->header_handlers = new; +} + +void +http_add_response_body_reader( http_req *req, http_accept_response acpt, + http_block_reader rdr, void *userdata ) +{ + struct body_reader *new = xmalloc( sizeof(struct body_reader) ); + new->accept_response = acpt; + new->handler = rdr; + new->userdata = userdata; + new->next = req->body_readers; + req->body_readers = new; +} + +void http_request_destroy( http_req *req ) +{ + + HTTP_FREE( req->uri ); + HTTP_FREE( req->abs_path ); + + sbuffer_destroy( req->headers ); + + DEBUG( DEBUG_HTTP, "Request ends.\n" ); + 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 HTTP_* on error. + * TODO?: only make one actual read() call in here... + */ +static int read_response_block( http_req *req, struct http_response *resp, + char *buffer, size_t *buflen ) +{ + int willread, readlen, socket = req->session->socket; + if( resp->te==te_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 ) { + long int chunk_len; + /* We are at the start of a new chunk. */ + DEBUG( DEBUG_HTTP, "New chunk.\n" ); + readlen = sock_readline( socket, buffer, *buflen ); + if( readlen <= 0 ) { + return set_sockerr( req, _("Could not read chunk size"), readlen ); + } + DEBUG( DEBUG_HTTP, "[Chunk Size] < %s", buffer ); + chunk_len = strtol( buffer, NULL, 16 ); + if( chunk_len == LONG_MIN || chunk_len == LONG_MAX ) { + DEBUG( DEBUG_HTTP, "Couldn't read chunk size.\n" ); + return -1; + } + DEBUG( DEBUG_HTTP, "Got chunk size: %ld\n", chunk_len ); + if( chunk_len == 0 ) { + /* Zero-size chunk */ + DEBUG( DEBUG_HTTP, "Zero-size chunk.\n" ); + *buflen = 0; + return HTTP_OK; + } + resp->chunk_left = chunk_len; + } + willread = min( *buflen - 1, resp->chunk_left ); + } else if( resp->length > 0 ) { + /* Have we finished reading the body? */ + if( resp->left == 0 ) { + *buflen = 0; + return HTTP_OK; + } + willread = min( *buflen - 1, resp->left ); + } else { + /* Read until socket-close */ + willread = *buflen - 1; + } + DEBUG( DEBUG_HTTP, "Reading %d bytes of response body.\n", willread ); + readlen = sock_read( socket, buffer, willread ); + DEBUG( DEBUG_HTTP, "Got %d bytes.\n", readlen ); + if( readlen < 0 || + (readlen == 0 && (resp->length > 0 || resp->te==te_chunked)) ) { + return set_sockerr( req, _("Could not read response body"), readlen ); + } + buffer[readlen] = '\0'; + *buflen = readlen; + DEBUG( DEBUG_HTTPBODY, "Read block:\n%s\n", buffer ); + if( resp->te==te_chunked ) { + resp->chunk_left -= readlen; + if( resp->chunk_left == 0 ) { + char crlfbuf[2]; + /* If we've read a whole chunk, read a CRLF */ + readlen = sock_fullread( socket, crlfbuf, 2 ); + if( readlen < 0 || strncmp( crlfbuf, EOL, 2 ) != 0 ) { + return set_sockerr( req, _("Error reading chunked response body"), + readlen ); + } + } + } else if( resp->length > 0 ) { + resp->left -= readlen; + } + return HTTP_OK; +} + +/* Build a request string into the buffer. + * If we sent the data as we generated it, it's possible that multiple + * packets could go out on the wire, which is less efficient. */ +static void build_request( http_req *req, sbuffer buf ) +{ + const char *uri; + char *tmp; + + /* If we are talking to a proxy, we send them the absoluteURI + * as the Request-URI. If we are talking to a server, we just + * send abs_path. */ + if( req->use_proxy ) + uri = req->uri; + else + uri = req->abs_path; + + sbuffer_clear( buf ); + + /* Add in the request and the user-supplied headers */ + sbuffer_concat( buf, req->method, " ", uri, " HTTP/1.1" EOL, + sbuffer_data(req->headers), NULL ); + + /* Add the authorization headers in */ + tmp = http_auth_request_header( &req->session->server.auth ); + if( tmp != NULL ) { + sbuffer_concat( buf, "Authorization: ", tmp, NULL ); + free( tmp ); + } + + if( req->use_proxy ) { + tmp = http_auth_request_header( &req->session->proxy.auth ); + if( tmp != NULL ) { + sbuffer_concat( buf, "Proxy-Authorization: ", tmp, NULL ); + free( tmp ); + } + } + + /* Now handle the body. */ + req->use_expect100 = 0; + if( req->body!=body_none && + (req->session->expect100_works > -1) && + (req->body_size > HTTP_EXPECT_MINSIZE) && + !HTTP_VERSION_PRE11(req->session) ) { + /* Add Expect: 100-continue. */ + sbuffer_zappend( buf, "Expect: 100-continue" EOL ); + req->use_expect100 = 1; + } + +} + +static int set_sockerr( http_req *req, const char *doing, int code ) +{ + switch( code ) { + case 0: + if( req->use_proxy ) { + snprintf( req->session->error, BUFSIZ, + _("%s: connection was closed by proxy server."), doing ); + } else { + snprintf( req->session->error, BUFSIZ, + _("%s: connection was closed by server."), doing ); + } + return HTTP_ERROR; + case SOCK_TIMEOUT: + snprintf( req->session->error, BUFSIZ, + _("%s: connection timed out."), doing ); + return HTTP_TIMEOUT; + default: + snprintf( req->session->error, BUFSIZ, + "%s: %s", doing, strerror(errno) ); + return HTTP_ERROR; + } +} + +/* TODO: The retry loop may be overkill. It may only be necessary to + * try sending the request >1 time; making the loop shorter. */ +static int send_request( http_req *req, const char *request, + sbuffer buf, http_status *status ) +{ + http_session *sess = req->session; + int ret, try_again; + + do { + + try_again = 0; + + /* Open the connection if necessary */ + if( !sess->connected ) { + ret = open_connection( req ); + if( ret != HTTP_OK ) { + return ret; + } + } + +#ifdef DEBUGGING + if( (DEBUG_HTTPPLAIN&debug_mask) == DEBUG_HTTPPLAIN ) { + /* Display everything mode */ + DEBUG( DEBUG_HTTP, "Sending request headers:\n%s", request ); + } else { + /* Blank out the Authorization paramaters */ + char *reqdebug = xstrdup(request), *pnt = reqdebug; + while( (pnt = strstr( pnt, "Authorization: ")) != NULL ) { + for( pnt += 15; *pnt != '\r' && *pnt != '\0'; pnt++ ) { + *pnt = 'x'; + } + } + DEBUG( DEBUG_HTTP, "Sending request headers:\n%s", reqdebug ); + free( reqdebug ); + } +#endif /* DEBUGGING */ + + DEBUG( DEBUG_HTTP, "Request size: %d\n", strlen(request) ); + /* Send the Request-Line and headers */ + ret = sock_send_string( req->session->socket, request ); + if( ret < 0 ) { + return set_sockerr( req, _("Could not send request"), ret ); + } + + DEBUG( DEBUG_HTTP, "Request sent\n" ); + + /* Now, if we are doing a Expect: 100, hang around for a short + * amount of time, to see if the server actually cares about the + * Expect and sends us a 100 Continue response if the request + * is valid, else an error code if it's not. This saves sending + * big files to the server when they will be rejected. + */ + + if( req->use_expect100 ) { + DEBUG( DEBUG_HTTP, "Waiting for response...\n" ); + ret = sock_block( sess->socket, HTTP_EXPECT_TIMEOUT ); + switch( ret ) { + case SOCK_TIMEOUT: + /* Timed out - i.e. Expect: ignored. There is a danger + * here that the server DOES respect the Expect: header, + * but was going SO slowly that it didn't get time to + * respond within HTTP_EXPECT_TIMEOUT. + * TODO: while sending the body, check to see if the + * server has sent anything back - if it HAS, then + * stop sending - this is a spec compliance SHOULD */ + DEBUG( DEBUG_HTTP, "Wait timed out.\n" ); + sess->expect100_works = -1; /* don't try that again */ + /* Try sending the request again without using 100-continue */ + try_again = 1; + break; + case SOCK_ERROR: /* error */ + return set_sockerr( req, _("Error waiting for response"), ret ); + default: + DEBUG( DEBUG_HTTP, "Wait got data.\n" ); + sess->expect100_works = 1; /* it works - use it again */ + break; + } + } else if( req->body != body_none ) { + /* Just chuck the file down the socket */ + DEBUG( DEBUG_HTTP, "Sending body...\n" ); + ret = send_request_body( req ); + if( ret != HTTP_OK ) return ret; + DEBUG( DEBUG_HTTP, "Body sent.\n" ); + } + + /* Now, we have either: + * - Sent the header and body, or + * - Sent the header incl. Expect: line, and got some response. + * In any case, we get the status line of the response. + */ + + /* HTTP/1.1 says that the server MAY emit any number of + * interim 100 (Continue) responses prior to the normal + * response. So loop while we get them. */ + + do { + if( sock_readline( sess->socket, sbuffer_data(buf), BUFSIZ ) <= 0 ) { + if( try_again ) { + return set_sockerr( req, _("Could not read status line"), ret ); + } + try_again = 1; + break; + } + + DEBUG( DEBUG_HTTP, "[Status Line] < %s", sbuffer_data(buf) ); + + /* Got the status line - parse it */ + if( http_parse_statusline( sbuffer_data(buf), status ) ) { + http_set_error( sess, "Could not parse response status line." ); + return -1; + } + + sess->version_major = status->major_version; + sess->version_minor = status->minor_version; + snprintf( sess->error, BUFSIZ, "%d %s", + status->code, status->reason_phrase ); + STRIP_EOL( sess->error ); + + if( status->class == 1 ) { + DEBUG( DEBUG_HTTP, "Got 1xx-class.\n" ); + /* Skip any headers, we don't need them */ + do { + ret = sock_readline( sess->socket, sbuffer_data(buf), BUFSIZ ); + if( ret <= 0 ) { + return set_sockerr( + req, _("Error reading response headers"), ret ); + } + DEBUG( DEBUG_HTTP, "[Ignored header] < %s", + sbuffer_data(buf) ); + } while( strcmp( sbuffer_data(buf), EOL ) != 0 ); + + if( req->use_expect100 && (status->code == 100) ) { + /* We are using Expect: 100, and we got a 100-continue + * return code... send the request body */ + DEBUG( DEBUG_HTTP, "Got continue... sending body now.\n" ); + if( send_request_body( req ) != HTTP_OK ) + return HTTP_ERROR; + + DEBUG( DEBUG_HTTP, "Body sent.\n" ); + } + } + } while( status->class == 1 ); + + if( try_again ) { + /* If we're trying again, close the conn first */ + DEBUG( DEBUG_HTTP, "Retrying request, closing connection first.\n" ); + close_connection( sess ); + } + + } while( try_again ); + + return HTTP_OK; +} + +/* Read a message header from sock into buf. + * Returns an HTTP_* code. + */ +static int read_message_header( http_req *req, sbuffer buf ) +{ + char extra[BUFSIZ], *pnt; + int ret, sock = req->session->socket; + + sbuffer_clear(buf); + + ret = sock_readline( sock, sbuffer_data(buf), BUFSIZ ); + if( ret <= 0 ) + return set_sockerr( req, _("Error reading response headers"), ret ); + DEBUG( DEBUG_HTTP, "[Header:%d] < %s", + strlen(sbuffer_data(buf)), sbuffer_data(buf) ); + if( strcmp( sbuffer_data(buf), EOL ) == 0 ) { + DEBUG( DEBUG_HTTP, "CRLF: End of headers.\n" ); + return HTTP_OK; + } + while(sbuffer_size(buf) < HTTP_MAXIMUM_HEADER_LENGTH ) { + /* Collect any extra lines into buffer */ + ret = sock_recv( sock, extra, 1, MSG_PEEK); + if( ret <= 0 ) { + return set_sockerr( req, _("Error reading response headers"), ret ); + } + if( extra[0] != ' ' && extra[0] != '\t' ) { + /* No more headers */ + return HTTP_OK; + } + ret = sock_readline( sock, extra, BUFSIZ ); + if( ret <= 0 ) { + return set_sockerr( req, _("Error reading response headers"), ret); + } + DEBUG( DEBUG_HTTP, "[Cont:%d] < %s", strlen(extra), extra); + /* Append a space to the end of the last header, in + * place of the CRLF. */ + pnt = strchr( sbuffer_data(buf), '\r' ); + pnt[0] = ' '; pnt[1] = '\0'; + /* Skip leading whitespace off next line */ + for( pnt = extra; *pnt!='\0' && + ( *pnt == ' ' || *pnt =='\t' ); pnt++ ) /*oneliner*/; + DEBUG( DEBUG_HTTP, "[Continued] < %s", pnt ); + sbuffer_altered( buf ); + sbuffer_zappend( buf, pnt ); + } + return HTTP_ERROR; +} + +static void normalize_response_length( http_req *req, http_status *status ) +{ + /* Response entity-body length calculation, bit icky. + * Here, we set: + * length==-1 if we DO NOT know the exact body length + * length>=0 if we DO know the body length. + * + * RFC2616, section 4.4: + * NO body is returned if the method is HEAD, or the resp status + * is 204 or 304 + */ + if( req->method_is_head || status->code==204 || status->code==304 ) { + req->resp.length = 0; + } else { + /* RFC2616, section 4.4: if we have a transfer encoding + * and a content-length, then ignore the content-length. */ + if( (req->resp.length>-1) && + (req->resp.te!=te_unknown) ) { + req->resp.length = -1; + } + } +} + +/* Read response headers, using buffer buffer. + * Returns HTTP_* code. */ +static int read_response_headers( http_req *req, sbuffer buf ) +{ + http_session *sess = req->session; + struct header_handler *hdl; + char *name, *value, *pnt, *hdr; + int ret; + + /* Read response headers */ + while(1) { + ret = read_message_header( req, buf ); + if( ret != HTTP_OK ) return ret; + + hdr = sbuffer_data(buf); + + if( strcmp( hdr, EOL ) == 0 ) { + return HTTP_OK; + } + + /* Now parse the header line. This is all a bit noddy. */ + pnt = strchr( hdr, ':' ); + if( pnt == NULL ) { + http_set_error( sess, "Malformed header line." ); + return HTTP_ERROR; + } + + /* Null-term name at the : */ + *pnt = '\0'; + name = hdr; + /* Strip leading whitespace from the value */ + for( value = pnt+1; *value!='\0' && *value==' '; value++ ) + /* nullop */; + STRIP_EOL( value ); + DEBUG( DEBUG_HTTP, "Header Name: [%s], Value: [%s]\n", + name, value ); + /* Iterate through the header handlers */ + for( hdl = req->header_handlers; hdl != NULL; hdl = hdl->next ) { + if( strcasecmp( name, hdl->name ) == 0 ) { + (*hdl->handler)( hdl->userdata, value ); + } + } + } + + return HTTP_ERROR; +} + +/* Read the response message body */ +static int read_response_body( http_req *req, http_status *status ) +{ + char buffer[BUFSIZ]; + int readlen, ret = HTTP_OK; + struct body_reader *rdr; + + /* If there is nothing to do... */ + if( req->resp.length == 0 ) { + /* Do nothing */ + return HTTP_OK; + } + + /* First off, tell all of the response body handlers that they are + * going to get a body, and let them work out whether they want to + * handle it or not */ + for( rdr = req->body_readers; rdr != NULL; rdr=rdr->next ) { + rdr->use = (*rdr->accept_response)( rdr->userdata, req, status ); + } + + req->resp.left = req->resp.length; + req->resp.chunk_left = 0; + + /* Now actually read the thing */ + + do { + /* Read a block */ + readlen = BUFSIZ; + ret = read_response_block( req, &req->resp, buffer, &readlen ); + + /* TODO: Do we need to call them if readlen==0, or if + * readlen == -1, to tell them something has gone wrong? */ + + if( ret == HTTP_OK ) { + for( rdr = req->body_readers; rdr!=NULL; rdr=rdr->next ) { + if( rdr->use ) + (*rdr->handler)( rdr->userdata, buffer, readlen ); + } + } + + } while( ret == HTTP_OK && readlen > 0 ); + + if( ret != HTTP_OK ) + req->forced_close = 1; + + return ret; +} + +/* Handler for the "Transfer-Encoding" response header */ +static void te_hdr_handler( void *userdata, const char *value ) +{ + struct http_response *resp = userdata; + if( strcasecmp( value, "chunked" ) == 0 ) { + resp->te = te_chunked; + } else { + resp->te = te_unknown; + } +} + +/* Handler for the "Connection" response header */ +static void connection_hdr_handler( void *userdata, const char *value ) +{ + http_req *req = userdata; + if( strcasecmp( value, "close" ) == 0 ) { + req->forced_close = 1; + } else if( strcasecmp( value, "Keep-Alive" ) == 0 ) { + req->can_persist = 1; + } +} + + +/* HTTP/1.x request/response mechanism + * + * Returns an HTTP_* return code. + * + * The status information is placed in status. The error string is + * placed in req->session->error + * + */ +int http_request_dispatch( http_req *req, http_status *status ) +{ + http_session *sess = req->session; + sbuffer buf, request; + int ret, attempt, proxy_attempt, con_attempt, can_retry; + /* Response header storage */ + char *www_auth, *proxy_auth, *authinfo, *proxy_authinfo; + + /* Initialization... */ + DEBUG( DEBUG_HTTP, "Request started...\n" ); + http_set_error( sess, "Unknown error." ); + ret = HTTP_OK; + + if( get_request_bodysize( req ) ) + return HTTP_ERROR; + + buf = sbuffer_create_sized( BUFSIZ ); + + http_add_response_header_handler( req, "WWW-Authenticate", + http_duplicate_header, &www_auth ); + http_add_response_header_handler( req, "Proxy-Authenticate", + http_duplicate_header, &proxy_auth ); + http_add_response_header_handler( req, "Authentication-Info", + http_duplicate_header, &authinfo ); + http_add_response_header_handler( req, "Proxy-Authentication-Info", + http_duplicate_header, &proxy_authinfo ); + req->status = status; + + request = sbuffer_create(); + proxy_attempt = con_attempt = attempt = 1; + www_auth = proxy_auth = authinfo = proxy_authinfo = NULL; + + /* Loop sending the request: + * Retry whilst authentication fails and we supply it. */ + + do { + + can_retry = 0; + req->can_persist = 0; + req->forced_close = 0; + + /* Note that we pass the abs_path here... */ + http_auth_new_request( &sess->server.auth, req->method, req->uri, + req->body_buffer, req->body_stream ); + if( req->use_proxy ) { + /* ...and absoluteURI here. */ + http_auth_new_request( &sess->proxy.auth, req->method, req->uri, + req->body_buffer, req->body_stream ); + } + + build_request( req, request ); + +#ifdef USE_DAV_LOCKS + /* TODO: Move this into a hook structure */ + { + char *tmp = dav_lock_ifheader( req ); + if( tmp != NULL ) { + sbuffer_zappend( request, tmp ); + free( tmp ); + if( HTTP_VERSION_PRE11(sess) ) { + /* HTTP/1.0 */ + sbuffer_zappend( request, "Pragma: no-cache" EOL ); + } else { + /* HTTP/1.1 and above */ + sbuffer_zappend( request, "Cache-Control: no-cache" EOL ); + } + } + } +#endif /* USE_DAV_LOCKS */ + + /* Final CRLF */ + sbuffer_zappend( request, EOL ); + + /* Now send the request, and read the Status-Line */ + ret = send_request( req, sbuffer_data(request), buf, status ); + if( ret != HTTP_OK ) goto dispatch_error; + + req->resp.length = -1; + req->resp.te = te_unknown; + + /* Read the headers */ + if( read_response_headers( req, buf ) != HTTP_OK ) { + ret = HTTP_ERROR; + goto dispatch_error; + } + + normalize_response_length( req, status ); + + ret = read_response_body( req, status ); + if( ret != HTTP_OK ) goto dispatch_error; + + /* Read headers in chunked trailers */ + if( req->resp.te == te_chunked ) { + ret = read_response_headers( req, buf ); + if( ret != HTTP_OK ) goto dispatch_error; + } + + if( proxy_authinfo != NULL && + http_auth_verify_response( &sess->proxy.auth, proxy_authinfo ) ) { + DEBUG( DEBUG_HTTP, "Proxy response authentication invalid.\n" ); + ret = HTTP_SERVERAUTH; + http_set_error( sess, _("Proxy server was not authenticated correctly.") ); + } else if( authinfo != NULL && + http_auth_verify_response( &sess->server.auth, authinfo ) ) { + DEBUG( DEBUG_HTTP, "Response authenticated as invalid.\n" ); + ret = HTTP_PROXYAUTH; + http_set_error( sess, _("Server was not authenticated correctly.") ); + } else if( status->code == 401 && www_auth != NULL && attempt++ == 1) { + if( !http_auth_challenge( &sess->server.auth, www_auth ) ) { + can_retry = 1; + } + } else if( status->code == 407 && proxy_auth != NULL && proxy_attempt++ == 1 ) { + if( !http_auth_challenge( &sess->proxy.auth, proxy_auth ) ) { + can_retry = 1; + } + } + + HTTP_FREE( www_auth ); + HTTP_FREE( proxy_auth ); + HTTP_FREE( authinfo ); + HTTP_FREE( proxy_authinfo ); + + /* Close the connection if one of the following is true: + * - We have a forced close (e.g. "Connection: close" header) + * - We are not using persistent connections for this session + * - this is HTTP/1.0, and they haven't said they can do + * persistent connections + */ + if( req->forced_close || sess->no_persist || + (HTTP_VERSION_PRE11(sess) && !req->can_persist ) ) { + close_connection(sess); + } + + /* Retry it if we had an auth challenge */ + + } while( can_retry ); + + DEBUG( DEBUG_HTTP | DEBUG_FLUSH, + "Request ends, status %d class %dxx, error line:\n%s\n", + status->code, status->class, sess->error ); + DEBUG( DEBUG_HTTPBASIC, "Response: %d %s", status->code, sess->error ); + + switch( status->code ) { + case 401: + ret = HTTP_AUTH; + break; + case 407: + ret = HTTP_AUTHPROXY; + break; + default: + break; + } + +dispatch_error: + + sbuffer_destroy(request); + + HTTP_FREE( www_auth ); + HTTP_FREE( proxy_auth ); + HTTP_FREE( authinfo ); + HTTP_FREE( proxy_authinfo ); + + return ret; +} + +static int open_connection( http_req *req ) { + http_session *sess = req->session; + if( req->use_proxy ) { + DEBUG( DEBUG_SOCKET, "Connecting to proxy at %s:%d...\n", + sess->proxy.hostname, sess->proxy.port ); + sess->socket = sock_connect( sess->proxy.addr, sess->proxy.port ); + if( sess->socket < 0 ) { + (void) set_sockerr( req, _("Could not connect to proxy server"), -1 ); + return HTTP_CONNECT; + } + } else { + DEBUG( DEBUG_SOCKET, "Connecting to server at %s:%d...\n", + sess->server.hostname, sess->server.port ); + sess->socket = sock_connect( sess->server.addr, sess->server.port ); + if( sess->socket < 0 ) { + (void) set_sockerr( req, _("Could not connect to server"), -1 ); + return HTTP_CONNECT; + } + } + DEBUG( DEBUG_SOCKET, "Connected.\n" ); + sess->connected = 1; + return HTTP_OK; +} + +static int close_connection( http_session *sess ) { + DEBUG( DEBUG_SOCKET, "Closing socket.\n" ); + sock_close( sess->socket ); + sess->connected = 0; + DEBUG( DEBUG_SOCKET, "Socket closed.\n" ); + return 0; +} + diff --git a/neon/src/http_request.h b/neon/src/http_request.h new file mode 100644 index 000000000..6059f8a47 --- /dev/null +++ b/neon/src/http_request.h @@ -0,0 +1,261 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 HTTP_REQUEST_H +#define HTTP_REQUEST_H + +#include <stdio.h> /* For FILE * */ +#include "http_utils.h" /* For http_status */ +#include "string_utils.h" /* For sbuffer */ + +#define HTTP_OK (0) +#define HTTP_ERROR (1) /* Generic error */ +#define HTTP_LOOKUP (3) /* Name lookup failed */ +#define HTTP_AUTH (4) /* User authentication failed on server */ +#define HTTP_AUTHPROXY (5) /* User authentication failed on proxy */ +#define HTTP_SERVERAUTH (6) /* Server authentication failed */ +#define HTTP_PROXYAUTH (7) /* Proxy authentication failed */ +#define HTTP_CONNECT (8) /* Could not connect to server */ +#define HTTP_TIMEOUT (9) /* Connection timed out */ +#define HTTP_FAILED (10) /* The precondition failed */ + +/* Will be used by hooks: */ +#define HTTP_RETRY (101) + +#define EOL "\r\n" + +/****** Session Handling ******/ + +typedef struct http_session_s http_session; +typedef struct http_req_s http_req; + +/* + * Session handling: + * Call order: + * 1. http_session_init MANDATORY + * 2. http_session_proxy OPTIONAL + * 3. http_session_server MANDATORY + * 4. http_set_* OPTIONAL + * ... --- Any HTTP request method --- + * n. http_session_finish MANDATORY + */ + +/* Create a new HTTP session */ +http_session *http_session_init( void ); + + +/* Finish an HTTP session */ +int http_session_finish( http_session *sess ); + +/* Set the server or proxy server to be used for the given session. + * Returns: + * HTTP_LOOKUP if the DNS lookup for hostname failed. + * HTTP_OK otherwise. + * + * Note that if a proxy is being used, http_session_proxy should be + * called BEFORE http_session_server, so the DNS lookup can be skipped + * on the server. */ +int http_session_server( http_session *sess, const char *hostname, int port ); +int http_session_proxy( http_session *sess, const char *hostname, int port ); + +/* Set protocol options for session: + * expect100: Defaults to OFF + * persist: Defaults to ON + */ +void http_set_expect100( http_session *sess, int use_expect100 ); +void http_set_persist( http_session *sess, int persist ); +/* Sets the user-agent string. neon/VERSION will be appended, to make + * the full header "User-Agent: token neon/VERSION" */ +void http_set_useragent( http_session *sess, const char *token ); + +/* Determine if next-hop server claims HTTP/1.1 compliance. Returns: + * 0 if next-hop server does NOT claim HTTP/1.1 compliance + * non-zero if next-hop serer DOES claim HTTP/1.1 compliance + */ +int http_version_pre_http11( http_session *sess ); + +/* Returns the 'hostport' URI segment for the end-server, e.g. + * "my.server.com:8080" or "www.server.com" + * (port segment is ommitted if == 80) + */ +const char *http_get_server_hostport( http_session *sess ); + +/* The callback used to request the username and password in the given + * realm. The username and password must be placed in malloc()-allocated + * memory. + * Must return: + * 0 on success: *username and *password must be non-NULL, and will + * be free'd by the HTTP layer when necessary + * -1 to cancel (*username and *password are ignored.) + */ +typedef int (*http_request_auth)( + void *userdata, const char *realm, const char *hostname, + char **username, char **password ); + +/* Set callbacks to handle server and proxy authentication. + * userdata is passed as the first argument to the callback. */ +void http_set_server_auth( http_session *sess, http_request_auth callback, + void *userdata ); +void http_set_proxy_auth( http_session *sess, http_request_auth callback, + void *userdata ); + +/* Set the error string for the session */ +void http_set_error( http_session *sess, const char *errstring ); +/* Retrieve the error string for the session */ +const char *http_get_error( http_session *sess ); + +/**** Request hooks handling *****/ + +/* TODO: Unimplemented */ + +/* The aim of hooks is to support HTTP authentication, SSL/TLS, and possibly + * DAV locking (though this is harder), in a modular fashion - i.e., without + * requiring any code in http_request.c. + * + * DAV locking is harder, as it is not transparent to the caller: they + * specify what locks are needed (i.e. MKCOL modifies the *parent* collection + * of the resource at Request-URI as well as the resource itself.). + * This will probably require some way of retrieving the private data of + * the hook externally, hence the handler_id hack. + */ +typedef struct { + /* A slight hack? Allows access to the hook private information, + * externally... */ + const char *id; /* id could be a URI to be globally unique */ + /* e.g. "http://webdav.org/neon/hooks/davlock" */ + + /* Register a new request */ + void (*create)( void **private, + http_req *req, const char *method, const char *uri ); + /* Tell them what request body we are using: either buffer or stream + * will be non-NULL. */ + void (*use_body)( void *private, const char *buffer, FILE *stream ); + /* Just before sending the request: let them add headers if they want */ + void (*pre_send)( void *private, sbuffer request ); + /* After sending the request. May return: + * HTTP_OK everything is okay + * HTTP_RETRY try sending the request again */ + int (*post_send)( void *private, http_status *stat ); + /* Clean up after yourself */ + void (*destroy)( void *private ); +} http_request_hooks; + +/* Add in hooks */ +void http_add_hooks( http_session *sess, http_request_hooks hooks ); + +/* Return private data for a new request */ +void *http_get_hook_private( http_req *req, const char *id ); + +/***** Request Handling *****/ + +/* Create a new request, with given method and URI. + * Returns request pointer. */ +http_req * +http_request_create( http_session *sess, const char *method, const char *uri ); + +/* 'buffer' will be sent as the request body with given request. */ +void http_set_request_body_buffer( http_req *req, const char *buffer ); +/* Contents of stream will be sent as the request body with the given + * request */ +void http_set_request_body_stream( http_req *req, FILE *stream ); + +/* 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. */ +typedef int (*http_accept_response)( + void *userdata, http_req *req, http_status *st ); + +/* An 'acceptance' callback which only accepts 2xx-style responses. + * Ignores userdata. */ +int http_accept_2xx( void *userdata, http_req *req, http_status *st ); + +/* The 'reader' callback type */ +typedef void (*http_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. */ +void http_add_response_body_reader( http_req *req, http_accept_response accpt, + http_block_reader rdr, 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 (*http_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. */ +void http_add_response_header_handler( + http_req *req, const char *name, http_header_handler hdl, void *userdata ); + +/* Stock header handlers: + * 'duplicate': *(char *)userdata = strdup(value) + * 'numeric': *(int *)userdata = atoi(value) + * e.g. + * int mynum; + * http_add_response_header_handler( myreq, "Content-Length", + * http_handle_numeric_handler, &mynum ); + * ... arranges mynum to be set to the value of the Content-Length header. + */ +void http_duplicate_header( void *userdata, const char *value ); +void http_handle_numeric_header( void *userdata, const char *value ); + +/* Returns the sbuffer which is the request header string, allowing + * extra headers to be added to the request. */ +/* TODO: This is a bit noddy. */ +sbuffer http_get_request_header( http_req *req ); + +/* http_request_dispatch: Sends the given request, and reads the + * response. Response-Status information is written into 'status'. + * + * Returns: + * HTTP_OK if request sent + response read okay. + * HTTP_AUTH if user authentication failed on origin server + * HTTP_AUTHPROXY if user authentication failed on proxy server + * HTTP_SERVERAUTH server authentication failed + * HTTP_PROXYAUTH proxy authentication failed + * HTTP_CONNECT could not connect to server/proxy server + * HTTP_TIMEOUT connection timed out mid-request + * HTTP_ERROR for other errors, and http_get_error() should + * return a meaningful error string + * + * NB: HTTP_AUTH and HTTP_AUTHPROXY mean that the USER supplied the + * wrong username/password. SERVER/PROXYAUTH mean that, after the + * server has accepted a valid username/password, the server/proxy did + * not authenticate the response message correctly. + * */ +int http_request_dispatch( http_req *req, http_status *status ); + +/* Destroy memory associated with request pointer */ +void http_request_destroy( http_req *req ); + +#endif /* HTTP_REQUEST_H */ + diff --git a/neon/src/http_utils.c b/neon/src/http_utils.c new file mode 100644 index 000000000..f4548ac51 --- /dev/null +++ b/neon/src/http_utils.c @@ -0,0 +1,116 @@ +/* + HTTP utility functions + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: http_utils.c,v 1.4 2000/05/10 13:26:07 joe Exp +*/ + +#include <config.h> + +#include <sys/types.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <ctype.h> /* isdigit() for http_parse_statusline */ + +#include "dates.h" + +#include "http_utils.h" + +int http_debug_mask; +FILE *http_debug_stream; + +void http_debug( int ch, char *template, ...) +{ +#ifdef DEBUGGING + va_list params; + if( (ch&http_debug_mask) != ch ) return; + fflush( stdout ); + va_start( params, template ); + vfprintf( http_debug_stream, template, params ); + va_end( params ); + if( (ch&DEBUG_FLUSH) == DEBUG_FLUSH ) { + fflush( http_debug_stream ); + } +#else + /* No debugging here */ +#endif +} + +/* HTTP-date parser */ +time_t http_dateparse( const char *date ) { + time_t tmp; + tmp = rfc1123_parse( date ); + if( tmp == -1 ) { + tmp = rfc1036_parse( date ); + if( tmp == -1 ) + tmp = asctime_parse( date ); + } + return tmp; +} + +int http_parse_statusline( const char *status_line, http_status *st ) { + const char *part; + int major, minor, status_code, class; + /* Check they're speaking the right language */ + if( strncmp( status_line, "HTTP/", 5 ) != 0 ) { + 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]) ) { + return -1; + } + status_code = 100*(part[0]-'0') + 10*(part[1]-'0') + (part[2]-'0'); + class = part[0]-'0'; + /* Skip whitespace between status-code and reason-phrase */ + for( part+=3; *part == ' ' || *part == '\t'; part++ ) /* noop */; + if( *part == '\0' ) { + return -1; + } + /* Fill in the results */ + st->major_version = major; + st->minor_version = minor; + st->reason_phrase = part; + st->code = status_code; + st->class = class; + return 0; +} diff --git a/neon/src/http_utils.h b/neon/src/http_utils.h new file mode 100644 index 000000000..d18ac6f72 --- /dev/null +++ b/neon/src/http_utils.h @@ -0,0 +1,105 @@ +/* + HTTP utility functions + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 HTTP_UTILS_H +#define HTTP_UTILS_H + +#include <config.h> + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_STDARG_H +#include <stdarg.h> +#endif + +#include <stdio.h> + +#ifdef HAVE_DEBUG_FUNC +#include "common.h" +#endif + +#define HTTP_QUOTES "\"'" +#define HTTP_WHITESPACE " \r\n\t" + +/* Handy macro to free things. */ +#define HTTP_FREE(x) \ +do { if( (x)!=NULL ) free( (x) ); (x) = NULL; } while(0) + +#define HTTP_PORT 80 + +time_t http_dateparse( const char *date ); + +#undef min +#define min(a,b) ((a)<(b)?(a):(b)) + +#ifndef HAVE_DEBUG_FUNC +/* CONSIDER: mutt has a nicer way of way of doing debugging output... maybe + * switch to like that. */ +#ifdef DEBUGGING +#define DEBUG if(0) http_debug +#else /* !DEBUGGING */ +#define DEBUG http_debug +#endif /* DEBUGGING */ + +#define DEBUG_SOCKET (1<<0) +#define DEBUG_HTTP (1<<3) +#define DEBUG_XML (1<<5) +#define DEBUG_HTTPAUTH (1<<7) +#define DEBUG_HTTPPLAIN (1<<8) +#define DEBUG_LOCKS (1<<10) +#define DEBUG_XMLPARSE (1<<11) +#define DEBUG_HTTPBODY (1<<12) +#define DEBUG_HTTPBASIC (1<<13) +#define DEBUG_FLUSH (1<<22) + +extern FILE *http_debug_stream; +extern int http_debug_mask; + +void http_debug( int ch, char *, ... ) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif /* __GNUC__ */ +; + +#endif /* HAVE_DEBUG_FUNC */ + +/* Storing an HTTP status result */ +typedef struct { + int major_version; + int minor_version; + int code; /* Status-Code value */ + int class; /* Class of Status-Code (1-5) */ + const char *reason_phrase; +} http_status; + +/* Parser for strings which follow the Status-Line grammar from + * RFC2616. + * Returns: + * 0 on success, *s will be filled in. + * -1 on parse error. + */ +int http_parse_statusline( const char *status_line, http_status *s ); + +#endif /* HTTP_UTILS_H */ diff --git a/neon/src/md5.c b/neon/src/md5.c new file mode 100644 index 000000000..00aab1582 --- /dev/null +++ b/neon/src/md5.c @@ -0,0 +1,447 @@ +/* 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. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> + +#if STDC_HEADERS || defined _LIBC +# include <stdlib.h> +# include <string.h> +#else +# ifndef HAVE_MEMCPY +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#include "md5.h" + +#ifdef _LIBC +# include <endian.h> +# if __BYTE_ORDER == __BIG_ENDIAN +# define WORDS_BIGENDIAN 1 +# endif +/* We need to keep the namespace clean so define the MD5 function + protected using leading __ and use weak aliases. */ +# define md5_init_ctx __md5_init_ctx +# define md5_process_block __md5_process_block +# define md5_process_bytes __md5_process_bytes +# define md5_finish_ctx __md5_finish_ctx +# define md5_read_ctx __md5_read_ctx +# define md5_stream __md5_stream +# define md5_buffer __md5_buffer +#endif + +#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; +} + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. 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. */ +void * +md5_buffer (buffer, len, resblock) + const char *buffer; + size_t len; + void *resblock; +{ + struct md5_ctx ctx; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + md5_process_bytes (buffer, len, &ctx); + + /* Put result in desired memory area. */ + return md5_finish_ctx (&ctx, resblock); +} + + +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 md5_uint32 *words = buffer; + size_t nwords = len / sizeof (md5_uint32); + const md5_uint32 *endp = words + nwords; + 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 \ + { \ + a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \ + ++words; \ + 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; +} + + +#ifdef _LIBC +/* Define weak aliases. */ +# undef md5_init_ctx +weak_alias (__md5_init_ctx, md5_init_ctx) +# undef md5_process_block +weak_alias (__md5_process_block, md5_process_block) +# undef md5_process_bytes +weak_alias (__md5_process_bytes, md5_process_bytes) +# undef md5_finish_ctx +weak_alias (__md5_finish_ctx, md5_finish_ctx) +# undef md5_read_ctx +weak_alias (__md5_read_ctx, md5_read_ctx) +# undef md5_stream +weak_alias (__md5_stream, md5_stream) +# undef md5_buffer +weak_alias (__md5_buffer, md5_buffer) +#endif diff --git a/neon/src/md5.h b/neon/src/md5.h new file mode 100644 index 000000000..ce4091dea --- /dev/null +++ b/neon/src/md5.h @@ -0,0 +1,157 @@ +/* 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 _MD5_H +#define _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 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 __md5_init_ctx __P ((struct md5_ctx *ctx)); +extern void md5_init_ctx __P ((struct 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 __md5_process_block __P ((const void *buffer, size_t len, + struct md5_ctx *ctx)); +extern void md5_process_block __P ((const void *buffer, size_t len, + struct 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 __md5_process_bytes __P ((const void *buffer, size_t len, + struct md5_ctx *ctx)); +extern void md5_process_bytes __P ((const void *buffer, size_t len, + struct 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 *__md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf)); +extern void *md5_finish_ctx __P ((struct 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 *__md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf)); +extern void *md5_read_ctx __P ((const struct 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 __md5_stream __P ((FILE *stream, void *resblock)); +extern int md5_stream __P ((FILE *stream, void *resblock)); + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. 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. */ +extern void *__md5_buffer __P ((const char *buffer, size_t len, + void *resblock)); +extern void *md5_buffer __P ((const char *buffer, size_t len, + void *resblock)); + +#endif /* md5.h */ diff --git a/neon/src/ne_207.c b/neon/src/ne_207.c new file mode 100644 index 000000000..ac6065b70 --- /dev/null +++ b/neon/src/ne_207.c @@ -0,0 +1,378 @@ +/* + WebDAV 207 multi-status response handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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; + /* current position */ + void *response, *propstat; + /* caching */ + ne_status status; + char *description, *href, *status_line; +}; + +static const struct ne_xml_elm elements[] = { + { "DAV:", "multistatus", NE_ELM_multistatus, 0 }, + { "DAV:", "response", NE_ELM_response, 0 }, + { "DAV:", "responsedescription", NE_ELM_responsedescription, + NE_XML_CDATA }, + { "DAV:", "href", NE_ELM_href, NE_XML_CDATA }, + { "DAV:", "propstat", NE_ELM_propstat, 0 }, + { "DAV:", "prop", NE_ELM_prop, 0 }, + { "DAV:", "status", NE_ELM_status, NE_XML_CDATA }, + { NULL } +}; + +/* 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; +} + +static int +start_element(void *userdata, const struct ne_xml_elm *elm, + const char **atts) +{ + ne_207_parser *p = userdata; + + switch (elm->id) { + case NE_ELM_response: + /* Create new response delayed until we get HREF */ + break; + case NE_ELM_propstat: + if (p->start_propstat) { + p->propstat = (*p->start_propstat)(p->userdata, p->response); + } + break; + } + return 0; +} + +static int +end_element(void *userdata, const struct ne_xml_elm *elm, const char *cdata) +{ + ne_207_parser *p = userdata; + + switch (elm->id) { + case NE_ELM_responsedescription: + if (cdata != NULL) { + NE_FREE(p->description); + p->description = ne_strdup(cdata); + } + break; + case NE_ELM_href: + /* Now we have the href, begin the response */ + if (p->start_response && cdata != NULL) { + p->response = (*p->start_response)(p->userdata, cdata); + } + break; + case NE_ELM_status: + if (cdata) { + NE_FREE(p->status_line); + p->status_line = ne_strdup(cdata); + if (ne_parse_statusline(p->status_line, &p->status)) { + char buf[500]; + NE_DEBUG(NE_DBG_HTTP, "Status line: %s\n", cdata); + snprintf(buf, 500, + _("Invalid HTTP status line in status element at line %d of response:\nStatus line was: %s"), + ne_xml_currentline(p->parser), p->status_line); + ne_xml_set_error(p->parser, buf); + NE_FREE(p->status_line); + return -1; + } else { + NE_DEBUG(NE_DBG_XML, "Decoded status line: %s\n", p->status_line); + } + } + break; + case NE_ELM_propstat: + if (p->end_propstat) { + (*p->end_propstat)(p->userdata, p->propstat, p->status_line, + p->status_line?&p->status:NULL, p->description); + } + p->propstat = NULL; + NE_FREE(p->description); + NE_FREE(p->status_line); + break; + case NE_ELM_response: + if (p->end_response) { + (*p->end_response)(p->userdata, p->response, p->status_line, + p->status_line?&p->status:NULL, p->description); + } + p->response = NULL; + NE_FREE(p->status_line); + NE_FREE(p->description); + break; + } + return 0; +} + +/* This should map directly from the DTD... with the addition of + * ignoring anything we don't understand, being liberal in what we + * accept. */ +static int check_context(ne_xml_elmid parent, ne_xml_elmid child) +{ + NE_DEBUG(NE_DBG_XML, "207cc: %d in %d\n", child, parent); + switch (parent) { + case NE_ELM_root: + switch (child) { + case NE_ELM_multistatus: + case NE_ELM_response: /* not sure why this is here... */ + return NE_XML_VALID; + default: + break; + } + break; + case NE_ELM_multistatus: + /* <!ELEMENT multistatus (response+, responsedescription?) > */ + switch (child) { + case NE_ELM_response: + case NE_ELM_responsedescription: + return NE_XML_VALID; + default: + break; + } + break; + case NE_ELM_response: + /* <!ELEMENT response (href, ((href*, status)|(propstat+)), + responsedescription?) > */ + switch (child) { + case NE_ELM_href: + case NE_ELM_status: + case NE_ELM_propstat: + case NE_ELM_responsedescription: + return NE_XML_VALID; + default: + break; + } + break; + case NE_ELM_propstat: + /* <!ELEMENT propstat (prop, status, responsedescription?) > */ + switch (child) { + case NE_ELM_prop: + case NE_ELM_status: + case NE_ELM_responsedescription: + return NE_XML_VALID; + default: + break; + } + break; + default: + break; + } + + return NE_XML_DECLINE; +} + +static int ignore_cc(ne_xml_elmid parent, ne_xml_elmid child) +{ + if (child == NE_ELM_unknown || parent == NE_ELM_unknown) { + NE_DEBUG(NE_DBG_XML, "207 catch-all caught %d in %d\n", child, parent); + return NE_XML_VALID; + } + + return NE_XML_DECLINE; +} + +void ne_207_ignore_unknown(ne_207_parser *p) +{ + static const struct ne_xml_elm any_elms[] = { + { "", "", NE_ELM_unknown, NE_XML_COLLECT }, + { NULL } + }; + + ne_xml_push_handler(p->parser, any_elms, + ignore_cc, NULL, NULL, NULL); + +} + +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; + + /* Add handler for the standard 207 elements */ + ne_xml_push_handler(parser, elements, check_context, + start_element, end_element, p); + + return p; +} + +void ne_207_destroy(ne_207_parser *p) +{ + free(p); +} + +int ne_accept_207(void *userdata, ne_request *req, 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 char *status_line, const ne_status *status, + const char *description) +{ + if (status && status->klass != 2 && status->code != 424) { + ctx->is_error = 1; + ne_buffer_concat(ctx->buf, ctx->href, ": ", status_line, "\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 char *status_line, + const ne_status *status, const char *description) +{ + struct context *ctx = userdata; + handle_error(ctx, status_line, status, description); +} + +static void +end_propstat(void *userdata, void *propstat, const char *status_line, + const ne_status *status, const char *description) +{ + struct context *ctx = userdata; + handle_error(ctx, status_line, status, description); +} + +/* Dispatch a DAV request and handle a 207 error response appropriately */ +int ne_simple_request(ne_session *sess, ne_request *req) +{ + int ret; + ne_content_type ctype = {0}; + 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); + ne_add_response_header_handler(req, "Content-Type", + ne_content_type_handler, &ctype); + + ne_207_ignore_unknown(p207); + + 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_FREE(ctype.value); + 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/neon/src/ne_207.h b/neon/src/ne_207.h new file mode 100644 index 000000000..4b748800e --- /dev/null +++ b/neon/src/ne_207.h @@ -0,0 +1,100 @@ +/* + WebDAV 207 multi-status response handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 http_req */ + +BEGIN_NEON_DECLS + +#define NE_ELM_207_first (NE_ELM_UNUSED) + +#define NE_ELM_multistatus (NE_ELM_207_first) +#define NE_ELM_response (NE_ELM_207_first + 1) +#define NE_ELM_responsedescription (NE_ELM_207_first + 2) +#define NE_ELM_href (NE_ELM_207_first + 3) +#define NE_ELM_propstat (NE_ELM_207_first + 4) +#define NE_ELM_prop (NE_ELM_207_first + 5) +#define NE_ELM_status (NE_ELM_207_first + 6) + +#define NE_ELM_207_UNUSED (NE_ELM_UNUSED + 100) + +struct ne_207_parser_s; +typedef struct ne_207_parser_s ne_207_parser; + +/* The name of a WebDAV property. */ +typedef struct { + const char *nspace, *name; +} ne_propname; + +/* The handler structure: you provide a set of callbacks. + * They are called in the order they are listed... start/end_prop + * multiple times before end_prop, start/end_propstat multiple times + * before an end_response, start/end_response multiple times. + */ + +/* TODO: do we need to pass userdata to ALL of these? We could get away with + * only passing the userdata to the start_'s and relying on the caller + * to send it through as the _start return value if they need it. */ + +typedef void *(*ne_207_start_response)(void *userdata, const char *href); +typedef void (*ne_207_end_response)( + void *userdata, void *response, const char *status_line, + const ne_status *status, const char *description); + +typedef void *(*ne_207_start_propstat)(void *userdata, void *response); +typedef void (*ne_207_end_propstat)( + void *userdata, void *propstat, const char *status_line, + const ne_status *status, const char *description); + +/* Create a 207 parser */ + +ne_207_parser *ne_207_create(ne_xml_parser *parser, void *userdata); + +/* 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); + +void ne_207_set_propstat_handlers( + ne_207_parser *p, ne_207_start_propstat start, ne_207_end_propstat end); + +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, ne_status *status); + +void *ne_207_get_current_propstat(ne_207_parser *p); +void *ne_207_get_current_response(ne_207_parser *p); + +/* Call this as the LAST thing before beginning parsing, to install a + * catch-all handler which means all unknown XML returned in the 207 + * response is ignored gracefully. */ +void ne_207_ignore_unknown(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/neon/src/ne_acl.c b/neon/src/ne_acl.c new file mode 100644 index 000000000..888cb752e --- /dev/null +++ b/neon/src/ne_acl.c @@ -0,0 +1,129 @@ +/* + 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" + +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", "text/xml"); + 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/neon/src/ne_acl.h b/neon/src/ne_acl.h new file mode 100644 index 000000000..bf1a1ece4 --- /dev/null +++ b/neon/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/neon/src/ne_alloc.c b/neon/src/ne_alloc.c new file mode 100644 index 000000000..f37662100 --- /dev/null +++ b/neon/src/ne_alloc.c @@ -0,0 +1,61 @@ +/* + Replacement memory allocation handling etc. + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: ne_alloc.c,v 1.2 2000/08/02 10:23:03 joe Exp +*/ + +#include <config.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "ne_alloc.h" + +void *ne_malloc(size_t len) +{ + void *ptr = malloc(len); + if (!ptr) { + /* uh-oh */ + abort(); + } + return ptr; +} + +void *ne_calloc(size_t len) +{ + return memset(ne_malloc(len), 0, len); +} + +char *ne_strdup(const char *s) +{ + return strcpy(ne_malloc(strlen(s) + 1), s); +} + +char *ne_strndup(const char *s, size_t n) +{ + char *new = ne_malloc(n + 1); + new[n] = '\0'; + memcpy(new, s, n); + return new; +} diff --git a/neon/src/ne_alloc.h b/neon/src/ne_alloc.h new file mode 100644 index 000000000..e120c6b53 --- /dev/null +++ b/neon/src/ne_alloc.h @@ -0,0 +1,34 @@ +/* + Replacement memory allocation handling etc. + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + +#include <sys/types.h> + +void *ne_malloc(size_t len); +void *ne_calloc(size_t len); + +char *ne_strdup(const char *s); + +char *ne_strndup(const char *s, size_t n); + +#endif /* NE_ALLOC_H */ diff --git a/neon/src/ne_auth.c b/neon/src/ne_auth.c new file mode 100644 index 000000000..e613726e9 --- /dev/null +++ b/neon/src/ne_auth.c @@ -0,0 +1,1095 @@ +/* + HTTP Authentication routines + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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: + * - Improve cnonce? Make it really random using /dev/random or whatever? + * - Test auth-int support + */ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <time.h> + +#ifdef HAVE_SNPRINTF_H +#include "snprintf.h" +#endif + +#include "base64.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" + +/* The MD5 digest of a zero-length entity-body */ +#define DIGEST_MD5_EMPTY "d41d8cd98f00b204e9800998ecf8427e" + +#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; + char *realm; + char *domain; + char *nonce; + 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 char *qop_values[] = { + NULL, + "auth", + "auth-int" +}; +static const char *algorithm_names[] = { + "MD5", + "MD5-sess", + NULL +}; + +/* The callback used to request the username and password in the given + * realm. The username and password must be placed in malloc()-allocate + * memory. + * Must return: + * 0 on success, + * -1 to cancel. + */ + +/* Authentication session state. */ +typedef struct { + ne_session *sess; + + /* for making this proxy/server generic. */ + const char *req_hdr, *resp_hdr, *resp_info_hdr, *fail_msg; + int status_code; + int fail_code; + + /* The scheme used for this authentication session */ + auth_scheme scheme; + /* The callback used to request new username+password */ + ne_request_auth reqcreds; + void *reqcreds_udata; + + /*** Session details ***/ + + /* The username and password we are using to authenticate with */ + char *username; + /* 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 *unq_realm; + char *unq_nonce; + char *unq_cnonce; + char *opaque; + /* A list of domain strings */ + unsigned int domain_count; + char **domain; + 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; + +} auth_session; + +struct auth_request { + auth_session *session; + + /*** 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; + + int tries; + /* Results of response-header callbacks */ + char *auth_hdr, *auth_info_hdr; +}; + +/* Private prototypes which used to be public. */ + +static auth_session *auth_create(ne_session *sess, + ne_request_auth callback, + void *userdata); + +/* Pass this the value of the "(Proxy,WWW)-Authenticate: " header field. + * Returns: + * 0 if we can now authenticate ourselves with the server. + * non-zero if we can't + */ +static int auth_challenge(auth_session *sess, const char *value); + +/* If you receive a "(Proxy-)Authentication-Info:" header, pass its value to + * this function. Returns zero if this successfully authenticates + * the response as coming from the server, and false if it hasn't. */ +static int verify_response(struct auth_request *req, + auth_session *sess, const char *value); + +/* Private prototypes */ +static char *get_cnonce(void); +static void clean_session(auth_session *sess); +static int digest_challenge(auth_session *, struct auth_challenge *); +static int basic_challenge(auth_session *, struct auth_challenge *); +static char *request_digest(auth_session *sess, struct auth_request *req); +static char *request_basic(auth_session *); + +/* Domain handling */ +static int is_in_domain(auth_session *sess, const char *uri); +static int parse_domain(auth_session *sess, const char *domain); + +/* Get the credentials, passing a temporary store for the password value */ +static int get_credentials(auth_session *sess, char **password); + +auth_session *auth_create(ne_session *sess, + ne_request_auth callback, + void *userdata) +{ + auth_session *ret = ne_calloc(sizeof(auth_session)); + + ret->reqcreds = callback; + ret->reqcreds_udata = userdata; + ret->sess = sess; + + return ret; +} + +#if 0 +void ne_auth_set_server(auth_session *sess, + const char *host, unsigned int port, const char *scheme) +{ + sess->host = host; + sess->port = port; + sess->req_scheme = scheme; +} +#endif + +static void clean_session(auth_session *sess) +{ + sess->can_handle = 0; + NE_FREE(sess->basic); + NE_FREE(sess->unq_realm); + NE_FREE(sess->unq_nonce); + NE_FREE(sess->unq_cnonce); + NE_FREE(sess->opaque); + NE_FREE(sess->username); + if (sess->domain_count > 0) { + split_string_free(sess->domain); + sess->domain_count = 0; + } +} + +/* Returns cnonce-value. We just use base64(time). + * TODO: Could improve this? */ +static char *get_cnonce(void) +{ + char *ret, *tmp; + tmp = ne_rfc1123_date(time(NULL)); + ret = ne_base64(tmp, strlen(tmp)); + free(tmp); + return ret; +} + +static int +get_credentials(auth_session *sess, char **password) +{ + return (*sess->reqcreds)(sess->reqcreds_udata, sess->unq_realm, + &sess->username, password); +} + +static int parse_domain(auth_session *sess, const char *domain) { + char *unq, **doms; + int count, ret; + + unq = shave_string(domain, '"'); + doms = split_string_c(unq, ' ', NULL, " \r\n\t", &count); + if (doms != NULL) { + if (count > 0) { + sess->domain = doms; + sess->domain_count = count; + ret = 0; + } else { + free(doms); + ret = -1; + } + } else { + ret = -1; + } + free(unq); + return ret; +} + +/* Returns: + * 0: if uri is in NOT in domain of session + * else: uri IS in domain of session (or no domain known) + */ +static int is_in_domain(auth_session *sess, const char *uri) +{ +#if 1 + return 1; +#else + /* TODO: Need proper URI comparison for this to work. */ + int ret, dom; + const char *abs_path; + if (sess->domain_count == 0) { + NE_DEBUG(NE_DBG_HTTPAUTH, "No domain, presuming okay.\n"); + return 1; + } + ret = 0; + abs_path = uri_abspath(uri); + for (dom = 0; dom < sess->domain_count; dom++) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Checking domain: %s\n", sess->domain[dom]); + if (uri_childof(sess->domain[dom], abs_path)) { + ret = 1; + break; + } + } + return ret; +#endif +} + +/* 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; + + /* 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->unq_realm = shave_string(parms->realm, '"'); + + if (get_credentials(sess, &password)) { + /* Failed to get credentials */ + NE_FREE(sess->unq_realm); + return -1; + } + + sess->scheme = auth_scheme_basic; + + CONCAT3(tmp, sess->username, ":", password?password:""); + sess->basic = ne_base64(tmp, strlen(tmp)); + free(tmp); + + NE_FREE(password); + + return 0; +} + +/* Add Basic authentication credentials to a request */ +static char *request_basic(auth_session *sess) +{ + char *buf; + CONCAT3(buf, "Basic ", sess->basic, "\r\n"); + return buf; +} + +/* 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; + + /* 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->unq_realm = shave_string(parms->realm, '"'); + + /* Not a stale response: really need user authentication */ + if (get_credentials(sess, &password)) { + /* Failed to get credentials */ + NE_FREE(sess->unq_realm); + return -1; + } + } + sess->alg = parms->alg; + sess->scheme = auth_scheme_digest; + sess->unq_nonce = shave_string(parms->nonce, '"'); + sess->unq_cnonce = get_cnonce(); + if (parms->domain) { + if (parse_domain(sess, parms->domain)) { + /* TODO: Handle the error? */ + } + } else { + sess->domain = NULL; + sess->domain_count = 0; + } + 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->unq_realm, strlen(sess->unq_realm), &tmp); + ne_md5_process_bytes(":", 1, &tmp); + if (password != NULL) + ne_md5_process_bytes(password, strlen(password), &tmp); + 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->unq_nonce, strlen(sess->unq_nonce), &a1); + ne_md5_process_bytes(":", 1, &a1); + ne_md5_process_bytes(sess->unq_cnonce, strlen(sess->unq_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_FREE(password); + } + + 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++; + 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 = qop_values[sess->qop]; + + /* 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->unq_nonce, strlen(sess->unq_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->unq_cnonce, qop_value); + ne_md5_process_bytes(nc_value, 8, &rdig); + ne_md5_process_bytes(":", 1, &rdig); + ne_md5_process_bytes(sess->unq_cnonce, strlen(sess->unq_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->unq_realm, "\", " + "nonce=\"", sess->unq_nonce, "\", " + "uri=\"", req->uri, "\", " + "response=\"", rdig_md5_ascii, "\", " + "algorithm=\"", algorithm_names[sess->alg], "\"", NULL); + + if (sess->opaque != NULL) { + /* We never unquote it, so it's still quoted here */ + 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->unq_cnonce, "\", " + "nc=", nc_value, ", " + "qop=\"", qop_values[sess->qop], "\"", 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); +} + +/* 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!). + */ +int verify_response(struct auth_request *req, auth_session *sess, const char *value) +{ + char **pairs; + 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 */ + *unquoted, *qop_value = NULL; + int n, 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; + } + + NE_DEBUG(NE_DBG_HTTPAUTH, "Auth-Info header: %s\n", value); + + pairs = pair_string(value, ',', '=', "\"'", " \r\n\t"); + + for (n = 0; pairs[n]!=NULL; n+=2) { + unquoted = shave_string(pairs[n+1], '"'); + if (strcasecmp(pairs[n], "qop") == 0) { + qop_value = ne_strdup(pairs[n+1]); + if (strcasecmp(pairs[n+1], "auth-int") == 0) { + qop = auth_qop_auth_int; + } else if (strcasecmp(pairs[n+1], "auth") == 0) { + qop = auth_qop_auth; + } else { + qop = auth_qop_none; + } + } else if (strcasecmp(pairs[n], "nextnonce") == 0) { + nextnonce = ne_strdup(unquoted); + } else if (strcasecmp(pairs[n], "rspauth") == 0) { + rspauth = ne_strdup(unquoted); + } else if (strcasecmp(pairs[n], "cnonce") == 0) { + cnonce = ne_strdup(unquoted); + } else if (strcasecmp(pairs[n], "nc") == 0) { + nc = ne_strdup(pairs[n]); + if (sscanf(pairs[n+1], "%x", &nonce_count) != 1) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Couldn't scan [%s] for nonce count.\n", + pairs[n+1]); + } else { + NE_DEBUG(NE_DBG_HTTPAUTH, "Got nonce_count: %d\n", nonce_count); + } + } + free(unquoted); + } + pair_string_free(pairs); + + /* 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->unq_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!"); + } + free(rspauth); + free(cnonce); + free(nc); + } + free(qop_value); + } 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->unq_nonce != NULL) + free(sess->unq_nonce); + sess->unq_nonce = nextnonce; + } + + return okay; +} + +/* A new challenge presented by the server */ +int auth_challenge(auth_session *sess, const char *value) +{ + char **pairs, *pnt, *unquoted, *key; + struct auth_challenge *chall = NULL, *challenges = NULL; + int n, success; + + 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. + */ + pairs = pair_string(value, ',', '=', "\"'", " \r\n\t"); + + for (n = 0; pairs[n]!=NULL; n+=2) { + /* Look for an auth-scheme in the key */ + pnt = strchr(pairs[n], ' '); + if (pnt != NULL) { + /* We have a new challenge */ + NE_DEBUG(NE_DBG_HTTPAUTH, "New challenge.\n"); + chall = ne_calloc(sizeof *chall); + + chall->next = challenges; + challenges = chall; + /* Initialize the challenge parameters */ + /* Which auth-scheme is it (case-insensitive matching) */ + if (strncasecmp(pairs[n], "basic ", 6) == 0) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Basic scheme.\n"); + chall->scheme = auth_scheme_basic; + } else if (strncasecmp(pairs[n], "digest ", 7) == 0) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Digest scheme.\n"); + chall->scheme = auth_scheme_digest; + } else { + NE_DEBUG(NE_DBG_HTTPAUTH, "Unknown scheme.\n"); + free(chall); + challenges = NULL; + break; + } + /* Now, the real key for this pair starts after the + * auth-scheme... skipping whitespace */ + while (strchr(" \r\n\t", *(++pnt)) != NULL) + /* nullop */; + key = pnt; + } 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; + } else { + key = pairs[n]; + } + NE_DEBUG(NE_DBG_HTTPAUTH, "Got pair: [%s] = [%s]\n", key, pairs[n+1]); + /* Most values are quoted, so unquote them here */ + unquoted = shave_string(pairs[n+1], '"'); + /* Now parse the attribute */ + NE_DEBUG(NE_DBG_HTTPAUTH, "Unquoted pair is: [%s]\n", unquoted); + if (strcasecmp(key, "realm") == 0) { + chall->realm = pairs[n+1]; + } else if (strcasecmp(key, "nonce") == 0) { + chall->nonce = pairs[n+1]; + } else if (strcasecmp(key, "opaque") == 0) { + chall->opaque = pairs[n+1]; + } else if (strcasecmp(key, "domain") == 0) { + chall->domain = pairs[n+1]; + } else if (strcasecmp(key, "stale") == 0) { + /* Truth value */ + chall->stale = + (strcasecmp(unquoted, "true") == 0); + } else if (strcasecmp(key, "algorithm") == 0) { + if (strcasecmp(unquoted, "md5") == 0) { + chall->alg = auth_alg_md5; + } else if (strcasecmp(unquoted, "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(unquoted, ',', 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); + } + free(unquoted); + } + + NE_DEBUG(NE_DBG_HTTPAUTH, "Finished parsing parameters.\n"); + + /* Did we find any challenges */ + if (challenges == NULL) { + pair_string_free(pairs); + 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; + free(challenges); + challenges = chall; + } + + /* Free up the parsed header values */ + pair_string_free(pairs); + + 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 %d bytes of response body.\n", length); + ne_md5_process_bytes(block, length, ctx); +} + +static void *ah_create(void *session, ne_request *request, 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->resp_hdr); + + areq->session = sess; + areq->method = method; + areq->uri = uri; + areq->request = request; + + ne_add_response_header_handler(request, sess->resp_hdr, + ne_duplicate_header, &areq->auth_hdr); + + + ne_add_response_header_handler(request, sess->resp_info_hdr, + ne_duplicate_header, + &areq->auth_info_hdr); + + return areq; +} + + +static void ah_pre_send(void *cookie, ne_buffer *request) +{ + struct auth_request *req = cookie; + auth_session *sess = req->session; + + + if (!sess->can_handle) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Not handling session.\n"); + } else if (!is_in_domain(sess, req->uri)) { + /* We have moved out of the authentication domain */ + NE_DEBUG(NE_DBG_HTTPAUTH, + "URI %s outside session domain, not handling.\n", req->uri); + req->will_handle = 0; + } 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->req_hdr, ": ", value, NULL); + free(value); + } + + /* increase counter so we don't retry this >1. */ + req->tries++; + + } + +} + +#define SAFELY(x) ((x) != NULL?(x):"null") + +static int ah_post_send(void *cookie, const ne_status *status) +{ + struct auth_request *areq = cookie; + auth_session *sess = areq->session; + int ret = NE_OK; + + NE_DEBUG(NE_DBG_HTTPAUTH, + "ah_post_send (#%d), code is %d (want %d), %s is %s\n", + areq->tries, status->code, sess->status_code, + SAFELY(sess->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->fail_msg); + ret = NE_ERROR; + } else if (status->code == sess->status_code && + areq->auth_hdr != NULL && areq->tries == 0) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Got challenge with code %d.\n", status->code); + if (!auth_challenge(sess, areq->auth_hdr)) { + ret = NE_RETRY; + } + } else if (areq->tries > 0 && sess->status_code == status->code) { + NE_DEBUG(NE_DBG_HTTPAUTH, "Authentication failed - bad password?\n"); + clean_session(sess); + ret = sess->fail_code; + } + + NE_FREE(areq->auth_info_hdr); + NE_FREE(areq->auth_hdr); + + return ret; +} + +static void ah_destroy(void *cookie) +{ + free(cookie); +} + +static const ne_request_hooks ah_server_hooks = { + HOOK_SERVER_ID, + ah_create, + ah_pre_send, + ah_post_send, + ah_destroy +}; + +static const ne_request_hooks ah_proxy_hooks = { + HOOK_PROXY_ID, + ah_create, + ah_pre_send, + ah_post_send, + ah_destroy +}; + +static void free_auth(void *cookie) +{ + auth_session *sess = cookie; + + clean_session(sess); + free(sess); +} + +void ne_set_server_auth(ne_session *sess, ne_request_auth callback, + void *userdata) +{ + auth_session *auth_sess = auth_create(sess, callback, userdata); + + /* Server auth details */ + auth_sess->status_code = 401; + auth_sess->fail_code = NE_AUTH; + auth_sess->resp_hdr = "WWW-Authenticate"; + auth_sess->resp_info_hdr = "Authentication-Info"; + auth_sess->req_hdr = "Authorization"; + auth_sess->fail_msg = _("Server was not authenticated correctly."); + + ne_add_hooks(sess, &ah_server_hooks, auth_sess, free_auth); +} + +void ne_set_proxy_auth(ne_session *sess, ne_request_auth callback, + void *userdata) +{ + auth_session *auth_sess = auth_create(sess, callback, userdata); + + /* Proxy auth details */ + auth_sess->status_code = 407; + auth_sess->fail_code = NE_PROXYAUTH; + auth_sess->resp_hdr = "Proxy-Authenticate"; + auth_sess->resp_info_hdr = "Proxy-Authentication-Info"; + auth_sess->req_hdr = "Proxy-Authorization"; + auth_sess->fail_msg = _("Proxy server was not authenticated correctly."); + + ne_add_hooks(sess, &ah_proxy_hooks, auth_sess, free_auth); +} + +void ne_forget_auth(ne_session *sess) +{ + clean_session(ne_session_hook_private(sess, HOOK_SERVER_ID)); + clean_session(ne_session_hook_private(sess, HOOK_PROXY_ID)); +} + diff --git a/neon/src/ne_auth.h b/neon/src/ne_auth.h new file mode 100644 index 000000000..12be1eff0 --- /dev/null +++ b/neon/src/ne_auth.h @@ -0,0 +1,53 @@ +/* + HTTP authentication routines + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 http_session */ + +BEGIN_NEON_DECLS + +/* The callback used to request the username and password in the given + * realm. The username and password must be placed in malloc()-allocated + * memory. + * Must return: + * 0 on success: *username and *password must be non-NULL, and will + * be free'd by the HTTP layer when necessary + * -1 to cancel (*username and *password are ignored.) + */ +typedef int (*ne_request_auth)( + void *userdata, const char *realm, + char **username, char **password); + +/* Set callbacks to handle server and proxy authentication. + * userdata is passed as the first argument to the callback. */ +void ne_set_server_auth(ne_session *sess, ne_request_auth callback, + void *userdata); +void ne_set_proxy_auth(ne_session *sess, ne_request_auth callback, + 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/neon/src/ne_basic.c b/neon/src/ne_basic.c new file mode 100644 index 000000000..22ff2ee1b --- /dev/null +++ b/neon/src/ne_basic.c @@ -0,0 +1,563 @@ +/* + Basic HTTP and WebDAV methods + Copyright (C) 1999-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 + +*/ + +#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); + 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; + size_t total, progress; + int fd; /* used in get_to_fd */ + ne_content_range *range; +}; + +int ne_read_file(ne_session *sess, const char *uri, + ne_block_reader reader, void *userdata) { + struct get_context ctx; + ne_request *req = ne_request_create(sess, "GET", uri); + int ret; + + /* 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, reader, userdata); + + ret = ne_request_dispatch(req); + + if (ret == NE_OK && ne_get_status(req)->klass != 2) + ret = NE_ERROR; + + ne_request_destroy(req); + + return ret; +} + +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) { + ctx->error = errno; + break; + } else { + length -= ret; + } + } + } +} + +static int accept_206(void *ud, ne_request *req, 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->range->total) { + NE_DEBUG(NE_DBG_HTTP, + "Expecting %ld bytes, got entity of length %ld\n", + (long int) ctx->range->total, (long int) len); + 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) { + 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; + int ret; + + if (range->end == -1) { + ctx.total = -1; + } + else { + ctx.total = (range->end - range->start) + 1; + } + + ctx.fd = fd; + ctx.error = 0; + ctx.range = range; + + 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); + + /* icky casts to long int, which should be at least as large as the + * off_t's */ + if (range->end == -1) { + ne_print_request_header(req, "Range", "bytes=%ld-", + (long int) range->start); + } + else { + ne_print_request_header(req, "Range", "bytes=%ld-%ld", + (long int) range->start, + (long int)range->end); + } + ne_add_request_header(req, "Accept-Ranges", "bytes"); + + ret = ne_request_dispatch(req); + + if (ret == NE_OK && ne_get_status(req)->klass != 2) { + ret = NE_ERROR; + } + else if (ne_get_status(req)->code != 206) { + ne_set_error(sess, _("Server does not allow partial GETs.")); + 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.progress = 0; + ctx.fd = fd; + ctx.error = 0; + + /* 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) { + char buf[BUFSIZ]; + snprintf(buf, BUFSIZ, + _("Could not write to file: %s"), strerror(ctx.error)); + ne_set_error(sess, buf); + ret = NE_ERROR; + } + + 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; + + /* 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) { + char buf[BUFSIZ]; + snprintf(buf, BUFSIZ, + _("Could not write to file: %s"), strerror(ctx.error)); + ne_set_error(sess, buf); + ret = NE_ERROR; + } + + if (ret == NE_OK && ne_get_status(req)->klass != 2) { + ret = NE_ERROR; + } + + ne_request_destroy(req); + + return ret; +} + +static void server_hdr_handler(void *userdata, const char *value) +{ + char **tokens = split_string(value, ' ', "\"'", NULL); + ne_server_capabilities *caps = userdata; + int n; + + for (n = 0; tokens[n] != NULL; n++) { + if (strncasecmp(tokens[n], "Apache/", 7) == 0 && + strlen(tokens[n]) > 11) { /* 12 == "Apache/1.3.0" */ + const char *ver = tokens[n] + 7; + int count; + char **vers; + vers = split_string_c(ver, '.', NULL, NULL, &count); + /* Apache/1.3.6 and before have broken Expect: 100 support */ + if (count > 1 && atoi(vers[0]) < 2 && + atoi(vers[1]) < 4 && atoi(vers[2]) < 7) { + caps->broken_expect100 = 1; + } + split_string_free(vers); + } + } + + split_string_free(tokens); +} + +void ne_content_type_handler(void *userdata, const char *value) +{ + ne_content_type *ct = userdata; + char *sep, *parms; + + ct->value = ne_strdup(value); + + sep = strchr(ct->value, '/'); + if (!sep) { + NE_FREE(ct->value); + return; + } + + *++sep = '\0'; + ct->type = ct->value; + ct->subtype = sep; + + parms = strchr(ct->value, ';'); + + if (parms) { + *parms = '\0'; + /* TODO: handle charset. */ + } +} + +static void dav_hdr_handler(void *userdata, const char *value) +{ + char **classes, **class; + ne_server_capabilities *caps = userdata; + + classes = split_string(value, ',', "\"'", " \r\t\n"); + for (class = classes; *class!=NULL; class++) { + + if (strcmp(*class, "1") == 0) { + caps->dav_class1 = 1; + } else if (strcmp(*class, "2") == 0) { + caps->dav_class2 = 1; + } else if (strcmp(*class, "<http://apache.org/dav/propset/fs/1>") == 0) { + caps->dav_executable = 1; + } + } + + split_string_free(classes); + +} + +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, "Server", server_hdr_handler, caps); + 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, + const char *src, const char *dest ) +{ + ne_request *req = ne_request_create( sess, is_move?"MOVE":"COPY", src ); + +#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, + const char *src, const char *dest) +{ + return copy_or_move(sess, 0, overwrite, src, dest); +} + +int ne_move(ne_session *sess, int overwrite, + const char *src, const char *dest) +{ + return copy_or_move(sess, 1, overwrite, 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 (uri_has_trailing_slash(uri)) { + real_uri = ne_strdup(uri); + } else { + CONCAT2(real_uri, uri, "/"); + } + + 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); + + free(real_uri); + + return ret; +} + +#endif /* NEON_NODAV */ diff --git a/neon/src/ne_basic.h b/neon/src/ne_basic.h new file mode 100644 index 000000000..2daf91960 --- /dev/null +++ b/neon/src/ne_basic.h @@ -0,0 +1,136 @@ +/* + HTTP/1.1 methods + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 + +/* PUT resource at uri, reading request body from f */ +int ne_put(ne_session *sess, const char *uri, int fd); + +/* GET resource at uri, writing response body into f */ +int ne_get(ne_session *sess, const char *uri, int fd); + +#ifndef NEON_NODAV + +/* Basic WebDAV methods: + * ne_copy: copy resoure from src to dest + * ne_move: move resource from src to dest + * -> if overwrite is non-zero, the destination resource + * will be overwritten if it exists. + * ne_delete: delete resource at uri + * ne_mkcol: create a collection at uri (uri MUST have a trailing slash). + */ +int ne_copy(ne_session *sess, int overwrite, const char *src, const char *dest); +int ne_move(ne_session *sess, int overwrite, const char *src, const char *dest); +int ne_delete(ne_session *sess, const char *uri); +int ne_mkcol(ne_session *sess, const char *uri); + +#define NE_DEPTH_ZERO (0) +#define NE_DEPTH_ONE (1) +#define NE_DEPTH_INFINITE (2) + +/* Adds a Depth: header to a request */ +void ne_add_depth_header(ne_request *req, int depth); + +#endif /* NEON_NODAV */ + +/* PUT resource at uri 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 *uri, int fd, time_t modtime); + +/* GET resource at uri, passing response body blocks to 'reader' */ +int ne_read_file(ne_session *sess, const char *uri, + ne_block_reader reader, void *userdata); + +/* Retrieve modification time of resource at uri, place in *modtime. + * (uses HEAD) */ +int ne_getmodtime(ne_session *sess, const char *uri, 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 broken_expect100:1; /* True if the server is known to + * have broken Expect: + * 100-continue support; Apache + * 1.3.6 and earlier. */ + + 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 uri="*" to determine proxy server capabilities if using + * a proxy server. */ +int ne_options(ne_session *sess, const char *uri, + 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, uri, &range, myfile); */ +int ne_get_range(ne_session *sess, const char *uri, + ne_content_range *range, int fd); + +/* Post using buffer as request-body: stream response into f */ +int ne_post(ne_session *sess, const char *uri, int fd, const char *buffer); + +END_NEON_DECLS + +#endif /* NE_BASIC_H */ diff --git a/neon/src/ne_compress.c b/neon/src/ne_compress.c new file mode 100644 index 000000000..6478fb9e5 --- /dev/null +++ b/neon/src/ne_compress.c @@ -0,0 +1,332 @@ +/* + Handling of compressed HTTP responses + 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 + +*/ + +#include "config.h" + +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <assert.h> + +#include "ne_request.h" +#include "ne_compress.h" +#include "ne_utils.h" + +#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 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; + 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; + unsigned char xflags; + unsigned char os; + } hdr; + } in; + size_t incount; /* bytes in in.buf */ + + /* current state. */ + enum state { + BEFORE_DATA, /* not received any response blocks yet. */ + PASSTHROUGH, /* response not compressed: passing through. */ + IN_HEADER, /* received a few bytes of response data, but not + * got past the gzip header yet. */ + POST_HEADER, /* waiting for the end of the NUL-terminated bits. */ + INFLATING, /* inflating response bytes. */ + FINISHED, /* inflate has returned Z_STREAM_END: not expecting + * any more response data. */ + 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 = 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 = POST_HEADER; + return HDR_EXTENDED; + } else if (h->flags != 0) { + ctx->state = ERROR; + ne_set_error(ctx->session, "Compressed stream not supported"); + return HDR_ERROR; + } + + NE_DEBUG(NE_DBG_HTTP, "compress: Good stream.\n"); + + ctx->state = INFLATING; + return HDR_DONE; +} + +static int find_token(const char *value, const char *wanted) +{ + char *buf = ne_strdup(value), *pnt = buf, *tok; + int ret = 0; + + while ((tok = ne_token(&pnt, ',', NULL)) != NULL) + { + if (strcasecmp(tok, wanted) == 0) { + ret = 1; + break; + } + } + + free(buf); + + return ret; +} + +/* inflate it baby. */ +static void do_inflate(ne_decompress *ctx, const char *buf, size_t len) +{ + int ret; + + ctx->zstr.avail_in = len; + ctx->zstr.next_in = (char *)buf; + ctx->zstr.total_in = 0; + + do { + ctx->zstr.avail_out = BUFSIZ; + ctx->zstr.next_out = 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 + + /* pass on the inflated data */ + ctx->reader(ctx->userdata, ctx->outbuf, ctx->zstr.total_out); + + } while (ret == Z_OK && ctx->zstr.avail_out == 0); + + if (ret == Z_STREAM_END) { + NE_DEBUG(NE_DBG_HTTP, "compress: end of stream.\n"); + ctx->state = FINISHED; + } else if (ret != Z_OK) { + ctx->state = 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); + } +} + +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 PASSTHROUGH: + /* move along there. */ + ctx->reader(ctx->userdata, buf, len); + return; + + case ERROR: + /* beyond hope. */ + return; + + case FINISHED: + NE_DEBUG(NE_DBG_HTTP, + "compress: %d bytes in after end of stream.\n", len); + return; + + case BEFORE_DATA: + /* work out whether this is a compressed response or not. */ + if (ctx->enchdr != NULL && find_token(ctx->enchdr, "gzip")) { + 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) { + /* bugger. can't get to the session. */ + ne_set_error(ctx->session, ctx->zstr.msg); + ctx->state = ERROR; + return; + } + + } 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 = PASSTHROUGH; + ctx->reader(ctx->userdata, buf, len); + return; + } + + ctx->state = IN_HEADER; + /* FALLTHROUGH */ + + case 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 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 %d header bytes.\n", + zbuf - buf); + /* found end of string. */ + len -= (1 + zbuf - buf); + buf = zbuf + 1; + ctx->state = INFLATING; + if (len == 0) { + /* end of string was at end of buffer. */ + return; + } + + /* FALLTHROUGH */ + + case INFLATING: + do_inflate(ctx, buf, len); + break; + } + +} + +int ne_decompress_destroy(ne_decompress *ctx) +{ + if (ctx->state != PASSTHROUGH && ctx->state != BEFORE_DATA) { + /* stream must have been initialized */ + inflateEnd(&ctx->zstr); + } + if (ctx->enchdr) + free(ctx->enchdr); + free(ctx); + return (ctx->state == ERROR); +} + +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 = BEFORE_DATA; + ctx->reader = rdr; + ctx->userdata = userdata; + ctx->session = ne_get_session(req); + + return ctx; +} diff --git a/neon/src/ne_compress.h b/neon/src/ne_compress.h new file mode 100644 index 000000000..9fa6b73bb --- /dev/null +++ b/neon/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/neon/src/ne_cookies.c b/neon/src/ne_cookies.c new file mode 100644 index 000000000..4d99ef60c --- /dev/null +++ b/neon/src/ne_cookies.c @@ -0,0 +1,142 @@ +/* + Basic cookie support for neon + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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" + +static void set_cookie_hdl(void *userdata, const char *value) +{ + char **pairs = pair_string(value, ';', '=', HTTP_QUOTES, HTTP_WHITESPACE); + 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(ne_cookie)); + memset(cook, 0, sizeof(ne_cookie)); + cook->name = pairs[0]; + cook->next = cache->cookies; + cache->cookies = cook; + } else { + /* Free the old value */ + free(cook->value); + } + + cook->value = 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 = pairs[n+1]; + pairs[n+1] = NULL; + } 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 = pairs[n+1]; + pairs[n+1] = NULL; + } + } + + NE_DEBUG(NE_DBG_HTTP, "End of parms.\n"); + + pair_string_free(pairs); +} + +static void *create(void *session, ne_request *req, const char *method, const char *uri) +{ + ne_cookie_cache *cache = session; + ne_add_response_header_handler(req, "Set-Cookie", set_cookie_hdl, cache); + return cache; +} + +/* Just before sending the request: let them add headers if they want */ +static void pre_send(void *private, ne_buffer *request) +{ + ne_cookie_cache *cache = private; + 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, EOL); + +} + +static void destroy(void *private) +{ + /* FIXME */ + return; +} + +ne_request_hooks ne_cookie_hooks = { + "http://www.webdav.org/neon/hooks/cookies", + create, + pre_send, + NULL, + destroy +}; diff --git a/neon/src/ne_cookies.h b/neon/src/ne_cookies.h new file mode 100644 index 000000000..0025ac3df --- /dev/null +++ b/neon/src/ne_cookies.h @@ -0,0 +1,50 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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; + +extern ne_request_hooks ne_cookie_hooks; + +END_NEON_DECLS + +#endif /* NE_COOKIES_H */ diff --git a/neon/src/ne_dates.c b/neon/src/ne_dates.c new file mode 100644 index 000000000..34f353970 --- /dev/null +++ b/neon/src/ne_dates.c @@ -0,0 +1,191 @@ +/* + Date manipulation routines + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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 + +#ifdef HAVE_SNPRINTF_H +#include "snprintf.h" +#endif + +#include "ne_alloc.h" +#include "ne_dates.h" + +/* Generic date manipulation routines. */ + +/* 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" +}; + +/* 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 */ + 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 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); +} + +/* 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); + /* 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); +} + + +/* (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); +} + +/* 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/neon/src/ne_dates.h b/neon/src/ne_dates.h new file mode 100644 index 000000000..70163003f --- /dev/null +++ b/neon/src/ne_dates.h @@ -0,0 +1,49 @@ +/* + Date manipulation routines + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 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/neon/src/ne_defs.h b/neon/src/ne_defs.h new file mode 100644 index 000000000..f029edf28 --- /dev/null +++ b/neon/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/neon/src/ne_i18n.c b/neon/src/ne_i18n.c new file mode 100644 index 000000000..a0df0ac07 --- /dev/null +++ b/neon/src/ne_i18n.c @@ -0,0 +1,32 @@ +/* + Internationalization of neon + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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/neon/src/ne_i18n.h b/neon/src/ne_i18n.h new file mode 100644 index 000000000..c1770e916 --- /dev/null +++ b/neon/src/ne_i18n.h @@ -0,0 +1,37 @@ +/* + Internationalization of neon + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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/neon/src/ne_locks.c b/neon/src/ne_locks.c new file mode 100644 index 000000000..b6f4686e5 --- /dev/null +++ b/neon/src/ne_locks.c @@ -0,0 +1,618 @@ +/* + WebDAV Class 2 locking operations + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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" + +/* The list of locks to submit in an If header + * for the current requests */ +struct submit_locks { + const struct ne_lock *lock; + const char *uri; + struct submit_locks *next; +}; + +struct ne_lock_session_s { + struct ne_lock *locks; +}; + +/* Per-request lock structure */ +struct request_locks { + struct submit_locks *locks; /* for the If header */ + ne_lock_session *session; +}; + +/* Context for PROPFIND/lockdiscovery callbacks */ +struct discover_ctx { + ne_lock_result results; + void *userdata; +}; + +/* Hook callbacks */ +static void *create(void *session, ne_request *req, + const char *method, const char *uri); +static void pre_send(void *private, ne_buffer *req); +static void destroy(void *private); + +/* The hooks are needed for construction of the If header */ +static ne_request_hooks lock_hooks = { + HOOK_ID, /* unique id for the locking hooks */ + create, + pre_send, + NULL, /* post_send not needed */ + destroy +}; + +/* Element ID's start at HIP_ELM_UNUSED and work upwards */ + +#define NE_ELM_LOCK_FIRST (NE_ELM_207_UNUSED) + +#define NE_ELM_lockdiscovery (NE_ELM_LOCK_FIRST) +#define NE_ELM_activelock (NE_ELM_LOCK_FIRST + 1) +#define NE_ELM_lockscope (NE_ELM_LOCK_FIRST + 2) +#define NE_ELM_locktype (NE_ELM_LOCK_FIRST + 3) +#define NE_ELM_depth (NE_ELM_LOCK_FIRST + 4) +#define NE_ELM_owner (NE_ELM_LOCK_FIRST + 5) +#define NE_ELM_timeout (NE_ELM_LOCK_FIRST + 6) +#define NE_ELM_locktoken (NE_ELM_LOCK_FIRST + 7) +#define NE_ELM_lockinfo (NE_ELM_LOCK_FIRST + 8) +#define NE_ELM_write (NE_ELM_LOCK_FIRST + 9) +#define NE_ELM_exclusive (NE_ELM_LOCK_FIRST + 10) +#define NE_ELM_shared (NE_ELM_LOCK_FIRST + 11) + +static const struct ne_xml_elm lock_elms[] = { +#define A(x) { "DAV:", #x, NE_ELM_ ## x, NE_XML_COLLECT } /* ANY */ +#define D(x) { "DAV:", #x, NE_ELM_ ## x, 0 } /* normal */ +#define C(x) { "DAV:", #x, NE_ELM_ ## x, NE_XML_CDATA } /* (#PCDATA) */ +#define E(x) { "DAV:", #x, NE_ELM_ ## x, 0 /* LEAF */ } /* EMPTY */ + D(lockdiscovery), D(activelock), + D(prop), + D(lockscope), D(locktype), C(depth), A(owner), C(timeout), D(locktoken), + /* no lockentry */ + D(lockinfo), D(lockscope), D(locktype), + E(write), E(exclusive), E(shared), + C(href), +#undef A +#undef D +#undef C +#undef E + { NULL, 0, 0 } +}; + +static const ne_propname lock_props[] = { + { "DAV:", "lockdiscovery" }, + { NULL } +}; + +static void *create(void *session, ne_request *req, + const char *method, const char *uri) +{ + struct request_locks *rl = ne_calloc(sizeof *rl); + rl->session = session; + return rl; +} + +static void pre_send(void *private, ne_buffer *req) +{ + struct request_locks *rl = private; + + if (rl->locks != NULL) { + struct submit_locks *item; + + /* Add in the If header */ + ne_buffer_zappend(req, "If:"); + for (item = rl->locks; item != NULL; item = item->next) { + ne_buffer_concat(req, " <", item->lock->uri, "> (<", + item->lock->token, ">)", NULL); + } + ne_buffer_zappend(req, EOL); + } +} + +static void destroy(void *priv) +{ + struct request_locks *rl = priv; + struct submit_locks *lock, *next; + + for (lock = rl->locks; lock != NULL; lock = next) { + next = lock->next; + free(lock); + } + + free(rl); +} + +static void free_locks(void *session) +{ + ne_lock_session *sess = session; + struct ne_lock *lk = sess->locks; + + while (lk) { + struct ne_lock *nextlk = lk->next; + ne_lock_free(lk); + lk = nextlk; + } + + free(sess); +} + +ne_lock_session *ne_lock_register(ne_session *sess) +{ + ne_lock_session *locksess = ne_calloc(sizeof *locksess); + + /* Register the hooks */ + ne_add_hooks(sess, &lock_hooks, locksess, free_locks); + + return locksess; +} + +/* Submit the given lock for the given URI */ +static void submit_lock(struct request_locks *rl, struct ne_lock *lock, + const char *uri) +{ + struct submit_locks *slock; + + /* Check for dups */ + for (slock = rl->locks; slock != NULL; slock = slock->next) { + if (strcasecmp(slock->lock->token, lock->token) == 0) + return; + } + + slock = ne_calloc(sizeof *slock); + slock->lock = lock; + slock->uri = uri; + slock->next = rl->locks; + rl->locks = slock; +} + +struct ne_lock *ne_lock_find(ne_lock_session *sess, const char *uri) +{ + struct ne_lock *cur; + for (cur = sess->locks; cur != NULL; cur = cur->next) { + if (uri_compare(uri, cur->uri) == 0) + return cur; + } + return NULL; +} + +void ne_lock_using_parent(ne_request *req, const char *uri) +{ + struct request_locks *rl = ne_request_hook_private(req, HOOK_ID); + char *parent; + + if (rl == NULL) + return; + + parent = uri_parent(uri); + + if (parent != NULL) { + struct ne_lock *lock; + /* Find any locks on the parent resource. + * FIXME: should check for depth-infinity locks too. */ + lock = ne_lock_find(rl->session, parent); + if (lock) { + NE_DEBUG(NE_DBG_LOCKS, "Locked parent, %s on %s\n", lock->token, + lock->uri); + submit_lock(rl, lock, uri); + } + free(parent); + } +} + +int ne_lock_iterate(ne_lock_session *sess, + ne_lock_walkfunc func, void *userdata) +{ + struct ne_lock *lock; + int count = 0; + + for (lock = sess->locks; lock != NULL; lock = lock->next) { + if (func != NULL) { + (*func)(lock, userdata); + } + count++; + } + + return count; +} + +void ne_lock_using_resource(ne_request *req, const char *uri, int depth) +{ + /* Grab the private cookie for this request */ + struct request_locks *rl = ne_request_hook_private(req, HOOK_ID); + struct ne_lock *lock; /* all the known locks */ + int match; + + if (rl == NULL) + return; + + /* Iterate over the list of session locks to see if any of + * them apply to this resource */ + for (lock = rl->session->locks; lock != NULL; lock = lock->next) { + + match = 0; + + if (depth == NE_DEPTH_INFINITE && uri_childof(uri, lock->uri)) { + /* 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", lock->token); + match = 1; + } + else if (uri_compare(uri, lock->uri) == 0) { + /* Case 2: this request is directly on a locked resource */ + NE_DEBUG(NE_DBG_LOCKS, "Has direct lock: %s\n", lock->token); + match = 1; + } + else if (lock->depth == NE_DEPTH_INFINITE && + uri_childof(lock->uri, 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", lock->token); + match = 1; + } + + if (match) { + submit_lock(rl, lock, uri); + } + } + +} + +void ne_lock_add(ne_lock_session *sess, struct ne_lock *lock) +{ + if (sess->locks != NULL) { + sess->locks->prev = lock; + } + lock->prev = NULL; + lock->next = sess->locks; + sess->locks = lock; +} + +void ne_lock_remove(ne_lock_session *sess, struct ne_lock *lock) +{ + if (lock->prev != NULL) { + lock->prev->next = lock->next; + } else { + sess->locks = lock->next; + } + if (lock->next != NULL) { + lock->next->prev = lock->prev; + } +} + +struct ne_lock *ne_lock_copy(const struct ne_lock *lock) +{ + struct ne_lock *ret = ne_calloc(sizeof *ret); + + /* TODO: check whether uri or token are NULL? Or, maybe, might as + * well crash now if they are, since something else will later + * on. */ + ret->uri = ne_strdup(lock->uri); + ret->depth = lock->depth; + ret->type = lock->type; + ret->scope = lock->scope; + ret->token = ne_strdup(lock->token); + if (lock->owner) ret->owner = ne_strdup(lock->owner); + ret->timeout = lock->timeout; + + return ret; +} + +void ne_lock_free(struct ne_lock *lock) +{ + NE_FREE(lock->uri); + NE_FREE(lock->owner); + NE_FREE(lock->token); + free(lock); +} + +int ne_unlock(ne_session *sess, struct ne_lock *lock) +{ + ne_request *req = ne_request_create(sess, "UNLOCK", lock->uri); + int ret; + + ne_print_request_header(req, "Lock-Token", "<%s>", lock->token); + + /* TODO: need this or not? + * it definitely goes away if lock-null resources go away */ + ne_lock_using_parent(req, lock->uri); + + 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 check_context(ne_xml_elmid parent, ne_xml_elmid child) { + NE_DEBUG(NE_DBG_XML, "ne_locks: check_context %d in %d\n", child, parent); + switch (parent) { + case NE_ELM_root: + /* TODO: for LOCK requests only... + * shouldn't allow this for PROPFIND really */ + if (child == NE_ELM_prop) + return NE_XML_VALID; + break; + case NE_ELM_prop: + if (child == NE_ELM_lockdiscovery) + return NE_XML_VALID; + break; + case NE_ELM_lockdiscovery: + if (child == NE_ELM_activelock) + return NE_XML_VALID; + break; + case NE_ELM_activelock: + switch (child) { + case NE_ELM_lockscope: + case NE_ELM_locktype: + case NE_ELM_depth: + case NE_ELM_owner: + case NE_ELM_timeout: + case NE_ELM_locktoken: + return NE_XML_VALID; + default: + break; + } + break; + case NE_ELM_lockscope: + switch (child) { + case NE_ELM_exclusive: + case NE_ELM_shared: + return NE_XML_VALID; + default: + break; + } + case NE_ELM_locktype: + if (child == NE_ELM_write) + return NE_XML_VALID; + break; + /* ... depth is PCDATA, owner is COLLECT, timeout is PCDATA */ + case NE_ELM_locktoken: + if (child == NE_ELM_href) + return NE_XML_VALID; + break; + } + return NE_XML_DECLINE; +} + +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, 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); + + if (lock == NULL) + return; + + lock->uri = ne_strdup(href); + + ctx->results(ctx->userdata, lock, + href, ne_propset_status(set, &lock_props[0])); + + + ne_lock_free(lock); + + NE_DEBUG(NE_DBG_LOCKS, "End of response for %s\n", href); +} + +static int +end_element_common(struct ne_lock *l, const struct ne_xml_elm *elm, + const char *cdata) +{ + switch (elm->id){ + case NE_ELM_write: + l->type = ne_locktype_write; + break; + case NE_ELM_exclusive: + l->scope = ne_lockscope_exclusive; + break; + case NE_ELM_shared: + l->scope = ne_lockscope_shared; + break; + case NE_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 NE_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 NE_ELM_owner: + l->owner = strdup(cdata); + break; + case NE_ELM_href: + l->token = strdup(cdata); + break; + } + return 0; +} + +/* End-element handler for lock discovery PROPFIND response */ +static int +end_element_ldisc(void *userdata, const struct ne_xml_elm *elm, + const char *cdata) +{ + struct ne_lock *lock = ne_propfind_current_private(userdata); + + return end_element_common(lock, elm, cdata); +} + +/* End-element handler for LOCK response */ +static int +end_element_lock(void *userdata, const struct ne_xml_elm *elm, + const char *cdata) +{ + struct ne_lock *lock = userdata; + return end_element_common(lock, elm, cdata); +} + +static void *create_private(void *userdata, const char *uri) +{ + return ne_calloc(sizeof(struct ne_lock)); +} + +/* 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; + + handler = ne_propfind_create(sess, uri, NE_DEPTH_ZERO); + + ne_propfind_set_private(handler, create_private, NULL); + + ne_xml_push_handler(ne_propfind_get_parser(handler), lock_elms, + check_context, NULL, end_element_ldisc, handler); + + ret = ne_propfind_named(handler, lock_props, discover_results, &ctx); + + ne_propfind_destroy(handler); + + return ret; +} + +int ne_lock(ne_session *sess, struct ne_lock *lock) +{ + ne_request *req = ne_request_create(sess, "LOCK", lock->uri); + ne_buffer *body = ne_buffer_create(); + ne_xml_parser *parser = ne_xml_create(); + int ret, parse_failed; + + ne_xml_push_handler(parser, lock_elms, check_context, + NULL, end_element_lock, lock); + + /* 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", "text/xml"); + ne_add_depth_header(req, lock->depth); + + /* 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); + /* This one is clearer from 2518 sec 8.10.4. */ + ne_lock_using_resource(req, lock->uri, lock->depth); + + ret = ne_request_dispatch(req); + + ne_buffer_destroy(body); + 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); + + /* TODO: free the list */ + return ret; +} diff --git a/neon/src/ne_locks.h b/neon/src/ne_locks.h new file mode 100644 index 000000000..12199cd3e --- /dev/null +++ b/neon/src/ne_locks.h @@ -0,0 +1,126 @@ +/* + WebDAV Class 2 locking operations + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 + http_req */ + +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. Lock objects are kept on doubly-linked lists. */ +struct ne_lock { + char *uri; /* the URI which this lock covers. */ + 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_*) */ + struct ne_lock *next, *prev; +}; +/* 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 + +typedef struct ne_lock_session_s ne_lock_session; + +/* TODO: + * "session" is a bad word, and it's already used for ne_session, + * maybe think up a better name. lock_store is quite good. + */ + +/* Register the locking hooks with an ne_session. Owned locks + * persist for the duration of this session. The lock session lasts + * exactly as long as the corresponding ne_session. Once you call + * ne_session_destroy(sess), any use of the lock session has + * undefined results. */ +ne_lock_session *ne_lock_register(ne_session *sess); + +/* Add a lock to the given session. The lock will subsequently be + * submitted as required in an If: header with any requests created + * using the ne_session which the lock session is tied to. Requests + * indicate to the locking layer which locks they might need using + * ne_lock_using_*, as described below. */ +void ne_lock_add(ne_lock_session *sess, struct ne_lock *lock); + +/* Remove lock, which must have been previously added to the + * session using 'ne_lock_add' above. */ +void ne_lock_remove(ne_lock_session *sess, struct ne_lock *lock); + +typedef void (*ne_lock_walkfunc)(struct ne_lock *lock, void *userdata); + +/* For each lock added to the session, call func, passing the lock + * and the given userdata. Returns the number of locks. func may be + * pass as NULL, in which case, can be used to simply return number + * of locks in the session. */ +int ne_lock_iterate(ne_lock_session *sess, + ne_lock_walkfunc func, void *userdata); + +/* Issue a LOCK request for the given lock. */ +int ne_lock(ne_session *sess, struct ne_lock *lock); +/* Issue an UNLOCK request for the given lock */ +int ne_unlock(ne_session *sess, struct ne_lock *lock); + +/* Find a lock in the session with given URI */ +struct ne_lock *ne_lock_find(ne_lock_session *sess, const char *uri); + +/* Deep-copy a lock structure. */ +struct ne_lock *ne_lock_copy(const struct ne_lock *lock); + +/* Free a lock structure */ +void ne_lock_free(struct ne_lock *lock); + +/* Callback for lock discovery. If 'lock' is NULL, + * something went wrong retrieving lockdiscover for the resource, + * look at 'status' for the details. */ +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 URI. 'result' is called + * with the results (possibly >1 times). */ +int ne_lock_discover(ne_session *sess, const char *uri, + ne_lock_result result, void *userdata); + +/*** For use by method functions */ + +/* Indicate that this request is of depth n on given uri */ +void ne_lock_using_resource(ne_request *req, const char *uri, int depth); +/* Indicate that this request will modify parent collection of given URI */ +void ne_lock_using_parent(ne_request *req, const char *uri); + +END_NEON_DECLS + +#endif /* NE_LOCKS_H */ diff --git a/neon/src/ne_md5.c b/neon/src/ne_md5.c new file mode 100644 index 000000000..208d8312b --- /dev/null +++ b/neon/src/ne_md5.c @@ -0,0 +1,460 @@ +/* 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. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#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" + +#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_buffer ne_md5_buffer +#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; +} + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. 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. */ +void * +md5_buffer (buffer, len, resblock) + const char *buffer; + size_t len; + void *resblock; +{ + struct md5_ctx ctx; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + md5_process_bytes (buffer, len, &ctx); + + /* Put result in desired memory area. */ + return md5_finish_ctx (&ctx, resblock); +} + + +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; +} + +#define ASC2HEX(x) (((x) <= '9') ? ((x) - '0') : (tolower((x)) + 10 - 'a')) +#define HEX2ASC(x) ((x) > 9 ? ((x) - 10 + 'a') : ((x) + '0')) + +/* 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] = HEX2ASC(md5_buf[count] >> 4); + buffer[count*2+1] = 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] = ((ASC2HEX(buffer[count*2])) << 4) | + ASC2HEX(buffer[count*2+1]); + } +} diff --git a/neon/src/ne_md5.h b/neon/src/ne_md5.h new file mode 100644 index 000000000..7033b8b0e --- /dev/null +++ b/neon/src/ne_md5.h @@ -0,0 +1,151 @@ +/* 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)); + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. 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. */ +extern void *ne_md5_buffer __P ((const char *buffer, size_t len, + 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/neon/src/ne_private.h b/neon/src/ne_private.h new file mode 100644 index 000000000..38f2e3ab4 --- /dev/null +++ b/neon/src/ne_private.h @@ -0,0 +1,204 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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" + +struct host_info { + /* hostname is not const since it changes on redirects. */ + char *hostname; + int port; + struct in_addr addr; + char *hostport; /* URI hostport segment */ +}; + +/* 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; +}; + +/* Store hook information */ +struct hook { + const ne_request_hooks *hooks; + void *private; + ne_free_hooks free; + struct hook *next; +}; + +/* Per-request store for hooks. + * This is a bit noddy really. */ +struct hook_request { + struct hook *hook; + void *cookie; + struct hook_request *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 */ + nsocket *socket; + + struct host_info server, proxy; + + /* Connection states: + * 0: Not connected at all. + * 1: We have a TCP connection to the next-hop server. + * 2: We have a negotiated an SSL connection over the proxy's + * TCP tunnel. + * + * Note, 1 is all we need if we don't have a proxy server, or + * if we do have a proxy server and we're not using SSL. + */ + unsigned int connected:2; + + /* Settings */ + unsigned int have_proxy:1; /* do we have a proxy server? */ + unsigned int no_persist:1; /* set to disable persistent connections */ + unsigned int use_secure:1; /* whether a secure connection is required */ + int expect100_works:2; /* known state of 100-continue support */ + unsigned int in_connect:1; /* doing a proxy CONNECT */ + unsigned int request_secure_upgrade:1; + unsigned int accept_secure_upgrade:1; + + ne_use_proxy proxy_decider; + void *proxy_decider_udata; + + nssl_context *ssl_context; + + sock_progress progress_cb; + void *progress_ud; + + ne_notify_status notify_cb; + void *notify_ud; + + struct hook *hooks; + + char *user_agent; /* full User-Agent string */ + + /* The last HTTP-Version returned by the server */ + int version_major; + int version_minor; + + /* Error string */ + char error[BUFSIZ]; +}; + +struct ne_request_s { + const char *method; + char *uri, *abs_path; + + /*** Request ***/ + + ne_buffer *headers; + ne_provide_body body_cb; + void *body_ud; + + int body_fd; + const char *body_buffer, *body_pnt; + size_t body_size, body_left; + + /* temporary store for request. */ + ne_buffer *reqbuf; + + /* temporary store for response lines. */ + ne_buffer *respbuf; + + /**** 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. */ + unsigned int is_chunked; /* Are we using chunked TE? */ + } resp; + + /* List of callbacks which are passed response headers */ + struct header_handler *header_catchers; + + /* We store response header handlers in a hash table. The hash is + * derived from the header name in lower case. */ + + /* 53 is magic, of course. For a standard http_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_proxy:1; + unsigned int use_expect100:1; + unsigned int can_persist:1; + unsigned int forced_close:1; + unsigned int upgrade_to_tls:1; + + ne_session *session; + ne_status status; + + /* stores request-private hook info */ + struct hook_request *hook_store; + +#ifdef USE_DAV_LOCKS + /* TODO: move this to hooks... list of locks to submit */ + struct dav_submit_locks *if_locks; +#endif + +}; + +/* Set a new URI for the given request. */ +void ne_set_request_uri(ne_request *req, const char *uri); + +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); + +#endif /* HTTP_PRIVATE_H */ diff --git a/neon/src/ne_props.c b/neon/src/ne_props.c new file mode 100644 index 000000000..ff27d99ab --- /dev/null +++ b/neon/src/ne_props.c @@ -0,0 +1,589 @@ +/* + WebDAV Properties manipulation + Copyright (C) 2000-2001, Joe Orton <joe@light.plus.com> + + 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" + +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. */ + ne_prop_result_set *current; + + ne_props_result callback; + void *userdata; +}; + +#define ELM_namedprop (NE_ELM_207_UNUSED) + +static const struct ne_xml_elm flat_elms[] = { + { "", "", NE_ELM_unknown, NE_XML_COLLECT }, + { NULL } +}; + +/* 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; +}; + +struct propstat { + struct prop *props; + int numprops; + ne_status status; +} propstat; + +/* Results set. */ +struct ne_prop_result_set_s { + struct propstat *pstats; + int numpstats; + void *private; + char *href; +}; + + +static int +startelm(void *userdata, const struct ne_xml_elm *elm, + const char **atts); +static int +endelm(void *userdata, const struct ne_xml_elm *elm, const char *cdata); +static int check_context(ne_xml_elmid parent, ne_xml_elmid child); + +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, flat_elms, + check_context, startelm, endelm, handler); + + /* Register the catch-all handler to ignore any cruft the + * server returns. */ + ne_207_ignore_unknown(handler->parser207); + + 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", "text/xml"); /* TODO: UTF-8? */ + + 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=\"", + 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 + "<propertyupdate xmlns=\"DAV:\">"); + + for (n = 0; items[n].name != NULL; n++) { + switch (items[n].type) { + case ne_propset: + /* <set><prop><prop-name>value</prop-name></prop></set> */ + ne_buffer_concat(body, "<set><prop>" + "<", items[n].name->name, " xmlns=\"", + items[n].name->nspace, "\">", items[n].value, + "</", items[n].name->name, "></prop></set>" EOL, + NULL); + break; + + case ne_propremove: + /* <remove><prop><prop-name/></prop></remove> */ + ne_buffer_concat(body, + "<remove><prop><", items[n].name->name, " xmlns=\"", + items[n].name->nspace, "\"/></prop></remove>" EOL, + NULL); + break; + } + } + + ne_buffer_zappend(body, "</propertyupdate>" EOL); + + ne_set_request_body_buffer(req, body->data, ne_buffer_size(body)); + ne_add_request_header(req, "Content-Type", "text/xml"); /* TODO: UTF-8? */ + + 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) +{ + return (strcasecmp(pn1->nspace, pn2->nspace) || + strcasecmp(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->private; +} + +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; + } +} + + +/* Pick up all properties as flat properties. */ +static int check_context(ne_xml_elmid parent, ne_xml_elmid child) +{ + if (child == NE_ELM_unknown && parent == NE_ELM_prop) + return NE_XML_VALID; + + return NE_XML_DECLINE; +} + +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 = 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, const struct ne_xml_elm *elm, + 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; + + /* Paranoia */ + if (pstat == NULL) { + NE_DEBUG(NE_DBG_XML, "gp_startelm: No propstat found, or not my element."); + return -1; + } + + /* Add a property to this propstat */ + n = pstat->numprops; + + pstat->props = 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(elm->name); + prop->pname.nspace = prop->nspace = ne_strdup(elm->nspace); + prop->value = NULL; + + NE_DEBUG(NE_DBG_XML, "Got property #%d: %s@@%s.\n", n, + 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(atts, "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; + } + + return 0; +} + +static int +endelm(void *userdata, const struct ne_xml_elm *elm, const char *cdata) +{ + ne_propfind_handler *hdl = userdata; + struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207); + int n; + + if (pstat == NULL) { + NE_DEBUG(NE_DBG_XML, "gp_endelm: No propstat found, or not my element."); + return -1; + } + + n = pstat->numprops - 1; + + NE_DEBUG(NE_DBG_XML, "Value of property #%d is %s\n", n, cdata); + + pstat->props[n].value = ne_strdup(cdata); + + return 0; +} + +static void end_propstat(void *userdata, void *pstat_v, + const char *status_line, const ne_status *status, + const char *description) +{ + struct propstat *pstat = pstat_v; + + /* 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++) { + free(pstat->props[n].value); + pstat->props[n].value = NULL; + } + } + + pstat->status = *status; +} + +/* 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++) { + free(p->props[m].nspace); + free(p->props[m].name); + NE_FREE(p->props[m].lang); + NE_FREE(p->props[m].value); + } + + free(set->pstats[n].props); + } + + free(set->pstats); + free(set); +} + +static void end_response(void *userdata, void *resource, + const char *status_line, + const ne_status *status, + const char *description) +{ + ne_propfind_handler *handler = userdata; + ne_prop_result_set *set = resource; + + /* TODO: Handle status here too? The status element is mandatory + * inside each propstat, so, not much point probably. */ + + /* Pass back the results for this resource. */ + if (handler->callback != NULL) { + handler->callback(handler->userdata, set->href, set); + } + + free(set->href); + + /* Clean up the propset tree we've just built. */ + free_propset(set); +} + +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); + + 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_207_destroy(handler->parser207); + ne_xml_destroy(handler->parser); + ne_buffer_destroy(handler->body); + ne_request_destroy(handler->request); + 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/neon/src/ne_props.h b/neon/src/ne_props.h new file mode 100644 index 000000000..0e8e2b2f6 --- /dev/null +++ b/neon/src/ne_props.h @@ -0,0 +1,224 @@ +/* + WebDAV Properties manipulation + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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) + * */ + + +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 *uri, int depth, + const ne_propname *props, + ne_props_result results, void *userdata); + +/* A PROPPATCH request may include any number of operations. Pass an + * array of these operations to ne_proppatch, with the last item + * having the name element being NULL. If the type is propset, the + * property of the given name is set to the new value. If the type is + * propremove, the property of the given name is deleted, and the + * value is ignored. */ +typedef struct { + const ne_propname *name; + enum { + ne_propset, + ne_propremove + } type; + const char *value; +} ne_proppatch_operation; + +int ne_proppatch(ne_session *sess, const char *uri, + const ne_proppatch_operation *items); + +/* Retrieve property names for the resources at 'href'. '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 *href, 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 *uri, 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); + +/* 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). */ +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 *uri); + +void ne_propfind_set_private(ne_propfind_handler *handler, + ne_props_create_complex creator, + void *userdata); + +/* Find all properties. + * + * Returns NE_*. */ +int ne_propfind_allprop(ne_propfind_handler *handler, + ne_props_result result, void *userdata); + +/* Find properties named in a call to ne_propfind_set_flat and/or + * ne_propfind_set_complex. + * + * Returns NE_*. */ +int ne_propfind_named(ne_propfind_handler *handler, + const ne_propname *prop, + 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/neon/src/ne_redirect.c b/neon/src/ne_redirect.c new file mode 100644 index 000000000..bb07a48eb --- /dev/null +++ b/neon/src/ne_redirect.c @@ -0,0 +1,194 @@ +/* + HTTP-redirect support + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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" + +/* TODO: should remove this. */ +#include "ne_private.h" + +#define REDIRECT_ID "http://www.webdav.org/neon/hooks/http-redirect" + +struct redirect { + /* per-request stuff. */ + char *location; + ne_request *req; + const char *method, *request_uri; + + /* per-session stuff. */ + ne_session *session; + ne_redirect_confirm confirm; + ne_redirect_notify notify; + void *userdata; +}; + +static void *create(void *session, ne_request *req, + const char *method, const char *uri); +static int post_send(void *private, const ne_status *status); + +static const ne_request_hooks redirect_hooks = { + REDIRECT_ID, + create, + NULL, + post_send, + NULL +}; + +static void * +create(void *session, ne_request *req, const char *method, const char *uri) +{ + struct redirect *red = session; + + /* Free up the location. */ + NE_FREE(red->location); + + /* for handling 3xx redirects */ + ne_add_response_header_handler(req, "Location", + ne_duplicate_header, &red->location); + + red->req = req; + red->method = method; + red->request_uri = uri; + + return red; +} + +/* 2616 says we can't auto-redirect if the method is not GET or HEAD. + * We extend this to PROPFIND and OPTIONS, which violates a 2616 MUST, + * but is following the spirit of the spec, I think. + * + * In fact Roy Fielding said as much in a new-httpd posting + * <20010224232203.G799@waka.ebuilt.net>: the spec should allow + * auto-redirects of any read-only method. + * + * We have no interface for saying "this method is read-only", + * although this might be useful. + */ +static int auto_redirect(struct redirect *red) +{ + return (strcasecmp(red->method, "HEAD") == 0 || + strcasecmp(red->method, "GET") == 0 || + strcasecmp(red->method, "PROPFIND") == 0 || + strcasecmp(red->method, "OPTIONS") == 0); +} + +static int post_send(void *private, const ne_status *status) +{ + struct redirect *red = private; + struct uri uri; + int ret = NE_OK; + + if ((status->code != 302 && status->code != 301) || + red->location == NULL) { + /* Nothing to do. */ + return NE_OK; + } + + if (uri_parse(red->location, &uri, NULL)) { + /* Couldn't parse the URI */ + ne_set_error(red->session, _("Could not parse redirect location.")); + return NE_ERROR; + } + + if ((uri.host != NULL && + strcasecmp(uri.host, red->session->server.hostname) != 0) || + (uri.port != -1 && + uri.port != red->session->server.port) || + (uri.scheme != NULL && + strcasecmp(uri.scheme, ne_get_scheme(red->session)) != 0)) { + /* Cannot redirect to another server. Throw this back to the caller + * to have them start a new session. */ + NE_DEBUG(NE_DBG_HTTP, + "Redirected to different host/port/scheme:\n" + "From %s://%s:%d to %s//%s:%d\n", + ne_get_scheme(red->session), + red->session->server.hostname, + red->session->server.port, + uri.scheme, uri.host, uri.port); + ret = NE_REDIRECT; + ne_set_error(red->session, _("Redirected to a different server.\n")); + } else { + + /* Same-server redirect. Can we auto-redirect? */ + + if (!auto_redirect(red) && + (red->confirm == NULL || + !(*red->confirm)(red->userdata, red->request_uri, uri.path))) { + /* No auto-redirect or confirm failed. */ + ret = NE_ERROR; + } else { + /* Okay, follow it: set the new URI and retry the request. */ + ne_set_request_uri(red->req, uri.path); + ret = NE_RETRY; + /* Notify them that we're following the redirect. */ + if (red->notify != NULL) { + red->notify(red->userdata, red->request_uri, uri.path); + } + } + } + + /* Free up the URI. */ + uri_free(&uri); + + return ret; +} + +static void free_redirect(void *cookie) +{ + struct redirect *red = cookie; + NE_FREE(red->location); + free(red); +} + +void ne_redirect_register(ne_session *sess, + ne_redirect_confirm confirm, + ne_redirect_notify notify, + void *userdata) +{ + struct redirect *red = ne_calloc(sizeof *red); + + red->confirm = confirm; + red->notify = notify; + red->userdata = userdata; + red->session = sess; + + ne_add_hooks(sess, &redirect_hooks, red, free_redirect); +} + +const char *ne_redirect_location(ne_session *sess) +{ + struct redirect *red = ne_session_hook_private(sess, REDIRECT_ID); + return red->location; +} + diff --git a/neon/src/ne_redirect.h b/neon/src/ne_redirect.h new file mode 100644 index 000000000..d18baf572 --- /dev/null +++ b/neon/src/ne_redirect.h @@ -0,0 +1,74 @@ +/* + HTTP-redirect support + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 + +/* The redirect code does not handle redirects to a different server. + * For a redirect to a different server, the request fails and returns + * HTTP_REDIRECT. You can then retrieve the redirect location using the + * call http_redirect_location. + * + * (you must have called http_redirect_register for this to happen). + * */ + +/* Get confirmation from the user that a redirect from + * URI 'src' to URI 'dest' is acceptable. Should return: + * Non-Zero to FOLLOW the redirect + * Zero to NOT follow the redirect + */ +typedef int (*ne_redirect_confirm)(void *userdata, + const char *src, const char *dest); + +/* Notify the user that a redirect has been automatically + * followed from URI 'src' to URI 'dest' */ +typedef void (*ne_redirect_notify)(void *userdata, + const char *src, const char *dest); + +/* Register redirect handling for the given session. + * Some redirect responses will be automatically followed. + * If the redirect is automatically followed, the 'notify' callback + * is called. + * For redirects which are NOT automatically followed, the + * 'confirm' callback is called: if this returns zero, the redirect + * is ignored. + * + * 'confirm' may be passed as NULL: in this case, only automatic + * redirects are followed. 'notify' may also be passed as NULL, + * automatic redirects are still followed. + * + * 'userdata' is passed as the first argument to the confirm and + * notify callbacks. */ +void ne_redirect_register(ne_session *sess, + ne_redirect_confirm confirm, + ne_redirect_notify notify, + void *userdata); + +/* Return location of last redirect. */ +const char *ne_redirect_location(ne_session *sess); + +END_NEON_DECLS + +#endif /* NE_REDIRECT_H */ diff --git a/neon/src/ne_request.c b/neon/src/ne_request.c new file mode 100644 index 000000000..af524498f --- /dev/null +++ b/neon/src/ne_request.c @@ -0,0 +1,1340 @@ +/* + HTTP request/response handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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> /* just for Win32? */ +#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 /* HAVE_STDLIB_H */ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#ifdef HAVE_SNPRINTF_H +#include "snprintf.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) + +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, char) (((hash)*33 + char) % HH_HASHSIZE); + +/* Produce the hash value for a header name, which MUST be in lower + * case. */ +static unsigned int hdr_hash(const char *name) +{ + const char *pnt; + unsigned int hash = 0; + + for (pnt = name; *pnt != '\0'; pnt++) { + hash = HH_ITERATE(hash,*pnt); + } + + return hash; +} + +static int set_sockerr(ne_request *req, const char *doing, int code) +{ + switch(code) { + case 0: /* FIXME: still needed? */ + case SOCK_CLOSED: + if (req->use_proxy) { + snprintf(req->session->error, BUFSIZ, + _("%s: connection was closed by proxy server."), doing); + } else { + snprintf(req->session->error, BUFSIZ, + _("%s: connection was closed by server."), doing); + } + return NE_ERROR; + case SOCK_TIMEOUT: + snprintf(req->session->error, BUFSIZ, + _("%s: connection timed out."), doing); + return NE_TIMEOUT; + default: + if (req->session->socket != NULL) { + const char *err = sock_get_error(req->session->socket); + if (err != NULL) { + snprintf(req->session->error, BUFSIZ, "%s: %s", doing, err); + } else { + snprintf(req->session->error, BUFSIZ, _("%s: socket error."), + doing); + } + } else { + snprintf(req->session->error, BUFSIZ, + "%s: %s", doing, strerror(errno)); + } + return NE_ERROR; + } +} + +static char *lower_string(const char *str) +{ + char *ret = ne_malloc(strlen(str) + 1), *pnt; + + for (pnt = ret; *str != '\0'; str++) { + *pnt++ = tolower(*str); + } + + *pnt = '\0'; + + 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); +} + +void ne_add_hooks(ne_session *sess, + const ne_request_hooks *hooks, void *private, + ne_free_hooks free_hooks) +{ + struct hook *hk = ne_malloc(sizeof(struct hook)); + hk->hooks = hooks; + hk->private = private; + hk->next = sess->hooks; + hk->free = free_hooks; + sess->hooks = hk; +} + +void *ne_request_hook_private(ne_request *req, const char *id) +{ + struct hook_request *hk; + + for (hk = req->hook_store; hk != NULL; hk = hk->next) { + if (strcasecmp(hk->hook->hooks->id, id) == 0) { + return hk->cookie; + } + } + + return NULL; +} + +void *ne_session_hook_private(ne_session *sess, const char *id) +{ + struct hook *hk; + + for (hk = sess->hooks; hk != NULL; hk = hk->next) { + if (strcasecmp(hk->hooks->id, id) == 0) { + return hk->private; + } + } + + return NULL; +} + +static ssize_t body_string_send(void *userdata, char *buffer, size_t count) +{ + ne_request *req = userdata; + + if (count == 0) { + req->body_left = req->body_size; + req->body_pnt = req->body_buffer; + } else { + /* if body_left == 0 we fall through and return 0. */ + if (req->body_left < count) + count = req->body_left; + + memcpy(buffer, req->body_pnt, count); + req->body_pnt += count; + req->body_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]; + size_t bytes; + + /* tell the source to start again from the beginning. */ + (void) req->body_cb(req->body_ud, NULL, 0); + + while ((bytes = req->body_cb(req->body_ud, buffer, BUFSIZ)) > 0) { + ret = fn(ud, buffer, bytes); + if (ret < 0) + break; + } + + if (bytes < 0) { + ne_set_error(req->session, _("Error reading request body.")); + ret = NE_ERROR; + } + + 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_pull_request_body(req, (ne_push_fn)sock_fullwrite, + req->session->socket); + + if (ret < 0) { + /* transfer failed */ + req->forced_close = 1; + ret = set_sockerr(req, _("Could not send request body"), ret); + } + + 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 (ne_version_pre_http11(req->session) && !req->use_proxy) { + ne_buffer_zappend(req->headers, "Keep-Alive: " EOL); + ne_buffer_zappend(req->headers, "Connection: TE, Keep-Alive"); + } else { + ne_buffer_zappend(req->headers, "Connection: TE"); + } + if (req->upgrade_to_tls) { + ne_buffer_zappend(req->headers, ", Upgrade"); + } + ne_buffer_zappend(req->headers, EOL); + if (req->upgrade_to_tls) { + ne_buffer_zappend(req->headers, "Upgrade: TLS/1.0" 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, ne_status *st) +{ + return 1; +} + +int ne_accept_2xx(void *userdata, ne_request *req, ne_status *st) +{ + return (st->klass == 2); +} + +/* Exposed since redirect hooks need this. + * + * DESIGN FLAW: mutating the URI does not get passed down to hooks. + * auth hooks need the URI. */ +void ne_set_request_uri(ne_request *req, const char *uri) +{ + ne_buffer *real_uri = ne_buffer_create(); + req->abs_path = ne_strdup(uri); + if (req->use_proxy && strcmp(uri, "*") != 0) + ne_buffer_concat(real_uri, ne_get_scheme(req->session), "://", + req->session->server.hostport, NULL); + ne_buffer_zappend(real_uri, uri); + req->uri = ne_buffer_finish(real_uri); +} + +/* Handler for the "Transfer-Encoding" response header */ +static void te_hdr_handler(void *userdata, const char *value) +{ + struct ne_response *resp = userdata; + if (strcasecmp(value, "chunked") == 0) { + resp->is_chunked = 1; + } else { + resp->is_chunked = 0; + } +} + +/* 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->forced_close = 1; + } else if (strcasecmp(value, "Keep-Alive") == 0) { + req->can_persist = 1; + } +} + +/* Initializes the request with given method and URI. + * URI must be abs_path - i.e., NO scheme+hostname. It will BREAK + * otherwise. */ +ne_request *ne_request_create(ne_session *sess, + const char *method, const char *uri) +{ + ne_request *req = ne_calloc(sizeof(ne_request)); + + NE_DEBUG(NE_DBG_HTTP, "Creating request...\n"); + + req->session = sess; + req->headers = ne_buffer_create(); + req->reqbuf = ne_buffer_create(); + req->respbuf = ne_buffer_create_sized(BUFSIZ); + + /* Add in the fixed headers */ + add_fixed_headers(req); + + /* Set the standard stuff */ + req->method = method; + req->method_is_head = (strcmp(req->method, "HEAD") == 0); + + /* FIXME: the proxy_decider is broken if they called + * ne_session_proxy before ne_session_server, since in that + * case we have not done a name lookup on the session server. */ + if (sess->have_proxy && sess->proxy_decider != NULL) { + req->use_proxy = + (*sess->proxy_decider)(sess->proxy_decider_udata, + ne_get_scheme(sess), sess->server.hostname); + } + else { + req->use_proxy = sess->have_proxy; + } + + if (sess->request_secure_upgrade == 1) { + req->upgrade_to_tls = 1; + } + + /* Add in handlers for all the standard HTTP headers. */ + + ne_add_response_header_handler(req, "Content-Length", + ne_handle_numeric_header, &req->resp.length); + ne_add_response_header_handler(req, "Transfer-Encoding", + te_hdr_handler, &req->resp); + ne_add_response_header_handler(req, "Connection", + connection_hdr_handler, req); + + if (uri) { + ne_set_request_uri(req, uri); + } + + { + struct hook *hk; + struct hook_request *store; + void *cookie; + + NE_DEBUG(NE_DBG_HTTP, "Running request create hooks.\n"); + + for (hk = sess->hooks; hk != NULL; hk = hk->next) { + cookie = (*hk->hooks->create)(hk->private, req, method, uri); + if (cookie != NULL) { + store = ne_malloc(sizeof(struct hook_request)); + store->hook = hk; + store->cookie = cookie; + store->next = req->hook_store; + req->hook_store = store; + } + } + } + + 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", "%d", size); +} + +void ne_set_request_body_buffer(ne_request *req, const char *buffer, + size_t size) +{ + req->body_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) { + const char *err = strerror(errno); + /* Stat failed */ + snprintf(req->session->error, BUFSIZ, + _("Could not find 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); + vsnprintf(buf, BUFSIZ, 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); + int hash; + new->name = lower_string(name); + new->handler = hdl; + new->userdata = userdata; + hash = hdr_hash(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(struct body_reader)); + 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_request *st, *next_st; + int n; + + NE_FREE(req->uri); + NE_FREE(req->abs_path); + + for (rdr = req->body_readers; rdr != NULL; rdr = next_rdr) { + next_rdr = rdr->next; + free(rdr); + } + + for (hdlr = req->header_catchers; hdlr != NULL; hdlr = next_hdlr) { + next_hdlr = hdlr->next; + free(hdlr); + } + + for (n = 0; n < HH_HASHSIZE; n++) { + for (hdlr = req->header_handlers[n]; hdlr != NULL; + hdlr = next_hdlr) { + next_hdlr = hdlr->next; + free(hdlr->name); + free(hdlr); + } + } + + ne_buffer_destroy(req->headers); + ne_buffer_destroy(req->reqbuf); + ne_buffer_destroy(req->respbuf); + + NE_DEBUG(NE_DBG_HTTP, "Running destroy hooks.\n"); + for (st = req->hook_store; st!=NULL; st = next_st) { + next_st = st->next; + if (HAVE_HOOK(st,destroy)) { + HOOK_FUNC(st,destroy)(st->cookie); + } + free(st); + } + + NE_DEBUG(NE_DBG_HTTP, "Request ends.\n"); + 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) +{ + int willread, readlen; + nsocket *sock = req->session->socket; + if (resp->is_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) { + long int chunk_len; + /* We are at the start of a new chunk. */ + NE_DEBUG(NE_DBG_HTTP, "New chunk.\n"); + readlen = sock_readline(sock, buffer, *buflen); + if (readlen <= 0) { + return set_sockerr(req, _("Could not read chunk size"), readlen); + } + NE_DEBUG(NE_DBG_HTTP, "[Chunk Size] < %s", buffer); + chunk_len = strtol(buffer, NULL, 16); + if (chunk_len == LONG_MIN || chunk_len == LONG_MAX) { + NE_DEBUG(NE_DBG_HTTP, "Couldn't read chunk size.\n"); + ne_set_error(req->session, _("Could not parse chunk size")); + return -1; + } + NE_DEBUG(NE_DBG_HTTP, "Got chunk size: %ld\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 = min(*buflen - 1, resp->chunk_left); + } else if (resp->length > 0) { + /* Have we finished reading the body? */ + if (resp->left == 0) { + *buflen = 0; + return NE_OK; + } + willread = min(*buflen - 1, resp->left); + } else { + /* Read until socket-close */ + willread = *buflen - 1; + } + NE_DEBUG(NE_DBG_HTTP, "Reading %d bytes of response body.\n", willread); + readlen = sock_read(sock, buffer, willread); + + /* EOF is valid if we don't know the response body length, or + * we've read all of the response body, and we're not using + * chunked. */ + if (readlen == SOCK_CLOSED && resp->length <= 0 && !resp->is_chunked) { + NE_DEBUG(NE_DBG_HTTP, "Got EOF.\n"); + readlen = 0; + } else if (readlen < 0) { + return set_sockerr(req, _("Could not read response body"), readlen); + } else { + NE_DEBUG(NE_DBG_HTTP, "Got %d bytes.\n", readlen); + } + buffer[readlen] = '\0'; + *buflen = readlen; + NE_DEBUG(NE_DBG_HTTPBODY, "Read block:\n%s\n", buffer); + if (resp->is_chunked) { + resp->chunk_left -= readlen; + if (resp->chunk_left == 0) { + char crlfbuf[2]; + /* If we've read a whole chunk, read a CRLF */ + readlen = sock_fullread(sock, crlfbuf, 2); + if (readlen < 0 || strncmp(crlfbuf, EOL, 2) != 0) { + return set_sockerr(req, + _("Error reading chunked response body"), + readlen); + } + } + } else if (resp->length > 0) { + 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 (req->resp.length == 0) { + return 0; + } + + if (read_response_block(req, &req->resp, buffer, &readlen)) { + /* it failed. */ + req->forced_close = 1; + return -1; + } + + if (req->session->progress_cb) { + req->resp.total += readlen; + req->session->progress_cb(req->session->progress_ud, req->resp.total, + req->resp.is_chunked?-1:req->resp.length); + } + + /* 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. */ +static const char *build_request(ne_request *req) +{ + const char *uri; + struct hook_request *st; + ne_buffer *buf = req->reqbuf; + + /* If we are talking to a proxy, we send them the absoluteURI + * as the Request-URI. If we are talking to a server, we just + * send abs_path. */ + if (req->use_proxy) + uri = req->uri; + else + uri = req->abs_path; + + ne_buffer_clear(buf); + + /* Add in the request and the user-supplied headers */ + ne_buffer_concat(buf, req->method, " ", uri, " HTTP/1.1" EOL, + req->headers->data, NULL); + + /* And the all-important Host header. This is done here since it + * might change for a new server. */ + ne_buffer_concat(buf, "Host: ", req->session->server.hostport, + EOL, NULL); + + + /* Now handle the body. */ + req->use_expect100 = 0; + if ((req->session->expect100_works > -1) && + (req->body_size > HTTP_EXPECT_MINSIZE) && + !ne_version_pre_http11(req->session)) { + /* Add Expect: 100-continue. */ + ne_buffer_zappend(buf, "Expect: 100-continue" EOL); + req->use_expect100 = 1; + } + + NE_DEBUG(NE_DBG_HTTP, "Running pre_send hooks\n"); + for (st = req->hook_store; st!=NULL; st = st->next) { + if (HAVE_HOOK(st,pre_send)) { + HOOK_FUNC(st,pre_send)(st->cookie, buf); + } + } + + /* Final CRLF */ + ne_buffer_zappend(buf, EOL); + + return buf->data; +} + +/* FIXME: this function need re-writing. + * + * Returns HTTP_*, and sets session error appropriately. + */ +static int send_request(ne_request *req) +{ + ne_session *sess = req->session; + int ret, try_again, send_attempt; + const char *request = build_request(req); + char *buffer = req->respbuf->data; + + try_again = 1; + + do { + + /* FIXME: this is broken */ + try_again--; + +#ifdef DEBUGGING + { + 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); + free(reqdebug); + } + } +#endif /* DEBUGGING */ + + /* Send the Request-Line and headers */ + for (send_attempt = 0; send_attempt < 2; send_attempt++) { + NE_DEBUG(NE_DBG_HTTP, "Sending headers: attempt %d\n", send_attempt); + /* Open the connection if necessary */ + ret = open_connection(req); + if (ret != NE_OK) { + return ret; + } + ret = sock_send_string(req->session->socket, request); + if (ret == SOCK_CLOSED) { + /* Could happen due to a persistent connection timeout. + * Or the server being restarted. */ + NE_DEBUG(NE_DBG_HTTP, "Connection was closed by server.\n"); + ne_close_connection(req->session); + } else { + break; + } + } + + if (ret < 0) { + return set_sockerr(req, _("Could not send request"), ret); + } + + NE_DEBUG(NE_DBG_HTTP, "Request sent\n"); + + /* Now, if we are doing a Expect: 100, hang around for a short + * amount of time, to see if the server actually cares about the + * Expect and sends us a 100 Continue response if the request + * is valid, else an error code if it's not. This saves sending + * big files to the server when they will be rejected. + */ + + if (req->use_expect100) { + NE_DEBUG(NE_DBG_HTTP, "Waiting for response...\n"); + ret = sock_block(sess->socket, HTTP_EXPECT_TIMEOUT); + switch(ret) { + case SOCK_TIMEOUT: + /* Timed out - i.e. Expect: ignored. There is a danger + * here that the server DOES respect the Expect: header, + * but was going SO slowly that it didn't get time to + * respond within HTTP_EXPECT_TIMEOUT. + * TODO: while sending the body, check to see if the + * server has sent anything back - if it HAS, then + * stop sending - this is a spec compliance SHOULD */ + NE_DEBUG(NE_DBG_HTTP, "Wait timed out.\n"); + sess->expect100_works = -1; /* don't try that again */ + /* Try sending the request again without using 100-continue */ + try_again++; + continue; + break; + case SOCK_CLOSED: + case SOCK_ERROR: /* error */ + return set_sockerr(req, _("Error waiting for response"), ret); + default: + NE_DEBUG(NE_DBG_HTTP, "Wait got data.\n"); + sess->expect100_works = 1; /* it works - use it again */ + break; + } + } else if (req->body_size) { + /* Just chuck the file down the socket */ + NE_DEBUG(NE_DBG_HTTP, "Sending body...\n"); + ret = send_request_body(req); + if (ret == SOCK_CLOSED) { + /* This happens if the persistent connection times out: + * the first write() of the headers gets a delayed write + * seemingly, so the write doesn't fail till this one. + */ + NE_DEBUG(NE_DBG_HTTP, "Connection closed before request sent, retrying\n"); + try_again++; + ne_close_connection(req->session); + continue; + } else if (ret < 0) { + NE_DEBUG(NE_DBG_HTTP, "Body send failed.\n"); + return ret; + } + NE_DEBUG(NE_DBG_HTTP, "Body sent.\n"); + + } + + /* Now, we have either: + * - Sent the header and body, or + * - Sent the header incl. Expect: line, and got some response. + * In any case, we get the status line of the response. + */ + + /* HTTP/1.1 says that the server MAY emit any number of + * interim 100 (Continue) responses prior to the normal + * response. So loop while we get them. */ + + do { + if (sock_readline(sess->socket, buffer, BUFSIZ) <= 0) { + if (try_again) { + return set_sockerr(req, _("Could not read status line"), ret); + } + NE_DEBUG(NE_DBG_HTTP, "Failed to read status line.\n"); + try_again++; + break; + } + + NE_DEBUG(NE_DBG_HTTP, "[Status Line] < %s", buffer); + + /* Got the status line - parse it */ + if (ne_parse_statusline(buffer, &req->status)) { + ne_set_error(sess, _("Could not parse response status line.")); + return -1; + } + + sess->version_major = req->status.major_version; + sess->version_minor = req->status.minor_version; + snprintf(sess->error, BUFSIZ, "%d %s", + req->status.code, req->status.reason_phrase); + STRIP_EOL(sess->error); + + if (req->status.klass == 1) { + NE_DEBUG(NE_DBG_HTTP, "Got 1xx-class.\n"); + /* Skip any headers, we don't need them */ + do { + ret = sock_readline(sess->socket, buffer, BUFSIZ); + if (ret <= 0) { + return set_sockerr( + req, _("Error reading response headers"), ret); + } + NE_DEBUG(NE_DBG_HTTP, "[Ignored header] < %s", buffer); + } while (strcmp(buffer, EOL) != 0); + + if (req->use_expect100 && (req->status.code == 100)) { + /* We are using Expect: 100, and we got a 100-continue + * return code... send the request body */ + NE_DEBUG(NE_DBG_HTTP, "Got continue... sending body now.\n"); + HTTP_ERR(send_request_body(req)); + NE_DEBUG(NE_DBG_HTTP, "Body sent.\n"); + } else if (req->upgrade_to_tls && (req->status.code == 101)) { + /* Switch to TLS on the fly */ + if (sock_make_secure(sess->socket, sess->ssl_context)) { + set_sockerr(req, _("Could not negotiate SSL session"), + SOCK_ERROR); + ne_close_connection(sess); + return NE_ERROR; + } + } + } + } while (req->status.klass == 1); + + if (try_again == 1) { + /* If we're trying again, close the conn first */ + NE_DEBUG(NE_DBG_HTTP, "Retrying request, closing connection first.\n"); + ne_close_connection(sess); + } + + } while (try_again == 1); + + return NE_OK; +} + +/* 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) +{ + int ret, n; + size_t len; /* holds strlen(buf) */ + nsocket *sock = req->session->socket; + + n = sock_readline(sock, buf, buflen); + if (n <= 0) + return set_sockerr(req, _("Error reading response headers"), n); + NE_DEBUG(NE_DBG_HTTP, "[hdr] %s", buf); + + STRIP_EOL(buf); + + len = strlen(buf); + + if (len == 0) { + NE_DEBUG(NE_DBG_HTTP, "End of headers.\n"); + return NE_OK; + } + + while (buflen > 0) { + char ch; + + /* Collect any extra lines into buffer */ + ret = sock_peek(sock, &ch, 1); + if (ret <= 0) { + /* FIXME: can sock_peek return 0? */ + return set_sockerr(req, _("Error reading response headers"), ret); + } + if (ch != ' ' && ch != '\t') { + /* No continuation of this header. NUL-terminate to be paranoid. */ + return NE_RETRY; + } + + /* Otherwise, read the next line onto the end of 'buf'. */ + buf += len; + buflen -= len; + + /* Read BUFSIZ-1 bytes to guarantee that we have a \0 */ + ret = sock_readline(sock, buf, buflen); + if (ret <= 0) { + return set_sockerr(req, _("Error reading response headers"), ret); + } + + NE_DEBUG(NE_DBG_HTTP, "[cont] %s", buf); + + STRIP_EOL(buf); + len = strlen(buf); + + /* 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 (len) buf[0] = ' '; + } + + ne_set_error(req->session, _("Response header too long")); + return NE_ERROR; +} + +static void normalize_response_length(ne_request *req) +{ + /* Response entity-body length calculation, bit icky. + * Here, we set: + * length==-1 if we DO NOT know the exact body length + * length>=0 if we DO know the body length. + * + * RFC2616, section 4.4: + * NO body is returned if the method is HEAD, or the resp status + * is 204 or 304 + */ + if (req->method_is_head || req->status.code==204 || + req->status.code==304) { + req->resp.length = 0; + } else { + /* RFC2616, section 4.4: if we have a transfer encoding + * and a content-length, then ignore the content-length. */ + if ((req->resp.length>-1) && + (req->resp.is_chunked)) { + req->resp.length = -1; + } + } + /* Noddy noddy noddy. Testing from Apache/mod_proxy, CONNECT does + * not return a Content-Length... */ + if (req->resp.length == -1 && req->session->in_connect && + req->status.klass == 2) { + req->resp.length = 0; + } + +} + +/* Apache's default is 100, seems reasonable. */ +#define MAX_HEADER_FIELDS 100 + +#define MAX_HEADER_LENGTH 8192 + +/* Read response headers, using buffer buffer. + * Returns NE_* code, sets session error. */ +static int read_response_headers(ne_request *req) +{ + char hdr[MAX_HEADER_LENGTH] = {0}; + int ret, count = 0; + + /* Read response headers. This loop was once optimized so that + * GCC will put all the local vars in registers, but that was a + * long time ago. */ + while ((ret = read_message_header(req, hdr, MAX_HEADER_LENGTH)) == NE_RETRY + && ++count < MAX_HEADER_FIELDS) { + struct header_handler *hdl; + /* hint to the compiler that we'd like these in registers */ + register char *pnt; + register int hash = 0; + + for (hdl = req->header_catchers; hdl != NULL; hdl = hdl->next) { + (*hdl->handler)(hdl->userdata, hdr); + } + + /* Iterate over the header name, converting it to lower case and + * calculating the hash value as we go. */ + for (pnt = hdr; *pnt != '\0' && *pnt != ':'; pnt++) { + *pnt = tolower(*pnt); + hash = HH_ITERATE(hash,*pnt); + } + + if (*pnt != '\0') { + /* Null-term name at the : */ + *pnt = '\0'; + + /* Value starts after any whitespace... */ + do { + pnt++; + } while (*pnt == ' ' || *pnt == '\t'); + + 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); + } + } + } else { + ne_set_error(req->session, _("Malformed header line.")); + return NE_ERROR; + } + } + + if (count == MAX_HEADER_FIELDS) { + ne_set_error(req->session, + _("Response exceeded maximum number of header fields.")); + ret = NE_ERROR; + } + + return ret; +} + +int ne_begin_request(ne_request *req) +{ + struct body_reader *rdr; + + NE_DEBUG(NE_DBG_HTTPBASIC, "Request: %s %s\n", req->method, req->uri); + + req->can_persist = 0; + req->forced_close = 0; + req->resp.length = -1; + req->resp.is_chunked = 0; + + /* Now send the request, and read the Status-Line */ + HTTP_ERR(send_request(req)); + + /* Read the headers */ + HTTP_ERR(read_response_headers(req)); + + /* response message logic */ + normalize_response_length(req); + + /* 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, &req->status); + } + + req->resp.left = req->resp.length; + req->resp.chunk_left = 0; + + return NE_OK; +} + +int ne_end_request(ne_request *req) +{ + struct hook_request *st; + int ret = NE_OK; + + NE_DEBUG(NE_DBG_HTTPBASIC, "Response: %s\n", req->session->error); + + /* Read headers in chunked trailers */ + if (req->resp.is_chunked) { + HTTP_ERR(read_response_headers(req)); + } + + NE_DEBUG(NE_DBG_HTTP, "Running post_send hooks\n"); + for (st = req->hook_store; ret == NE_OK && st != NULL; st = st->next) { + if (HAVE_HOOK(st,post_send)) { + ret = HOOK_FUNC(st,post_send)(st->cookie, &req->status); + } + } + + NE_DEBUG(NE_DBG_HTTP, "Connection status: %s, %s, %s\n", + req->forced_close?"forced close":"no forced close", + req->session->no_persist?"no persistent connection":"persistent connection", + ne_version_pre_http11(req->session)?"pre-HTTP/1.1":"HTTP/1.1 or later"); + + /* Close the connection if any of the following are true: + * - We have a forced close (e.g. "Connection: close" header) + * - We are not using persistent connections for this session + * - All of the following are true: + * * this is HTTP/1.0 + * * and they haven't said they can do persistent connections + * * we've not just done a successful CONNECT + */ + if (req->forced_close || req->session->no_persist || + (ne_version_pre_http11(req->session) && + !req->can_persist && + (!req->session->in_connect || req->status.klass != 2))) { + ne_close_connection(req->session); + } + + /* Retry if any hook asked us too (i.e. authentication hooks) */ + + return ret; +} + +/* HTTP/1.x request/response mechanism + * + * Returns an NE_* return code. + * + * The status information is placed in status. The error string is + * placed in req->session->error + */ +int ne_request_dispatch(ne_request *req) +{ + int ret; + ssize_t len; + + /* Loop sending the request: + * Retry whilst authentication fails and we supply it. */ + + do { + char buffer[BUFSIZ]; + + HTTP_ERR(ne_begin_request(req)); + + do { + len = ne_read_response_block(req, buffer, BUFSIZ); + } 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(ne_request *req) +{ + return &(req->status); +} + +/* 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 = ne_request_create(sess, "CONNECT", NULL); + int ret = NE_OK; + + /* Fudge the URI to be how we want it */ + req->uri = ne_strdup(sess->server.hostport); + + sess->connected = 1; + sess->in_connect = 1; + + ret = ne_request_dispatch(req); + + sess->in_connect = 0; + + if (ret != NE_OK || !sess->connected || + req->status.klass != 2) { + /* It failed */ + ne_set_error(sess, + _("Could not create SSL connection through proxy server")); + ret = NE_ERROR; + } + + ne_request_destroy(req); + + return ret; +} + +static int open_connection(ne_request *req) +{ + ne_session *sess = req->session; + + if (req->use_proxy) { + switch(sess->connected) { + case 0: + /* Make the TCP connection to the proxy */ + NE_DEBUG(NE_DBG_SOCKET, "Connecting to proxy at %s:%d...\n", + sess->proxy.hostname, sess->proxy.port); + notify_status(sess, ne_conn_connecting, sess->proxy.hostport); + sess->socket = sock_connect(sess->proxy.addr, sess->proxy.port); + if (sess->socket == NULL) { + (void) set_sockerr(req, _("Could not connect to proxy server"), SOCK_ERROR); + return NE_CONNECT; + } + + notify_status(sess, ne_conn_connected, sess->proxy.hostport); + + if (sess->progress_cb) { + sock_register_progress(sess->socket, sess->progress_cb, + sess->progress_ud); + } + sess->connected = 1; + /* FALL-THROUGH */ + case 1: + if (sess->use_secure && !sess->in_connect) { + int ret; + ret = proxy_tunnel(sess); + if (ret != NE_OK) { + ne_close_connection(sess); + return ret; + } + if (sock_make_secure(sess->socket, sess->ssl_context)) { + (void) set_sockerr(req, _("Could not negotiate SSL session"), SOCK_ERROR); + ne_close_connection(sess); + return NE_CONNECT; + } + sess->connected = 2; + notify_status(sess, ne_conn_secure, + sock_get_version(sess->socket)); + } else { + break; + } + break; + default: + /* We've got everything we need */ + break; + } + } else if (sess->connected == 0) { + + NE_DEBUG(NE_DBG_SOCKET, "Connecting to server at %s:%d...\n", + sess->server.hostname, sess->server.port); + + notify_status(sess, ne_conn_connecting, sess->server.hostport); + sess->socket = sock_connect(sess->server.addr, sess->server.port); + + if (sess->socket == NULL) { + (void) set_sockerr(req, _("Could not connect to server"), -1); + return NE_CONNECT; + } + + notify_status(sess, ne_conn_connected, sess->server.hostport); + + if (sess->progress_cb) { + sock_register_progress(sess->socket, sess->progress_cb, + sess->progress_ud); + } + + if (sess->use_secure) { + NE_DEBUG(NE_DBG_SOCKET, "Starting SSL...\n"); + if (sock_make_secure(sess->socket, sess->ssl_context)) { + (void) set_sockerr(req, _("Could not negotiate SSL session"), SOCK_ERROR); + ne_close_connection(sess); + return NE_CONNECT; + } + + notify_status(sess, ne_conn_secure, sock_get_version(sess->socket)); + + } + + sess->connected = 1; + } + return NE_OK; +} diff --git a/neon/src/ne_request.h b/neon/src/ne_request.h new file mode 100644 index 000000000..336355fcf --- /dev/null +++ b/neon/src/ne_request.h @@ -0,0 +1,271 @@ +/* + HTTP Request Handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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) +#define NE_ERROR (1) /* Generic error; use ne_get_error(session) for message */ +#define NE_LOOKUP (3) /* Name lookup failed */ +#define NE_AUTH (4) /* User authentication failed on server */ +#define NE_AUTHPROXY (5) /* User authentication failed on proxy */ +#define NE_SERVERAUTH (6) /* Server authentication failed */ +#define NE_PROXYAUTH (7) /* Proxy authentication failed */ +#define NE_CONNECT (8) /* Could not connect to server */ +#define NE_TIMEOUT (9) /* Connection timed out */ +#define NE_FAILED (10) /* The precondition failed */ +#define NE_RETRY (11) /* Retry request (ne_end_request ONLY) */ +#define NE_REDIRECT (12) /* See ne_redirect.h */ + +#define EOL "\r\n" + +typedef struct ne_request_s ne_request; + +/***** Request Handling *****/ + +/* Create a new request, with given method and URI. + * Returns request pointer. */ +ne_request * +ne_request_create(ne_session *sess, const char *method, const char *uri); + +/* '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); + +/* Contents of stream will be sent as the request body with the given + * request. Returns: + * 0 on okay. + * non-zero if could not determine length of file. + */ +int ne_set_request_body_fd(ne_request *req, int fd); + +/* Callback for providing request body blocks. + * + * 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. + * 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); + +/* Callback is called to provide blocks of request body. */ +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, ne_status *st); + +/* An 'acceptance' callback which only accepts 2xx-class responses. + * Ignores userdata. */ +int ne_accept_2xx(void *userdata, ne_request *req, ne_status *st); + +/* An acceptance callback which accepts all responses. Ignores + * userdata. */ +int ne_accept_always(void *userdata, ne_request *req, ne_status *st); + +/* The 'reader' callback type */ +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 rdr, 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). + * + * Returns: + * NE_OK if request sent + response read okay. + * NE_AUTH if user authentication failed on origin server + * NE_AUTHPROXY if user authentication failed on proxy server + * NE_SERVERAUTH server authentication failed + * NE_PROXYAUTH proxy authentication failed + * 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 + * + * NB: NE_AUTH and NE_AUTHPROXY mean that the USER supplied the + * wrong username/password. SERVER/PROXYAUTH mean that, after the + * server has accepted a valid username/password, the server/proxy did + * not authenticate the response message correctly. + * */ +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(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__ */ + ; + +/* 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 ...; + * 3. ne_end_request + * If ne_end_request returns NE_RETRY, MUST go back to 1. + */ +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 struct { + /* A slight hack? Allows access to the hook private information, + * externally... */ + const char *id; /* id could be a URI to be globally unique */ + /* e.g. "http://webdav.org/neon/hooks/davlock" */ + + /* Register a new request: return value passed to subsequent + * handlers as '_private'. Session cookie passed as 'session'. */ + void *(*create)(void *cookie, ne_request *req, + const char *method, const char *uri); + /* Just before sending the request: let them add headers if they want */ + void (*pre_send)(void *_private, ne_buffer *request); + /* After sending the request. May return: + * NE_OK everything is okay + * NE_RETRY try sending the request again. + * otherwise: request fails. Returned to _dispatch caller. + */ + int (*post_send)(void *_private, const ne_status *status); + /* Clean up after yourself. */ + void (*destroy)(void *_private); +} ne_request_hooks; + +typedef void (*ne_free_hooks)(void *cookie); + +/* Add in hooks. + * 'cookie' will be passed to each call to the 'create' handler of the hooks. + * If 'free_hooks' is non-NULL, it will called when the session is destroyed, + * to free any data associated with 'cookie'. + */ +void ne_add_hooks(ne_session *sess, + const ne_request_hooks *hooks, void *cookie, + ne_free_hooks free_cookie); + +/* Return private data for a new request */ +void *ne_request_hook_private(ne_request *req, const char *id); + +/* Return private data for the session */ +void *ne_session_hook_private(ne_session *sess, const char *id); + +END_NEON_DECLS + +#endif /* NE_REQUEST_H */ + diff --git a/neon/src/ne_session.c b/neon/src/ne_session.c new file mode 100644 index 000000000..9f730d5ac --- /dev/null +++ b/neon/src/ne_session.c @@ -0,0 +1,264 @@ +/* + HTTP session handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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_session.h" +#include "ne_alloc.h" +#include "ne_utils.h" + +#include "ne_private.h" + +#define NEON_USERAGENT "neon/" NEON_VERSION; + +/* Initializes an HTTP session */ +ne_session *ne_session_create(void) +{ + ne_session *sess = ne_calloc(sizeof *sess); + + NE_DEBUG(NE_DBG_HTTP, "HTTP session begins.\n"); + strcpy(sess->error, "Unknown error."); + sess->version_major = -1; + sess->version_minor = -1; + /* Default expect-100 to OFF. */ + sess->expect100_works = -1; + return sess; +} + +int ne_session_destroy(ne_session *sess) +{ + struct hook *hk; + + NE_DEBUG(NE_DBG_HTTP, "ne_session_destroy called.\n"); + + /* Clear the hooks. */ + hk = sess->hooks; + while (hk) { + struct hook *nexthk = hk->next; + if (hk->free) { + hk->free(hk->private); + } + free(hk); + hk = nexthk; + } + + NE_FREE(sess->server.hostname); + NE_FREE(sess->server.hostport); + NE_FREE(sess->proxy.hostport); + NE_FREE(sess->user_agent); + + if (sess->connected) { + ne_close_connection(sess); + } + + free(sess); + return NE_OK; +} + +int ne_version_pre_http11(ne_session *s) +{ + return (s->version_major<1 || (s->version_major==1 && s->version_minor<1)); +} + +static char *get_hostport(struct host_info *host) +{ + size_t len = strlen(host->hostname); + char *ret = ne_malloc(len + 10); + strcpy(ret, host->hostname); + if (host->port != 80) { + snprintf(ret + len, 9, ":%d", host->port); + } + return ret; +} + +static void +set_hostinfo(struct host_info *info, const char *hostname, int port) +{ + NE_FREE(info->hostport); + NE_FREE(info->hostname); + info->hostname= ne_strdup(hostname); + info->port = port; + info->hostport = get_hostport(info); +} + +static int lookup_host(ne_session *sess, struct host_info *info) +{ + if (sess->notify_cb) { + sess->notify_cb(sess->notify_ud, ne_conn_namelookup, info->hostname); + } + if (sock_name_lookup(info->hostname, &info->addr)) { + return NE_LOOKUP; + } else { + return NE_OK; + } +} + +int ne_session_server(ne_session *sess, const char *hostname, int port) +{ + if (sess->connected && !sess->have_proxy) { + /* Force reconnect */ + ne_close_connection(sess); + } + set_hostinfo(&sess->server, hostname, port); + /* We do a name lookup on the origin server if either: + * 1) we do not have a proxy server + * 2) we *might not* have a proxy server (since we have a 'proxy decider' function). + */ + if (!sess->have_proxy || sess->proxy_decider) { + return lookup_host(sess, &sess->server); + } else { + return NE_OK; + } +} + +void ne_set_secure_context(ne_session *sess, nssl_context *ctx) +{ + sess->ssl_context = ctx; +} + +int ne_set_request_secure_upgrade(ne_session *sess, int req_upgrade) +{ +#ifdef ENABLE_SSL + sess->request_secure_upgrade = req_upgrade; + return 0; +#else + return -1; +#endif +} + +int ne_set_accept_secure_upgrade(ne_session *sess, int acc_upgrade) +{ +#ifdef ENABLE_SSL + sess->accept_secure_upgrade = acc_upgrade; + return 0; +#else + return -1; +#endif +} + +int ne_set_secure(ne_session *sess, int use_secure) +{ +#ifdef ENABLE_SSL + sess->use_secure = use_secure; + return 0; +#else + return -1; +#endif +} + +void ne_session_decide_proxy(ne_session *sess, ne_use_proxy use_proxy, + void *userdata) +{ + sess->proxy_decider = use_proxy; + sess->proxy_decider_udata = userdata; +} + +int ne_session_proxy(ne_session *sess, const char *hostname, int port) +{ + if (sess->connected) { + /* Force reconnect */ + ne_close_connection(sess); + } + sess->have_proxy = 1; + set_hostinfo(&sess->proxy, hostname, port); + return lookup_host(sess, &sess->proxy); +} + +void ne_set_error(ne_session *sess, const char *errstring) +{ + strncpy(sess->error, errstring, BUFSIZ); + sess->error[BUFSIZ-1] = '\0'; + STRIP_EOL(sess->error); +} + + +void ne_set_progress(ne_session *sess, + sock_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_useragent(ne_session *sess, const char *token) +{ + static const char *fixed = " " NEON_USERAGENT; + NE_FREE(sess->user_agent); + CONCAT2(sess->user_agent, token, fixed); +} + +const char *ne_get_server_hostport(ne_session *sess) { + return sess->server.hostport; +} + +const char *ne_get_scheme(ne_session *sess) +{ + if (sess->use_secure) { + return "https"; + } else { + return "http"; + } +} + + +const char *ne_get_error(ne_session *sess) { + return sess->error; +} + +int ne_close_connection(ne_session *sess) +{ + NE_DEBUG(NE_DBG_SOCKET, "Closing connection.\n"); + if (sess->connected > 0) { + sock_close(sess->socket); + sess->socket = NULL; + } + sess->connected = 0; + NE_DEBUG(NE_DBG_SOCKET, "Connection closed.\n"); + return 0; +} + diff --git a/neon/src/ne_session.h b/neon/src/ne_session.h new file mode 100644 index 000000000..7397349a6 --- /dev/null +++ b/neon/src/ne_session.h @@ -0,0 +1,194 @@ +/* + HTTP session handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 "ne_socket.h" /* for nssl_context */ +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +typedef struct ne_session_s ne_session; + +/* + * Session handling: + * Call order: + * 0. sock_init MANDATORY + * 1. ne_session_create MANDATORY + * 2. ne_session_proxy OPTIONAL + * 3. ne_session_server MANDATORY + * 4. ne_set_* OPTIONAL + * ... --- Any HTTP request method --- + * n. ne_session_destroy MANDATORY + */ + +/* Create a new HTTP session */ +ne_session *ne_session_create(void); + +/* Finish an HTTP session */ +int ne_session_destroy(ne_session *sess); + +/* Prematurely force the connection to be closed for the given + * session. */ +int ne_close_connection(ne_session *sess); + +/* Set the server or proxy server to be used for the session. + * Returns: + * NE_LOOKUP if the DNS lookup for hostname failed. + * NE_OK otherwise. + * + * Note that if a proxy is being used, ne_session_proxy should be + * called BEFORE ne_session_server, so the DNS lookup can be skipped + * on the server. */ +int ne_session_server(ne_session *sess, const char *hostname, int port); +int ne_session_proxy(ne_session *sess, const char *hostname, int port); + +/* Callback to determine whether the proxy server should be used or + * not for a request to the given hostname using the given scheme. + * Scheme will be "http" or "https" etc. + * Must return: + * Zero to indicate the proxy should NOT be used + * Non-zero to indicate the proxy SHOULD be used. + */ +typedef int (*ne_use_proxy)(void *userdata, + const char *scheme, const char *hostname); + +/* Register the callback for determining whether the proxy server + * should be used or not here. 'userdata' will be passed as the first + * argument to the callback. The callback is only called if a proxy + * server has been set up using ne_session_proxy. + * + * This function MUST be called before calling ne_session_server. + */ +void ne_session_decide_proxy(ne_session *sess, ne_use_proxy use_proxy, + void *userdata); + +/* 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); + +/* Set a progress callback for the session. */ +void ne_set_progress(ne_session *sess, + sock_progress progress, void *userdata); + +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); + +/* Using SSL/TLS connections: + * + * Session settings: + * secure: Defaults to OFF + * secure_context: No callbacks, any protocol allowed. + * request_secure_upgrade: Defaults to OFF + * accept_secure_ugprade: Defaults to OFF + * + * secure_context: When set, specifies the settings to use for secure + * connections, and any callbacks (see nsocket.h). The lifetime of + * the context object must be >= to the lifetime of the session + * object. + * + * secure: When set, use a secure (SSL/TLS) connection to the origin + * server. This will tunnel (using CONNECT) through the proxy server + * if one is being used. + * + * NB: ne_set_scure will return -1 if SSL is not supported by the + * library (i.e., it was not built against OpenSSL), or 0 if it is. + * */ +void ne_set_secure_context(ne_session *sess, nssl_context *ctx); +int ne_set_secure(ne_session *sess, int secure); + +/* + * NOTE: don't bother using the two _secure_update functions yet. + * "secure upgrades" (RFC2817) are not currently supported by any HTTP + * servers. + * + * request_secure_upgrade: When set, notify the server with each + * request made that an upgrade to a secure connection is desired, if + * available. + * + * accept_secure_upgrade: When set, allow a server-requested upgrade + * to a secure connection. + * + * These return as per ne_set_secure. */ +int ne_set_request_secure_upgrade(ne_session *sess, int req_upgrade); +int ne_set_accept_secure_upgrade(ne_session *sess, int acc_upgrade); + +/* 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); + +/* Determine if next-hop server claims HTTP/1.1 compliance. Returns: + * 0 if next-hop server does NOT claim HTTP/1.1 compliance + * non-zero if next-hop server DOES claim HTTP/1.1 compliance + * Not that the "next-hop" server is the proxy server if one is being + * used, otherwise, the origin server. + */ +int ne_version_pre_http11(ne_session *sess); + +/* Returns the 'hostport' URI segment for the end-server, e.g. + * "my.server.com:8080" or "www.server.com" + * (port segment is ommitted if == 80) + */ +const char *ne_get_server_hostport(ne_session *sess); + +/* Returns the URL scheme being used for the current session. + * Does NOT include a trailing ':'. + * Example returns: "http" or "https". + */ +const char *ne_get_scheme(ne_session *sess); + +/* Set the error string for the session */ +void ne_set_error(ne_session *sess, const char *errstring); +/* 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/neon/src/ne_socket.c b/neon/src/ne_socket.c new file mode 100644 index 000000000..1ab4c1e2b --- /dev/null +++ b/neon/src/ne_socket.c @@ -0,0 +1,924 @@ +/* + socket handling routines + Copyright (C) 1998-2001, Joe Orton <joe@light.plus.com>, + except where otherwise indicated. + + Portions are: + Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> + Originally under GPL in Mutt, http://www.mutt.org/ + Relicensed under LGPL for neon, http://www.webdav.org/neon/ + + 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 + + The sock_readline() function is: + + Copyright (c) 1999 Eric S. Raymond + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + +*/ + +#include "config.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef WIN32 +#include <WinSock2.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#endif /* WIN32 */ + +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include <sys/stat.h> + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#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 /* HAVE_STDLIB_H */ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +/* SOCKS support. */ +#ifdef HAVE_SOCKS_H +#include <socks.h> +#endif + +#include "ne_i18n.h" +#include "ne_utils.h" +#include "ne_string.h" +#include "ne_socket.h" +#include "ne_alloc.h" + +#if defined(BEOS_PRE_BONE) +#define NEON_WRITE(a,b,c) send(a,b,c,0) +#define NEON_READ(a,b,c) recv(a,b,c,0) +#define NEON_CLOSE(s) closesocket(s) +#elif defined(WIN32) +#define NEON_WRITE(a,b,c) send(a,b,c,0) +#define NEON_READ(a,b,c) recv(a,b,c,0) +#define NEON_CLOSE(s) closesocket(s) +#else /* really Unix! */ +#define NEON_WRITE(a,b,c) write(a,b,c) +#define NEON_READ(a,b,c) read(a,b,c) +#define NEON_CLOSE(s) close(s) +#endif + +#ifdef ENABLE_SSL +#include <openssl/ssl.h> +#include <openssl/err.h> + +/* Whilst the OpenSSL interface *looks* like it is not thread-safe, it + * appears to do horrible gymnastics to maintain per-thread global + * variables for error reporting. UGH! */ +#define ERROR_SSL_STRING (ERR_reason_error_string(ERR_get_error())) + +#endif + +/* BeOS doesn't have fd==sockets on anything pre-bone, so see if + * we need to drop back to our old ways... + */ +#ifdef __BEOS__ + #ifndef BONE_VERSION /* if we have BONE this isn't an issue... */ + #define BEOS_PRE_BONE + #endif +#endif + +struct nsocket_s { + int fd; + const char *error; /* Store error string here */ + sock_progress progress_cb; + void *progress_ud; +#ifdef ENABLE_SSL + SSL *ssl; + SSL_CTX *default_ctx; +#endif +#ifdef BEOS_PRE_BONE +#define MAX_PEEK_BUFFER 1024 + char peeked_bytes[MAX_PEEK_BUFFER]; + char *peeked_bytes_curpos; + int peeked_bytes_avail; +#endif +}; + +struct nssl_context_s { +#ifdef ENABLE_SSL + SSL_CTX *ctx; +#endif + nssl_accept cert_accept; + void *accept_ud; /* userdata for callback */ + + /* private key prompt callback */ + nssl_key_prompt key_prompt; + void *key_userdata; + const char *key_file; +}; + +void sock_register_progress(nsocket *sock, sock_progress cb, void *userdata) +{ + sock->progress_cb = cb; + sock->progress_ud = userdata; +} + +void sock_call_progress(nsocket *sock, off_t progress, off_t total) +{ + if (sock->progress_cb) { + sock->progress_cb(sock->progress_ud, progress, total); + } +} + +int sock_init(void) +{ +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) + return -1; + +#endif + +#ifdef ENABLE_SSL + SSL_load_error_strings(); + SSL_library_init(); + + NE_DEBUG(NE_DBG_SOCKET, "Initialized SSL.\n"); +#endif + + return 0; +} + +void sock_exit(void) +{ +#ifdef WIN32 + WSACleanup(); +#endif +} + +/* sock_read is read() with a timeout of SOCKET_READ_TIMEOUT. */ +int sock_read(nsocket *sock, char *buffer, size_t count) +{ + int ret; + + if (count == 0) { + NE_DEBUG(NE_DBG_SOCKET, "Passing 0 to sock_read is probably bad.\n"); + /* But follow normal read() semantics anyway... */ + return 0; + } + + ret = sock_block(sock, SOCKET_READ_TIMEOUT); + if (ret == 0) { + /* Got data */ + do { +#ifdef ENABLE_SSL + if (sock->ssl) { + ret = SSL_read(sock->ssl, buffer, count); + } else { +#endif +#ifndef BEOS_PRE_BONE + ret = NEON_READ(sock->fd, buffer, count); +#else + if (sock->peeked_bytes_avail > 0) { + /* simply return the peeked bytes.... */ + if (count >= sock->peeked_bytes_avail){ + /* we have room */ + strncpy(buffer, sock->peeked_bytes_curpos, + sock->peeked_bytes_avail); + ret = sock->peeked_bytes_avail; + sock->peeked_bytes_avail = 0; + } else { + strncpy(buffer, sock->peeked_bytes_curpos, count); + sock->peeked_bytes_curpos += count; + sock->peeked_bytes_avail -= count; + ret = count; + } + } else { + ret = recv(sock->fd, buffer, count, 0); + } +#endif +#ifdef ENABLE_SSL + } +#endif + } while (ret == -1 && errno == EINTR); + if (ret < 0) { + sock->error = strerror(errno); + ret = SOCK_ERROR; + } else if (ret == 0) { + /* This might not or might not be an error depending on + the context. */ + sock->error = _("Connection was closed by server"); + NE_DEBUG(NE_DBG_SOCKET, "read returned zero.\n"); + ret = SOCK_CLOSED; + } + } + return ret; +} + +/* sock_peek is recv(...,MSG_PEEK) with a timeout of SOCKET_TIMEOUT. + * Returns length of data read or SOCK_* on error */ +int sock_peek(nsocket *sock, char *buffer, size_t count) +{ + int ret; + ret = sock_block(sock, SOCKET_READ_TIMEOUT); + if (ret < 0) { + return ret; + } + /* Got data */ +#ifdef ENABLE_SSL + if (sock->ssl) { + ret = SSL_peek(sock->ssl, buffer, count); + /* TODO: This is the fetchmail fix as in sock_readline. + * Do we really need it? */ + if (ret == 0) { + if (sock->ssl->shutdown) { + return SOCK_CLOSED; + } + if (0 != ERR_get_error()) { + sock->error = ERROR_SSL_STRING; + return SOCK_ERROR; + } + } + } else { +#endif + do { +#ifndef BEOS_PRE_BONE + ret = recv(sock->fd, buffer, count, MSG_PEEK); +#else /* we're on BeOS pre-BONE so we need to use the buffer... */ + if (sock->peeked_bytes_avail > 0) { + /* we've got some from last time!!! */ + if (count >= sock->peeked_bytes_avail) { + strncpy(buffer, sock->peeked_bytes_curpos, sock->peeked_bytes_avail); + ret = sock->peeked_bytes_avail; + } else { + strncpy(buffer, sock->peeked_bytes_curpos, count); + ret = count; + } + } else { + if (count > MAX_PEEK_BUFFER) + count = MAX_PEEK_BUFFER; + ret = recv(sock->fd, buffer, count, 0); + sock->peeked_bytes_avail = ret; + strncpy(sock->peeked_bytes, buffer, ret); + sock->peeked_bytes_curpos = sock->peeked_bytes; + } +#endif + } while (ret == -1 && errno == EINTR); +#ifdef ENABLE_SSL + } +#endif + /* According to the Single Unix Spec, recv() will return + * zero if the socket has been closed the other end. */ + if (ret == 0) { + ret = SOCK_CLOSED; + } else if (ret < 0) { + sock->error = strerror(errno); + ret = SOCK_ERROR; + } + return ret; +} + +/* Blocks waiting for read input on the given socket for the given time. + * Returns: + * 0 if data arrived + * SOCK_TIMEOUT if data did not arrive before timeout + * SOCK_ERROR on error + */ +int sock_block(nsocket *sock, int timeout) +{ + struct timeval tv; + fd_set fds; + int ret; + +#ifdef ENABLE_SSL + if (sock->ssl) { + /* There may be data already available in the + * SSL buffers */ + if (SSL_pending(sock->ssl)) { + return 0; + } + /* Otherwise, we should be able to select on + * the socket as per normal. Probably? */ + } +#endif +#ifdef BEOS_PRE_BONE + if (sock->peeked_bytes_avail > 0) { + return 0; + } +#endif + + /* Init the fd set */ + FD_ZERO(&fds); + FD_SET(sock->fd, &fds); + /* Set the timeout */ + tv.tv_sec = timeout; + tv.tv_usec = 0; + do { + ret = select(sock->fd+1, &fds, NULL, NULL, &tv); + } while (ret == -1 && errno == EINTR); + + switch(ret) { + case 0: + return SOCK_TIMEOUT; + case -1: + sock->error = strerror(errno); + return SOCK_ERROR; + default: + return 0; + } +} + +/* Send the given line down the socket with CRLF appended. + * Returns 0 on success or SOCK_* on failure. */ +int sock_sendline(nsocket *sock, const char *line) +{ + char *buffer; + int ret; + CONCAT2(buffer, line, "\r\n"); + ret = sock_send_string(sock, buffer); + free(buffer); + return ret; +} + +int sock_readfile_blocked(nsocket *sock, off_t length, + sock_block_reader reader, void *userdata) +{ + char buffer[BUFSIZ]; + int ret; + off_t done = 0; + do { + ret = sock_read(sock, buffer, BUFSIZ); + if (ret < 0) { + if (length == -1 && ret == SOCK_CLOSED) { + /* Not an error condition. */ + return 0; + } + return ret; + } + done += ret; + sock_call_progress(sock, done, length); + (*reader)(userdata, buffer, ret); + } while ((length == -1) || (done < length)); + return 0; +} + + +/* Send a block of data down the given fd. + * Returns 0 on success or SOCK_* on failure */ +int sock_fullwrite(nsocket *sock, const char *data, size_t length) +{ + ssize_t wrote; + +#ifdef ENABLE_SSL + if (sock->ssl) { + /* joe: ssl.h says SSL_MODE_ENABLE_PARTIAL_WRITE must + * be enabled to have SSL_write return < length... + * so, SSL_write should never return < length. */ + wrote = SSL_write(sock->ssl, data, length); + if (wrote >= 0 && (size_t)wrote < length) { + NE_DEBUG(NE_DBG_SOCKET, "SSL_write returned less than length!\n"); + sock->error = ERROR_SSL_STRING; + return SOCK_ERROR; + } + } else { +#endif + const char *pnt = data; + size_t sent = 0; + + while (sent < length) { + wrote = NEON_WRITE(sock->fd, pnt, length-sent); + if (wrote < 0) { + if (errno == EINTR) { + continue; + } else if (errno == EPIPE) { + return SOCK_CLOSED; + } else { + sock->error = strerror(errno); + return SOCK_ERROR; + } + } + sent += wrote; + pnt += wrote; +#ifdef ENABLE_SSL + } +#endif + } + return 0; +} + +/* Sends the given string down the given socket. + * Returns 0 on success or -1 on failure. */ +int sock_send_string(nsocket *sock, const char *data) +{ + return sock_fullwrite(sock, data, strlen(data)); +} + +/* This is from from Eric Raymond's fetchmail (SockRead() in socket.c) + * since I wouldn't have a clue how to do it properly. + * This function is Copyright 1999 (C) Eric Raymond. + * Modifications Copyright 2000 (C) Joe Orton + */ +int sock_readline(nsocket *sock, char *buf, int len) +{ + char *newline, *bp = buf; + int n; + + do { + /* + * The reason for these gymnastics is that we want two things: + * (1) to read \n-terminated lines, + * (2) to return the true length of data read, even if the + * data coming in has embedded NULS. + */ +#ifdef ENABLE_SSL + + if (sock->ssl) { + /* Hack alert! */ + /* OK... SSL_peek works a little different from MSG_PEEK + Problem is that SSL_peek can return 0 if there is no + data currently available. If, on the other hand, we + loose the socket, we also get a zero, but the SSL_read + then SEGFAULTS! To deal with this, we'll check the + error code any time we get a return of zero from + SSL_peek. If we have an error, we bail. If we don't, + we read one character in SSL_read and loop. This + should continue to work even if they later change the + behavior of SSL_peek to "fix" this problem... :-(*/ + NE_DEBUG(NE_DBG_SOCKET, "SSL readline... \n"); + if ((n = SSL_peek(sock->ssl, bp, len)) < 0) { + sock->error = ERROR_SSL_STRING; + return(-1); + } + if (0 == n) { + /* SSL_peek says no data... Does he mean no data + or did the connection blow up? If we got an error + then bail! */ + NE_DEBUG(NE_DBG_SOCKET, "SSL_Peek says no data!\n"); + /* Check properly to see if the connection has closed */ + if (sock->ssl->shutdown) { + NE_DEBUG(NE_DBG_SOCKET, "SSL says shutdown."); + return SOCK_CLOSED; + } else if (0 != (n = ERR_get_error())) { + NE_DEBUG(NE_DBG_SOCKET, "SSL error occured.\n"); + sock->error = ERROR_SSL_STRING; + return -1; + } + + /* We didn't get an error so read at least one + character at this point and loop */ + n = 1; + /* Make sure newline start out NULL! We don't have a + * string to pass through the strchr at this point yet + * */ + newline = NULL; + } else if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + n = SSL_read(sock->ssl, bp, n); + NE_DEBUG(NE_DBG_SOCKET, "SSL_read returned %d\n", n); + if (n == -1) { + sock->error = ERROR_SSL_STRING; + return(-1); + } + /* Check for case where our single character turned out to + * be a newline... (It wasn't going to get caught by + * the strchr above if it came from the hack...). */ + if (NULL == newline && 1 == n && '\n' == *bp) { + /* Got our newline - this will break + out of the loop now */ + newline = bp; + } + } else { +#endif + if ((n = sock_peek(sock, bp, len)) <= 0) + return n; + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = sock_read(sock, bp, n)) < 0) + return n; +#ifdef ENABLE_SSL + } +#endif + bp += n; + len -= n; + if (len < 1) { + sock->error = _("Line too long"); + return SOCK_FULL; + } + } while (!newline && len); + *bp = '\0'; + return bp - buf; +} + +/*** End of ESR-copyrighted section ***/ + +/* Reads readlen bytes from fd and write to sock. + * If readlen == -1, then it reads from srcfd until EOF. + * Returns number of bytes written to destfd, or -1 on error. + */ +int sock_transfer(int fd, nsocket *sock, off_t readlen) +{ + char buffer[BUFSIZ]; + size_t curlen; /* total bytes yet to read from srcfd */ + off_t sumwrlen; /* total bytes written to destfd */ + + if (readlen == -1) { + curlen = BUFSIZ; /* so the buffer size test works */ + } else { + curlen = readlen; /* everything to do */ + } + sumwrlen = 0; /* nowt done yet */ + + while (curlen > 0) { + int rdlen, wrlen; + + /* Get a chunk... if the number of bytes that are left to read + * is less than the buffer size, only read that many bytes. */ + rdlen = read(fd, buffer, (readlen==-1)?BUFSIZ:(min(BUFSIZ, curlen))); + sock_call_progress(sock, sumwrlen, readlen); + if (rdlen < 0) { + if (errno == EPIPE) { + return SOCK_CLOSED; + } else { + sock->error = strerror(errno); + return SOCK_ERROR; + } + } else if (rdlen == 0) { + /* End of file... get out of here */ + break; + } + if (readlen != -1) + curlen -= rdlen; + + /* Otherwise, we have bytes! Write them to destfd */ + + wrlen = sock_fullwrite(sock, buffer, rdlen); + if (wrlen < 0) { + return wrlen; + } + + sumwrlen += rdlen; + } + sock_call_progress(sock, sumwrlen, readlen); + return sumwrlen; +} + +/* Reads buflen bytes into buffer until it's full. + * Returns 0 on success, -1 on error */ +int sock_fullread(nsocket *sock, char *buffer, int buflen) +{ + char *pnt; /* current position within buffer */ + int len; + pnt = buffer; + while (buflen > 0) { + len = sock_read(sock, pnt, buflen); + if (len < 0) return len; + buflen -= len; + pnt += len; + } + return 0; +} + +/* Do a name lookup on given hostname, writes the address into + * given address buffer. Return -1 on failure. + */ +int sock_name_lookup(const char *hostname, struct in_addr *addr) +{ + struct hostent *hp; + unsigned long laddr; + + /* TODO?: a possible problem here, is that if we are passed an + * invalid IP address e.g. "500.500.500.500", then this gets + * passed to gethostbyname and returned as "Host not found". + * Arguably wrong, but maybe difficult to detect correctly what is + * an invalid IP address and what is a hostname... can hostnames + * begin with a numeric character? */ + laddr = (unsigned long)inet_addr(hostname); + if ((int)laddr == -1) { + /* inet_addr failed. */ + hp = gethostbyname(hostname); + if (hp == NULL) { +#if 0 + /* Need to get this back somehow, but we don't have + * an nsocket * yet... */ + switch(h_errno) { + case HOST_NOT_FOUND: + sock->error = _("Host not found"); + break; + case TRY_AGAIN: + sock->error = _("Host not found (try again later?)"); + break; + case NO_ADDRESS: + sock->error = _("Host exists but has no address."); + break; + case NO_RECOVERY: + default: + sock->error = _("Non-recoverable error in resolver library."); + break; + } +#endif + return SOCK_ERROR; + } + memcpy(addr, hp->h_addr, hp->h_length); + } else { + addr->s_addr = laddr; + } + return 0; +} + +static nsocket *create_sock(int fd) +{ + nsocket *sock = ne_calloc(sizeof *sock); +#ifdef ENABLE_SSL + sock->default_ctx = SSL_CTX_new(SSLv23_client_method()); +#endif + sock->fd = fd; + return sock; +} + +/* Opens a socket to the given port at the given address. + * Returns -1 on failure, or the socket on success. + * portnum must be in HOST byte order */ +nsocket *sock_connect(const struct in_addr addr, + unsigned short int portnum) +{ + struct sockaddr_in sa; + int fd; + + /* Create the socket */ + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) + return NULL; + /* Connect the nsocket */ + sa.sin_family = AF_INET; + sa.sin_port = htons(portnum); /* host -> net byte orders */ + sa.sin_addr = addr; + if (connect(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) { + (void) NEON_CLOSE(fd); + return NULL; + } + /* Success - return the nsocket */ + return create_sock(fd); +} + +nsocket *sock_accept(int listener) +{ + int fd = accept(listener, NULL, NULL); + if (fd > 0) { + return create_sock(fd); + } else { + return NULL; + } +} + +int sock_get_fd(nsocket *sock) +{ + return sock->fd; +} + +nssl_context *sock_create_ssl_context(void) +{ + nssl_context *ctx = ne_calloc(sizeof *ctx); +#ifdef ENABLE_SSL + ctx->ctx = SSL_CTX_new(SSLv23_client_method()); +#endif + return ctx; +} + +void sock_destroy_ssl_context(nssl_context *ctx) +{ +#ifdef ENABLE_SSL + SSL_CTX_free(ctx->ctx); +#endif + free(ctx); +} + +#ifdef ENABLE_SSL +void sock_disable_tlsv1(nssl_context *c) +{ + SSL_CTX_set_options(c->ctx, SSL_OP_NO_TLSv1); +} +void sock_disable_sslv2(nssl_context *c) +{ + SSL_CTX_set_options(c->ctx, SSL_OP_NO_SSLv2); + +} +void sock_disable_sslv3(nssl_context *c) +{ + SSL_CTX_set_options(c->ctx, SSL_OP_NO_SSLv3); +} + +/* The callback neon installs with OpenSSL for giving the private key + * prompt. FIXME: WTH is 'rwflag'? */ +static int key_prompt_cb(char *buf, int len, int rwflag, void *userdata) +{ + nssl_context *ctx = userdata; + int ret; + ret = ctx->key_prompt(ctx->key_userdata, ctx->key_file, buf, len); + if (ret) + return -1; + /* Obscurely OpenSSL requires the callback to return the length of + * the password, this seems a bit weird so we don't expose this in + * the neon API. */ + return strlen(buf); +} + +void sock_set_key_prompt(nssl_context *ctx, + nssl_key_prompt prompt, void *userdata) +{ + SSL_CTX_set_default_passwd_cb(ctx->ctx, key_prompt_cb); + SSL_CTX_set_default_passwd_cb_userdata(ctx->ctx, ctx); + ctx->key_prompt = prompt; + ctx->key_userdata = userdata; +} + +#else +void sock_disable_tlsv1(nssl_context *c) {} +void sock_disable_sslv2(nssl_context *c) {} +void sock_disable_sslv3(nssl_context *c) {} +void sock_set_key_prompt(nssl_context *c, nssl_key_prompt p, void *u) {} +#endif + +int sock_make_secure(nsocket *sock, nssl_context *ctx) +{ +#ifdef ENABLE_SSL + int ret; + SSL_CTX *ssl_ctx; + + if (ctx) { + ssl_ctx = ctx->ctx; + } else { + ssl_ctx = sock->default_ctx; + } + + sock->ssl = SSL_new(ssl_ctx); + if (!sock->ssl) { + sock->error = ERROR_SSL_STRING; + /* Usually goes wrong because: */ + fprintf(stderr, "Have you called sock_init()!?\n"); + return SOCK_ERROR; + } + +#ifdef SSL_MODE_AUTO_RETRY + /* OpenSSL 0.9.6 and later... */ + (void) SSL_set_mode(sock->ssl, SSL_MODE_AUTO_RETRY); +#endif + + SSL_set_fd(sock->ssl, sock->fd); + + ret = SSL_connect(sock->ssl); + if (ret == -1) { + sock->error = ERROR_SSL_STRING; + SSL_free(sock->ssl); + sock->ssl = NULL; + return SOCK_ERROR; + } + +#if 0 + /* Tommi Komulainen <Tommi.Komulainen@iki.fi> has donated his SSL + * cert verification from the mutt IMAP/SSL code under the + * LGPL... it will plug in here */ + ret = sock_check_certicate(sock); + if (ret) { + SSL_shutdown(sock->ssl); + SSL_free(sock->ssl); + sock->ssl = NULL; + return ret; + } +#endif + + NE_DEBUG(NE_DBG_SOCKET, "SSL connected: version %s\n", + SSL_get_version(sock->ssl)); + return 0; +#else + sock->error = _("This application does not have SSL support."); + return SOCK_ERROR; +#endif +} + +const char *sock_get_version(nsocket *sock) +{ +#ifdef ENABLE_SSL + return SSL_get_version(sock->ssl); +#else + return NULL; +#endif +} + +const char *sock_get_error(nsocket *sock) +{ + return sock->error; +} + +/* Closes given nsocket */ +int sock_close(nsocket *sock) { + int ret; +#ifdef ENABLE_SSL + if (sock->ssl) { + SSL_shutdown(sock->ssl); + SSL_free(sock->ssl); + } + SSL_CTX_free(sock->default_ctx); +#endif + ret = NEON_CLOSE(sock->fd); + free(sock); + return ret; +} + +/* FIXME: get error messages back to the caller. */ +int sock_set_client_cert(nssl_context *ctx, const char *cert, const char *key) +{ +#ifdef ENABLE_SSL + if (SSL_CTX_use_certificate_file(ctx->ctx, cert, SSL_FILETYPE_PEM) <= 0) { + NE_DEBUG(NE_DBG_SOCKET, "Could not load cert file.\n"); + return -1; + } + + /* The cert file can contain the private key too, apparently. Not + * sure under what circumstances this is sensible, but hey. */ + if (key == NULL) + key = cert; + + /* Set this so the callback can tell the user what's going on. */ + ctx->key_file = key; + + if (SSL_CTX_use_PrivateKey_file(ctx->ctx, key, SSL_FILETYPE_PEM) <= 0) { + NE_DEBUG(NE_DBG_SOCKET, "Could not load private key file.\n"); + return -1; + } + + /* Sanity check. */ + if (!SSL_CTX_check_private_key(ctx->ctx)) { + NE_DEBUG(NE_DBG_SOCKET, "Private key does not match certificate.\n"); + return -1; + } + + return 0; +#else + return -1; +#endif +} + +/* Returns HOST byte order port of given name */ +int sock_service_lookup(const char *name) { + struct servent *ent; + ent = getservbyname(name, "tcp"); + if (ent == NULL) { + return 0; + } else { + return ntohs(ent->s_port); + } +} diff --git a/neon/src/ne_socket.h b/neon/src/ne_socket.h new file mode 100644 index 000000000..d0e0cf9c7 --- /dev/null +++ b/neon/src/ne_socket.h @@ -0,0 +1,228 @@ +/* + socket handling interface + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 + +#ifdef WIN32 +#include <WinSock2.h> +#include <stddef.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +#include "ne_defs.h" + +BEGIN_NEON_DECLS + +#define SOCK_ERROR -1 +/* Read/Write timed out */ +#define SOCK_TIMEOUT -2 +/* Passed buffer was full */ +#define SOCK_FULL -3 +/* Socket was closed */ +#define SOCK_CLOSED -4 + +/* Socket read timeout */ +#define SOCKET_READ_TIMEOUT 120 + +struct nsocket_s; +typedef struct nsocket_s nsocket; + +typedef void (*sock_block_reader) ( + void *userdata, const char *buf, size_t len); + +typedef void (*sock_progress)(void *userdata, off_t progress, off_t total); + +void sock_register_progress(nsocket *sock, sock_progress cb, void *userdata); + +void sock_call_progress(nsocket *sock, off_t progress, off_t total); + +/* Initialize the socket library. If you don't do this, SSL WILL NOT WORK. + * Returns 0 on success, or non-zero on screwed up SSL library. */ +int sock_init(void); + +/* Shutdown the socket library. */ +void sock_exit(void); + +/* sock_read is read() with a timeout of SOCKET_TIMEOUT. + * Returns: + * SOCK_* on error, + * 0 on no data to read (due to EOF), + * >0 length of data read into buffer. + */ +int sock_read(nsocket *sock, char *buffer, size_t count); + +/* sock_peek is recv() with a timeout of SOCKET_TIMEOUT. + * Returns: + * SOCK_* on error, + * 0 on no data to read (due to EOF), + * >0 length of data read into buffer. + */ +int sock_peek(nsocket *sock, char *buffer, size_t count); + +/* Blocks waiting for data on the given socket for the given time. + * Returns: + * SOCK_* on error, + * SOCK_TIMEOUT on no data within timeout, + * 0 if data arrived on the socket. + */ +int sock_block(nsocket *sock, int timeout); + +/* Reads readlen bytes from fd and writes to socket. + * (Not all in one go, obviously). + * If readlen == -1, then it reads from srcfd until EOF. + * Returns number of bytes written to destfd, or SOCK_* on error. + */ +int sock_transfer(int fd, nsocket *sock, off_t readlen); + +/* Sends the given line to given socket, CRLF appended */ +int sock_sendline(nsocket *sock, const char *line); +/* Sends the given block of data down the nsocket */ +int sock_fullwrite(nsocket *sock, const char *data, size_t length); +/* Sends the null-terminated string down the given nsocket */ +int sock_send_string(nsocket *sock, const char *string); + +/* Reads a line from given nsocket */ +int sock_readline(nsocket *sock, char *line, int len); +/* Reads a chunk of data. */ +int sock_fullread(nsocket *sock, char *buffer, int buflen); + +/* Creates and connects a nsocket */ +nsocket *sock_connect(const struct in_addr host, + unsigned short int portnum); + +/* Not as good as accept(2), missing parms 2+3. + * Addings parms 2+3 would probably mean passing socklen_t as an + * int then casting internally, since we don't really want to + * autogenerate the header file to be correct for the build platform. + */ +nsocket *sock_accept(int listener); + +/* Returns the file descriptor used for the socket */ +int sock_get_fd(nsocket *sock); + +/* Closes the socket and frees the nsocket object. */ +int sock_close(nsocket *sock); + +const char *sock_get_version(nsocket *sock); + +const char *sock_get_error(nsocket *sock); + +/* Do a name lookup on given hostname, writes the address into + * given address buffer. Return -1 on failure. */ +int sock_name_lookup(const char *hostname, struct in_addr *addr); + +/* Returns the standard TCP port for the given service */ +int sock_service_lookup(const char *name); + +/* Read from socket, passing each block read to reader callback. + * Pass userdata as first argument to reader callback. + * + * If length is -1, keep going till EOF is returned. SOCK_CLOSED + * is never returned in this case. + * + * Otherwise, read exactly 'length' bytes. If EOF is encountered + * before length bytes have been read, and SOCK_CLOSED will be + * returned. + * + * Returns: + * 0 on success, + * SOCK_* on error (SOCK_CLOSED is a special case, as above) + */ +int sock_readfile_blocked(nsocket *sock, off_t length, + sock_block_reader reader, void *userdata); + +/* Auxiliary for use with SSL. */ +struct nssl_context_s; +typedef struct nssl_context_s nssl_context; + +/* Netscape's prompts on getting a certificate which it doesn't + * recognize the CA for: + * 1. Hey, I don't recognize the CA for this cert. + * 2. Here is the certificate: for foo signed by BLAH, + * using encryption level BLEE + * 3. Allow: accept for this session only, + * don't accept + * accept forever + */ +nssl_context *sock_create_ssl_context(void); + +void sock_destroy_ssl_context(nssl_context *ctx); + +/* Callback to decide whether the user will accept the + * given certificate or not */ +typedef struct { + char *owner; /* Multi-line string describing owner of + * certificate */ + char *issuer; /* As above for issuer of certificate */ + /* Strings the certificate is valid between */ + char *valid_from, *valid_till; + /* Certificate fingerprint */ + char *fingerprint; +} nssl_certificate; + +/* Returns: + * 0 -> User accepts the certificate + * non-zero -> user does NOT accept the certificate. + */ +typedef int (*nssl_accept)(void *userdata, const nssl_certificate *info); + +void sock_set_cert_accept(nssl_context *c, + nssl_accept accepter, void *userdata); + +/* Callback for retrieving the private key password. + * Filename will be the filename of the private key file. + * Must return: + * 0 on success. buf must be filled in with the password. + * non-zero if the user cancelled the prompt. + * + * FIXME: this is inconsistent with the HTTP authentication callbacks. */ +typedef int (*nssl_key_prompt)(void *userdata, const char *filename, + char *buf, int buflen); + +void sock_set_key_prompt(nssl_context *c, + nssl_key_prompt prompt, void *userdata); + +/* For PEM-encoded client certificates: use the given client + * certificate and private key file. + * Returns: 0 if certificate is read okay, + * non-zero otherwise. + + * For decoding the private key file, the callback above will be used + * to prompt for the password. If no callback has been set, then the + * OpenSSL default will be used: the prompt appears on the terminal. + * */ +int sock_set_client_cert(nssl_context *ctx, const char *certfile, + const char *keyfile); + +void sock_disable_tlsv1(nssl_context *c); +void sock_disable_sslv2(nssl_context *c); +void sock_disable_sslv3(nssl_context *c); + +/* Ctx is OPTIONAL. If it is NULL, defaults are used. */ +int sock_make_secure(nsocket *sock, nssl_context *ctx); + +END_NEON_DECLS + +#endif /* NE_SOCKET_H */ diff --git a/neon/src/ne_string.c b/neon/src/ne_string.c new file mode 100644 index 000000000..b205eb31e --- /dev/null +++ b/neon/src/ne_string.c @@ -0,0 +1,469 @@ +/* + String utility functions + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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 "ne_alloc.h" +#include "ne_string.h" + +char *ne_token(char **str, char separator, const char *quotes) +{ + char *pnt, *ret = NULL; + + if (quotes) { + + 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; + break; + } + } + + } else { + pnt = strchr(*str, separator); + if (pnt != NULL) { + *pnt = '\0'; + ret = *str; + *str = pnt + 1; + } + } + + if (ret == NULL) { + /* 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 (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; + } + free(comps); + pairs[2*count] = pairs[2*count+1] = NULL; + return pairs; +} + +void split_string_free(char **components) +{ + char **pnt = components; + while (*pnt != NULL) { + free(*pnt); + pnt++; + } + free(components); +} + +void pair_string_free(char **pairs) +{ + int n; + for (n = 0; pairs[n] != NULL; n+=2) { + free(pairs[n]); + } + free(pairs); +} + +char *shave_string(const char *str, const char ch) +{ + size_t len = strlen(str); + char *ret; + if (str[len-1] == ch) { + len--; + } + if (str[0] == ch) { + len--; + str++; + } + ret = ne_malloc(len + 1); + memcpy(ret, str, len); + ret[len] = '\0'; + return ret; +} + +char *ne_concat(const char *str, ...) +{ + va_list ap; + ne_buffer *tmp = ne_buffer_create(); + + ne_buffer_zappend(tmp, str); + + va_start(ap, str); + ne_buffer_concat(tmp, ap); + va_end(ap); + + return ne_buffer_finish(tmp); +} + +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. */ +int ne_buffer_grow(ne_buffer *buf, size_t newsize) +{ + size_t newlen, oldbuflen; + +#define NE_BUFFER_GROWTH 512 + + if (newsize <= buf->length) return 0; /* big enough already */ + /* FIXME: ah, can't remember my maths... better way to do this? */ + newlen = ((newsize / NE_BUFFER_GROWTH) + 1) * NE_BUFFER_GROWTH; + + oldbuflen = buf->length; + /* Reallocate bigger buffer */ + buf->data = realloc(buf->data, newlen); + if (buf->data == NULL) return -1; + buf->length = newlen; + /* Zero-out the new bit of buffer */ + memset(buf->data+oldbuflen, 0, newlen-oldbuflen); + + return 0; +} + +int ne_buffer_concat(ne_buffer *buf, ...) +{ + va_list ap; + char *next; + size_t totallen = buf->used; + + /* Find out how much space we need for all the args */ + va_start(ap, buf); + do { + next = va_arg(ap, char *); + if (next != NULL) { + totallen += strlen(next); + } + } while (next != NULL); + va_end(ap); + + /* Grow the buffer */ + if (ne_buffer_grow(buf, totallen)) + return -1; + + /* Now append the arguments to the buffer */ + va_start(ap, buf); + do { + next = va_arg(ap, char *); + if (next != NULL) { + /* TODO: use stpcpy */ + strcat(buf->data, next); + } + } while (next != NULL); + va_end(ap); + + buf->used = totallen; + return 0; +} + +/* Append zero-terminated string... returns 0 on success or -1 on + * realloc failure. */ +int ne_buffer_zappend(ne_buffer *buf, const char *str) +{ + size_t len = strlen(str); + + if (ne_buffer_grow(buf, buf->used + len)) { + return -1; + } + strcat(buf->data, str); + buf->used += len; + return 0; +} + +int ne_buffer_append(ne_buffer *buf, const char *data, size_t len) +{ + if (ne_buffer_grow(buf, buf->used + len)) { + return -1; + } + memcpy(buf->data + buf->used - 1, data, len); + buf->used += len; + buf->data[buf->used - 1] = '\0'; + return 0; +} + +ne_buffer *ne_buffer_create(void) +{ + return ne_buffer_create_sized(512); +} + +ne_buffer *ne_buffer_create_sized(size_t s) +{ + ne_buffer *buf = ne_malloc(sizeof(struct ne_buffer_s)); + buf->data = ne_calloc(s); + buf->length = s; + buf->used = 1; + return buf; +} + +void ne_buffer_destroy(ne_buffer *buf) +{ + if (buf->data) { + free(buf->data); + } + free(buf); +} + +char *ne_buffer_finish(ne_buffer *buf) +{ + char *ret = buf->data; + free(buf); + return ret; +} + +void ne_buffer_altered(ne_buffer *buf) +{ + buf->used = strlen(buf->data) + 1; +} + +char *ne_utf8_encode(const char *str) +{ + char *buffer = ne_malloc(strlen(str) * 2 + 1); + int in, len = strlen(str); + char *out; + + for (in = 0, out = buffer; in < len; in++, out++) { + if ((unsigned char)str[in] <= 0x7D) { + *out = str[in]; + } else { + *out++ = 0xC0 | ((str[in] & 0xFC) >> 6); + *out = str[in] & 0xBF; + } + } + + /* nul-terminate */ + *out = '\0'; + return buffer; +} + +/* Single byte range 0x00 -> 0x7F */ +#define SINGLEBYTE_UTF8(ch) (((unsigned char) (ch)) < 0x80) + +char *ne_utf8_decode(const char *str) +{ + int n, m, len = strlen(str); + char *dest = ne_malloc(len + 1); + + for (n = 0, m = 0; n < len; n++, m++) { + if (SINGLEBYTE_UTF8(str[n])) { + dest[m] = str[n]; + } else { + /* We only deal with 8-bit data, which will be encoded as + * two bytes of UTF-8 */ + if ((len - n < 2) || (str[n] & 0xFC) != 0xC0) { + free(dest); + return NULL; + } else { + /* see hip_xml.c for comments. */ + dest[m] = ((str[n] & 0x03) << 6) | (str[n+1] & 0x3F); + n++; + } + } + } + + dest[m] = '\0'; + return dest; +} diff --git a/neon/src/ne_string.h b/neon/src/ne_string.h new file mode 100644 index 000000000..517bcfefc --- /dev/null +++ b/neon/src/ne_string.h @@ -0,0 +1,212 @@ +/* + String utility functions + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 + +/* Returns an ne_malloc-allocated UTF-8 encoded copy of 'str'. */ +char *ne_utf8_encode(const char *str); + +/* Returns an ne_malloc-allocated UTF-8 decode copy of 'str'. + * Returns NULL if any of the characters in 'str' are non-8-bit. + */ +char *ne_utf8_decode(const char *str); + +/* Simple string tokenizer. + * + * Returns the next token in *str between *str and 'separator' + * or NUL terminator, skipping over any parts quoted using + * a pair of any character found in 'quotes'. After returning, + * *str will point to the next character after the separator, + * or NULL if no more separator characters were found. + * + * quotes may be NULL. + * + * Returns NULL if unbalanced quotes are found. (this will not + * happen if quotes == NULL). + */ +char *ne_token(char **str, char separator, 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); + +/* Splits str into component parts using given seperator, + * skipping given whitespace around separators and at the + * beginning and end of str. Separators are ignored within + * any pair of characters specified as being quotes. + * Returns array of components followed by a NULL pointer. The + * components are taken from dynamically allocated memory, and + * the entire array can be easily freed using split_string_free. + * + * aka: What strtok should have been. + */ +char **split_string(const char *str, const char seperator, + const char *quotes, const char *whitespace); + +/* As above, but returns count of items as well */ +char **split_string_c(const char *str, const char seperator, + const char *quotes, const char *whitespace, int *count); + +/* A bit like split_string, except each component is split into a pair. + * Each pair is returned consecutively in the return array. + * e.g.: + * strpairs("aa=bb,cc=dd", ',', '=', NULL, NULL) + * => "aa", "bb", "cc", "dd", NULL, NULL + * Note, that if a component is *missing* it's key/value separator, + * then the 'value' in the array will be a NULL pointer. But, if the + * value is zero-length (i.e., the component separator follows directly + * after the key/value separator, or with only whitespace inbetween), + * then the value in the array will be a zero-length string. + * e.g.: + * pair_string("aaaa,bb=cc,dd=,ee=ff", ',', '=', NULL, NULL) + * => "aaaa", NULL, "bb", "cc", "dd", "", "ee", "ff" + * A NULL key indicates the end of the array (the value will also + * be NULL, for convenience). + */ +char **pair_string(const char *str, const char compsep, const char kvsep, + const char *quotes, const char *whitespace); + +/* Frees the array returned by split_string */ +void split_string_free(char **components); + +/* Frees the array returned by pair_string */ +void pair_string_free(char **pairs); + +/* Returns a string which is str with ch stripped from + * beggining and end, if present in either. The string returned + * is dynamically allocated using malloc(). + * + * e.g. shave_string("abbba", 'a') => "bbb". */ +char *shave_string(const char *str, const char ch); + +#define EOL "\r\n" + +#define STRIP_EOL(str) \ +do { \ + char *p; \ + if ((p = strrchr(str, '\r')) != NULL) *p = '\0'; \ + if ((p = strrchr(str, '\n')) != NULL) *p = '\0'; \ +} while (0) + +/* Return concatenation of all given strings. */ +char *ne_concat(const char *str, ...); + +/* String buffer handling. (Strings are zero-terminated still). + * A string buffer sbuffer * which grows dynamically with the string. */ + +struct ne_buffer_s { + char *data; /* contents: null-terminated string. */ + size_t used; /* used bytes in buffer */ + size_t length; /* length of buffer */ +}; + +typedef struct ne_buffer_s 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 be null-terminated, and MUST be followed by a + * NULL argument marking the end of the list. + * Returns: + * 0 on success + * non-zero on error + */ +int ne_buffer_concat(ne_buffer *buf, ...); + +/* Create a new ne_buffer. Returns NULL on error */ +ne_buffer *ne_buffer_create(void); + +/* Create a new ne_buffer of given minimum size. Returns NULL on error */ +ne_buffer *ne_buffer_create_sized(size_t size); + +/* Destroys (deallocates) a buffer */ +void ne_buffer_destroy(ne_buffer *buf); + +/* Append a zero-terminated string 'str' to buf. + * Returns 0 on success, non-zero on error. */ +int ne_buffer_zappend(ne_buffer *buf, const char *str); + +/* Append 'len' bytes of 'data' to buf. 'data' does not need to be + * zero-terminated. The resultant string will have a zero-terminator, + * either way. Returns 0 on success, non-zero on error. */ +int 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. + * Returns 0 on success, non-zero on error */ +int 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); + +/* TODO: do these with stpcpy instead... more efficient, but means + * bloat on non-GNU platforms. */ + +/* TODO: could replace with glib equiv's where available, too */ + +/* NOTES: + * - These abort() on malloc() returning NULL + * - You will need to #include <string.h> / <strings.h> YOURSELF to + * prototype strlen and strcat. + */ + +#define CONCAT2(out, str1, str2) \ +do { \ + out = ne_malloc(strlen(str1) + strlen(str2) + 1); \ + strcpy(out, str1); \ + strcat(out, str2); \ +} while (0) + +#define CONCAT3(out, str1, str2, str3) \ +do { \ + out = ne_malloc(strlen(str1) + strlen(str2) + strlen(str3) + 1); \ + strcpy(out, str1); \ + strcat(out, str2); \ + strcat(out, str3); \ +} while (0) + +#define CONCAT4(out, str1, str2, str3, str4) \ +do { \ + out = ne_malloc(strlen(str1) + strlen(str2) \ + + strlen(str3) + strlen(str4) + 1); \ + strcpy(out, str1); \ + strcat(out, str2); \ + strcat(out, str3); \ + strcat(out, str4); \ +} while (0) + +END_NEON_DECLS + +#endif /* NE_STRING_H */ diff --git a/neon/src/ne_uri.c b/neon/src/ne_uri.c new file mode 100644 index 000000000..fbc2a8d0d --- /dev/null +++ b/neon/src/ne_uri.c @@ -0,0 +1,313 @@ +/* + HTTP URI handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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_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 CONCAT3 */ +#include "ne_alloc.h" +#include "ne_uri.h" + +char *uri_parent(const char *uri) +{ + const char *pnt; + char *ret; + pnt = uri+strlen(uri)-1; + while (*(--pnt) != '/' && pnt >= uri) /* noop */; + if (pnt < uri) { + /* not a valid absPath */ + return NULL; + } + /* uri + * V + * |---| + * /foo/bar/ + */ + ret = ne_malloc((pnt - uri) + 2); + memcpy(ret, uri, (pnt - uri) + 1); + ret[1+(pnt-uri)] = '\0'; + pnt++; + return ret; +} + +int uri_has_trailing_slash(const char *uri) +{ + size_t len = strlen(uri); + return ((len > 0) && + (uri[len-1] == '/')); +} + +const char *uri_abspath(const char *uri) +{ + const char *ret; + /* Look for the scheme: */ + ret = strstr(uri, "://"); + if (ret == NULL) { + /* No scheme */ + ret = uri; + } else { + /* Look for the abs_path */ + ret = strchr(ret+3, '/'); + if (ret == NULL) { + /* Uh-oh */ + ret = uri; + } + } + return ret; +} + +/* TODO: not a proper URI parser */ +int uri_parse(const char *uri, struct uri *parsed, + const struct uri *defaults) +{ + const char *pnt, *slash, *colon, *atsign; + + parsed->port = -1; + parsed->host = NULL; + parsed->path = NULL; + parsed->scheme = NULL; + parsed->authinfo = NULL; + + if (strlen(uri) == 0) { + return -1; + } + + pnt = strstr(uri, "://"); + if (pnt) { + parsed->scheme = ne_strndup(uri, pnt - uri); + pnt += 3; /* start of hostport segment */ + } else { + pnt = uri; + if (defaults && defaults->scheme != NULL) { + parsed->scheme = ne_strdup(defaults->scheme); + } + } + + atsign = strchr(pnt, '@'); + slash = 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; + } + + colon = strchr(pnt, ':'); + + if (slash == NULL) { + parsed->path = ne_strdup("/"); + if (colon == NULL) { + if (defaults) parsed->port = defaults->port; + 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 (defaults) parsed->port = defaults->port; + 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 uri_free(struct uri *uri) +{ + NE_FREE(uri->host); + NE_FREE(uri->path); + NE_FREE(uri->scheme); + NE_FREE(uri->authinfo); +} + +/* Returns an absoluteURI */ +char *uri_absolute(const char *uri, const char *scheme, + const char *hostport) +{ + char *ret; + /* Is it absolute already? */ + if (strncmp(uri, scheme, strlen(scheme)) == 0) { + /* Yes it is */ + ret = ne_strdup(uri); + } else { + /* Oh no it isn't */ + CONCAT3(ret, scheme, hostport, uri); + } + return ret; +} + +/* Un-escapes a URI. Returns ne_malloc-allocated URI */ +char *uri_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 */ + 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 + +/* Escapes the abspath segment of a URI. + * Returns ne_malloc-allocated string. + */ +char *uri_abspath_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 + +/* TODO: implement properly */ +int uri_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 = uri_has_trailing_slash(a), + trailb = uri_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; +} + +/* Give it a path segment, it returns non-zero if child is + * a child of parent. */ +int uri_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 = (uri_compare(parent, root) == 0); + } + free(root); + return ret; +} diff --git a/neon/src/ne_uri.h b/neon/src/ne_uri.h new file mode 100644 index 000000000..12cb49728 --- /dev/null +++ b/neon/src/ne_uri.h @@ -0,0 +1,75 @@ +/* + HTTP URI handling + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 URI. Returns malloc-allocated URI on success, + * or NULL on failure (malloc failure or invalid %<HEX><HEX> sequence). */ +char *uri_unescape(const char *uri); + +/* Escapes the abspath segment of a URI. + * Returns malloc-allocated string on success, or NULL on malloc failure. + */ +char *uri_abspath_escape(const char *abs_path); + +/* Returns abspath segment in (absolute) uri */ +const char *uri_abspath(const char *uri); + +/* Returns parent of path */ +char *uri_parent(const char *path); + +/* Returns strcmp-like value giving comparison between a and b, + * ignoring trailing-slashes. */ +int uri_compare(const char *a, const char *b); + +/* Returns an absolute URI from a possibly-relative 'uri', using + * given scheme + hostport segment. + * Returns malloc-allocated string on success, or NULL on malloc failure. */ +char *uri_absolute(const char *uri, const char *scheme, const char *hostport); + +/* Returns non-zero if child is a child of parent */ +int uri_childof(const char *parent, const char *child); + +/* Returns non-zero if uri has a trailing slash character */ +int uri_has_trailing_slash(const char *uri); + +struct uri { + char *scheme; + char *host; + int port; + char *path; + char *authinfo; +}; + +/* Parse 'uri' and place parsed segments in *parsed. */ +int uri_parse(const char *uri, struct uri *parsed, const struct uri *defaults); + +void uri_free(struct uri *parsed); + +END_NEON_DECLS + +#endif /* NE_URI_H */ + diff --git a/neon/src/ne_utils.c b/neon/src/ne_utils.c new file mode 100644 index 000000000..fe6afa87b --- /dev/null +++ b/neon/src/ne_utils.c @@ -0,0 +1,160 @@ +/* + HTTP utility functions + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 <ctype.h> /* isdigit() for ne_parse_statusline */ + +#include "ne_utils.h" + +#include "ne_dates.h" + +#ifdef ENABLE_SSL +#include <openssl/opensslv.h> +#endif + +#ifdef HAVE_XMLVERSION_H +/* libxml2: pick up the version string. */ +#include <xmlversion.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; +} + +void ne_debug(int ch, const char *template, ...) +{ + va_list params; + if ((ch&ne_debug_mask) != ch) 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); + } +} + +static const char *version_string = "neon " NEON_VERSION ": " +#ifdef NEON_IS_LIBRARY + "Library build" +#else + "Bundled build" +#endif +#ifdef HAVE_EXPAT + ", Expat" +#else +#ifdef HAVE_LIBXML + ", libxml" +#ifdef LIBXML_DOTTED_VERSION + " " LIBXML_DOTTED_VERSION +#endif +#endif +#endif +#ifdef ENABLE_SSL + ", SSL support using " +#ifdef OPENSSL_VERSION_TEXT + OPENSSL_VERSION_TEXT +#else + "unknown SSL library" +#endif /* OPENSSL_VERSION_TEXT */ +#else /* !ENABLE_SSL */ + ", no OpenSSL support" +#endif /* ENABLE_SSL */ + "." +; + +const char *ne_version_string(void) +{ + return version_string; +} + +int ne_version_minimum(int major, int minor) +{ + return + (NEON_VERSION_MAJOR < major) || + (NEON_VERSION_MINOR < minor); +} + +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] != ' ') { + 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 */; + if (*part == '\0') { + return -1; + } + /* Fill in the results */ + st->major_version = major; + st->minor_version = minor; + st->reason_phrase = part; + st->code = status_code; + st->klass = klass; + return 0; +} diff --git a/neon/src/ne_utils.h b/neon/src/ne_utils.h new file mode 100644 index 000000000..f269a681f --- /dev/null +++ b/neon/src/ne_utils.h @@ -0,0 +1,133 @@ +/* + HTTP utility functions + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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" + +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 the neon API compiled in is less than + * major.minor. i.e. + * I am: 1.2 - neon_version_check(1, 3) => -1 + * I am: 0.10 - neon_version_check(0, 9) => 0 + */ +int ne_version_minimum(int major, int minor); + +#define HTTP_QUOTES "\"'" +#define HTTP_WHITESPACE " \r\n\t" + +#ifndef WIN32 +#undef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#ifdef WIN32 +/* some of the API uses ssize_t so we need to define it. */ +#define ssize_t int +#endif + +/* CONSIDER: mutt has a nicer way of way of doing debugging output... maybe + * switch to like that. */ + +#ifdef __GNUC__ +/* really, we want to do this if we have any C99-capable compiler, so + * what's a better test? */ + +#ifndef NE_DEBUGGING +#define NE_DEBUG(x, fmt, args...) +#else +#define NE_DEBUG(x, fmt, args...) do { \ + if (((x)&ne_debug_mask) == (x)) { \ + fflush(stdout); \ + fprintf(ne_debug_stream, fmt, ##args); \ + if (((x) & NE_DBG_FLUSH) == NE_DBG_FLUSH) \ + fflush(ne_debug_stream); \ + } \ +} while (0) +#endif + +#else /* !__GNUC__ */ + +#ifndef NE_DEBUGGING +#define NE_DEBUG if (0) ne_debug +#else /* DEBUGGING */ +#define NE_DEBUG ne_debug +#endif /* DEBUGGING */ + +#endif + +#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_HTTPBASIC (1<<8) +#define NE_DBG_FLUSH (1<<30) + +void ne_debug_init(FILE *stream, int mask); +extern int ne_debug_mask; +extern FILE *ne_debug_stream; + +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 */ + /* We can't use 'class' as the member name since this crashes + * with the C++ reserved keyword 'class', annoyingly. + * This was '_class' previously, but that was even MORE annoying. + * So know it is klass. */ + int klass; /* Class of Status-Code (1-5) */ + const char *reason_phrase; +} ne_status; + +/* Parser for strings which follow the Status-Line grammar from + * RFC2616. + * 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/neon/src/ne_xml.c b/neon/src/ne_xml.c new file mode 100644 index 000000000..0920c1fa9 --- /dev/null +++ b/neon/src/ne_xml.c @@ -0,0 +1,862 @@ +/* + Higher Level Interface to XML Parsers. + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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_i18n.h" + +#include "ne_alloc.h" +#include "ne_xml.h" +#include "ne_utils.h" +#include "ne_string.h" + +#ifdef HAVE_EXPAT +/******** Expat ***********/ +#ifdef HAVE_OLD_EXPAT +/* jclark expat */ +#include "xmlparse.h" +#else /* not HAVE_OLD_EXPAT */ +/* new-style expat */ +#include "expat.h" +#endif + +typedef XML_Char ne_xml_char; + +#else /* not HAVE_EXPAT */ +# ifdef HAVE_LIBXML + +#ifdef HAVE_XMLVERSION_H +/* libxml2: pick up the version string. */ +#include <xmlversion.h> +#endif + +/******** libxml **********/ +# include <parser.h> +typedef xmlChar ne_xml_char; + +# else /* not HAVE_LIBXML */ +# error need an XML parser +# endif /* not HAVE_LIBXML */ +#endif /* not HAVE_EXPAT */ + +/* Approx. one screen of text: */ +#define ERR_SIZE (2048) + +/* A list of elements */ +struct ne_xml_handler { + const struct ne_xml_elm *elements; /* put it in static memory */ + ne_xml_validate_cb validate_cb; /* validation function */ + ne_xml_startelm_cb startelm_cb; /* on-complete element function */ + ne_xml_endelm_cb endelm_cb; /* on-complete element function */ + ne_xml_cdata_cb cdata_cb; /* cdata callback for mixed mode */ + void *userdata; + struct ne_xml_handler *next; +}; + +#ifdef HAVE_LIBXML +static void sax_error(void *ctx, const char *msg, ...); +#endif + +struct ne_xml_state { + /* The element details */ + const struct ne_xml_elm *elm; + + /* Storage for an unknown element */ + struct ne_xml_elm elm_real; + char *real_name; + + /* Namespaces declared in this element */ + ne_xml_char *default_ns; /* A default namespace */ + struct ne_xml_nspace *nspaces; /* List of other namespace scopes */ + + unsigned int mixed:1; /* are we in MIXED mode? */ + + /* Extras */ + struct ne_xml_handler *handler; /* Where the element was declared */ + struct ne_xml_state *parent; /* The parent in the tree */ +}; + + +/* TODO: + * could move 'valid' into state, maybe allow optional + * continuation past an invalid branch. + */ + +/* 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 ne_xml_state *root; /* the root of the document */ + struct ne_xml_state *current; /* current element in the branch */ + ne_buffer *buffer; /* the CDATA/collect buffer */ + unsigned int valid:1; /* currently valid? */ + unsigned int want_cdata:1; /* currently collecting CDATA? */ + unsigned int collect; /* current collect depth */ + struct ne_xml_handler *top_handlers; /* always points at the + * handler on top of the stack. */ +#ifdef HAVE_EXPAT + XML_Parser parser; +#else + xmlParserCtxtPtr parser; +#endif + char error[ERR_SIZE]; +}; + +static void destroy_state(struct ne_xml_state *s); + +static const char *friendly_name(const struct ne_xml_elm *elm) +{ + switch(elm->id) { + case NE_ELM_root: + return _("document root"); + case NE_ELM_unknown: + return _("unknown element"); + default: + if (elm->name) { + return elm->name; + } else { + return _("unspecified"); + } + } +} + +const static struct ne_xml_elm root_element = +{ "@<root>@", NE_ELM_root, 0 }; + +/* 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); + +#define NE_XML_DECODE_UTF8 + +#ifdef NE_XML_DECODE_UTF8 + +/* UTF-8 decoding */ + +/* Single byte range 0x00 -> 0x7F */ +#define SINGLEBYTE_UTF8(ch) (((unsigned char) (ch)) < 0x80) + +/* Decode a double byte UTF8 string. + * Returns 0 on success or non-zero on error. */ +static inline int decode_utf8_double(char *dest, const char *src); + +#endif + +/* Linked list of namespace scopes */ +struct ne_xml_nspace { + ne_xml_char *name; + ne_xml_char *uri; + struct ne_xml_nspace *next; +}; + +/* And an auxiliary */ +static int parse_element(ne_xml_parser *p, struct ne_xml_state *state, + const ne_xml_char *name, const ne_xml_char **atts); + +#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 */ + NULL, /* xmlParserError */ + NULL, /* getParameterEntity */ + char_data /* cdataBlock */ +}; + +#endif /* HAVE_LIBXML */ + +#ifdef NE_XML_DECODE_UTF8 + +static inline int +decode_utf8_double(char *dest, const char *src) +{ + /* From utf-8 man page; two-byte encoding is: + * 0x00000080 - 0x000007FF: + * 110xxxxx 10xxxxxx + * If more than 8-bits of those x's are set, we fail. + * So, we check that the first 6 bits of the first byte are: + * 110000. + * Then decode like: + * 110000xx 10yyyyyy -> xxyyyyyy + * Do this with a mask and a compare: + * zzzzzzzz + * & 11111100 <=> 0xFC + * == 11000000 <=> 0xC0 + * + * joe: A real C hacker would probably do some funky bit + * inversion, and turn this into an is-not-zero test, + * but I'm a fake, so... + */ + if ((src[0] & 0xFC) == 0xC0) { + dest[0] = ((src[0] & 0x03) << 6) | (src[1] & 0x3F); + /* nb. + * 00000011 = 0x03 + * 00111111 = 0x3F + */ + return 0; + } else { + return -1; + } +} + +#endif + +int ne_xml_currentline(ne_xml_parser *p) +{ +#ifdef HAVE_EXPAT + return XML_GetCurrentLineNumber(p->parser); +#else + return p->parser->input->line; +#endif +} + +static int find_handler(ne_xml_parser *p, struct ne_xml_state *state) +{ + struct ne_xml_handler *cur, *unk_handler = NULL; + const char *name = state->elm_real.name, *nspace = state->elm_real.nspace; + int n, got_unknown = 0; + + for (cur = state->parent->handler; cur != NULL; cur = cur->next) { + for (n = 0; (cur->elements[n].nspace != NULL || ( + cur->elements[n].nspace == NULL && + cur->elements[n].id == NE_ELM_unknown)); n++) { + if (cur->elements[n].nspace != NULL && + (strcasecmp(cur->elements[n].name, name) == 0 && + strcasecmp(cur->elements[n].nspace, nspace) == 0)) { + + switch ((*cur->validate_cb)(state->parent->elm->id, cur->elements[n].id)) { + case NE_XML_VALID: + NE_DEBUG(NE_DBG_XML, "Validated by handler.\n"); + state->handler = cur; + state->elm = &cur->elements[n]; + return 0; + case NE_XML_INVALID: + NE_DEBUG(NE_DBG_XML, "Invalid context.\n"); + snprintf(p->error, ERR_SIZE, + _("XML is not valid (%s found in parent %s)"), + friendly_name(&cur->elements[n]), + friendly_name(state->parent->elm)); + return -1; + default: + /* ignore it */ + NE_DEBUG(NE_DBG_XML, "Declined by handler.\n"); + break; + } + } + if (!got_unknown && cur->elements[n].id == NE_ELM_unknown) { + switch ((*cur->validate_cb)(state->parent->elm->id, NE_ELM_unknown)) { + case NE_XML_VALID: + unk_handler = cur; + got_unknown = 1; + state->elm_real.id = NE_ELM_unknown; + state->elm_real.flags = cur->elements[n].flags; + break; + case NE_XML_INVALID: + NE_DEBUG(NE_DBG_XML, "Invalid context.\n"); + snprintf(p->error, ERR_SIZE, + _("XML is not valid (%s found in parent %s)"), + friendly_name(&cur->elements[n]), + friendly_name(state->parent->elm)); + return -1; + default: + NE_DEBUG(NE_DBG_XML, "Declined by handler.\n"); + break; + } + } + } + } + if (!cur && got_unknown) { + /* Give them the unknown handler */ + NE_DEBUG(NE_DBG_XMLPARSE, "Using unknown element handler\n"); + state->handler = unk_handler; + state->elm = &state->elm_real; + return 0; + } else { + NE_DEBUG(NE_DBG_XMLPARSE, "Unexpected element\n"); + snprintf(p->error, ERR_SIZE, + _("Unknown XML element `%s (in %s)'"), name, nspace); + return -1; + } +} + +/* 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 ne_xml_state *s; + + if (!p->valid) { + /* We've stopped parsing */ + NE_DEBUG(NE_DBG_XML, "Parse died. Ignoring start of element: %s\n", name); + return; + } + + /* If we are in collect mode, print the element to the buffer */ + if (p->collect) { + /* In Collect Mode. */ + const ne_xml_char *pnt = strchr(name, ':'); + if (pnt == NULL) { + pnt = name; + } else { + pnt++; + } + ne_buffer_concat(p->buffer, "<", pnt, NULL); + if (atts != NULL) { + int n; + for (n = 0; atts[n] != NULL; n+=2) { + ne_buffer_concat(p->buffer, " ", atts[n], "=\"", atts[n+1], "\"", + NULL); + } + } + ne_buffer_zappend(p->buffer, ">"); + /* One deeper */ + p->collect++; + return; + } + + /* Set the new state */ + s = ne_calloc(sizeof(struct ne_xml_state)); + s->parent = p->current; + p->current = s; + + /* We need to handle namespaces ourselves */ + if (parse_element(p, s, name, atts)) { + /* it bombed. */ + p->valid = 0; + return; + } + + /* Map the element name to an id */ + NE_DEBUG(NE_DBG_XML, "Mapping element name %s@@%s... ", + s->elm_real.nspace, s->elm_real.name); + + if (find_handler(p, s)) { + p->valid = 0; + return; + } + + NE_DEBUG(NE_DBG_XMLPARSE, "mapped to id %d\n", s->elm->id); + + /* Do we want cdata? */ + p->want_cdata = ((s->elm->flags & NE_XML_CDATA) == NE_XML_CDATA); + p->collect = ((s->elm->flags & NE_XML_COLLECT) == NE_XML_COLLECT); + + /* Is this element using mixed-mode? */ + s->mixed = ((s->elm->flags & NE_XML_MIXED) == NE_XML_MIXED); + + if (s->handler->startelm_cb) { + if ((*s->handler->startelm_cb)(s->handler->userdata, s->elm, + (const char **) atts)) { + NE_DEBUG(NE_DBG_XML, "Startelm callback failed.\n"); + p->valid = 0; + } + } else { + NE_DEBUG(NE_DBG_XML, "No startelm handler.\n"); + } + +} + +/* Destroys given state */ +static void destroy_state(struct ne_xml_state *s) +{ + struct ne_xml_nspace *this_ns, *next_ns; + NE_DEBUG(NE_DBG_XMLPARSE, "Freeing namespaces...\n"); + NE_FREE(s->default_ns); + NE_FREE(s->real_name); + /* Free the namespaces */ + this_ns = s->nspaces; + while (this_ns != NULL) { + next_ns = this_ns->next; + free(this_ns->name); + free(this_ns->uri); + free(this_ns); + this_ns = next_ns; + }; + NE_DEBUG(NE_DBG_XMLPARSE, "Finished freeing namespaces.\n"); + free(s); +} + +static void char_data(void *userdata, const ne_xml_char *data, int len) +{ + ne_xml_parser *p = userdata; + + if (p->current->mixed) { + (*p->current->handler->cdata_cb)( + p->current->handler->userdata, p->current->elm, data, len); + return; + } + + if (!p->want_cdata || !p->valid) return; + /* First, if this is the beginning of the CDATA, skip all + * leading whitespace, we don't want it. */ + NE_DEBUG(NE_DBG_XMLPARSE, "Given %d bytes of cdata.\n", len); + if (ne_buffer_size(p->buffer) == 0) { + int wslen = 0; + /* Ignore any leading whitespace */ + while (wslen < len && + (data[wslen] == ' ' || data[wslen] == '\r' || + data[wslen] == '\n' || data[wslen] == '\t')) { + wslen++; + } + data += wslen; + len -= wslen; + NE_DEBUG(NE_DBG_XMLPARSE, "Skipped %d bytes of leading whitespace.\n", + wslen); + if (len == 0) { + NE_DEBUG(NE_DBG_XMLPARSE, "Zero bytes of content.\n"); + return; + } + } + +#ifdef NE_XML_DECODE_UTF8 + + if ((p->current->elm->flags & NE_XML_UTF8DECODE) == NE_XML_UTF8DECODE) { + int n, m, clen; + char *dest; + + clen = ne_buffer_size(p->buffer); + ne_buffer_grow(p->buffer, clen + len + 1); + dest = p->buffer->data + clen; + +/* #define TOO_MUCH_DEBUG 1 */ + for (n = 0, m = 0; n < len; n++, m++) { +#ifdef TOO_MUCH_DEBUG + NE_DEBUG(NE_DBG_XML, "decoding 0x%02x", 0xFF & data[n]); +#endif + if (SINGLEBYTE_UTF8(data[n])) { + dest[m] = data[n]; + } else { + /* An optimisation here: we only deal with 8-bit + * data, which will be encoded as two bytes of UTF-8 */ + if ((len - n < 2) || + decode_utf8_double(&dest[m], &data[n])) { + /* Failed to decode! */ + NE_DEBUG(NE_DBG_XML, "Could not decode UTF-8 data.\n"); + strcpy(p->error, "XML parser received non-8-bit data"); + p->valid = 0; + return; + } else { +#ifdef TOO_MUCH_DEBUG + NE_DEBUG(NE_DBG_XML, "UTF-8 two-bytes decode: " + "0x%02hx 0x%02hx -> 0x%02hx!\n", + data[n] & 0xFF, data[n+1] & 0xFF, dest[m] & 0xFF); +#endif + /* Skip the second byte */ + n++; + } + } + } + ne_buffer_altered(p->buffer); + } else { + ne_buffer_append(p->buffer, data, len); + } + +#else /* !NE_XML_DECODE_UTF8 */ + + ne_buffer_append(p->buffer, data, len); + +#endif + +} + +/* Called with the end of an element */ +static void end_element(void *userdata, const ne_xml_char *name) +{ + ne_xml_parser *p = userdata; + struct ne_xml_state *s = p->current; + if (!p->valid) { + /* We've stopped parsing */ + NE_DEBUG(NE_DBG_XML, "Parse died. Ignoring end of element: %s\n", name); + return; + } + if (p->collect > 0) { + if (--p->collect) { + const ne_xml_char *pnt = strchr(name, ':'); + if (pnt == NULL) { + pnt = name; + } else { + pnt++; + } + ne_buffer_concat(p->buffer, "</", pnt, ">", NULL); + return; + } + } + + /* process it */ + if (s->handler->endelm_cb) { + NE_DEBUG(NE_DBG_XMLPARSE, "Calling endelm callback for %s.\n", s->elm->name); + if ((*s->handler->endelm_cb)(s->handler->userdata, s->elm, + p->want_cdata?p->buffer->data:NULL)) { + NE_DEBUG(NE_DBG_XML, "Endelm callback failed.\n"); + p->valid = 0; + } + } + p->current = s->parent; + /* Move the current pointer up the branch */ + NE_DEBUG(NE_DBG_XML, "Back in element: %s\n", friendly_name(p->current->elm)); + if (p->want_cdata) { + ne_buffer_clear(p->buffer); + } + destroy_state(s); +} + +/* Parses the attributes, and handles XML namespaces. + * With a little bit of luck. + * Returns: + * the element name on success + * or NULL on error. + */ +static int parse_element(ne_xml_parser *p, struct ne_xml_state *state, + const ne_xml_char *name, const ne_xml_char **atts) +{ + struct ne_xml_nspace *ns; + const ne_xml_char *pnt; + struct ne_xml_state *xmlt; + + NE_DEBUG(NE_DBG_XMLPARSE, "Parsing elm of name: [%s]\n", name); + /* Parse the atts for namespace declarations... if we have any atts. + * expat will never pass us atts == NULL, but libxml will. */ + if (atts != NULL) { + int attn; + for (attn = 0; atts[attn]!=NULL; attn+=2) { + NE_DEBUG(NE_DBG_XMLPARSE, "Got attribute: [%s] = [%s]\n", atts[attn], atts[attn+1]); + if (strcasecmp(atts[attn], "xmlns") == 0) { + /* New default namespace */ + state->default_ns = ne_strdup(atts[attn+1]); + NE_DEBUG(NE_DBG_XMLPARSE, "New default namespace: %s\n", + state->default_ns); + } else if (strncasecmp(atts[attn], "xmlns:", 6) == 0) { + /* New namespace scope */ + ns = ne_calloc(sizeof(struct ne_xml_nspace)); + ns->next = state->nspaces; + state->nspaces = ns; + ns->name = ne_strdup(atts[attn]+6); /* skip the xmlns= */ + ns->uri = ne_strdup(atts[attn+1]); + NE_DEBUG(NE_DBG_XMLPARSE, "New namespace scope: %s -> %s\n", + ns->name, ns->uri); + } + } + } + /* Now check the elm name for a namespace scope */ + pnt = strchr(name, ':'); + if (pnt == NULL) { + /* No namespace prefix - have we got a default? */ + state->real_name = ne_strdup(name); + NE_DEBUG(NE_DBG_XMLPARSE, "No prefix found, searching for default.\n"); + for (xmlt = state; xmlt!=NULL; xmlt=xmlt->parent) { + if (xmlt->default_ns != NULL) { + state->elm_real.nspace = xmlt->default_ns; + break; + } + } + if (state->elm_real.nspace == NULL) { + NE_DEBUG(NE_DBG_XMLPARSE, "No default namespace, using empty.\n"); + state->elm_real.nspace = ""; + } + } else { + NE_DEBUG(NE_DBG_XMLPARSE, "Got namespace scope. Trying to resolve..."); + /* Have a scope - resolve it */ + for (xmlt = state; state->elm_real.nspace==NULL && xmlt!=NULL; xmlt=xmlt->parent) { + for (ns = xmlt->nspaces; ns!=NULL; ns=ns->next) { + /* Just compare against the bit before the : + * pnt points to the colon. */ + if (strncasecmp(ns->name, name, pnt-name) == 0) { + /* Scope matched! Hoorah */ + state->elm_real.nspace = ns->uri; + /* end the search */ + break; + } + } + } + if (state->elm_real.nspace != NULL) { + NE_DEBUG(NE_DBG_XMLPARSE, "Resolved prefix to [%s]\n", state->elm_real.nspace); + /* The name is everything after the ':' */ + if (pnt[1] == '\0') { + snprintf(p->error, ERR_SIZE, + "Element name missing in '%s' at line %d.", + name, ne_xml_currentline(p)); + NE_DEBUG(NE_DBG_XMLPARSE, "No element name after ':'. Failed.\n"); + return -1; + } + state->real_name = ne_strdup(pnt+1); + } else { + NE_DEBUG(NE_DBG_XMLPARSE, "Undeclared namespace.\n"); + snprintf(p->error, ERR_SIZE, + "Undeclared namespace in '%s' at line %d.", + name, ne_xml_currentline(p)); + return -1; + } + } + state->elm_real.name = state->real_name; + return 0; +} + +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(struct ne_xml_state)); + p->root->elm = &root_element; + /* Initialize the cdata buffer */ + p->buffer = ne_buffer_create(); +#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); +#else + p->parser = xmlCreatePushParserCtxt(&sax_handler, + (void *)p, NULL, 0, NULL); + if (p->parser == NULL) { + abort(); + } +#endif + return p; +} + +static void push_handler(ne_xml_parser *p, + struct ne_xml_handler *handler) +{ + + /* If this is the first handler registered, update the + * base pointer too. */ + if (p->top_handlers == NULL) { + p->root->handler = handler; + p->top_handlers = handler; + } else { + p->top_handlers->next = handler; + p->top_handlers = handler; + } +} + +void ne_xml_push_handler(ne_xml_parser *p, + const struct ne_xml_elm *elements, + ne_xml_validate_cb validate_cb, + ne_xml_startelm_cb startelm_cb, + ne_xml_endelm_cb endelm_cb, + void *userdata) +{ + struct ne_xml_handler *hand = ne_calloc(sizeof(struct ne_xml_handler)); + + hand->elements = elements; + hand->validate_cb = validate_cb; + hand->startelm_cb = startelm_cb; + hand->endelm_cb = endelm_cb; + hand->userdata = userdata; + + push_handler(p, hand); +} + +void ne_xml_push_mixed_handler(ne_xml_parser *p, + const struct ne_xml_elm *elements, + ne_xml_validate_cb validate_cb, + ne_xml_startelm_cb startelm_cb, + ne_xml_cdata_cb cdata_cb, + ne_xml_endelm_cb endelm_cb, + void *userdata) +{ + struct ne_xml_handler *hand = ne_calloc(sizeof *hand); + + hand->elements = elements; + hand->validate_cb = validate_cb; + hand->startelm_cb = startelm_cb; + hand->cdata_cb = cdata_cb; + hand->endelm_cb = endelm_cb; + hand->userdata = userdata; + + push_handler(p, 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 %d 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 %d 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) { + 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); + if (p->parser->errNo && p->valid) { + /* FIXME: error handling */ + 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 ne_xml_state *s, *parent; + struct ne_xml_handler *hand, *next; + + ne_buffer_destroy(p->buffer); + + /* 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; + free(hand); + } + + /* Clean up any states which may remain. + * If p.valid, then this should be only the root element. */ + for (s = p->current; s!=NULL; s=parent) { + parent = s->parent; + destroy_state(s); + } + +#ifdef HAVE_EXPAT + XML_ParserFree(p->parser); +#else + xmlFreeParserCtxt(p->parser); +#endif + + free(p); +} + +void ne_xml_set_error(ne_xml_parser *p, const char *msg) +{ + 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); + vsnprintf(buf, 1024, msg, ap); + va_end(ap); + + 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(const char **attrs, const char *name) +{ + int n; + +#ifdef HAVE_LIBXML + /* libxml passes a NULL attribs array to the startelm callback if + * there are no attribs on the element. expat passes a + * zero-length array. */ + if (attrs == NULL) { + return NULL; + } +#endif + + for (n = 0; attrs[n] != NULL; n += 2) { + if (strcmp(attrs[n], name) == 0) { + return attrs[n+1]; + } + } + + return NULL; +} diff --git a/neon/src/ne_xml.h b/neon/src/ne_xml.h new file mode 100644 index 000000000..2d2c36585 --- /dev/null +++ b/neon/src/ne_xml.h @@ -0,0 +1,184 @@ +/* + Higher Level Interface to XML Parsers. + Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com> + + 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 + +BEGIN_NEON_DECLS + +/* Generic XML parsing interface... + + Definitions: + + * A handler knows how to parse a certain set of XML elements. + * The handler stack + + * + + + Basic principle is that you provide these things: + 1) a list of elements which you wish to handle + 2) a validation callback which tells the parser whether you + want to handle this element. + 3) a callback function which is called when a complete element + has been parsed (provided you are handling the element). + + This code then deals with the boring stuff like: + - Dealing with XML namespaces + - Collecting CDATA + + The element list is stored in a 'ne_xml_elm' array. For each + element, you specify the element name, an element id, and some + flags for that element. + +const static struct ne_xml_elm[] = { + { "DAV:", "resourcetype", ELM_resourcetype, 0 }, + { "DAV:", "collection", ELM_collection, NE_ELM_CDATA }, + { NULL } +}; + + This list contains two elements, resourcetype and collection, both in + the "DAV:" namespace. The collection element can contain cdata. + +*/ + +/* Reserved element id's */ +#define NE_ELM_unknown -1 +#define NE_ELM_root 0 + +#define NE_ELM_UNUSED (100) + +typedef int ne_xml_elmid; + +struct ne_xml_elm; + +/* An element */ +struct ne_xml_elm { + const char *nspace, *name; + ne_xml_elmid id; + unsigned int flags; +}; + +/* Function to check element context... + This callback must return: + NE_XML_VALID -> + Yes, this is valid XML, and I want to handle this element. + NE_XML_INVALID -> + No, this is NOT valid XML, and parsing should stop. + NE_XML_DECLINE -> + I don't know anything about this element, someone else + can handle it. +*/ + + +#define NE_XML_VALID (0) +#define NE_XML_INVALID (-1) +#define NE_XML_DECLINE (-2) + +/* Validate a new child element. */ +typedef int (*ne_xml_validate_cb) + (ne_xml_elmid parent, ne_xml_elmid child); + +typedef int (*ne_xml_startelm_cb) + (void *userdata, const struct ne_xml_elm *elm, const char **atts); + +/* Called when a complete element is parsed */ +typedef int (*ne_xml_endelm_cb) + (void *userdata, const struct ne_xml_elm *s, const char *cdata); + +typedef void (*ne_xml_cdata_cb) + (void *userdata, const struct ne_xml_elm *s, + const char *cdata, int len); + +struct ne_xml_parser_s; +typedef struct ne_xml_parser_s ne_xml_parser; + +/* Flags */ +/* This element has no children */ +#define NE_XML_CDATA (1<<1) +/* Collect complete contents of this node as cdata */ +#define NE_XML_COLLECT ((1<<2) | NE_XML_CDATA) +/* Decode UTF-8 data in cdata. */ +#define NE_XML_UTF8DECODE (1<<3) +/* This element uses MIXED mode */ +#define NE_XML_MIXED (1<<4) + +/* Initialise the parser */ +ne_xml_parser *ne_xml_create(void); + +/* Push a handler onto the handler stack for the given list of elements. + * elements must be an array, with the last element .nspace being NULL. + * Callbacks are called in order: + * 1. validate_cb + * 2. startelm_cb + * 3. endelm_cb + * (then back to the beginning again). + * If any of the callbacks ever return non-zero, the parse will STOP. + * userdata is passed as the first argument to startelm_cb and endelm_cb. + */ +void ne_xml_push_handler(ne_xml_parser *p, + const struct ne_xml_elm *elements, + ne_xml_validate_cb validate_cb, + ne_xml_startelm_cb startelm_cb, + ne_xml_endelm_cb endelm_cb, + void *userdata); + +/* Add a handler which uses a mixed-mode cdata callback */ +void ne_xml_push_mixed_handler(ne_xml_parser *p, + const struct ne_xml_elm *elements, + ne_xml_validate_cb validate_cb, + ne_xml_startelm_cb startelm_cb, + ne_xml_cdata_cb cdata_cb, + ne_xml_endelm_cb endelm_cb, + void *userdata); + +/* Returns non-zero if the parse was valid, zero if it failed (e.g., + * any of the callbacks return non-zero, 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); + +/* Destroys the parser. Any operations on it then have + * undefined results. */ +void ne_xml_destroy(ne_xml_parser *p); + +/* Parse the given block of input of length len. Block does + * not need to be NULL-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 http_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); + +/* Get attribute of given name. TODO: doesn't consider namespaces. */ +const char *ne_xml_get_attr(const char **attrs, const char *name); + +END_NEON_DECLS + +#endif /* NE_XML_H */ diff --git a/neon/src/neon.h b/neon/src/neon.h new file mode 100644 index 000000000..4ebf94722 --- /dev/null +++ b/neon/src/neon.h @@ -0,0 +1,3 @@ + +#undef NEON_VERSION +#define NEON_VERSION "0.1.0" diff --git a/neon/src/neon_config.h b/neon/src/neon_config.h new file mode 100644 index 000000000..d691bd545 --- /dev/null +++ b/neon/src/neon_config.h @@ -0,0 +1,8 @@ + +#ifndef NEON_CONFIG_H +#define NEON_CONFIG_H + +#undef NEON_VERSION +#define NEON_VERSION "0.2.0" + +#endif diff --git a/neon/src/neon_defs.h b/neon/src/neon_defs.h new file mode 100644 index 000000000..f029edf28 --- /dev/null +++ b/neon/src/neon_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/neon/src/neon_i18n.c b/neon/src/neon_i18n.c new file mode 100644 index 000000000..509ca9dba --- /dev/null +++ b/neon/src/neon_i18n.c @@ -0,0 +1,30 @@ +/* + Internationalization of neon + Copyright (C) 2000, Joe Orton <joe@orton.demon.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 + + Id: neon_i18n.c,v 1.2 2000/07/16 15:45:04 joe Exp +*/ + +void neon_i18n_init(void) +{ +#if defined(ENABLE_NLS) && defined(NEON_IS_LIBRARY) + bindtextdomain("neon", LOCALEDIR); + /* if neon is build bundled in, then there is probably + * no point in this... probably. */ +#endif +} diff --git a/neon/src/neon_i18n.h b/neon/src/neon_i18n.h new file mode 100644 index 000000000..5d82eacb8 --- /dev/null +++ b/neon/src/neon_i18n.h @@ -0,0 +1,11 @@ + +/* Include this to use I18N inside neon */ + +#undef _ +#ifdef ENABLE_NLS +#include <libintl.h> +#define _(str) gettext(str) +#else +#define _(str) (str) +#endif /* ENABLE_NLS */ +#define N_(str) (str) diff --git a/neon/src/neon_md5.h b/neon/src/neon_md5.h new file mode 100644 index 000000000..6ff784c5e --- /dev/null +++ b/neon/src/neon_md5.h @@ -0,0 +1,157 @@ +/* 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 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 __md5_init_ctx __P ((struct md5_ctx *ctx)); +extern void md5_init_ctx __P ((struct 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 __md5_process_block __P ((const void *buffer, size_t len, + struct md5_ctx *ctx)); +extern void md5_process_block __P ((const void *buffer, size_t len, + struct 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 __md5_process_bytes __P ((const void *buffer, size_t len, + struct md5_ctx *ctx)); +extern void md5_process_bytes __P ((const void *buffer, size_t len, + struct 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 *__md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf)); +extern void *md5_finish_ctx __P ((struct 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 *__md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf)); +extern void *md5_read_ctx __P ((const struct 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 __md5_stream __P ((FILE *stream, void *resblock)); +extern int md5_stream __P ((FILE *stream, void *resblock)); + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. 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. */ +extern void *__md5_buffer __P ((const char *buffer, size_t len, + void *resblock)); +extern void *md5_buffer __P ((const char *buffer, size_t len, + void *resblock)); + +#endif /* NEON_MD5_H */ diff --git a/neon/src/nsocket.h b/neon/src/nsocket.h new file mode 100644 index 000000000..dd5c30a14 --- /dev/null +++ b/neon/src/nsocket.h @@ -0,0 +1,192 @@ +/* + socket handling interface + Copyright (C) 1998-2000, Joe Orton <joe@orton.demon.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 NSOCKET_H +#define NSOCKET_H + +#include <sys/socket.h> +#include <sys/types.h> + +#include <netinet/in.h> + +#include <neon_defs.h> + +BEGIN_NEON_DECLS + +#define SOCK_ERROR -1 +/* Read/Write timed out */ +#define SOCK_TIMEOUT -2 +/* Passed buffer was full */ +#define SOCK_FULL -3 +/* Socket was closed */ +#define SOCK_CLOSED -4 + +/* Socket read timeout */ +#define SOCKET_READ_TIMEOUT 60 + +typedef enum { + sock_namelookup, /* Looking up hostname given by info */ + sock_connecting, /* Connecting to server */ + sock_connected, /* Connection established */ + sock_secure_details /* Secure connection details */ +} sock_status; + +struct nsocket_s; +typedef struct nsocket_s nsocket; + +typedef void (*sock_block_reader) ( + void *userdata, const char *buf, size_t len); + +typedef void (*sock_progress)(void *userdata, size_t progress, size_t total); +typedef void (*sock_notify)(void *userdata, + sock_status status, const char *info); + +void sock_register_progress(sock_progress cb, void *userdata); +void sock_register_notify(sock_notify cb, void *userdata); + +void sock_call_progress(size_t progress, size_t total); + +/* Initialize the socket library. If you don't do this, SSL WILL NOT WORK. + * Returns 0 on success, or non-zero on screwed up SSL library. */ +int sock_init(void); + +/* sock_read is read() with a timeout of SOCKET_TIMEOUT. + * Returns: + * SOCK_* on error, + * 0 on no data to read (due to EOF), + * >0 length of data read into buffer. + */ +int sock_read(nsocket *sock, char *buffer, size_t count); + +/* sock_peek is recv() with a timeout of SOCKET_TIMEOUT. + * Returns: + * SOCK_* on error, + * 0 on no data to read (due to EOF), + * >0 length of data read into buffer. + */ +int sock_peek(nsocket *sock, char *buffer, size_t count); + +/* Blocks waiting for data on the given socket for the given time. + * Returns: + * SOCK_* on error, + * SOCK_TIMEOUT on no data within timeout, + * 0 if data arrived on the socket. + */ +int sock_block(nsocket *sock, int timeout); + +/* Reads readlen bytes from fd and writes to socket. + * (Not all in one go, obviously). + * If readlen == -1, then it reads from srcfd until EOF. + * Returns number of bytes written to destfd, or SOCK_* on error. + */ +int sock_transfer(int fd, nsocket *sock, ssize_t readlen); + +/* Sends the given line to given socket, CRLF appended */ +int sock_sendline(nsocket *sock, const char *line); +/* Sends the given block of data down the nsocket */ +int sock_fullwrite(nsocket *sock, const char *data, size_t length); +/* Sends the null-terminated string down the given nsocket */ +int sock_send_string(nsocket *sock, const char *string); + +/* Reads a line from given nsocket */ +int sock_readline(nsocket *sock, char *line, int len); +/* Reads a chunk of data. */ +int sock_fullread(nsocket *sock, char *buffer, int buflen); + +/* Creates and connects a nsocket */ +nsocket *sock_connect(const struct in_addr host, int portnum); + +nsocket *sock_connect_u(const struct in_addr addr, int portnum, int call_fe); + +/* Not as good as accept(2), missing parms 2+3. + * Addings parms 2+3 would probably mean passing socklen_t as an + * int then casting internally, since we don't really want to + * autogenerate the header file to be correct for the build platform. + */ +nsocket *sock_accept(int listener); + +/* Returns the file descriptor used for the socket */ +int sock_get_fd(nsocket *sock); + +/* Closes the socket and frees the nsocket object. */ +int sock_close(nsocket *socket); + +const char *sock_get_error(nsocket *sock); + +/* Do a name lookup on given hostname, writes the address into + * given address buffer. Return -1 on failure. */ +int sock_name_lookup(const char *hostname, struct in_addr *addr); + +/* Returns the standard TCP port for the given service */ +int sock_service_lookup(const char *name); + +int sock_readfile_blocked(nsocket *socket, size_t length, + sock_block_reader reader, void *userdata); + +/* Auxiliary for use with SSL. */ +struct nssl_context_s; +typedef struct nssl_context_s nssl_context; + +/* Netscape's prompts on getting a certificate which it doesn't + * recognize the CA for: + * 1. Hey, I don't recognize the CA for this cert. + * 2. Here is the certificate: for foo signed by BLAH, + * using encryption level BLEE + * 3. Allow: accept for this session only, + * don't accept + * accept forever + */ +nssl_context *sock_create_ssl_context(void); + +void sock_destroy_ssl_context(nssl_context *ctx); + +/* Callback to decide whether the user will accept the + * given certificate or not */ +typedef struct { + char *owner; /* Multi-line string describing owner of + * certificate */ + char *issuer; /* As above for issuer of certificate */ + /* Strings the certificate is valid between */ + char *valid_from, *valid_till; + /* Certificate fingerprint */ + char *fingerprint; +} nssl_certificate; + +/* Returns: + * 0 -> User accepts the certificate + * non-zero -> user does NOT accept the certificate. + */ +typedef int (*nssl_accept)(void *userdata, const nssl_certificate *info); + +void sock_set_cert_accept(nssl_context *c, + nssl_accept accepter, void *userdata); +void sock_set_certificate_file(nssl_context *c, const char *fname); + +void sock_disable_tlsv1(nssl_context *c); +void sock_disable_sslv2(nssl_context *c); +void sock_disable_sslv3(nssl_context *c); + +/* Ctx is OPTIONAL. If it is NULL, defaults are used. */ +int sock_make_secure(nsocket *sock, nssl_context *ctx); + +END_NEON_DECLS + +#endif /* NSOCKET_H */ diff --git a/neon/src/socket.c b/neon/src/socket.c new file mode 100644 index 000000000..a4899ac28 --- /dev/null +++ b/neon/src/socket.c @@ -0,0 +1,592 @@ +/* + socket handling routines + Copyright (C) 1998, 1999, 2000, Joe Orton <joe@orton.demon.co.uk>, + except where otherwise indicated. + + 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 + + The sock_readline() function is: + + Copyright (c) 1999 Eric S. Raymond + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + Id: socket.c,v 1.7 2000/05/10 13:24:42 joe Exp +*/ + +#include <config.h> + +#include <sys/types.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/socket.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <errno.h> + +#include <fcntl.h> +#include <stdio.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 /* HAVE_STDLIB_H */ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#include "string_utils.h" +#include "http_utils.h" +#include "socket.h" + +static sock_progress progress_cb = NULL; +static sock_notify notify_cb = NULL; +static void *progress_ud, *notify_ud; + +void sock_register_progress( sock_progress cb, void *userdata ) +{ + progress_cb = cb; + progress_ud = userdata; +} + +void sock_register_notify( sock_notify cb, void *userdata ) +{ + notify_cb = cb; + notify_ud = userdata; +} + +void sock_call_progress( size_t progress, size_t total ) +{ + if( progress_cb ) { + (*progress_cb)( progress_ud, progress, total ); + } +} + +/* sock_read is read() with a timeout of SOCKET_READ_TIMEOUT. */ +int sock_read( int sock, void *buffer, size_t count ) { + int ret; + ret = sock_block( sock, SOCKET_READ_TIMEOUT ); + if( ret == 0 ) { + /* Got data */ + do { + ret = read( sock, buffer, count ); + } while( ret == -1 && errno == EINTR ); + if( ret < 0 ) { + ret = SOCK_ERROR; + } + } + return ret; +} + +/* sock_recv is recv() with a timeout of SOCKET_TIMEOUT. + * Returns length of data read or SOCK_* on error */ +int sock_recv( int sock, void *buffer, size_t count, unsigned int flags ) { + int ret; + ret = sock_block( sock, SOCKET_READ_TIMEOUT ); + if( ret < 0 ) { + return ret; + } + /* Got data */ + do { + ret = recv( sock, buffer, count, flags ); + } while( ret == -1 && errno == EINTR ); + if( ret < 0 ) { + ret = SOCK_ERROR; + } + return ret; +} + +/* Blocks waiting for read input on the given socket for the given time. + * Returns: + * 0 if data arrived + * SOCK_TIMEOUT if data did not arrive before timeout + * SOCK_ERROR on error + */ +int sock_block( int sock, int timeout ) { + struct timeval tv; + fd_set fds; + int ret; + + /* Init the fd set */ + FD_ZERO( &fds ); + FD_SET( sock, &fds ); + /* Set the timeout */ + tv.tv_sec = timeout; + tv.tv_usec = 0; + do { + ret = select( sock+1, &fds, NULL, NULL, &tv ); + } while( ret == -1 && errno == EINTR ); + + switch( ret ) { + case 0: + return SOCK_TIMEOUT; + case -1: + return SOCK_ERROR; + default: + return 0; + } +} + +/* Send the given line down the socket with CRLF appended. + * Returns 0 on success or SOCK_* on failure. */ +int sock_sendline( int sock, const char *line ) { + char *buffer; + int ret; + CONCAT2( buffer, line, "\r\n" ); + ret = sock_send_string( sock, buffer ); + free( buffer ); + return ret; +} + +/* Reads from fd, passing blocks to reader, also calling + * fe_t_p. + * Returns 0 on success or SOCK_* on error. */ +int sock_readfile_blocked( int fd, size_t length, + sock_block_reader reader, void *userdata ) { + char buffer[BUFSIZ]; + int ret; + size_t done = 0; + do { + ret = sock_read( fd, buffer, BUFSIZ ); + if( ret < 0 ) { + return ret; + } + done += ret; + sock_call_progress( done, length ); + (*reader)( userdata, buffer, ret ); + } while( (done < length) && ret ); + return 0; +} + + +/* Send a block of data down the given fd. + * Returns 0 on success or SOCK_* on failure */ +int sock_fullwrite( int fd, const char *data, size_t length ) { + ssize_t sent, wrote; + const char *pnt; + + sent = 0; + pnt = data; + while( sent < length ) { + wrote = write( fd, pnt, length-sent ); + if( wrote < 0 ) { + if( errno == EINTR ) { + continue; + } else if( errno == EPIPE ) { + return SOCK_CLOSED; + } else { + return SOCK_ERROR; + } + } + sent += wrote; + } + return 0; +} + +/* Sends the given string down the given socket. + * Returns 0 on success or -1 on failure. */ +int sock_send_string( int sock, const char *data ) { + return sock_fullwrite( sock, data, strlen( data ) ); +} + +/* This is from from Eric Raymond's fetchmail (SockRead() in socket.c) + * since I wouldn't have a clue how to do it properly. + * This function is Copyright 1999 (C) Eric Raymond. + * Modifications Copyright 2000 (C) Joe Orton + */ +int sock_readline(int sock, char *buf, int len) +{ + char *newline, *bp = buf; + int n; + + do { + /* + * The reason for these gymnastics is that we want two things: + * (1) to read \n-terminated lines, + * (2) to return the true length of data read, even if the + * data coming in has embedded NULS. + */ +#ifdef SSL_ENABLE + + /* joe: of course, we don't actually have SSL support here yet, + * but this will be handy when that day comes. */ + + SSL *ssl; + + if( NULL != ( ssl = SSLGetContext( sock ) ) ) { + /* Hack alert! */ + /* OK... SSL_peek works a little different from MSG_PEEK + Problem is that SSL_peek can return 0 if there + is no data currently available. If, on the other + hand, we loose the socket, we also get a zero, but + the SSL_read then SEGFAULTS! To deal with this, + we'll check the error code any time we get a return + of zero from SSL_peek. If we have an error, we bail. + If we don't, we read one character in SSL_read and + loop. This should continue to work even if they + later change the behavior of SSL_peek + to "fix" this problem... :-( */ + if ((n = SSL_peek(ssl, bp, len)) < 0) { + return(-1); + } + if( 0 == n ) { + /* SSL_peek says no data... Does he mean no data + or did the connection blow up? If we got an error + then bail! */ + if( 0 != ( n = ERR_get_error() ) ) { + return -1; + } + /* We didn't get an error so read at least one + character at this point and loop */ + n = 1; + /* Make sure newline start out NULL! + * We don't have a string to pass through + * the strchr at this point yet */ + newline = NULL; + } else if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = SSL_read(ssl, bp, n)) == -1) { + return(-1); + } + /* Check for case where our single character turned out to + * be a newline... (It wasn't going to get caught by + * the strchr above if it came from the hack... ). */ + if( NULL == newline && 1 == n && '\n' == *bp ) { + /* Got our newline - this will break + out of the loop now */ + newline = bp; + } + } else { + if ((n = recv(sock, bp, len, MSG_PEEK)) <= 0) + return(-1); + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = read(sock, bp, n)) == -1) + return(-1); + } +#else + if ((n = sock_recv(sock, bp, len, MSG_PEEK)) <= 0) + return n; + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = sock_read(sock, bp, n)) < 0) + return n; +#endif + bp += n; + len -= n; + if( len < 1 ) return SOCK_FULL; + } while (!newline && len); + *bp = '\0'; + return bp - buf; +} + +/*** End of ESR-copyrighted section ***/ + +/* Reads readlen bytes from srcfd and writes to destfd. + * (Not all in one go, obviously). + * If readlen == -1, then it reads from srcfd until EOF. + * Returns number of bytes written to destfd, or -1 on error. + */ +int sock_transfer( int srcfd, int destfd, ssize_t readlen ) { + char buffer[BUFSIZ], *pnt; + size_t curlen; /* total bytes yet to read from srcfd */ + size_t sumwrlen; /* total bytes written to destfd */ + + if( readlen == -1 ) { + curlen = BUFSIZ; /* so the buffer size test works */ + } else { + curlen = readlen; /* everything to do */ + } + sumwrlen = 0; /* nowt done yet */ + + while( curlen > 0 ) { + int rdlen, len2; + + /* Get a chunk... if the number of bytes that are left to read + * is less than the buffer size, only read that many bytes. */ + rdlen = sock_read( srcfd, buffer, + (readlen==-1)?BUFSIZ:(min( BUFSIZ, curlen )) ); + sock_call_progress( sumwrlen, readlen ); + if( rdlen < 0 ) { + return -1; + } else if( rdlen == 0 ) { + /* End of file... get out of here */ + break; + } + if( readlen != -1 ) + curlen -= rdlen; + /* Otherwise, we have bytes! Write them to destfd... might + * only manage to write a few of them at a time, so we have + * to deal with that too. */ + /* Replace this with a call to send_data? */ + + pnt = buffer; + len2 = rdlen; + while( len2 > 0 ) { + ssize_t wrlen; + wrlen = write( destfd, pnt, len2 ); + if( wrlen < 0 ) { + return SOCK_ERROR; + } + len2 -= wrlen; + pnt += wrlen; + sumwrlen += wrlen; + } + } + sock_call_progress( sumwrlen, readlen ); + return sumwrlen; +} + +/* Reads buflen bytes into buffer until it's full. + * Returns 0 on success, -1 on error */ +int sock_fullread( int sock, char *buffer, int buflen ) { + char *pnt; /* current position within buffer */ + int len; + pnt = buffer; + while( buflen > 0 ) { + len = sock_read( sock, pnt, buflen ); + if( len < 0 ) return len; + buflen -= len; + pnt += len; + } + return 0; +} + +/* Dump the given filename down the given socket. + * Returns 0 on success or non-zero on error. */ +int send_file_binary( int sock, const char *filename ) { + int fd, wrote; + struct stat fs; + +#if defined (__EMX__) || defined(__CYGWIN__) + if( (fd = open( filename, O_RDONLY | O_BINARY )) < 0 ) { +#else + if( (fd = open( filename, O_RDONLY )) < 0 ) { +#endif + return -1; + } + if( fstat( fd, &fs ) < 0 ) { + close( fd ); + return -2; + } + /* What's the Right Thing to do? Choices: + * a) Let transfer send everything from fd until EOF + * + If the EOF pos changes, we'll know and can signal an error + * - Unsafe - the transfer might not end if someone does + * yes > file + * b) Tell transfer to send only the number of bytes from the stat() + * + Safe - the transfer WILL end. + * - If the EOF pos changes, we'll have a partial (corrupt) file. + * I'm not sure. I think (a) gets my vote but it doesn't allow + * nice transfer progress bars in the FE under the current API + * so we go with (b). + */ + wrote = sock_transfer( fd, sock, fs.st_size ); + close( fd ); /* any point in checking that one? */ + if( wrote == fs.st_size ) { + return 0; + } else { + return -1; + } +} + +/* Dump the given filename down the given socket, in ASCII translation + * mode. Performs LF -> CRLF conversion. + * Returns zero on success or non-zero on error. */ +int send_file_ascii( int sock, const char *filename ) { + int ret; + char buffer[BUFSIZ], *pnt; + FILE *f; + ssize_t total = 0; + + f = fopen( filename, "r" ); + if( f == NULL ) { + return -1; + } + + /* Init to success */ + ret = 0; + + while(1) { + if( fgets( buffer, BUFSIZ - 1, f ) == NULL ) { + if( ferror( f ) ) { + ret = -1; + break; + } + /* Finished upload */ + ret = 0; + break; + } + /* To send in ASCII mode, we need to send CRLF as the EOL. + * We might or might not already have CRLF-delimited lines. + * So we mess about a bit to ensure that we do. + */ + pnt = strchr( buffer, '\r' ); + if( pnt == NULL ) { + /* We need to add the CR in */ + pnt = strchr( buffer, '\n' ); + if( pnt == NULL ) { + /* No CRLF found at all */ + pnt = strchr( buffer, '\0' ); + if( pnt == NULL ) /* crud in buffer */ + pnt = buffer; + } + /* Now, pnt points to the first character after the + * end of the line, i.e., where we want to put the CR. + */ + *pnt++ = '\r'; + /* And lob in an LF afterwards */ + *pnt-- = '\n'; + } + /* At this point, pnt points to the CR. + * We send everything between pnt and the beginning of the buffer, + * +2 for the CRLF + */ + if( sock_fullwrite( sock, buffer, (pnt - buffer) +2 ) != 0 ) { + ret = -1; + break; + } + total += (pnt - buffer) + 2; + sock_call_progress( total, -1 ); + } + fclose( f ); /* any point in checking that one? */ + /* Return true */ + return ret; +} + +/* Dump from given socket into given file. Reads only filesize bytes, + * or until EOF if filesize == -1. + * Returns number of bytes written on success, or -1 on error */ +int recv_file( int sock, const char *filename, ssize_t filesize ) { + int fd, wrote; +#if defined (__EMX__) || defined(__CYGWIN__) + /* We have to set O_BINARY, thus need open(). Otherwise it should be + equivalent to creat(). */ + if( (fd = open( filename, O_WRONLY|O_TRUNC|O_CREAT|O_BINARY, 0644 )) < 0 ) { + return -1; + } +#else + if( (fd = creat( filename, 0644 )) < 0 ) { + return -1; + } +#endif + wrote = sock_transfer( sock, fd, filesize ); + if( close( fd ) == -1 ) { + /* Close failed - file was not written correctly */ + return -1; + } + if( filesize == -1 ) { + return wrote; + } else { + return (wrote==filesize); + } +} + +/* Do a name lookup on given hostname, writes the address into + * given address buffer. Return -1 on failure. + */ +int host_lookup( const char *hostname, struct in_addr *addr ) { + struct hostent *hp; + unsigned long laddr; + + if( notify_cb ) + (*notify_cb)( notify_ud, sock_namelookup, hostname ); + + laddr = (unsigned long)inet_addr(hostname); + if ((int)laddr == -1) { + /* inet_addr failed. */ + hp = gethostbyname(hostname); + if( hp == NULL ) { + return -1; + } + memcpy( addr, hp->h_addr, hp->h_length ); + } else { + addr->s_addr = laddr; + } + return 0; +} + +/* Opens a socket to the given port at the given address. + * Returns -1 on failure, or the socket on success. + * portnum must be in HOST byte order */ +int sock_connect_u( const struct in_addr addr, int portnum, int call_fe ) { + struct sockaddr_in sa; + int sock; + /* Create the socket */ + if( ( sock = socket(AF_INET, SOCK_STREAM, 0) ) < 0) + return -1; + /* Connect the socket */ + sa.sin_family = AF_INET; + sa.sin_port = htons(portnum); /* host -> net byte orders */ + sa.sin_addr = addr; + if( call_fe && notify_cb ) (*notify_cb)( notify_ud, sock_connecting, NULL ); + if( connect(sock, (struct sockaddr *) &sa, sizeof(struct sockaddr_in)) < 0 ) + return -1; + if( call_fe && notify_cb ) (*notify_cb)( notify_ud, sock_connected, NULL ); + /* Success - return the socket */ + return sock; +} + +int sock_connect( const struct in_addr addr, int portnum ) { + return sock_connect_u( addr, portnum, 1 ); +} + +/* Closes given socket */ +int sock_close( int sock ) { + return close( sock ); +} + +/* Returns HOST byte order port of given name */ +int get_tcp_port( const char *name ) { + struct servent *ent; + ent = getservbyname( name, "tcp" ); + if( ent == NULL ) { + return 0; + } else { + return ntohs( ent->s_port ); + } +} diff --git a/neon/src/socket.h b/neon/src/socket.h new file mode 100644 index 000000000..26db328dc --- /dev/null +++ b/neon/src/socket.h @@ -0,0 +1,137 @@ +/* + socket handling routines + Copyright (C) 1998, Joe Orton <joe@orton.demon.co.uk>, except where + otherwise indicated. + + 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 SOCKET_H +#define SOCKET_H + +#include <sys/socket.h> +#include <sys/types.h> + +#include <netinet/in.h> + +#define SOCK_ERROR -1 +/* Read/Write timed out */ +#define SOCK_TIMEOUT -2 +/* Passed buffer was full */ +#define SOCK_FULL -3 +/* Socket was closed */ +#define SOCK_CLOSED -4 + +/* Socket read timeout */ +#define SOCKET_READ_TIMEOUT 30 + +typedef enum { + sock_namelookup, /* Looking up hostname given by info */ + sock_connecting, /* Connecting to server */ + sock_connected /* Connection established */ +} sock_status; + +typedef void (*sock_block_reader) ( + void *userdata, const char *buf, size_t len ); + +typedef void (*sock_progress)( void *userdata, size_t progress, size_t total ); +typedef void (*sock_notify)( void *userdata, sock_status status, const char *info ); + +void sock_register_progress( sock_progress cb, void *userdata ); +void sock_register_notify( sock_notify cb, void *userdata ); + +void sock_call_progress( size_t progress, size_t total ); + +/* sock_read is read() with a timeout of SOCKET_TIMEOUT. + * Returns: + * SOCK_ERROR on error, + * SOCK_TIMEOUT on timeout, + * 0 on no data to read (due to timeout or EOF), + * >0 length of data read into buffer. + */ +int sock_read( int sock, void *buffer, size_t count ); + +/* sock_recv is recv() with a timeout of SOCKET_TIMEOUT. + * Returns: + * -1 on error, + * 0 on no data to read (due to timeout or EOF), + * >0 length of data read into buffer. + */ +int sock_recv( int sock, void *buffer, size_t count, unsigned int flags); + +/* Blocks waiting for data on the given socket for the given time. + * Returns: + * -1 on error, + * 0 on no data within timeout, + * 1 if data arrived on the socket. + */ +int sock_block( int sock, int timeout ); + +/* Dump the given filename down the given socket. + * Returns 0 on success, non-zero on error */ +int send_file_binary( int sock, const char *filename ); + +/* Dump the given filename down the given socket, performing + * CR -> CRLF conversion. + * Returns 0 on success, non-zero on error */ +int send_file_ascii( int sock, const char *filename ); + +/* Dump from given socket into given file. Reads only filesize bytes, + * or until EOF if filesize == -1. + * Returns number of bytes written on success, or -1 on error */ +int recv_file( int sock, const char *filename, ssize_t filesize ); + +/* Reads readlen bytes from srcfd and writes to destfd. + * (Not all in one go, obviously). + * If readlen == -1, then it reads from srcfd until EOF. + * Returns number of bytes written to destfd, or -1 on error. + * Calls fe_transfer_progress( a, b ) during transfers, where + * a = bytes transferred so far, and b = readlen + */ +int sock_transfer( int srcfd, int destfd, ssize_t readlen ); + +/* Sends the given line to given socket, CRLF appended */ +int sock_sendline( int sock, const char *line ); +/* Sends the given block of data down the socket */ +int sock_fullwrite( int sock, const char *data, size_t length ); +/* Sends the null-terminated string down the given socket */ +int sock_send_string( int sock, const char *string ); + +/* Reads a line from given socket */ +int sock_readline( int sock, char *line, int len ); +/* Reads a chunk of data. */ +int sock_fullread( int sock, char *buffer, int buflen ); + +/* Creates and connects a socket */ +int sock_connect( const struct in_addr host, int portnum ); + +int sock_connect_u( const struct in_addr addr, int portnum, int call_fe ); + +int sock_close( int socket ); + +/* Do a name lookup on given hostname, writes the address into + * given address buffer. Return -1 on failure. + */ +int host_lookup( const char *hostname, struct in_addr *addr ); + +/* Returns the standard TCP port for the given service */ +int get_tcp_port( const char *name ); + +int sock_readfile_blocked( int fd, size_t length, + sock_block_reader reader, void *userdata ); + +#endif /* SOCKET_H */ diff --git a/neon/src/sslcerts.c b/neon/src/sslcerts.c new file mode 100644 index 000000000..b0c972bf1 --- /dev/null +++ b/neon/src/sslcerts.c @@ -0,0 +1,476 @@ +/* + * Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> + * Originally under GPL in Mutt, http://www.mutt.org/ + * Relicensed under LGPL for neon, http://www.webdav.org/neon/ + * + * 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 + */ + +/* for SSL NO_* defines */ +#include "config.h" + +#include <openssl/ssl.h> +#include <openssl/x509.h> +#include <openssl/err.h> +#include <openssl/rand.h> + +#undef _ + +#include <string.h> + +#if OPENSSL_VERSION_NUMBER >= 0x00904000L +#define READ_X509_KEY(fp, key) PEM_read_X509(fp, key, NULL, NULL) +#else +#define READ_X509_KEY(fp, key) PEM_read_X509(fp, key, NULL) +#endif + +/* This is ugly, but as RAND_status came in on OpenSSL version 0.9.5 + * and the code has to support older versions too, this is seemed to + * be cleaner way compared to having even uglier #ifdefs all around. + */ +#ifdef HAVE_RAND_STATUS +#define HAVE_ENTROPY() (RAND_status() == 1) +#define GOT_ENTROPY() return 0; +#else +static int needentropy = 1; +/* OpenSSL fills the entropy pool from /dev/urandom if it exists */ +#define HAVE_ENTROPY() (!access("/dev/urandom", R_OK) || !needentropy) +#define GOT_ENTROPY() do { needentropy = 1; return 0; } while (0) +#endif + +char *SslCertFile = NULL; +char *SslEntropyFile = NULL; + +typedef struct _sslsockdata +{ + SSL_CTX *ctx; + SSL *ssl; + X509 *cert; +} +sslsockdata; + +/* + * OpenSSL library needs to be fed with sufficient entropy. On systems + * with /dev/urandom, this is done transparently by the library itself, + * on other systems we need to fill the entropy pool ourselves. + * + * Even though only OpenSSL 0.9.5 and later will complain about the + * lack of entropy, we try to our best and fill the pool with older + * versions also. (That's the reason for the ugly #ifdefs and macros, + * otherwise I could have simply #ifdef'd the whole ssl_init funcion) + */ +int sock_ssl_init (nsocket_ssl *ctx) +{ + char path[_POSIX_PATH_MAX], *file; + + if (HAVE_ENTROPY()) return 0; + + mutt_message (_("Filling entropy pool")); + + /* try egd */ +#ifdef HAVE_RAND_EGD + file = SslEntropyFile; + if (file && RAND_egd(file) != -1) + GOT_ENTROPY(); + file = getenv("EGDSOCKET"); + if (file && RAND_egd(file) != -1) + GOT_ENTROPY(); + snprintf (path, sizeof(path), "%s/.entropy", NONULL(Homedir)); + if (RAND_egd(path) != -1) + GOT_ENTROPY(); + if (RAND_egd("/tmp/entropy") != -1) + GOT_ENTROPY(); +#endif + + /* try some files */ + file = SslEntropyFile; + if (!file || access(file, R_OK) == -1) + file = getenv("RANDFILE"); + if (!file || access(file, R_OK) == -1) { + snprintf (path, sizeof(path), "%s/.rnd", NONULL(Homedir)); + file = path; + } + if (access(file, R_OK) == 0) { + if (RAND_load_file(file, 10240) >= 16) + GOT_ENTROPY(); + } + + if (HAVE_ENTROPY()) return 0; + + mutt_error (_("Failed to find enough entropy on your system")); + sleep (2); + return -1; +} + +static int ssl_socket_open_err (CONNECTION *conn) +{ + mutt_error (_("SSL disabled due the lack of entropy")); + sleep (2); + return -1; +} + +static int ssl_check_certificate (sslsockdata * data); + +int ssl_socket_open (CONNECTION * conn) +{ + sslsockdata *data; + int err; + + if (raw_socket_open (conn) < 0) + return -1; + + data = (sslsockdata *) safe_calloc (1, sizeof (sslsockdata)); + conn->sockdata = data; + + SSL_library_init(); + data->ctx = SSL_CTX_new (SSLv23_client_method ()); + + /* disable SSL protocols as needed */ + if (!option(OPTTLSV1)) + { + SSL_CTX_set_options(data->ctx, SSL_OP_NO_TLSv1); + } + if (!option(OPTSSLV2)) + { + SSL_CTX_set_options(data->ctx, SSL_OP_NO_SSLv2); + } + if (!option(OPTSSLV3)) + { + SSL_CTX_set_options(data->ctx, SSL_OP_NO_SSLv3); + } + + data->ssl = SSL_new (data->ctx); + SSL_set_fd (data->ssl, conn->fd); + + if ((err = SSL_connect (data->ssl)) < 0) + { + ssl_socket_close (conn); + return -1; + } + + data->cert = SSL_get_peer_certificate (data->ssl); + if (!data->cert) + { + mutt_error (_("Unable to get certificate from peer")); + sleep (1); + return -1; + } + + if (!ssl_check_certificate (data)) + { + ssl_socket_close (conn); + return -1; + } + + mutt_message (_("SSL connection using %s"), SSL_get_cipher (data->ssl)); + sleep (1); + + return 0; +} + +int ssl_socket_close (CONNECTION * conn) +{ + sslsockdata *data = conn->sockdata; + SSL_shutdown (data->ssl); + + X509_free (data->cert); + SSL_free (data->ssl); + SSL_CTX_free (data->ctx); + + return raw_socket_close (conn); +} + + + +static char *x509_get_part (char *line, const char *ndx) +{ + static char ret[SHORT_STRING]; + char *c, *c2; + + strncpy (ret, _("Unknown"), sizeof (ret)); + + c = strstr (line, ndx); + if (c) + { + c += strlen (ndx); + c2 = strchr (c, '/'); + if (c2) + *c2 = '\0'; + strncpy (ret, c, sizeof (ret)); + if (c2) + *c2 = '/'; + } + + return ret; +} + +static void x509_fingerprint (char *s, int l, X509 * cert) +{ + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int n; + int j; + + if (!X509_digest (cert, EVP_md5 (), md, &n)) + { + snprintf (s, l, _("[unable to calculate]")); + } + else + { + for (j = 0; j < (int) n; j++) + { + char ch[8]; + snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : "")); + strncat (s, ch, l); + } + } +} + +static char *asn1time_to_string (ASN1_UTCTIME *tm) +{ + static char buf[64]; + BIO *bio; + + strncpy (buf, _("[invalid date]"), sizeof (buf)); + + bio = BIO_new (BIO_s_mem()); + if (bio) + { + if (ASN1_TIME_print (bio, tm)) + (void) BIO_read (bio, buf, sizeof (buf)); + BIO_free (bio); + } + + return buf; +} + +static int check_certificate_by_signer (X509 *peercert) +{ + X509_STORE_CTX xsc; + X509_STORE *ctx; + int pass; + + ctx = X509_STORE_new (); + if (ctx == NULL) return 0; + + if (option (OPTSSLSYSTEMCERTS) && !X509_STORE_set_default_paths (ctx)) + { + DEBUG (DEBUG_SOCKET, "X509_STORE_set_default_paths failed\n"); + X509_STORE_free (ctx); + return 0; + } + + if (!X509_STORE_load_locations (ctx, SslCertFile, NULL)) + { + DEBUG (DEBUG_SOCKET, "X509_STORE_load_locations failed\n"); + X509_STORE_free (ctx); + return 0; + } + + X509_STORE_CTX_init (&xsc, ctx, peercert, NULL); + + pass = (X509_verify_cert (&xsc) > 0); +#ifdef DEBUG + if (! pass) + { + char buf[SHORT_STRING]; + int err; + + err = X509_STORE_CTX_get_error (&xsc); + snprintf (buf, sizeof (buf), "%s (%d)", + X509_verify_cert_error_string(err), err); + DEBUG (DEBUG_SOCKET, "X509_verify_cert: %s\n", buf); + } +#endif + X509_STORE_CTX_cleanup (&xsc); + X509_STORE_free (ctx); + + return pass; +} + +static int check_certificate_by_digest (X509 *peercert) +{ + unsigned char peermd[EVP_MAX_MD_SIZE]; + unsigned int peermdlen; + X509 *cert = NULL; + int pass = 0; + FILE *fp; + + /* expiration check */ + if (X509_cmp_current_time (X509_get_notBefore (peercert)) >= 0) + { + DEBUG (DEBUG_SCOKET, "Server certificate is not yet valid\n"); + mutt_error (_("Server certificate is not yet valid")); + sleep (2); + return 0; + } + if (X509_cmp_current_time (X509_get_notAfter (peercert)) <= 0) + { + DEBUG (DEBUG_SOCKET, "Server certificate has expired"); + mutt_error (_("Server certificate has expired")); + sleep (2); + return 0; + } + + if ((fp = fopen (SslCertFile, "rt")) == NULL) + return 0; + + if (!X509_digest (peercert, EVP_sha1(), peermd, &peermdlen)) + { + fclose (fp); + return 0; + } + + while ((cert = READ_X509_KEY (fp, &cert)) != NULL) + { + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int mdlen; + + /* Avoid CPU-intensive digest calculation if the certificates are + * not even remotely equal. + */ + if (X509_subject_name_cmp (cert, peercert) != 0 || + X509_issuer_name_cmp (cert, peercert) != 0) + continue; + + if (!X509_digest (cert, EVP_sha1(), md, &mdlen) || peermdlen != mdlen) + continue; + + if (memcmp(peermd, md, mdlen) != 0) + continue; + + pass = 1; + break; + } + X509_free (cert); + fclose (fp); + + return pass; +} + +static int ssl_check_certificate (sslsockdata * data) +{ + char *part[] = + {"/CN=", "/Email=", "/O=", "/OU=", "/L=", "/ST=", "/C="}; + char helpstr[SHORT_STRING]; + char buf[SHORT_STRING]; + MUTTMENU *menu; + int done, row, i; + FILE *fp; + char *name = NULL, *c; + + if (check_certificate_by_signer (data->cert)) + { + dprint (1, (debugfile, "ssl_check_certificate: signer check passed\n")); + return 1; + } + + /* automatic check from user's database */ + if (SslCertFile && check_certificate_by_digest (data->cert)) + { + dprint (1, (debugfile, "ssl_check_certificate: digest check passed\n")); + return 1; + } + + /* interactive check from user */ + menu = mutt_new_menu (); + menu->max = 19; + menu->dialog = (char **) safe_calloc (1, menu->max * sizeof (char *)); + for (i = 0; i < menu->max; i++) + menu->dialog[i] = (char *) safe_calloc (1, SHORT_STRING * sizeof (char)); + + row = 0; + strncpy (menu->dialog[row++], _("This certificate belongs to:"), SHORT_STRING); + name = X509_NAME_oneline (X509_get_subject_name (data->cert), + buf, sizeof (buf)); + for (i = 0; i < 5; i++) + { + c = x509_get_part (name, part[i]); + snprintf (menu->dialog[row++], SHORT_STRING, " %s", c); + } + + row++; + strncpy (menu->dialog[row++], _("This certificate was issued by:"), SHORT_STRING); + name = X509_NAME_oneline (X509_get_issuer_name (data->cert), + buf, sizeof (buf)); + for (i = 0; i < 5; i++) + { + c = x509_get_part (name, part[i]); + snprintf (menu->dialog[row++], SHORT_STRING, " %s", c); + } + + row++; + snprintf (menu->dialog[row++], SHORT_STRING, _("This certificate is valid")); + snprintf (menu->dialog[row++], SHORT_STRING, _(" from %s"), + asn1time_to_string (X509_get_notBefore (data->cert))); + snprintf (menu->dialog[row++], SHORT_STRING, _(" to %s"), + asn1time_to_string (X509_get_notAfter (data->cert))); + + row++; + buf[0] = '\0'; + x509_fingerprint (buf, sizeof (buf), data->cert); + snprintf (menu->dialog[row++], SHORT_STRING, _("Fingerprint: %s"), buf); + + menu->title = _("SSL Certificate check"); + if (SslCertFile) + { + menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always"); + menu->keys = _("roa"); + } + else + { + menu->prompt = _("(r)eject, accept (o)nce"); + menu->keys = _("ro"); + } + + helpstr[0] = '\0'; + mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT); + strncat (helpstr, buf, sizeof (helpstr)); + mutt_make_help (buf, sizeof (buf), _("Help"), MENU_GENERIC, OP_HELP); + strncat (helpstr, buf, sizeof (helpstr)); + menu->help = helpstr; + + done = 0; + while (!done) + { + switch (mutt_menuLoop (menu)) + { + case -1: /* abort */ + case OP_MAX + 1: /* reject */ + case OP_EXIT: + done = 1; + break; + case OP_MAX + 3: /* accept always */ + done = 0; + if ((fp = fopen (SslCertFile, "a"))) + { + if (PEM_write_X509 (fp, data->cert)) + done = 1; + fclose (fp); + } + if (!done) + mutt_error (_("Warning: Couldn't save certificate")); + else + mutt_message (_("Certificate saved")); + sleep (1); + /* fall through */ + case OP_MAX + 2: /* accept once */ + done = 2; + break; + } + } + mutt_menuDestroy (&menu); + return (done == 2); +} diff --git a/neon/src/string_utils.c b/neon/src/string_utils.c new file mode 100644 index 000000000..6736a56c4 --- /dev/null +++ b/neon/src/string_utils.c @@ -0,0 +1,447 @@ +/* + String utility functions + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: string_utils.c,v 1.8 2000/05/09 18:32:07 joe Exp +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#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 "xalloc.h" + +#include "string_utils.h" + +struct sbuffer_s { + char *data; + size_t used; /* used bytes in buffer */ + size_t length; /* length of buffer */ +}; + +/* TODO: These are both crap. Rewrite to be like strsep(). */ + +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 = xmalloc( 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++] = xstrdup(""); + } 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] = xmalloc( 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] = xstrdup( "" ); + } else { + /* End of component - enter it into the array */ + length = (end - start) + 1; + comps[curr] = xmalloc( 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 = xmalloc( (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; + } + free( comps ); + pairs[2*count] = pairs[2*count+1] = NULL; + return pairs; +} + +void split_string_free( char **components ) +{ + char **pnt = components; + while( *pnt != NULL ) { + free( *pnt ); + pnt++; + } + free( components ); +} + +void pair_string_free( char **pairs ) +{ + int n; + for( n = 0; pairs[n] != NULL; n+=2 ) { + free( pairs[n] ); + } + free( pairs ); +} + +char *shave_string( const char *str, const char ch ) { + size_t len = strlen( str ); + char *ret; + if( str[len-1] == ch ) { + len--; + } + if( str[0] == ch ) { + len--; + str++; + } + ret = xmalloc( len + 1 ); + memcpy( ret, str, len ); + ret[len] = '\0'; + return ret; +} + +char *sbuffer_data( sbuffer buf ) { + return buf->data; +} + +int sbuffer_size( sbuffer buf ) { + return buf->used; +} + +void sbuffer_clear( sbuffer buf ) { + memset( buf->data, 0, buf->length ); + buf->used = 0; +} + +/* Grows for given size, 0 on success, -1 on error. + * Could make this externally accessible, but, there hasn't been + * a need currently. */ +int sbuffer_grow( sbuffer buf, size_t newsize ) { + size_t newlen, oldbuflen; + +#define SBUFFER_GROWTH 512 + + if( newsize <= buf->length ) return 0; /* big enough already */ + /* FIXME: ah, can't remember my maths... better way to do this? */ + newlen = ( (newsize / SBUFFER_GROWTH) + 1) * SBUFFER_GROWTH; + + oldbuflen = buf->length; + /* Reallocate bigger buffer */ + buf->data = realloc( buf->data, newlen ); + if( buf->data == NULL ) return -1; + buf->length = newlen; + /* Zero-out the new bit of buffer */ + memset( buf->data+oldbuflen, 0, newlen-oldbuflen ); + + return 0; +} + +int sbuffer_concat( sbuffer buf, ... ) { + va_list ap; + char *next; + size_t totallen = 1; /* initialized to 1 for the terminating \0 */ + + /* Find out how much space we need for all the args */ + va_start( ap, buf ); + do { + next = va_arg( ap, char * ); + if( next != NULL ) { + totallen += strlen( next ); + } + } while( next != NULL ); + va_end( ap ); + + /* Grow the buffer */ + if( sbuffer_grow( buf, buf->used + totallen ) ) + return -1; + + /* Now append the arguments to the buffer */ + va_start( ap, buf ); + do { + next = va_arg( ap, char * ); + if( next != NULL ) { + strcat( buf->data, next ); + } + } while( next != NULL ); + va_end( ap ); + + buf->used += totallen; + return 0; +} + +/* Append zero-terminated string... returns 0 on success or -1 on + * realloc failure. */ +int sbuffer_zappend( sbuffer buf, const char *str ) { + size_t len = strlen( str ); + if( sbuffer_grow( buf, buf->used + len + 1 ) ) { + return -1; + } + strcat( buf->data, str ); + buf->used += len; + return 0; +} + +int sbuffer_append( sbuffer buf, const char *data, size_t len ) { + if( sbuffer_grow( buf, buf->used + len ) ) { + return -1; + } + memcpy( buf->data+buf->used, data, len ); + buf->used += len; + return 0; +} + +sbuffer sbuffer_create( void ) { + return sbuffer_create_sized( 512 ); +} + +sbuffer sbuffer_create_sized( size_t s ) { + sbuffer buf = xmalloc( sizeof(struct sbuffer_s) ); + buf->data = xmalloc( s ); + memset( buf->data, 0, s ); + buf->length = s; + buf->used = 0; + return buf; +} + +void sbuffer_destroy( sbuffer buf ) { + if( buf->data ) { + free( buf->data ); + } + free( buf ); +} + +char *sbuffer_finish( sbuffer buf ) +{ + char *ret = buf->data; + free( buf ); + return ret; +} + +void sbuffer_altered( sbuffer buf ) +{ + buf->used = strlen( buf->data ); +} + +/* Writes the ASCII representation of the MD5 digest into the + * given buffer, which must be at least 33 characters long. */ +void md5_to_ascii( const unsigned char md5_buf[16], char *buffer ) +{ + int count; + for( count = 0; count<16; count++ ) { + buffer[count*2] = HEX2ASC( md5_buf[count] >> 4 ); + buffer[count*2+1] = 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 ascii_to_md5( const char *buffer, unsigned char md5_buf[16] ) +{ + int count; + for( count = 0; count<16; count++ ) { + md5_buf[count] = ((ASC2HEX(buffer[count*2])) << 4) | + ASC2HEX(buffer[count*2+1]); + } +} + +#ifdef SPLIT_STRING_TEST + +#include <stdio.h> + +int main( int argc, char *argv[] ) { + char *str, sep, **comps, *wspace, *quotes; + int count; + if( argc < 3 ) { + printf( "Usage: split_string <sep> <string> [whitespace] [quotes]\n" ); + return -1; + } + sep = *argv[1]; + str = argv[2]; + if( argc > 3 ) { + wspace = argv[3]; + } else { + wspace = " "; + } + if( argc > 4 ) { + quotes = argv[4]; + } else { + quotes = "\""; + } + printf( "String: [%s] Separator: `%c' Whitespace: [%s] Quotes: [%s]\n", str, sep, wspace, quotes ); + comps = split_string( str, sep, quotes, wspace ); + count = 0; + do { + printf( "Component #%d: [%s]\n", count, comps[count] ); + } while( comps[++count] != NULL ); + return 0; +} + +#endif + +#ifdef PAIR_STRING_TEST + +#include <stdio.h> + +int main( int argc, char *argv[] ) { + char *str, compsep, kvsep, **comps, *wspace, *quotes; + int count; + if( argc < 4 ) { + printf( "Usage: pair_string <compsep> <kvsep> <string> [whitespace] [quotes]\n" ); + return -1; + } + compsep = *argv[1]; + kvsep = *argv[2]; + str = argv[3]; + if( argc > 4 ) { + wspace = argv[4]; + } else { + wspace = " "; + } + if( argc > 5 ) { + quotes = argv[5]; + } else { + quotes = "\""; + } + printf( "String: [%s] CompSep: `%c' K/VSep: `%c'\nWhitespace: [%s] Quotes: [%s]\n", str, compsep, kvsep, wspace, quotes ); + comps = pair_string( str, compsep, kvsep, quotes, wspace ); + count = 0; + do { + printf( "Component #%d: Key [%s] Value [%s]\n", count, + comps[count], comps[count+1] ); + } while( comps[(count+=2)] != NULL ); + return 0; +} + +#endif + +/* variables: + * + * Local variables: + * compile-command: "gcc -g -O2 -Wall -I.. -ansi -DHAVE_CONFIG_H -DSPLIT_STRING_TEST -o split_string string_utils.c" + * End: + */ diff --git a/neon/src/string_utils.h b/neon/src/string_utils.h new file mode 100644 index 000000000..c7302e609 --- /dev/null +++ b/neon/src/string_utils.h @@ -0,0 +1,198 @@ +/* + String utility functions + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 STRING_UTILS_H +#define STRING_UTILS_H + +#include <config.h> + +#ifdef HAVE_STDARG_H +#include <stdarg.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> /* for strcat */ +#endif + +#include "xalloc.h" + +#include <ctype.h> /* for tolower */ + +#define ASC2HEX(x) (((x) <= '9') ? ((x) - '0') : (tolower((x)) + 10 - 'a')) +#define HEX2ASC(x) ((x) > 9 ? ((x) - 10 + 'a') : ((x) + '0')) + +/* Splits str into component parts using given seperator, + * skipping given whitespace around separators and at the + * beginning and end of str. Separators are ignored within + * any pair of characters specified as being quotes. + * Returns array of components followed by a NULL pointer. The + * components are taken from dynamically allocated memory, and + * the entire array can be easily freed using split_string_free. + * + * aka: What strtok should have been. + */ +char **split_string( const char *str, const char seperator, + const char *quotes, const char *whitespace ); + +/* As above, but returns count of items as well */ +char **split_string_c( const char *str, const char seperator, + const char *quotes, const char *whitespace, int *count ); + + +/* A bit like split_string, except each component is split into a pair. + * Each pair is returned consecutively in the return array. + * e.g.: + * strpairs( "aa=bb,cc=dd", ',', '=', NULL, NULL ) + * => "aa", "bb", "cc", "dd", NULL, NULL + * Note, that if a component is *missing* it's key/value separator, + * then the 'value' in the array will be a NULL pointer. But, if the + * value is zero-length (i.e., the component separator follows directly + * after the key/value separator, or with only whitespace inbetween), + * then the value in the array will be a zero-length string. + * e.g.: + * pair_string( "aaaa,bb=cc,dd=,ee=ff", ',', '=', NULL, NULL ) + * => "aaaa", NULL, "bb", "cc", "dd", "", "ee", "ff" + * A NULL key indicates the end of the array (the value will also + * be NULL, for convenience). + */ +char **pair_string( const char *str, const char compsep, const char kvsep, + const char *quotes, const char *whitespace ); + +/* Frees the array returned by split_string */ +void split_string_free( char **components ); + +/* Frees the array returned by pair_string */ +void pair_string_free( char **pairs ); + +/* Returns a string which is str with ch stripped from + * beggining and end, if present in either. The string returned + * is dynamically allocated using xmalloc(). + * + * e.g. shave_string( "abbba", 'a' ) => "bbb". */ +char *shave_string( const char *str, const char ch ); + +#define EOL "\r\n" + +#define STRIP_EOL( str ) \ +do { \ + char *p; \ + if( (p = strrchr( str, '\r' )) != NULL ) *p = '\0'; \ + if( (p = strrchr( str, '\n' )) != NULL ) *p = '\0'; \ +} while(0) + +/* String buffer handling. + * A string buffer sbuffer * which grows dynamically with the string. */ + +struct sbuffer_s; +typedef struct sbuffer_s *sbuffer; + +/* Returns contents of buffer at current point in time. + * NOTE: if the buffer is modified with _concat, _append etc, + * this value may no longer be correct. */ +char *sbuffer_data( sbuffer buf ); + +/* Returns size of data in buffer, equiv to strlen(sbuffer_data(buf)) */ +int sbuffer_size( sbuffer buf ); + +/* Concatenate all given strings onto the end of the buffer. + * The strings must be null-terminated, and MUST be followed by a + * NULL argument marking the end of the list. + * Returns: + * 0 on success + * non-zero on error + */ +int sbuffer_concat( sbuffer buf, ... ); + +/* Create a new sbuffer. Returns NULL on error */ +sbuffer sbuffer_create( void ); + +/* Create a new sbuffer of given minimum size. Returns NULL on error */ +sbuffer sbuffer_create_sized( size_t size ); + +/* Destroys (deallocates) a buffer */ +void sbuffer_destroy( sbuffer buf ); + +/* Append a zero-terminated string 'str' to buf. + * Returns 0 on success, non-zero on error. */ +int sbuffer_zappend( sbuffer buf, const char *str ); + +/* Append 'len' bytes of 'data' to buf. + * Returns 0 on success, non-zero on error. */ +int sbuffer_append( sbuffer buf, const char *data, size_t len ); + +/* Empties the contents of buf; makes the buffer zero-length. */ +void sbuffer_clear( sbuffer buf ); + +/* Grows the sbuffer to a minimum size. + * Returns 0 on success, non-zero on error */ +int sbuffer_grow( sbuffer buf, size_t size ); + +void sbuffer_altered( sbuffer buf ); + +/* MD5 ascii->binary conversion */ +void md5_to_ascii( const unsigned char md5_buf[16], char *buffer ); +void ascii_to_md5( const char *buffer, unsigned char md5_buf[16] ); + +/* Destroys a buffer, returning the buffer contents. */ +char *sbuffer_finish( sbuffer buf ); + +/* Handy macro to free things. */ +#define SAFE_FREE(x) \ +do { if( (x)!=NULL ) free( (x) ); (x) = NULL; } while(0) + +/* TODO: do these with stpcpy instead... more efficient, but means + * bloat on non-GNU platforms. */ + +/* TODO: could replace with glib equiv's where available, too */ + +#define CONCAT2( out, str1, str2 ) \ +do { \ + out = xmalloc( strlen(str1) + strlen(str2) + 1 ); \ + if( out != NULL ) { \ + strcpy( out, str1 ); \ + strcat( out, str2 ); \ + } \ +} while(0) + +#define CONCAT3( out, str1, str2, str3 ) \ +do { \ + out = xmalloc( strlen(str1) + strlen(str2) + strlen(str3) + 1 ); \ + if( out != NULL ) { \ + strcpy( out, str1 ); \ + strcat( out, str2 ); \ + strcat( out, str3 ); \ + } \ +} while(0) + +#define CONCAT4( out, str1, str2, str3, str4 ) \ +do { \ + out = xmalloc( strlen(str1) + strlen(str2) \ + + strlen(str3) + strlen(str4) + 1 ); \ + if( out != NULL ) { \ + strcpy( out, str1 ); \ + strcat( out, str2 ); \ + strcat( out, str3 ); \ + strcat( out, str4 ); \ + } \ +} while(0) + +#endif STRING_UTILS_H + diff --git a/neon/src/uri.c b/neon/src/uri.c new file mode 100644 index 000000000..622044f9b --- /dev/null +++ b/neon/src/uri.c @@ -0,0 +1,303 @@ +/* + HTTP URI handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: uri.c,v 1.7 2000/05/10 16:47:06 joe Exp +*/ + +#include <config.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 <ctype.h> +#include <stdio.h> + +#include "http_utils.h" /* for 'min' */ +#include "string_utils.h" /* for CONCAT3 */ +#include "uri.h" + +char *uri_parent( const char *uri ) { + const char *pnt; + char *ret; + pnt = uri+strlen(uri)-1; + while( *(--pnt) != '/' && pnt >= uri ) /* noop */; + if( pnt < uri ) { + /* not a valid absPath */ + return NULL; + } + /* uri + * V + * |---| + * /foo/bar/ + */ + ret = xmalloc( (pnt - uri) + 2 ); + memcpy( ret, uri, (pnt - uri) + 1 ); + ret[1+(pnt-uri)] = '\0'; + pnt++; + return ret; +} + +int uri_has_trailing_slash( const char *uri ) +{ + return (uri[strlen(uri)-1] == '/'); +} + +const char *uri_abspath( const char *uri ) { + const char *ret; + /* Look for the scheme: */ + ret = strstr( uri, "://" ); + if( ret == NULL ) { + /* No scheme */ + ret = uri; + } else { + /* Look for the abs_path */ + ret = strchr( ret+3, '/' ); + if( ret == NULL ) { + /* Uh-oh */ + ret = uri; + } + } + return ret; +} + +/* TODO: not a proper URI parser */ +int uri_parse( const char *uri, struct uri *parsed, + const struct uri *defaults ) +{ + const char *pnt, *slash, *colon; + + parsed->port = -1; + parsed->host = NULL; + parsed->path = NULL; + parsed->scheme = NULL; + + pnt = strstr( uri, "://" ); + if( pnt ) { + parsed->scheme = xstrndup( uri, pnt - uri ); + pnt += 3; /* start of hostport segment */ + slash = strchr( pnt, '/' ); + colon = strchr( pnt, ':' ); + if( slash == NULL ) { + parsed->path = xstrdup( "/" ); + if( colon == NULL ) { + if( defaults ) parsed->port = defaults->port; + parsed->host = xstrdup( pnt ); + } else { + parsed->port = atoi(colon+1); + parsed->host = xstrndup( pnt, colon - pnt ); + } + } else { + if( colon == NULL || colon > slash ) { + /* No port segment */ + if( defaults ) parsed->port = defaults->port; + parsed->host = xstrndup( pnt, slash - pnt ); + } else { + /* Port segment */ + parsed->port = atoi( colon + 1 ); + parsed->host = xstrndup( pnt, colon - pnt ); + } + parsed->path = xstrdup( slash ); + } + } else { + if( defaults && defaults->scheme != NULL ) { + parsed->scheme = xstrdup( defaults->scheme ); + } + if( defaults && defaults->host != NULL ) { + parsed->host = xstrdup( defaults->host ); + } + if( defaults ) parsed->port = defaults->port; + parsed->path = xstrdup(uri); + } + return 0; +} + +void uri_free( struct uri *uri ) +{ + HTTP_FREE( uri->host ); + HTTP_FREE( uri->path ); + HTTP_FREE( uri->scheme ); +} + +/* Returns an absoluteURI */ +char *uri_absolute( const char *uri, const char *scheme, + const char *hostport ) { + char *ret; + /* Is it absolute already? */ + if( strncmp( uri, scheme, strlen( scheme ) ) == 0 ) { + /* Yes it is */ + ret = xstrdup( uri ); + } else { + /* Oh no it isn't */ + CONCAT3( ret, scheme, hostport, uri ); + } + return ret; +} + +/* Un-escapes a URI. Returns xmalloc-allocated URI */ +char *uri_unescape( const char *uri ) { + const char *pnt; + char *ret, *retpos, buf[5] = { "0x00\0" }; + retpos = ret = xmalloc( strlen( uri ) + 1 ); + for( pnt = uri; *pnt != '\0'; pnt++ ) { + if( *pnt == '%' ) { + if( !isxdigit((unsigned char) pnt[1]) || + !isxdigit((unsigned char) pnt[2]) ) { + /* Invalid URI */ + return NULL; + } + buf[2] = *++pnt; buf[3] = *++pnt; /* bit faster than memcpy */ + *retpos++ = 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". + * ...where... + * unreserved = alphanum | mark + * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + * + * We need also to skip reserved characters + * reserved = ";" | "/" | "?" | ":" | "@" | "&" | + * "=" | "+" | "$" | "," + */ + +/* Lookup table: + * 1 marks an RESERVED character. 2 marks a UNRESERVED character. + * 0 marks everything else. + */ + +#define RE 1 +#define UN 2 +static const short uri_chars[128] = { +/* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 */ 0, UN, 0, 0, RE, 0, RE, UN, UN, UN, UN, RE, RE, UN, UN, RE, +/* 48 */ UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, RE, RE, 0, RE, 0, RE, +/* 64 */ RE, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, +/* 80 */ UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, 0, 0, 0, 0, UN, +/* 96 */ 0, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, +/* 112 */ UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, 0, 0, 0, UN, 0 +}; +#undef RE +#undef UN + +/* Escapes the abspath segment of a URI. + * Returns xmalloc-allocated string. + */ +char *uri_abspath_escape( const char *abs_path ) { + const char *pnt; + char *ret, *retpos; + /* Rather than mess about growing the buffer, allocate as much as + * the URI could possibly need, i.e. every character gets %XX + * escaped. Hence 3 times input size. + */ + retpos = ret = xmalloc( strlen( abs_path ) * 3 + 1 ); + for( pnt = abs_path; *pnt != '\0'; pnt++ ) { + /* Escape it: + * - if it isn't 7-bit + * - if it is a reserved character (but ignore '/') + * - otherwise, if it is not an unreserved character + * (note, there are many characters that are neither reserved + * nor unreserved) + */ + if( *pnt<0 || (uri_chars[(int) *pnt] < 2 && *pnt!='/' )) { + /* Escape it - %<hex><hex> */ + /* FIXME: Could overflow buffer with uri_abspath_escape("%") */ + sprintf( retpos, "%%%02x", (unsigned char) *pnt ); + retpos += 3; + } else { + /* It's cool */ + *retpos++ = *pnt; + } + } + *retpos = '\0'; + return ret; +} + +/* TODO: implement properly */ +int uri_compare( const char *a, const char *b ) { + int ret = strcasecmp( a, b ); + if( ret ) { + /* TODO: joe: I'm not 100% sure this is logically sound. + * It feels right, though */ + int traila = uri_has_trailing_slash( a ), + trailb = uri_has_trailing_slash( b ), + lena = strlen(a), lenb = strlen(b); + if( traila != trailb && abs(lena - lenb) == 1) { + /* They are the same length, apart from one has a trailing + * slash and the other doesn't. */ + if( strncasecmp( a, b, min(lena, lenb) ) == 0 ) + ret = 0; + } + } + return ret; +} + +/* Hrm, well, this is kind of not very generic over URI schemes, but wth */ +int uri_childof( const char *parent, const char *child ) +{ + char *root = xstrdup(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 = (uri_compare( parent, root ) == 0 ); + } + free( root ); + return ret; +} + +#ifdef URITEST + +int main( int argc, char *argv[] ) { + char *tmp; + if( argc<2 || argc>3 ) { + printf( "doh. usage:\nuritest uria [urib]\n" + "e.g. uritest \"/this/is/a silly<filename>/but/hey\"\n" ); + exit(-1); + } + if( argv[2] ) { + printf( "uri_compare: %s with %s: %s\n", + argv[1], argv[2], + uri_compare(argv[1], argv[2])==0?"true":"false" ); + } else { + printf( "Input URI: %s\n", argv[1] ); + tmp = uri_abspath_escape( argv[1] ); + printf( "Encoded: %s\n", tmp ); + printf( "Decoded: %s\n", uri_unescape( tmp ) ); + } + return 0; +} + +#endif /* URITEST */ diff --git a/neon/src/uri.h b/neon/src/uri.h new file mode 100644 index 000000000..01d7dd993 --- /dev/null +++ b/neon/src/uri.h @@ -0,0 +1,63 @@ +/* + HTTP URI handling + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 URI_H +#define URI_H + +/* Un-escapes a URI. Returns malloc-allocated URI on success, + * or NULL on failure (malloc failure or invalid %<HEX><HEX> sequence). */ +char *uri_unescape( const char *uri ); + +/* Escapes the abspath segment of a URI. + * Returns malloc-allocated string on success, or NULL on malloc failure. + */ +char *uri_abspath_escape( const char *abs_path ); + +const char *uri_abspath( const char *uri ); + +char *uri_parent( const char *uri ); + +int uri_compare( const char *a, const char *b ); + +/* Returns an absolute URI from a possibly-relative 'uri', using + * given scheme + hostport segment. + * Returns malloc-allocated string on success, or NULL on malloc failure. */ +char *uri_absolute( const char *uri, const char *scheme, + const char *hostport ); + +/* Returns non-zero if child is a child of parent */ +int uri_childof( const char *parent, const char *child ); + +int uri_has_trailing_slash( const char *uri ); + +struct uri { + char *scheme; + char *host; + int port; + char *path; +}; + +int uri_parse( const char *uri, struct uri *parsed, + const struct uri *defaults ); + +void uri_free( struct uri *parsed ); + +#endif URI_H diff --git a/neon/src/xalloc.c b/neon/src/xalloc.c new file mode 100644 index 000000000..95e3081be --- /dev/null +++ b/neon/src/xalloc.c @@ -0,0 +1,56 @@ +/* + Replacement memory allocation handling etc. + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 + + Id: xalloc.c,v 1.1 2000/05/09 22:29:56 joe Exp +*/ + +#include <config.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "xalloc.h" + +void *xmalloc( size_t len ) +{ + void *ptr = malloc(len); + if( !ptr ) { + /* uh-oh */ + abort(); + } + return ptr; +} + +char *xstrdup( const char *s ) +{ + return strcpy( xmalloc( strlen(s) + 1 ), s ); +} + +char *xstrndup( const char *s, size_t n ) +{ + char *new = xmalloc( n + 1 ); + new[n] = '\0'; + memcpy( new, s, n ); + return new; +} diff --git a/neon/src/xalloc.h b/neon/src/xalloc.h new file mode 100644 index 000000000..51b21cb34 --- /dev/null +++ b/neon/src/xalloc.h @@ -0,0 +1,33 @@ +/* + Replacement memory allocation handling etc. + Copyright (C) 1999-2000, Joe Orton <joe@orton.demon.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 XALLOC_H +#define XALLOC_H + +#include <sys/types.h> + +void *xmalloc( size_t len ); + +char *xstrdup( const char *s ); + +char *xstrndup( const char *s, size_t n ); + +#endif /* XALLOC_H */ diff --git a/neon/test/.cvsignore b/neon/test/.cvsignore new file mode 100644 index 000000000..9cb7ba7dd --- /dev/null +++ b/neon/test/.cvsignore @@ -0,0 +1,3 @@ +tests +*-tests +Makefile diff --git a/neon/test/COPYING b/neon/test/COPYING new file mode 100644 index 000000000..a43ea2126 --- /dev/null +++ b/neon/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/neon/test/ChangeLog b/neon/test/ChangeLog new file mode 100644 index 000000000..0ab69b845 --- /dev/null +++ b/neon/test/ChangeLog @@ -0,0 +1,51 @@ +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/neon/test/Makefile.in b/neon/test/Makefile.in new file mode 100644 index 000000000..7a514574e --- /dev/null +++ b/neon/test/Makefile.in @@ -0,0 +1,55 @@ + +SHELL = @SHELL@ +CFLAGS = @CFLAGS@ -I. -I../src +LDFLAGS = -L. @LDFLAGS@ -L../src -L../src/.libs +DEFS = @DEFS@ +top_builddir = .. +top_srcdir = @top_srcdir@ + +VPATH = @srcdir@ + +AR = ar + +RANLIB = @RANLIB@ +LIBS = @LIBS@ @NEON_LIBS@ +CC = @CC@ + +TESTS = uri-tests util-tests string-tests sock-tests http-tests + +LIBTEST = libtest.a + +all: $(TESTS) + +clean: + rm -f $(TESTS) *.o + +test: $(TESTS) + $(SHELL) run.sh $(TESTS) + +Makefile: Makefile.in + (cd .. && CONFIG_FILES=test/Makefile ./config.status) + +libtest.a: tests.o tests.h ../src/@NEON_TARGET@ + $(AR) cru $@ tests.o + $(RANLIB) $@ + +uri-tests: uri-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ uri-tests.o -ltest $(LIBS) + +util-tests: util-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ util-tests.o -ltest $(LIBS) + +string-tests: string-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ string-tests.o -ltest $(LIBS) + +sock-tests: sock-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ sock-tests.o -ltest $(LIBS) + +http-tests: http-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ http-tests.o -ltest $(LIBS) + +uri-tests.o: uri-tests.c tests.h +util-tests.o: util-tests.c tests.h +string-tests: string-tests.c tests.h +sock-tests.o: sock-tests.c tests.h +http-tests.o: http-tests.c tests.h diff --git a/neon/test/README b/neon/test/README new file mode 100644 index 000000000..02b632cd3 --- /dev/null +++ b/neon/test/README @@ -0,0 +1,14 @@ + +Stupidly Simple Test Suite for neon +----------------------------------- + +http-tests requires that you have a running HTTP server on localhost +port 80, and you have copied htdocs/* to server-htdocs-root/test/* + +The test code is licensed under the GPL. + +Credits +------- + +This test suite is inspired by the Subversion project and discussion +on the subversion mailing list. diff --git a/neon/test/STATUS b/neon/test/STATUS new file mode 100644 index 000000000..efb5c34b6 --- /dev/null +++ b/neon/test/STATUS @@ -0,0 +1,79 @@ + -*- 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 --- [1] + comparisons of scheme names MUST be ... --- + 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*: character set handling App/??? [3] + +3.5*: content codings App + +3.6: MUST requirements for multiple transfer-codings --- [4] + +3.6.1: parsing of chunked transfer coding ??? + TODO: translate that section into requirements + +3.6.1: MUST be able to handle "chunked" transfer-coding Yes + MUST ignore unknown chunk-extension extensions ??? + +3.7: parsing of Content-Type headers ??? + TODO: translate section into requirements +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 + + + + + +[1]: these are only applicable if neon had a full URI comparison +suite. + +[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/neon/test/acl.c b/neon/test/acl.c new file mode 100644 index 000000000..1b1efa76c --- /dev/null +++ b/neon/test/acl.c @@ -0,0 +1,101 @@ +/* + Dummy ACL tests + Copyright (C) 2001, 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" + +/**** 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 = ne_session_create(); + + ne_session_server(sess, "localhost", 7777); + ON(spawn_server(7777, 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()); + + return OK; +} + +static int grant_all(void) +{ + ne_acl_entry e; + + 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; + + 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; + + 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; + + 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) +}; diff --git a/neon/test/auth.c b/neon/test/auth.c new file mode 100644 index 000000000..729f55bf2 --- /dev/null +++ b/neon/test/auth.c @@ -0,0 +1,258 @@ +/* + Authentication tests + Copyright (C) 2001, 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" + +char username[BUFSIZ], password[BUFSIZ]; +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==" + NE_DEBUG(NE_DBG_HTTP, "Got auth header: [%s]\n", value); + NE_DEBUG(NE_DBG_HTTP, "Wanted header: [%s]\n", B); + auth_failed = strcmp(value, B); +#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(nsocket *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 sock_send_string(sock, buffer); +} + +static int auth_serve(nsocket *sock, void *userdata) +{ + auth_failed = 0; + + /* 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) +{ + int ret; + ne_session *sess = ne_session_create(); + + ne_session_server(sess, "localhost", 7777); + + ne_set_server_auth(sess, auth_cb, NULL); + + strcpy(username, "Aladdin"); + strcpy(password, "open sesame"); + + CALL(spawn_server(7777, auth_serve, NULL)); + + ret = any_request(sess, "/norman"); + + ne_session_destroy(sess); + + CALL(await_server()); + + ONREQ(ret); + return OK; +} + +static int retry_serve(nsocket *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); + + 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; + 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 = ne_session_create(); + int count = 0; + + ne_session_server(sess, "localhost", 7777); + ne_set_server_auth(sess, retry_cb, &count); + + CALL(spawn_server(7777, retry_serve, NULL)); + + /* 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); + + 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(basic), + T(retries), + T(NULL) +}; diff --git a/neon/test/basic.c b/neon/test/basic.c new file mode 100644 index 000000000..84be1ae6f --- /dev/null +++ b/neon/test/basic.c @@ -0,0 +1,101 @@ +/* + neon test suite + Copyright (C) 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> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_basic.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int content_type(void) +{ + int n; + struct { + const char *value, *type, *subtype, *charset; + } ctypes[] = { + { "text/plain", "text", "plain", NULL }, + { "text/plain ", "text", "plain", NULL }, + { "application/xml", "application", "xml", NULL }, +#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 */ + { "text/xml; charset=\"utf-8", "text", "xml", 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; +} + +ne_test tests[] = { + T(content_type), /* test functions here */ + + /* end of test functions. */ + T(NULL) +}; + diff --git a/neon/test/child.c b/neon/test/child.c new file mode 100644 index 000000000..1014ef06d --- /dev/null +++ b/neon/test/child.c @@ -0,0 +1,160 @@ +/* + Framework for testing with a server process + Copyright (C) 2001, 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> + +#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 <signal.h> + +#include "ne_socket.h" + +#include "tests.h" +#include "child.h" + +static pid_t child = 0; + +static struct in_addr lh_addr; + +int lookup_localhost(void) +{ + if (sock_name_lookup("localhost", &lh_addr)) + return FAILHARD; + + return OK; +} + +static nsocket *server_socket(int port) +{ + int ls = socket(PF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr; + nsocket *s; + int val = 1; + + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(int)); + + addr.sin_addr = lh_addr; + addr.sin_port = htons(port); + + if (bind(ls, (struct sockaddr *)&addr, sizeof(addr))) { + printf("bind failed: %s\n", strerror(errno)); + return NULL; + } + if (listen(ls, 5)) { + printf("listen failed: %s\n", strerror(errno)); + return NULL; + } + + s = sock_accept(ls); + close(ls); + + return s; +} + +static void minisleep(void) +{ + sleep(1); +} + +/* This runs as the child process. */ +static int server_child(int port, server_fn callback, void *userdata) +{ + nsocket *s; + int ret; + + in_child(); + + s = server_socket(port); + + if (s == NULL) + return FAIL; + + ret = callback(s, userdata); + + sock_close(s); + + return ret; +} + + +int spawn_server(int port, server_fn fn, void *ud) +{ + child = fork(); + + ONN("fork server", child == -1); + + if (child == 0) { + /* this is the child. */ + int ret = server_child(port, fn, ud); + + /* print the error out otherwise it gets lost. */ + if (ret) { + printf("server child failed: %s\n", name); + } + + /* and quit the child. */ + exit(ret); + } else { + /* this is the parent... sleep for a bit to let the parent + * get itself into gear. */ + minisleep(); + return OK; + } +} + +int await_server(void) +{ + int status; + + (void) wait(&status); + + i_am("error from server process"); + + /* so that we aren't reaped by mistake. */ + child = 0; + + return WEXITSTATUS(status); +} + +int reap_server(void) +{ + int status; + + if (child != 0) { + (void) kill(child, SIGTERM); + minisleep(); + (void) wait(&status); + } + + return OK; +} diff --git a/neon/test/child.h b/neon/test/child.h new file mode 100644 index 000000000..24121f1c3 --- /dev/null +++ b/neon/test/child.h @@ -0,0 +1,47 @@ +/* + Framework for testing with a server process + Copyright (C) 2001, 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 + +/* Test which does DNS lookup on "localhost": this must be the first + * named test. */ +int lookup_localhost(void); + +/* Callback for spawn_server. */ +typedef int (*server_fn)(nsocket *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); + +/* Blocks until child process exits, and gives return code of 'fn'. */ +int await_server(void); + +/* Kills child process. */ +int reap_server(void); + +#endif /* CHILD_H */ diff --git a/neon/test/common/ChangeLog b/neon/test/common/ChangeLog new file mode 100644 index 000000000..a7ca54d38 --- /dev/null +++ b/neon/test/common/ChangeLog @@ -0,0 +1,31 @@ +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/neon/test/common/README b/neon/test/common/README new file mode 100644 index 000000000..24c661483 --- /dev/null +++ b/neon/test/common/README @@ -0,0 +1,3 @@ + +Common test code directory for neon. + diff --git a/neon/test/common/child.c b/neon/test/common/child.c new file mode 100644 index 000000000..5331548d4 --- /dev/null +++ b/neon/test/common/child.c @@ -0,0 +1,202 @@ +/* + Framework for testing with a server process + Copyright (C) 2001, 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> + +#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 <signal.h> + +#include "ne_socket.h" + +#include "tests.h" +#include "child.h" + +static pid_t child = 0; + +static struct in_addr lh_addr; + +/* 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) +{ + if (sock_name_lookup("localhost", &lh_addr)) + return FAILHARD; + + return OK; +} + +static nsocket *server_socket(int readyfd, int port) +{ + int ls = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr; + nsocket *s; + int val = 1; + + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(int)); + + addr.sin_addr = lh_addr; + addr.sin_port = htons(port); + addr.sin_family = AF_INET; + + if (bind(ls, (struct sockaddr *)&addr, sizeof(addr))) { + printf("bind failed: %s\n", strerror(errno)); + return NULL; + } + if (listen(ls, 5)) { + printf("listen failed: %s\n", strerror(errno)); + return NULL; + } + +#ifdef USE_PIPE + /* wakey wakey. */ + write(readyfd, "a", 1); +#endif + + s = sock_accept(ls); + close(ls); + + return s; +} + +static void minisleep(void) +{ + sleep(1); +} + +/* This runs as the child process. */ +static int server_child(int readyfd, int port, + server_fn callback, void *userdata) +{ + nsocket *s; + int ret; + + in_child(); + + s = server_socket(readyfd, port); + + if (s == NULL) + return FAIL; + + ret = callback(s, userdata); + + sock_close(s); + + return ret; +} + +int spawn_server(int port, server_fn fn, void *ud) +{ + 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(); + + ONN("fork server", child == -1); + + if (child == 0) { + /* this is the child. */ + int ret; + + ret = server_child(fds[1], 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", name); + } + + /* 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 await_server(void) +{ + int status; + + (void) wait(&status); + + i_am("error from server process"); + + /* so that we aren't reaped by mistake. */ + child = 0; + + return WEXITSTATUS(status); +} + +int reap_server(void) +{ + int status; + + if (child != 0) { + (void) kill(child, SIGTERM); + minisleep(); + (void) wait(&status); + } + + return OK; +} diff --git a/neon/test/common/child.h b/neon/test/common/child.h new file mode 100644 index 000000000..24121f1c3 --- /dev/null +++ b/neon/test/common/child.h @@ -0,0 +1,47 @@ +/* + Framework for testing with a server process + Copyright (C) 2001, 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 + +/* Test which does DNS lookup on "localhost": this must be the first + * named test. */ +int lookup_localhost(void); + +/* Callback for spawn_server. */ +typedef int (*server_fn)(nsocket *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); + +/* Blocks until child process exits, and gives return code of 'fn'. */ +int await_server(void); + +/* Kills child process. */ +int reap_server(void); + +#endif /* CHILD_H */ diff --git a/neon/test/common/run.sh b/neon/test/common/run.sh new file mode 100755 index 000000000..01ed5a9c7 --- /dev/null +++ b/neon/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/neon/test/common/tests.c b/neon/test/common/tests.c new file mode 100644 index 000000000..b24e6d34f --- /dev/null +++ b/neon/test/common/tests.c @@ -0,0 +1,152 @@ +/* + Stupidly simple test framework + Copyright (C) 2001, 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/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" + +const char *name = NULL; +static FILE *child_debug; + +static int passes = 0, fails = 0, aborted = 0; +static const char *suite; + +void i_am(const char *testname) +{ + name = testname; +} + +#define TEST_DEBUG (NE_DBG_HTTP | NE_DBG_SOCKET) + +#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); +} + +void in_child(void) +{ + ne_debug_init(child_debug, TEST_DEBUG); + NE_DEBUG(TEST_DEBUG, "**** Child forked ****\n"); + signal(SIGSEGV, child_segv); +} + +int main(int argc, char *argv[]) +{ + int n; + FILE *debug; + + /* get basename(argv[0]) */ + suite = strrchr(argv[0], '/'); + if (suite == NULL) { + suite = argv[0]; + } else { + suite++; + } + + sock_init(); + + debug = fopen("debug.log", "a"); + if (debug == NULL) { + fprintf(stderr, "%s: Could not open debug.log: %s\n", 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", suite, + strerror(errno)); + fclose(debug); + return -1; + } + + ne_debug_init(debug, TEST_DEBUG); + + if (tests[0] == NULL) { + printf("-> no tests found in %s\n", suite); + return -1; + } + + printf("-> running %s:\n", suite); + + for (n = 0; !aborted && tests[n] != NULL; n++) { + printf("%d: ", n); + name = NULL; + fflush(stdout); + NE_DEBUG(TEST_DEBUG, "******* Running test %d ********\n", n); + switch (tests[n]()) { + case OK: + printf("pass.\n"); + passes++; + break; + case FAILHARD: + aborted = 1; + /* fall-through */ + case FAIL: + if (name != NULL) { + printf("%s - ", name); + } + printf("FAIL.\n"); + fails++; + break; + } + } + + printf("-> summary for %s: of %d tests: %d passed, %d failed. %.1f%%\n", + suite, n, passes, fails, 100*(float)passes/n); + + 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; + } + + return fails; +} + diff --git a/neon/test/common/tests.h b/neon/test/common/tests.h new file mode 100644 index 000000000..a299148f4 --- /dev/null +++ b/neon/test/common/tests.h @@ -0,0 +1,60 @@ +/* + Stupidly simple test framework + 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 TESTS_H +#define TESTS_H 1 + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <stdio.h> + +/* prototype of test function. */ +typedef int (*test_func)(void); + +/* array of test functions to call. */ +extern test_func tests[]; + +void i_am(const char *testname); + +#define OK 0 +#define FAIL 1 +#define FAILHARD 2 /* fail and skip succeeding tests. */ + +extern const char *name; + +/* are __FUNCTION__ and __LINE__ gcc-isms? Probably. */ + +static char on_err_buf[BUFSIZ]; + +/* child process should call this. */ +void in_child(void); + +#define ON(x) do { sprintf(on_err_buf, "%s line %d", __FUNCTION__, __LINE__ ); i_am(on_err_buf); if ((x)) return FAIL; } while(0) + +#define TESTFUNC do { i_am(__FUNCTION__); } while(0) + +#define ONN(n,x) do { i_am(n); if ((x)) return FAIL; } while(0) + +#define CALL(x) do { int ret = (x); if (ret != OK) return ret; } while (0) + +#endif /* TESTS_H */ diff --git a/neon/test/compress.c b/neon/test/compress.c new file mode 100644 index 000000000..1e33d3f1e --- /dev/null +++ b/neon/test/compress.c @@ -0,0 +1,183 @@ +/* + tests for compressed response handling. + Copyright (C) 2001, 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 "tests.h" + +#ifndef NEON_ZLIB + +static int skip_compress(void) +{ + return SKIP; +} + +test_func tests[] = { + skip_compress, + NULL +}; + +#else + +#include "ne_compress.h" + +#include "child.h" +#include "utils.h" + +static int failed = 0; + +static void reader(void *ud, const char *block, size_t len) +{ + const char **pnt = ud; + + if (memcmp(*pnt, block, len) != 0) + failed = 1; + + *pnt += len; +} + +static int file2buf(int fd, ne_buffer *buf) +{ + char buffer[BUFSIZ]; + ssize_t n; + + while ((n = read(fd, buffer, fd)) > 0) { + ne_buffer_append(buf, buffer, n); + } + + return 0; +} + +static int fetch(const char *realfn, const char *gzipfn, int chunked) +{ + ne_session *sess = ne_session_create(); + ne_request *req; + int fd; + ne_buffer *buf = ne_buffer_create(); + const char *pnt; + struct serve_file_args sfargs; + ne_decompress *dc; + + fd = open(realfn, O_RDONLY); + ONN("failed to open file", fd < 0); + file2buf(fd, buf); + (void) close(fd); + pnt = buf->data; + + ne_session_server(sess, "localhost", 7777); + + if (gzipfn) { + sfargs.fname = gzipfn; + sfargs.headers = "Content-Encoding: gzip\r\n"; + } else { + sfargs.fname = realfn; + sfargs.headers = NULL; + } + sfargs.chunks = chunked; + + CALL(spawn_server(7777, serve_file, &sfargs)); + + req = ne_request_create(sess, "GET", "/"); + dc = ne_decompress_reader(req, ne_accept_2xx, reader, &pnt); + + ONREQ(ne_request_dispatch(req)); + + ONN("error during decompress", ne_decompress_destroy(dc)); + + ne_request_destroy(req); + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + CALL(await_server()); + + ONN("inflated response compare", failed); + + return OK; +} + +/* Test the no-compression case. */ +static int compress_0(void) +{ + return fetch("../NEWS", NULL, 0); +} + +static int compress_1(void) +{ + return fetch("../NEWS", "file1.gz", 0); +} + +/* file1.gz has an embedded filename. */ +static int compress_2(void) +{ + return fetch("../NEWS", "file2.gz", 0); +} + +/* deliver various different sizes of chunks: tests the various + * decoding cases. */ +static int compress_3(void) +{ + return fetch("../NEWS", "file2.gz", 1); +} + +static int compress_4(void) +{ + return fetch("../NEWS", "file1.gz", 1); +} + +static int compress_5(void) +{ + return fetch("../NEWS", "file2.gz", 12); +} + +static int compress_6(void) +{ + return fetch("../NEWS", "file2.gz", 20); +} + +static int compress_7(void) +{ + return fetch("../NEWS", "file1.gz", 10); +} + +static int compress_8(void) +{ + return fetch("../NEWS", "file2.gz", 10); +} + +test_func tests[] = { + compress_0, + compress_1, + compress_2, + compress_3, + compress_4, + compress_5, + compress_6, + compress_7, + compress_8, + NULL, +}; + +#endif /* NEON_ZLIB */ diff --git a/neon/test/cookies.c b/neon/test/cookies.c new file mode 100644 index 000000000..a0ce3ed89 --- /dev/null +++ b/neon/test/cookies.c @@ -0,0 +1,140 @@ +/* + Test for cookies interface (ne_cookies.h) + Copyright (C) 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> + +#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/neon/test/expired.pem b/neon/test/expired.pem new file mode 100644 index 000000000..0062cc8c3 --- /dev/null +++ b/neon/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/neon/test/htdocs/plain b/neon/test/htdocs/plain new file mode 100644 index 000000000..bce1946c2 --- /dev/null +++ b/neon/test/htdocs/plain @@ -0,0 +1 @@ +Test file. diff --git a/neon/test/http-tests.c b/neon/test/http-tests.c new file mode 100644 index 000000000..6ae4a43bd --- /dev/null +++ b/neon/test/http-tests.c @@ -0,0 +1,195 @@ +/* + HTTP server tests + Copyright (C) 2001, 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 don't really test neon, they test an HTTP server. */ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "nsocket.h" +#include "http_utils.h" + +#include "tests.h" + +struct in_addr addr; +nsocket *sock; + +char buffer[BUFSIZ]; +int clength; + +#define HOSTNAME "localhost" + +static int hostname_lookup(void) { + if (sock_name_lookup(HOSTNAME, &addr)) + return FAILHARD; + return OK; +} + +static int connect_to_server(void) { + sock = sock_connect(addr, 80); + if (sock == NULL) + return FAILHARD; + return OK; +} + +static int close_conn(void) { + ON(sock_close(sock)); + return OK; +} + +#define EOL "\r\n" + +#define TRACE_REQ "TRACE / HTTP/1.0\r\n" "Host: " HOSTNAME "\r\n\r\n" + +#define ROOT_URL "/test/" + +#define GET_REQ(url) do { ON(sock_send_string(sock, "GET " url " HTTP/1.0" EOL "Host: " HOSTNAME EOL EOL)); } while (0) + +static int trace(void) { + CALL(connect_to_server()); + + ON(sock_send_string(sock, TRACE_REQ)); + ON(sock_readline(sock, buffer, 1024) < 0); + /* this is true for any Apache server. */ + ON(strcasecmp(buffer, "HTTP/1.1 200 OK\r\n")); + /* read hdrs */ + do { + ON(sock_readline(sock, buffer, 1024) < 0); + } while (strcmp(buffer, "\r\n") != 0); + /* read body */ + ON(sock_read(sock, buffer, 1024) < 0); + /* this will fail if you have a transparent proxy. */ + ONN("pristine TRACE loopback", + strncmp(buffer, TRACE_REQ, strlen(TRACE_REQ)) != 0); + + CALL(close_conn()); + + return OK; +} + +static int basic_syntax(void) { + char *value; + + CALL(connect_to_server()); + GET_REQ(ROOT_URL); + ON(sock_readline(sock, buffer, 1024) < 0); + ONN("CRLF on status-line", strstr(buffer, EOL) == NULL); + + ONN("HTTP-Version is 1.0 or 1.1", + strncasecmp(buffer, "HTTP/1.1", 8) != 0 && + strncasecmp(buffer, "HTTP/1.0", 8)); + + ONN("Status-Line syntax", buffer[8] != ' ' || buffer[12] != ' '); + + /* should be a warning if this fails. */ + ONN("Status-Code is 200", + buffer[9] != '2' || buffer[10] != '0' || buffer[11] != '0'); + + ONN("Reason-Phrase is present", strlen(buffer) < strlen("HTTP/x.y nnn X" EOL)); + + do { + ON(sock_readline(sock, buffer, 1024) < 0); + ONN("CRLF on request-header", strstr(buffer, EOL) == NULL); + if (strcmp(buffer, EOL) != 0) { + value = strchr(buffer, ':'); + ONN("colon in request-header", value == NULL); + ONN("field-name in request-header", value == buffer); + } + } while (strcmp(buffer, EOL) != 0); + + CALL(close_conn()); + + return OK; +} + +static int skip_header(void) +{ + do { + ON(sock_readline(sock, buffer, 1024) < 0); + if (strncasecmp(buffer, "content-length:", 15) == 0) { + clength = atoi(buffer + 16); + } + } while (strcmp(buffer, EOL) != 0); + return OK; +} + +static int simple_get(void) +{ + CALL(connect_to_server()); + GET_REQ(ROOT_URL "plain"); + clength = -1; + CALL(skip_header()); + ONN("Content-Length header present", clength == -1); + ONN("Content-Length of 'plain'", clength != 11); + ONN("read response body", sock_read(sock, buffer, BUFSIZ) != 11); + ONN("content of 'plain'", strncmp(buffer, "Test file.\n", 11) != 0); + + /* FIXME: I'm not sure if this is right, actually. */ + ONN("connection close after GET", + sock_peek(sock, buffer, BUFSIZ) != SOCK_CLOSED); + + CALL(close_conn()); + return OK; +} + +static int simple_head(void) +{ + CALL(connect_to_server()); + ON(sock_send_string(sock, "HEAD " ROOT_URL "plain HTTP/1.0" EOL + "Host: " HOSTNAME EOL EOL)); + clength = -1; + CALL(skip_header()); + ONN("Content-Length header present", clength == -1); + ONN("Content-Length of 'plain'", clength != 11); + + ONN("connection close after HEAD", + sock_peek(sock, buffer, BUFSIZ) != SOCK_CLOSED); + + CALL(close_conn()); + return OK; +} + +static int null_resource(void) +{ + http_status s = {0}; + CALL(connect_to_server()); + GET_REQ(ROOT_URL "nothing-here"); + ON(sock_readline(sock, buffer, BUFSIZ) < 0); + ON(http_parse_statusline(buffer, &s)); + ONN("null resource gives 404", s.code != 404); + CALL(close_conn()); + return OK; +} + +test_func tests[] = { + hostname_lookup, + basic_syntax, + trace, + simple_get, + simple_head, + null_resource, + NULL +}; diff --git a/neon/test/lock.c b/neon/test/lock.c new file mode 100644 index 000000000..94e75b527 --- /dev/null +++ b/neon/test/lock.c @@ -0,0 +1,101 @@ +/* + lock tests + Copyright (C) 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> + +#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 "tests.h" +#include "child.h" +#include "utils.h" + +/* return body of LOCK response for given lock. */ +static char *lock_response(enum ne_lock_scope scope, + const char *depth, + const char *owner, + const char *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><D:activelock>\n" + "<D:locktype><D:write/></D:locktype>\n" + "<D:lockscope><D:%s/></D:lockscope>\n" + "<D:depth>%s</D:depth>\n" + "<D:owner>%s</D:owner>\n" + "<D:timeout>%s</D:timeout>\n" + "<D:locktoken><D:href>%s</D:href></D:locktoken>\n" + "</D:activelock></D:lockdiscovery></D:prop>\n", + scope==ne_lockscope_exclusive?"exclusive":"shared", + depth, owner, timeout, token_href); + return buf; +} + +/* regression test for <= 0.18.2, where timeout field was not parsed correctly. */ +static int lock_timeout(void) +{ + ne_session *sess = ne_session_create(); + char *resp, *rbody = lock_response(ne_lockscope_exclusive, "0", "me", + "Second-6500", "opaquelocktoken:foo"); + struct ne_lock lock = {0}; + + ON(ne_session_server(sess, "localhost", 7777)); + + CONCAT2(resp, + "HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n" + "Connection: close\r\n\r\n", rbody); + + CALL(spawn_server(7777, single_serve_string, resp)); + + lock.uri = "/foo"; + lock.depth = 0; + lock.scope = ne_lockscope_exclusive; + lock.type = ne_locktype_write; + lock.timeout = 5; + + ONREQ(ne_lock(sess, &lock)); + + ONN("lock timeout ignored in response", + lock.timeout != 6500); + + ne_session_destroy(sess); + + CALL(await_server()); + + return OK; +} + +ne_test tests[] = { + T(lock_timeout), + T(NULL) +}; + diff --git a/neon/test/makekeys.sh b/neon/test/makekeys.sh new file mode 100755 index 000000000..e66dabf81 --- /dev/null +++ b/neon/test/makekeys.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# Helper script to create CA and server certificates. + +srcdir=${1-.} + +set -ex + +mkdir ca + +openssl genrsa -rand ${srcdir}/../configure > ca/key.pem +openssl genrsa -rand ${srcdir}/../configure > client.key + +openssl req -x509 -new -key ca/key.pem -out ca/cert.pem <<EOF +US +California +Oakland +Neosign +Random Dept +nowhere.example.com +neon@webdav.org +EOF + +openssl req -new -key ${srcdir}/server.key -out server.csr <<EOF +GB +Cambridgeshire +Cambridge +Neon Hackers Ltd +Neon QA Dept +localhost +neon@webdav.org +. +. +EOF + +openssl req -new -x509 -key ${srcdir}/server.key -out ssigned.pem <<EOF +GB +Cambridgeshire +Cambridge +Neon Hackers Ltd +Neon Self-Signed Dept +localhost +neon@webdav.org +. +. +EOF + +# Only works with a Linuxy hostname command +hostname=`hostname -s 2>/dev/null` +domain=`hostname -d 2>/dev/null` +fqdn=`hostname -f 2>/dev/null` +if [ "x${hostname}.${domain}" = "x${fqdn}" ]; then + openssl req -new -key ${srcdir}/server.key -out wildcard.csr <<EOF +GB +Cambridgeshire +Cambridge +Neon Hackers Ltd +Neon Wildcard Cert +*.${domain} +neon@webdav.org +. +. +EOF +fi + +openssl req -new -key client.key -out client.csr <<EOF +GB +Cambridgeshire +Cambridge +Neon Hackers Ltd +Neon Client Cert +ignored.example.com +neon@webdav.org +. +. +EOF + +touch ca/index.txt +echo 01 > ca/serial + +openssl ca -config ${srcdir}/openssl.conf -batch -days 900 -in server.csr \ + -out server.cert + +openssl ca -config ${srcdir}/openssl.conf -batch -days 900 -in wildcard.csr \ + -out wildcard.cert + +openssl ca -config ${srcdir}/openssl.conf -batch -days 900 -in client.csr \ + -out client.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 "Neon Client Cert" \ + -in client.cert -inkey client.key -out client.p12 diff --git a/neon/test/notvalid.pem b/neon/test/notvalid.pem new file mode 100644 index 000000000..42008b3aa --- /dev/null +++ b/neon/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/neon/test/openssl.conf b/neon/test/openssl.conf new file mode 100644 index 000000000..32dfa61c3 --- /dev/null +++ b/neon/test/openssl.conf @@ -0,0 +1,21 @@ +[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 + +[policy_any] +countryName = supplied +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional diff --git a/neon/test/props.c b/neon/test/props.c new file mode 100644 index 000000000..7c2a635aa --- /dev/null +++ b/neon/test/props.c @@ -0,0 +1,64 @@ +/* + Tests for property handling + Copyright (C) 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> + +#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(); +} + +ne_test tests[] = { + T(patch_simple), + T(NULL) +}; + diff --git a/neon/test/redirect.c b/neon/test/redirect.c new file mode 100644 index 000000000..582462ede --- /dev/null +++ b/neon/test/redirect.c @@ -0,0 +1,135 @@ +/* + Tests for 3xx redirect interface (ne_redirect.h) + Copyright (C) 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> + +#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; +}; + +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; +} + +static int check_redir(struct redir_args *args, const char *expect) +{ + ne_session *sess; + int ret; + const ne_uri *loc; + + CALL(make_session(&sess, serve_redir, args)); + + ne_redirect_register(sess); + + ret = any_request(sess, "/redir/me"); + + ONN("did not get NE_REDIRECT", ret != NE_REDIRECT); + + loc = ne_redirect_location(sess); + + ONN("redirect location was NULL", loc == NULL); + + ONV(strcmp(ne_uri_unparse(loc), expect), + ("redirected to `%s' not `%s'", ne_uri_unparse(loc), expect)); + + ne_session_destroy(sess); + + return OK; +} + +#define DEST "http://foo.com/blah/blah/bar" + +static int simple(void) +{ + struct redir_args args = {302, DEST}; + return check_redir(&args, DEST); +} + +static int redir_303(void) +{ + struct redir_args args = {303, DEST}; + return check_redir(&args, DEST); +} + +/* check that a non-absoluteURI is qualified properly */ +static int non_absolute(void) +{ + struct redir_args args = {302, "/foo/bar/blah"}; + return check_redir(&args, "http://localhost:7777/foo/bar/blah"); +} + +#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 + +ne_test tests[] = { + T(lookup_localhost), + T(simple), + T(redir_303), + T(non_absolute), + T(NULL) +}; + diff --git a/neon/test/regress.c b/neon/test/regress.c new file mode 100644 index 000000000..a2d70e5ba --- /dev/null +++ b/neon/test/regress.c @@ -0,0 +1,87 @@ +/* + Some miscellenaneous regression tests + Copyright (C) 2001, 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_props.h" + +#include "tests.h" +#include "child.h" + +/* This caused a segfault in 0.15.3 and earlier. */ +static int serve_dodgy_xml(nsocket *sock, void *ud) +{ + char buffer[BUFSIZ]; + + CALL(discard_request(sock)); + + sock_read(sock, buffer, clength); + + sock_send_string(sock, + "HTTP/1.0 207 OK\r\n" + "Server: foo\r\n" + "Connection: close\r\n" + "\r\n" + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<multistatus xmlns=\"DAV:\">" + "<response><propstat><prop><href>" + "</href></prop></propstat></response>" + "</multistatus>"); + + return 0; +} + +static void dummy_results(void *ud, const char *href, + const ne_prop_result_set *rset) +{ + +} + +static int propfind_segv(void) +{ + ne_session *sess = ne_session_create(); + + ne_session_server(sess, "localhost", 7777); + + CALL(spawn_server(7777, serve_dodgy_xml, NULL)); + + ne_simple_propfind(sess, "/", 0, NULL, dummy_results, NULL); + + ne_session_destroy(sess); + + await_server(); + + return OK; +} + +test_func tests[] = { + propfind_segv, + NULL +}; diff --git a/neon/test/request.c b/neon/test/request.c new file mode 100644 index 000000000..7d4180cb9 --- /dev/null +++ b/neon/test/request.c @@ -0,0 +1,423 @@ +/* + HTTP request handling tests + Copyright (C) 2001, 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" + +static char buffer[BUFSIZ]; +static int clength; + +static int discard_request(nsocket *sock) +{ + NE_DEBUG(NE_DBG_HTTP, "Discarding request...\n"); + do { + ON(sock_readline(sock, buffer, 1024) < 0); + if (strncasecmp(buffer, "content-length:", 15) == 0) { + clength = atoi(buffer + 16); + } + NE_DEBUG(NE_DBG_HTTP, "[req] %s", buffer); + } while (strcmp(buffer, EOL) != 0); + + return OK; +} + +#define RESP200 "HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n" +#define TE_CHUNKED "Transfer-Encoding: chunked\r\n" + +static int single_serve_string(nsocket *s, void *userdata) +{ + const char *str = userdata; + + ON(discard_request(s)); + ON(sock_send_string(s, str)); + + return OK; +} + +/* 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); +} + +#define ONREQ(x) do { int _ret = (x); if (_ret) { snprintf(on_err_buf, 500, "%s line %d: HTTP error:\n%s", __FUNCTION__, __LINE__, ne_get_error(sess)); i_am(on_err_buf); return FAIL; } } while (0); + + +typedef ne_request *(*construct_request)(ne_session *sess, void *userdata); + +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; +} + +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)); + + ONN("response status", ne_get_status(req)->code != status); + + ne_request_destroy(req); + + return OK; +} + +static int expect_response(const char *expect, server_fn fn, void *userdata) +{ + ne_session *sess = ne_session_create(); + ne_buffer *buf = ne_buffer_create(); + int ret; + + ON(sess == NULL || buf == NULL); + ON(ne_session_server(sess, "localhost", 7777)); + ON(spawn_server(7777, fn, userdata)); + + ret = run_request(sess, 200, construct_get, buf); + if (ret) { + reap_server(); + return ret; + } + + ON(await_server()); + + ONN("response body match", strcmp(buf->data, expect)); + + 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"); +} + +#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 chunk_syntax_1(void) +{ + /* lots of little chunks. */ + ON(expect_response("abcde", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + ABCDE_CHUNKS)); + return OK; +} + +static int chunk_syntax_2(void) +{ + /* leading zero's */ + ON(expect_response("0123456789abcdef", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "000000010\r\n" "0123456789abcdef\r\n" + "000000000\r\n" "\r\n")); + return OK; +} + +static int chunk_syntax_3(void) +{ + /* chunk-extensions. */ + ON(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")); + return OK; +} + +static int chunk_syntax_4(void) +{ + /* trailers. */ + ON(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")); + return OK; +} + +static int chunk_syntax_5(void) +{ + /* T-E dominates over C-L. */ + ON(expect_response("abcde", single_serve_string, + RESP200 TE_CHUNKED + "Content-Length: 300\r\n" + "\r\n" + ABCDE_CHUNKS)); + return OK; +} + +static int fold_headers(void) +{ + ON(expect_response("abcde", single_serve_string, + RESP200 "Content-Length: \r\n 5\r\n" + "\r\n" + "abcde")); + return OK; +} + +static int fold_many_headers(void) +{ + ON(expect_response("abcde", single_serve_string, + RESP200 "Content-Length: \r\n \r\n \r\n \r\n 5\r\n" + "\r\n" + "abcde")); + return 0; +} + +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(); + ne_request *req; + int ret, state = 0; + + ON(sess == NULL); + ON(ne_session_server(sess, "localhost", 7777)); + 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); + + ret = ne_request_dispatch(req); + if (ret) { + reap_server(); + ONREQ(ret); + ONN("shouldn't get here.", 1); + } + + ON(await_server()); + + ON(state != 3); + + ne_request_destroy(req); + ne_session_destroy(sess); + + return OK; +} + +struct body { + char *body; + size_t size; +}; + +static int want_body(nsocket *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 %d bytes...\n", b->size); + + ON(sock_fullread(sock, buf, b->size)); + + ON(sock_send_string(sock, RESP200 "Content-Length: 0\r\n\r\n")); + + ON(memcmp(buf, b->body, b->size)); + + 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) +{ + int n, m; + +#define B(x) { x, strlen(x) } + struct body bodies[] = { + B("abcde"), + { "\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(); + ne_request *req; + + ON(sess == NULL); + ON(ne_session_server(sess, "localhost", 7777)); + 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); + } + } + + return OK; +} + +/* TODO: */ + +/* test that header folding is bounded, e.g. have the server process do: + * send("Foo: norman"); + * while(1) send(" Jim\r\n"); + */ + +/* test sending request body. */ +static int send_body(void) +{ + return FAIL; +} + +test_func tests[] = { + lookup_localhost, + single_get_clength, + single_get_eof, + single_get_chunked, + chunk_syntax_1, + chunk_syntax_2, + chunk_syntax_3, + chunk_syntax_4, + chunk_syntax_5, + fold_headers, + fold_many_headers, + multi_header, + send_bodies, + NULL, + send_body, + NULL +}; diff --git a/neon/test/resolve.c b/neon/test/resolve.c new file mode 100644 index 000000000..be3cd68f5 --- /dev/null +++ b/neon/test/resolve.c @@ -0,0 +1,57 @@ +/* + Test program for the neon resolver interface + Copyright (C) 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 <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]); + + 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 = 1; + } else { + 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_addr_print(ia, buf, sizeof buf)); + } + putchar('\n'); + } + ne_addr_destroy(addr); + + return 0; +} diff --git a/neon/test/run.sh b/neon/test/run.sh new file mode 100755 index 000000000..2e0b7f125 --- /dev/null +++ b/neon/test/run.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +for f in $*; do + if ./$f; then + : + else + echo FAILURE + exit -1 + fi +done + +exit 0 diff --git a/neon/test/server.c b/neon/test/server.c new file mode 100644 index 000000000..1466638a0 --- /dev/null +++ b/neon/test/server.c @@ -0,0 +1,195 @@ +/* + HTTP server tests + Copyright (C) 2001, 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 don't really test neon, they test an HTTP server. */ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "nsocket.h" +#include "ne_utils.h" + +#include "tests.h" + +struct in_addr addr; +nsocket *sock; + +char buffer[BUFSIZ]; +int clength; + +#define HOSTNAME "localhost" + +static int hostname_lookup(void) { + if (sock_name_lookup(HOSTNAME, &addr)) + return FAILHARD; + return OK; +} + +static int connect_to_server(void) { + sock = sock_connect(addr, 80); + if (sock == NULL) + return FAILHARD; + return OK; +} + +static int close_conn(void) { + ON(sock_close(sock)); + return OK; +} + +#define EOL "\r\n" + +#define TRACE_REQ "TRACE / HTTP/1.0\r\n" "Host: " HOSTNAME "\r\n\r\n" + +#define ROOT_URL "/test/" + +#define GET_REQ(url) do { ON(sock_send_string(sock, "GET " url " HTTP/1.0" EOL "Host: " HOSTNAME EOL EOL)); } while (0) + +static int trace(void) { + CALL(connect_to_server()); + + ON(sock_send_string(sock, TRACE_REQ)); + ON(sock_readline(sock, buffer, 1024) < 0); + /* this is true for any Apache server. */ + ON(strcasecmp(buffer, "HTTP/1.1 200 OK\r\n")); + /* read hdrs */ + do { + ON(sock_readline(sock, buffer, 1024) < 0); + } while (strcmp(buffer, "\r\n") != 0); + /* read body */ + ON(sock_read(sock, buffer, 1024) < 0); + /* this will fail if you have a transparent proxy. */ + ONN("pristine TRACE loopback", + strncmp(buffer, TRACE_REQ, strlen(TRACE_REQ)) != 0); + + CALL(close_conn()); + + return OK; +} + +static int basic_syntax(void) { + char *value; + + CALL(connect_to_server()); + GET_REQ(ROOT_URL); + ON(sock_readline(sock, buffer, 1024) < 0); + ONN("CRLF on status-line", strstr(buffer, EOL) == NULL); + + ONN("HTTP-Version is 1.0 or 1.1", + strncasecmp(buffer, "HTTP/1.1", 8) != 0 && + strncasecmp(buffer, "HTTP/1.0", 8)); + + ONN("Status-Line syntax", buffer[8] != ' ' || buffer[12] != ' '); + + /* should be a warning if this fails. */ + ONN("Status-Code is 200", + buffer[9] != '2' || buffer[10] != '0' || buffer[11] != '0'); + + ONN("Reason-Phrase is present", strlen(buffer) < strlen("HTTP/x.y nnn X" EOL)); + + do { + ON(sock_readline(sock, buffer, 1024) < 0); + ONN("CRLF on request-header", strstr(buffer, EOL) == NULL); + if (strcmp(buffer, EOL) != 0) { + value = strchr(buffer, ':'); + ONN("colon in request-header", value == NULL); + ONN("field-name in request-header", value == buffer); + } + } while (strcmp(buffer, EOL) != 0); + + CALL(close_conn()); + + return OK; +} + +static int skip_header(void) +{ + do { + ON(sock_readline(sock, buffer, 1024) < 0); + if (strncasecmp(buffer, "content-length:", 15) == 0) { + clength = atoi(buffer + 16); + } + } while (strcmp(buffer, EOL) != 0); + return OK; +} + +static int simple_get(void) +{ + CALL(connect_to_server()); + GET_REQ(ROOT_URL "plain"); + clength = -1; + CALL(skip_header()); + ONN("Content-Length header present", clength == -1); + ONN("Content-Length of 'plain'", clength != 11); + ONN("read response body", sock_read(sock, buffer, BUFSIZ) != 11); + ONN("content of 'plain'", strncmp(buffer, "Test file.\n", 11) != 0); + + /* FIXME: I'm not sure if this is right, actually. */ + ONN("connection close after GET", + sock_peek(sock, buffer, BUFSIZ) != SOCK_CLOSED); + + CALL(close_conn()); + return OK; +} + +static int simple_head(void) +{ + CALL(connect_to_server()); + ON(sock_send_string(sock, "HEAD " ROOT_URL "plain HTTP/1.0" EOL + "Host: " HOSTNAME EOL EOL)); + clength = -1; + CALL(skip_header()); + ONN("Content-Length header present", clength == -1); + ONN("Content-Length of 'plain'", clength != 11); + + ONN("connection close after HEAD", + sock_peek(sock, buffer, BUFSIZ) != SOCK_CLOSED); + + CALL(close_conn()); + return OK; +} + +static int null_resource(void) +{ + ne_status s = {0}; + CALL(connect_to_server()); + GET_REQ(ROOT_URL "nothing-here"); + ON(sock_readline(sock, buffer, BUFSIZ) < 0); + ON(ne_parse_statusline(buffer, &s)); + ONN("null resource gives 404", s.code != 404); + CALL(close_conn()); + return OK; +} + +test_func tests[] = { + hostname_lookup, + basic_syntax, + trace, + simple_get, + simple_head, + null_resource, + NULL +}; diff --git a/neon/test/server.key b/neon/test/server.key new file mode 100644 index 000000000..cdfb91b97 --- /dev/null +++ b/neon/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/neon/test/session.c b/neon/test/session.c new file mode 100644 index 000000000..d183ebff0 --- /dev/null +++ b/neon/test/session.c @@ -0,0 +1,115 @@ +/* + Tests for session handling + Copyright (C) 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> + +#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) +{ + const static 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; +} + +ne_test tests[] = { + T(fill_uri), + T(hostports), + T(errors), + T(NULL) +}; + diff --git a/neon/test/skeleton.c b/neon/test/skeleton.c new file mode 100644 index 000000000..9260c852c --- /dev/null +++ b/neon/test/skeleton.c @@ -0,0 +1,51 @@ +/* + neon test suite + Copyright (C) 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> + +#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/neon/test/sock-tests.c b/neon/test/sock-tests.c new file mode 100644 index 000000000..05a3f1e0e --- /dev/null +++ b/neon/test/sock-tests.c @@ -0,0 +1,70 @@ +/* + Socket handling tests + Copyright (C) 2001, 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 "nsocket.h" + +#include "tests.h" + +static int namelookup(void) { + struct in_addr a; + ON(sock_name_lookup("www.apache.org", &a)); + ON(sock_name_lookup("asdkajhd.webdav.org", &a) == 0); + return OK; +} + +static int svclookup(void) { + ON(sock_service_lookup("http") != 80); + ON(sock_service_lookup("https") != 443); + return OK; +} + +static int http(void) { + struct in_addr a; + nsocket *s; + char buffer[1024]; + + ON(sock_name_lookup("www.apache.org", &a)); + s = sock_connect(a, 80); + ON(s == NULL); + ON(sock_send_string(s, + "GET / HTTP/1.0\r\n" + "Host: www.apache.org\r\n\r\n")); + ON(sock_readline(s, buffer, 1024) < 0); + ON(strcasecmp(buffer, "HTTP/1.1 200 OK\r\n")); + ON(sock_close(s)); + return OK; +} + +test_func tests[] = { + namelookup, + svclookup, + http, + NULL +}; + diff --git a/neon/test/socket.c b/neon/test/socket.c new file mode 100644 index 000000000..5d85f78f8 --- /dev/null +++ b/neon/test/socket.c @@ -0,0 +1,257 @@ +/* + Socket handling tests + Copyright (C) 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" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "ne_socket.h" + +#include "child.h" +#include "tests.h" + +static struct in_addr localhost; +static char buffer[BUFSIZ]; + +static int resolve(void) +{ + ONN("could not resolve `localhost'", + ne_name_lookup("localhost", &localhost)); + /* and again for child.c */ + CALL(lookup_localhost()); + return OK; +} + +static int serve_close(ne_socket *sock, void *ud) +{ + return 0; +} + +static int begin(ne_socket **sock, server_fn fn, void *ud) +{ + CALL(spawn_server(7777, fn, ud)); + *sock = ne_sock_connect(localhost, 7777); + ONN("could not connect to localhost:7777", sock == NULL); + return OK; +} + +static int just_connect(void) +{ + ne_socket *sock; + + CALL(begin(&sock, serve_close, NULL)); + ne_sock_close(sock); + CALL(await_server()); + + 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 != NE_SOCK_CLOSED, ("read gave %d not closure", n)); + return OK; +} + +/* 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 %d 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)); + CALL(await_server()); + return OK; +} + +/* 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)); + CALL(await_server()); + return OK; +} + + +struct string { + char *data; + size_t len; +}; + +static int serve_string(ne_socket *sock, void *ud) +{ + struct string *str = ud; + + ONN("write failed", ne_sock_fullwrite(sock, str->data, str->len)); + + 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 %d bytes not %d", ret, len)); + ONV(memcmp(str, buffer, len), + ("peek mismatch: `%.*s' not `%.*s'", len, buffer, 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 %d bytes not %d", ret, len)); + ONV(memcmp(str, buffer, len), + ("read mismatch: `%.*s' not `%.*s'", len, buffer, len, str)); + return OK; +} + +/* Test a simple read. */ +static int single_read(void) +{ + ne_socket *sock; + struct string hello = { STR, strlen(STR) }; + + CALL(begin(&sock, serve_string, &hello)); + CALL(read_expect(sock, STR, strlen(STR))); + CALL(expect_close(sock)); + + CALL(await_server()); + return OK; +} + +/* Test a simple peek. */ +static int single_peek(void) +{ + ne_socket *sock; + struct string hello = { STR, strlen(STR) }; + + CALL(begin(&sock, serve_string, &hello)); + CALL(peek_expect(sock, STR, strlen(STR))); + ONN("close failed", ne_sock_close(sock)); + + CALL(await_server()); + return OK; +} + +/* Test lots of 1-byte reads. */ +static int small_reads(void) +{ + ne_socket *sock; + struct string hello = { STR, strlen(STR) }; + char *pnt; + + CALL(begin(&sock, serve_string, &hello)); + + /* read the string byte-by-byte. */ + for (pnt = hello.data; *pnt; pnt++) { + CALL(read_expect(sock, pnt, 1)); + } + + ONN("close failed", ne_sock_close(sock)); + CALL(await_server()); + return OK; +} + +/* Stress out the read buffer handling a little. */ +static int read_and_peek(void) +{ + ne_socket *sock; + struct string hello = { STR, strlen(STR) }; + +#define READ(str) CALL(read_expect(sock, str, strlen(str))) +#define PEEK(str) CALL(peek_expect(sock, str, strlen(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."); + + CALL(expect_close(sock)); + + ONN("close failed", ne_sock_close(sock)); + CALL(await_server()); + return OK; +} + +/* Read more bytes than were written. */ +static int larger_read(void) +{ + ne_socket *sock; + struct string hello = { STR, strlen(STR) }; + ssize_t nb; + + 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 (%d)", nb)); + ONN("read gave wrong data", memcmp(buffer, hello.data, hello.len)); + + CALL(expect_close(sock)); + + ONN("close failed", ne_sock_close(sock)); + CALL(await_server()); + return OK; +} + +ne_test tests[] = { + T(resolve), + T(just_connect), + T(read_close), + T(peek_close), + T(single_read), + T(single_peek), + T(small_reads), + T(read_and_peek), + T(larger_read), + T(NULL) +}; diff --git a/neon/test/ssl.c b/neon/test/ssl.c new file mode 100644 index 000000000..7d1b22259 --- /dev/null +++ b/neon/test/ssl.c @@ -0,0 +1,131 @@ +/* + neon test suite + Copyright (C) 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> + +#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" + +#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())) + +SSL_CTX *server_ctx; + +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; +} + +static int serve_ssl(nsocket *sock, void *ud) +{ + int fd = sock_get_fd(sock); + /* we don't want OpenSSL to close this socket for us. */ + BIO *bio = BIO_new_socket(fd, BIO_NOCLOSE); + SSL *ssl = SSL_new(server_ctx); + char buf[BUFSIZ]; + + ONN("SSL_new failed", ssl == NULL); + + SSL_set_bio(ssl, bio, bio); + + ONV(SSL_accept(ssl) != 1, + ("SSL_accept failed: %s", ERROR_SSL_STRING)); + + SSL_read(ssl, buf, BUFSIZ); + + CALL(s_strwrite(ssl, "HTTP/1.0 200 OK\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n\r\n")); + + /* 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 OK; +} + +static int init(void) +{ + ONN("sock_init failed.\n", 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.pem", SSL_FILETYPE_PEM)); + return OK; +} + +static int simple(void) +{ + ne_session *sess; + int ret; + + CALL(make_session(&sess, serve_ssl, NULL)); + + ne_set_secure(sess, 1); + + ret = any_request(sess, "/foo"); + + CALL(await_server()); + + ONREQ(ret); + + ne_session_destroy(sess); + return OK; +} + +ne_test tests[] = { + T(init), + + T(simple), + + T(NULL) +}; diff --git a/neon/test/string-tests.c b/neon/test/string-tests.c new file mode 100644 index 000000000..c288d1e23 --- /dev/null +++ b/neon/test/string-tests.c @@ -0,0 +1,89 @@ +/* + String handling tests + Copyright (C) 2001, 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 "string_utils.h" + +#include "tests.h" + +static int simple(void) { + sbuffer s = sbuffer_create(); + ON(s == NULL); + ON(sbuffer_zappend(s, "abcde")); + ON(strcmp(sbuffer_data(s), "abcde")); + ON(sbuffer_size(s) != 5); + sbuffer_destroy(s); + return OK; +} + +static int concat(void) { + sbuffer s = sbuffer_create(); + ON(s == NULL); + ON(sbuffer_concat(s, "a", "b", "c", "d", "e", "f", "g", NULL)); + ON(strcmp(sbuffer_data(s), "abcdefg")); + ON(sbuffer_size(s) != 7); + sbuffer_destroy(s); + return OK; +} + +static int append(void) { + sbuffer s = sbuffer_create(); + ON(s == NULL); + ON(sbuffer_append(s, "a", 1)); + ON(sbuffer_append(s, "b", 1)); + ON(sbuffer_append(s, "c", 1)); + ON(strcmp(sbuffer_data(s), "abc")); + ON(sbuffer_size(s) != 3); + sbuffer_destroy(s); + return OK; +} + +static int alter(void) { + sbuffer s = sbuffer_create(); + char *d; + ON(s == NULL); + ON(sbuffer_zappend(s, "abcdefg")); + d = sbuffer_data(s); + ON(d == NULL); + d[2] = '\0'; + sbuffer_altered(s); + ON(strcmp(sbuffer_data(s), "ab")); + ON(sbuffer_size(s) != 2); + ON(sbuffer_zappend(s, "hijkl")); + ON(strcmp(sbuffer_data(s), "abhijkl")); + return OK; +} + +test_func tests[] = { + simple, + concat, + append, + alter, + NULL +}; + diff --git a/neon/test/stubs.c b/neon/test/stubs.c new file mode 100644 index 000000000..5c6151398 --- /dev/null +++ b/neon/test/stubs.c @@ -0,0 +1,126 @@ +/* + neon test suite + Copyright (C) 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. + +*/ + +/** 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); + + /* these should all fail when SSL is not supported. */ + ONN("load default CA succeeded", ne_ssl_load_default_ca(sess) == 0); + ONN("load CA succeeded", ne_ssl_load_ca(sess, "Makefile") == 0); + ONN("load PKCS12 succeeded", ne_ssl_load_pkcs12(sess, "Makefile") == 0); + ONN("load PEM succeeded", ne_ssl_load_pem(sess, "Makefile", NULL) == 0); + + 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/neon/test/tests.c b/neon/test/tests.c new file mode 100644 index 000000000..7d25a12fc --- /dev/null +++ b/neon/test/tests.c @@ -0,0 +1,103 @@ +/* + Stupidly simple test framework + Copyright (C) 2001, 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/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 + +#include "tests.h" + +static const char *name = NULL; +static int passes = 0, fails = 0, aborted = 0; +static const char *suite; + +void i_am(const char *testname) +{ + name = testname; +} + +static void segv(int signo) +{ + write(0, "SEGFAULT.\n", 10); + exit(-1); +} + +int main(int argc, char *argv[]) { + int n; + + /* get basename(argv[0]) */ + suite = strrchr(argv[0], '/'); + if (suite == NULL) { + suite = argv[0]; + } else { + suite++; + } + + (void) signal(SIGSEGV, segv); + + if (tests[0] == NULL) { + printf("-> no tests found in %s\n", suite); + return -1; + } + + printf("-> running %s:\n", suite); + + for (n = 0; !aborted && tests[n] != NULL; n++) { + printf("%d: ", n); + name = NULL; + fflush(stdout); + switch (tests[n]()) { + case OK: + printf("pass.\n"); + passes++; + break; + case FAILHARD: + aborted = 1; + /* fall-through */ + case FAIL: + if (name != NULL) { + printf("%s - ", name); + } + printf("fail.\n"); + fails++; + break; + } + } + + printf("-> summary for %s: of %d tests: %d passed, %d failed. %.1f%%\n", + suite, n, passes, fails, 100*(float)passes/n); + + return fails; +} + diff --git a/neon/test/tests.h b/neon/test/tests.h new file mode 100644 index 000000000..c68c6273c --- /dev/null +++ b/neon/test/tests.h @@ -0,0 +1,49 @@ +/* + Stupidly simple test framework + 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 TESTS_H +#define TESTS_H 1 + +#include <stdio.h> + +/* prototype of test function. */ +typedef int (*test_func)(void); + +/* array of test functions to call. */ +extern test_func tests[]; + +void i_am(const char *testname); + +#define OK 0 +#define FAIL 1 +#define FAILHARD 2 /* fail and skip succeeding tests. */ + +/* are __FUNCTION__ and __LINE__ gcc-isms? Probably. */ + +#define ON(x) do { char _buf[50]; sprintf(_buf, "%s line %d", __FUNCTION__, __LINE__ ); i_am(_buf); if ((x)) return FAIL; } while(0) + +#define TESTFUNC do { i_am(__FUNCTION__); } while(0) + +#define ONN(n,x) do { i_am(n); if ((x)) return FAIL; } while(0) + +#define CALL(x) do { int ret = (x); if (ret != OK) return ret; } while (0) + +#endif /* TESTS_H */ diff --git a/neon/test/uri-tests.c b/neon/test/uri-tests.c new file mode 100644 index 000000000..761c477ba --- /dev/null +++ b/neon/test/uri-tests.c @@ -0,0 +1,188 @@ +/* + URI tests + Copyright (C) 2001, 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 <uri.h> + +#include "tests.h" + +struct test_uris { + const char *uri; + const char *scheme, *hostname; + int port; + const char *path; +} uritests[] = { + { "http://webdav.org/norman", "http:", "webdav.org", 80, "/norman" }, + { "https://webdav.org/foo", "https:", "webdav.org", 443, "/foo" }, + { "http://a/b", "a:", "a", 80, "/b" }, + { NULL } +}; + +static int simple(void) +{ + struct uri p = {0}; + ON(uri_parse("http://www.webdav.org/foo", &p, NULL)); + ON(strcmp(p.host, "www.webdav.org")); + ON(strcmp(p.path, "/foo")); + ON(strcmp(p.scheme, "http")); + ON(p.port != -1); + ON(p.authinfo != NULL); + return 0; +} + +static int simple_ssl(void) +{ + struct uri p = {0}; + ON(uri_parse("https://webdav.org/", &p, NULL)); + ON(strcmp(p.scheme, "https")); + ON(p.port != -1); + return OK; +} + +static int no_path(void) +{ + struct uri p = {0}; + ON(uri_parse("https://webdav.org", &p, NULL)); + ON(strcmp(p.path, "/")); + return OK; +} + +static int authinfo(void) +{ + struct uri p = {0}; + ON(uri_parse("ftp://jim:bob@jim.com", &p, NULL)); + ON(strcmp(p.host, "jim.com")); + ON(strcmp(p.authinfo, "jim:bob")); + return OK; +} + +#define STR "/a¹²³¼½/" +static int escapes(void) +{ + char *un, *esc; + esc = uri_abspath_escape(STR); + ON(esc == NULL); + un = uri_unescape(esc); + ON(un == NULL); + ON(strcmp(un, STR)); + free(un); + free(esc); + return OK; +} + +static int parents(void) +{ + char *p = uri_parent("/a/b/c"); + ON(p == NULL); + ON(strcmp(p, "/a/b/")); + free(p); + p = uri_parent("/a/b/c/"); + ON(p == NULL); + ON(strcmp(p, "/a/b/")); + free(p); + return OK; +} + +static int abspath(void) +{ + const char *p = uri_abspath("http://norman:1234/a/b"); + ON(p == NULL); + ON(strcmp(p, "/a/b")); + return OK; +} + +static int compares(void) +{ + ON(uri_compare("/a", "/a/") != 0); + ON(uri_compare("/a/", "/a") != 0); + ON(uri_compare("/ab", "/a/") == 0); + ON(uri_compare("/a/", "/ab") == 0); + ON(uri_compare("/a/", "/a/") != 0); + ON(uri_compare("/a/b/c/d", "/a/b/c/") == 0); + return OK; +} + +static int children(void) +{ + ON(uri_childof("/a", "/a/b") == 0); + ON(uri_childof("/a/", "/a/b") == 0); + ON(uri_childof("/aa/b/c", "/a/b/c/d/e") != 0); + ON(uri_childof("////", "/a") != 0); + return OK; +} + +static int slash(void) +{ + ON(uri_has_trailing_slash("/a/") == 0); + ON(uri_has_trailing_slash("/a") != 0); + { + /* check the uri == "" case. */ + char *foo = "/"; + ON(uri_has_trailing_slash(&foo[1])); + } + return OK; +} + +static int just_hostname(void) +{ + struct uri p = {0}; + ON(uri_parse("host.name.com", &p, NULL)); + ON(strcmp(p.host, "host.name.com")); + return 0; +} + +static int just_path(void) +{ + struct uri p = {0}; + ON(uri_parse("/argh", &p, NULL)); + ON(strcmp(p.path, "/argh")); + return 0; +} + +static int null_uri(void) { + struct uri p = {0}; + ON(uri_parse("", &p, NULL) == 0); + return 0; +} + +test_func tests[] = { + simple, + simple_ssl, + authinfo, + no_path, + escapes, + parents, + abspath, + compares, + children, + slash, + just_hostname, + just_path, + null_uri, + NULL +}; diff --git a/neon/test/util-tests.c b/neon/test/util-tests.c new file mode 100644 index 000000000..284f46b9e --- /dev/null +++ b/neon/test/util-tests.c @@ -0,0 +1,139 @@ +/* + utils tests + Copyright (C) 2001, 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 "http_utils.h" +#include "neon_md5.h" +#include "base64.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" }, + /* these aren't really valid but we should be able to parse them. */ + { "HTTP/1.1 200 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", + "HTTP/ 200 OK", + NULL +}; + +static int status_lines(void) +{ + http_status s; + int n; + char buf[50], *pnt; + + for (n = 0; accept_sl[n].status != NULL; n++) { + sprintf(buf, "valid %d: ", n); + pnt = buf + strlen(buf); + strcpy(pnt, "parse"); + ONN(buf, http_parse_statusline(accept_sl[n].status, &s)); + strcpy(pnt, "major"); + ONN(buf, accept_sl[n].major != s.major_version); + strcpy(pnt, "minor"); + ONN(buf, accept_sl[n].minor != s.minor_version); + strcpy(pnt, "code"); + ONN(buf, accept_sl[n].code != s.code); + strcpy(pnt, "reason-phrase"); + ONN(buf, strcmp(accept_sl[n].rp, s.reason_phrase)); + } + + for (n = 0; bad_sl[n] != NULL; n++) { + sprintf(buf, "invalid %d", n); + ONN(buf, http_parse_statusline(bad_sl[n], &s) == 0); + } + + return OK; +} + +static int md5(void) +{ + unsigned char buf[17] = {0}, buf2[17] = {0}; + char ascii[33] = {0}; + + ne_md5_to_ascii(ne_md5_buffer("", 0, buf), ascii); + ONN("MD5(null)", strcmp(ascii, "d41d8cd98f00b204e9800998ecf8427e")); + + ne_md5_to_ascii(ne_md5_buffer("foobar", 7, buf), ascii); + ONN("MD5(foobar)", strcmp(ascii, "b4258860eea29e875e2ee4019763b2bb")); + + ne_ascii_to_md5(ascii, buf2); + + ON(memcmp(buf, buf2, 16)); + + return 0; +} + +static int base64(void) +{ +#define B64(x, y) \ +do { char *_b = ne_base64(x); ON(strcmp(_b, y)); free(_b); } while (0) + + /* invent these with + * $ printf "string" | uuencode -m blah + */ + B64("a", "YQ=="); + B64("bb", "YmI="); + B64("ccc", "Y2Nj"); + B64("Hello, world", "SGVsbG8sIHdvcmxk"); + B64("I once saw a dog called norman.\n", + "SSBvbmNlIHNhdyBhIGRvZyBjYWxsZWQgbm9ybWFuLgo="); +#if 0 + /* duh, base64() doesn't handle binary data. which moron wrote + * that then? add a length argument. */ + B64("\0\0\0\0\0", "AAAAAAAK"); +#endif + +#undef B64 + return OK; +} + +test_func tests[] = { + status_lines, + md5, + base64, + NULL +}; diff --git a/neon/test/utils.c b/neon/test/utils.c new file mode 100644 index 000000000..50dbdbefa --- /dev/null +++ b/neon/test/utils.c @@ -0,0 +1,40 @@ +/* + Utility functions for HTTP client tests + Copyright (C) 2001, 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 "child.h" +#include "tests.h" +#include "utils.h" + +int single_serve_string(nsocket *s, void *userdata) +{ + const char *str = userdata; + char buf[1024]; + + ON(discard_request(s)); + + while (clength > 0) { + clength -= sock_read(s, buf, clength); + } + + ON(sock_send_string(s, str)); + + return OK; +} + diff --git a/neon/test/utils.h b/neon/test/utils.h new file mode 100644 index 000000000..6484cf119 --- /dev/null +++ b/neon/test/utils.h @@ -0,0 +1,29 @@ +/* + neon-specific test utils + Copyright (C) 2001, 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) { snprintf(on_err_buf, 500, "%s line %d: HTTP error:\n%s", __FUNCTION__, __LINE__, ne_get_error(sess)); i_am(on_err_buf); return FAIL; } } while (0); + + +#endif /* UTILS_H */ diff --git a/neon/test/wrongcn.pem b/neon/test/wrongcn.pem new file mode 100644 index 000000000..846ed7a41 --- /dev/null +++ b/neon/test/wrongcn.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDVDCCAv6gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBqjELMAkGA1UEBhMCR0Ix +FzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlkZ2UxGjAY +BgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFBIERlcHQx +GzAZBgNVBAMTEmhvaG9zdC5leGFtcGxlLmNvbTEeMBwGCSqGSIb3DQEJARYPbmVv +bkB3ZWJkYXYub3JnMB4XDTAyMDEzMTIwNDExNFoXDTAyMDMwMjIwNDExNFowgaox +CzAJBgNVBAYTAkdCMRcwFQYDVQQIEw5DYW1icmlkZ2VzaGlyZTESMBAGA1UEBxMJ +Q2FtYnJpZGdlMRowGAYDVQQKExFOZW9uIEhhY2tlcnMgTHRkLjEVMBMGA1UECxMM +TmVvbiBRQSBEZXB0MRswGQYDVQQDExJob2hvc3QuZXhhbXBsZS5jb20xHjAcBgkq +hkiG9w0BCQEWD25lb25Ad2ViZGF2Lm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC +QQDzRU5sZ8+CWQPvPkqJw9KloEgT2FqzZR9RT/qbJuRBmRphiRr0g7JOh5Mr7LXa +KShedFLhGidutyKKwIZJnRhtAgMBAAGjggELMIIBBzAdBgNVHQ4EFgQURQN5Lcx0 +rg/bgepiTlqBJZjrZJ8wgdcGA1UdIwSBzzCBzIAURQN5Lcx0rg/bgepiTlqBJZjr +ZJ+hgbCkga0wgaoxCzAJBgNVBAYTAkdCMRcwFQYDVQQIEw5DYW1icmlkZ2VzaGly +ZTESMBAGA1UEBxMJQ2FtYnJpZGdlMRowGAYDVQQKExFOZW9uIEhhY2tlcnMgTHRk +LjEVMBMGA1UECxMMTmVvbiBRQSBEZXB0MRswGQYDVQQDExJob2hvc3QuZXhhbXBs +ZS5jb20xHjAcBgkqhkiG9w0BCQEWD25lb25Ad2ViZGF2Lm9yZ4IBADAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBAUAA0EAaFru+DGQ3JiV+jEoZF68PIolHwOAHBM6 +4wfO/lHI7Hb8Tn9SQh1jgIa5/7LRue4hCo9GvIrlA5DHXnZX64DEUQ== +-----END CERTIFICATE----- diff --git a/neon/test/xml.c b/neon/test/xml.c new file mode 100644 index 000000000..10a2d8838 --- /dev/null +++ b/neon/test/xml.c @@ -0,0 +1,137 @@ +/* + neon test suite + Copyright (C) 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> + +#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" + +/* validelm, startelm, endelm re-create an XML-like representation of + * the original document, to verify namespace handling etc works + * correctly. */ +static int validelm(void *userdata, ne_xml_elmid parent, ne_xml_elmid child) +{ + return NE_XML_VALID; +} + +static int startelm(void *userdata, const struct ne_xml_elm *s, + const char **atts) +{ + ne_buffer *buf = userdata; + int n; + + ne_buffer_concat(buf, "<", "{", s->nspace, "}", s->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 0; +} + +static int endelm(void *userdata, const struct ne_xml_elm *s, + const char *cdata) +{ + ne_buffer *buf = userdata; + ne_buffer_concat(buf, cdata?cdata:"NOCDATA", "</{", s->nspace, "}", + s->name, ">", NULL); + return 0; +} + +static int parse_match(struct ne_xml_elm *elms, + const char *doc, const char *result) +{ + ne_xml_parser *p = ne_xml_create(); + ne_buffer *buf = ne_buffer_create(); + + ne_xml_push_handler(p, elms, validelm, startelm, endelm, buf); + + ne_xml_parse(p, doc, strlen(doc)); + + ONN("parse failed", !ne_xml_valid(p)); + + 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; + int flags; + } ms[] = { + { PFX "<hello/>", "<{}hello></{}hello>", 0 }, + { PFX "<hello foo='bar'/>", + "<{}hello foo='bar'></{}hello>", 0 }, + /*** CDATA handling. ***/ + { PFX "<hello> world</hello>", "<{}hello> world</{}hello>", 0 }, + { PFX "<hello> world</hello>", "<{}hello>world</{}hello>", + NE_XML_STRIPWS }, + /* test that the XML interface is truly borked. */ + { PFX "<hello>\r\n<wide> world</wide></hello>", + "<{}hello><{}wide> world</{}wide></{}hello>", 0 }, + /*** 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>", 0 }, + { NULL, NULL } + }; + struct ne_xml_elm elms[] = { + { "", "", NE_ELM_unknown, 0 }, + { NULL } + }; + int n; + + for (n = 0; ms[n].in != NULL; n++) { + elms[0].flags = NE_XML_CDATA | ms[n].flags; + CALL(parse_match(elms, ms[n].in, ms[n].out)); + } + + return OK; +} + +ne_test tests[] = { + T(matches), + + T(NULL) +}; + diff --git a/neon/tools/ChangeLog b/neon/tools/ChangeLog new file mode 100644 index 000000000..c85b59c16 --- /dev/null +++ b/neon/tools/ChangeLog @@ -0,0 +1,26 @@ +Wed May 10 14:45:55 2000 Joe Orton <joe@orton.demon.co.uk> + + * cvsdist: Run autoheader if necessary. + +Wed May 10 14:36:17 2000 Joe Orton <joe@orton.demon.co.uk> + + * reconf: New file. + +Sun Apr 30 21:59:56 2000 Joe Orton <joe@orton.demon.co.uk> + + * cvsdist: Handle version numbers with a '-' in like 0.10.0-beta. + +Sun Apr 30 19:53:06 2000 Joe Orton <joe@orton.demon.co.uk> + + * cvsdist: Run aclocal if there is a configure.in but no + aclocal.m4. Pass "-I macros" if there is a macros directory. + +Sun Mar 26 11:58:26 2000 Joe Orton <joe@orton.demon.co.uk> + + * mk: Only display new cwd if it has changed. + +Tue Mar 7 20:30:13 2000 Joe Orton <joe@orton.demon.co.uk> + + * cvsdist: Updated for optional arguments. Fixed specfile + creation. + diff --git a/neon/tools/README b/neon/tools/README new file mode 100644 index 000000000..725f5c367 --- /dev/null +++ b/neon/tools/README @@ -0,0 +1,4 @@ + +This directory contains scripts which may or may not be useful +to the maintainer. + diff --git a/neon/tools/cvsdist b/neon/tools/cvsdist new file mode 100755 index 000000000..2ef58a37d --- /dev/null +++ b/neon/tools/cvsdist @@ -0,0 +1,143 @@ +#!/bin/sh +# Usage: cvsdist module [tag] [version] +# Creates a tarball from the CVS archive, generating a 'configure' +# script and .spec file as necessary. +# HEAD is the default tag, and mmmyy is the default version string. +# +# Copyright (C) 1999 Joe Orton <joe@orton.demon.co.uk> +# +# Id: cvsdist,v 1.8 2000/05/10 13:51:27 joe Exp + +# Usage, redistribution, modification under the terms of the GNU GPL, +# see COPYING for full details. + +replace_version() { +if [ -e $1.in ]; then + echo Creating $1 from $1.in for release $rel + if ! sed -e s/@VERSION@/$rel/ < $1.in > $1; then + echo sed failed + exit -5 + fi +fi +} + +if [ $# -eq 0 -o $# -gt 3 -o "$1" = "--help" -o "$1" = "-h" ]; then + cat<<EOF +Usage: + cvsdist foobar-x.y.z + => Distribute package 'foobar', at version 'x.y.z', at tag 'foobar_x-y-z' + cvsdist foobar + => Distribute package 'foobar', at version 'mmmyy', at tag 'HEAD' + cvsdist foobar random_tag + => Distribute package 'foobar', at version 'mmmyy', at tag 'random_tag' + cvsdist foobar random_tag x.y.z + => Distribute package 'foobar', at version 'x.y.z', at tag 'random_tag' +EOF + exit -1 +fi + +# mod is the package name (e.g. cadaver) +# rel is the version name (e.g. 0.10.0, mar12) +# tag is the CVS tag + +if [ -z "$3" ]; then + # No version argument + if [ -z "$2" ]; then + # No tag argument either + # Look for a version in the package name, like cadaver-0.10.0 + if echo $1 | grep '-' > /dev/null; then + # Found one, convert it from 0.10.0 to 0-10-0 for the tag + # and strip the package name from the beginning + mod=`echo $1 | sed "s/-.*$//g"` + rel=`echo $1 | sed "s/^[^-]*-//g"` + tag=${mod}_`echo $rel | sed "s/\./-/g"` + else + # No version given, use HEAD + mod=$1 + rel=`date +%b%d | dd conv=lcase 2>/dev/null` + tag=HEAD + fi + else + # Got a tag, but no release name + mod=$1 + tag=$2 + rel=`date +%b%d | dd conv=lcase 2>/dev/null` + fi +else + # Got all the info we need + mod=$1 + tag=$2 + rel=$3 +fi + +echo "Distributing \`$mod' for release \`$rel' at tag \`$tag'" +mname=$mod-$rel +tname=/tmp/$mname +ball=/tmp/${mname}.tar.gz +if [ -d $tname ]; then + echo $tname exists, cannot proceed + exit -1 +fi +if [ -r $ball ]; then + echo $ball exists, cannot proceed + exit -2 +fi +echo Exporting $mod from CVS at $tag... +if ! cvs -Q export -d $tname -r $tag $mod; then + echo cvs export failed + exit -3 +fi +cd $tname + +# Do we need to generate a configure script? +if [ -e configure.in ]; then + if [ ! -e aclocal.m4 ]; then + # We need to run aclocal + ACLARGS="" + if [ -d macros ]; then + ACLARGS="-I macros" + fi + echo Running aclocal... + if ! aclocal $ACLARGS; then + echo aclocal failed + exit -4 + fi + fi + echo Running `autoconf --version`... + if ! autoconf; then + echo autoconf failed + exit -4 + fi + if [ ! -r config.h.in ]; then + AUHARGS="" + if [ -r macros/acconfig.h ]; then + AUHARGS="-l macros" + fi + echo Running autoheader... + if ! autoheader $AUHARGS; then + echo autoheader failed + exit -4 + fi + fi +fi + +# Replace @VERSION@ in the following files: +replace_version $mod.spec +replace_version Makefile.emx +replace_version config.h.emx + +### It would be nice to be able to generate the po files here too, +### but it's a bit complex, since normally you have to run configure +### to have po/Makefile exist + +cd /tmp +echo Creating tarball... +tar czf $ball $mname +ls -l $ball + +if [ -e $mname/$mod.lsm.in ]; then + echo Creating .lsm file for release $rel... + mklsm $mod $rel $ball < $mname/$mod.lsm.in > $mod.lsm +fi + +rm -r $mname diff --git a/neon/tools/mk b/neon/tools/mk new file mode 100755 index 000000000..e09f6efdc --- /dev/null +++ b/neon/tools/mk @@ -0,0 +1,19 @@ +#!/bin/sh +showpwd= +until [ -f Makefile ]; do + pre=`basename \`pwd\``/$pre + cd .. + showpwd=1 +done +if [ ! -z $showpwd ]; then + echo cd `pwd` +fi +if [ $# -gt 0 ]; then + until [ $# -eq 0 ]; do + tar="$tar $pre$1" + shift + done + exec make $tar +else + exec make +fi diff --git a/neon/tools/mklsm b/neon/tools/mklsm new file mode 100755 index 000000000..a73f705d2 --- /dev/null +++ b/neon/tools/mklsm @@ -0,0 +1,37 @@ +#!/bin/sh +# Usage: +# cat lsmtemplate.in | mklsm target version [tarball] > mylsmfile +# Copyright (C) 1999 Joe Orton +# +# Usage, redistribution, modification under the terms of the GNU GPL, +# see COPYING for full details. +# +# Id: mklsm,v 1.1.1.1 2000/03/23 19:17:11 joe Exp + +# You need a 'filesize' command, which does the obvious thing. + +#### need to configure this #### + +# Where the archive files are stored +archroot=$HOME/store/archive + +#### that should be all #### + +archive=${3-$archroot/$1-$2.tar.gz} + +if [ ! -e $archive ]; then + echo Could not find archive file $archive + exit -1 +fi +bytes=`filesize $archive` +size=`expr $bytes \/ 1024` +case `date -r $archive +%m` in +01) month=JAN;; 02) month=FEB;; 03) month=MAR;; 04) month=APR;; +05) month=MAY;; 06) month=JUN;; 07) month=JUL;; 08) month=AUG;; +09) month=SEP;; 10) month=OCT;; 11) month=NOV;; 12) month=DEC;; +esac +date=`date -r $archive +%d`${month}`date -r $archive +%y` +version=$2 +target=$1 +sedscr="s/@VERSION@/${version}/;s/@DATE@/${date}/;s/@SIZE@/${size}kb/" +exec sed -e $sedscr - diff --git a/neon/tools/reconf b/neon/tools/reconf new file mode 100755 index 000000000..9cd28e513 --- /dev/null +++ b/neon/tools/reconf @@ -0,0 +1,6 @@ +#!/bin/sh +rm -f config.cache +([ ! -d macros ] || aclocal -I macros) && \ +([ ! -r acconfig.h ] || autoheader) && \ +([ ! -r macros/acconfig.h ] || autoheader -l macros) && \ +autoconf && CONFIG_SITE= ./configure $* diff --git a/neon/tools/update-pot.sh b/neon/tools/update-pot.sh new file mode 100755 index 000000000..b8ef81613 --- /dev/null +++ b/neon/tools/update-pot.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# Nicked from gnome-vfs/po, modified to be package-independant +# -joe + +xgettext --default-domain=$1 --directory=.. \ + --add-comments --keyword=_ --keyword=N_ \ + --files-from=./POTFILES.in \ +&& test ! -f $1.po \ + || ( rm -f ./$1.pot \ + && mv $1.po ./$1.pot ) |