]> Zhao Yanbai Git Server - minix.git/commitdiff
lib: added libfetch
authorBen Gras <ben@minix3.org>
Wed, 30 Jun 2010 23:55:55 +0000 (23:55 +0000)
committerBen Gras <ben@minix3.org>
Wed, 30 Jun 2010 23:55:55 +0000 (23:55 +0000)
16 files changed:
lib/Makefile
lib/libfetch/Makefile [new file with mode: 0644]
lib/libfetch/common.c [new file with mode: 0644]
lib/libfetch/common.h [new file with mode: 0644]
lib/libfetch/errlist.sh [new file with mode: 0755]
lib/libfetch/fetch.3 [new file with mode: 0644]
lib/libfetch/fetch.c [new file with mode: 0644]
lib/libfetch/fetch.cat3 [new file with mode: 0644]
lib/libfetch/fetch.h [new file with mode: 0644]
lib/libfetch/file.c [new file with mode: 0644]
lib/libfetch/ftp.c [new file with mode: 0644]
lib/libfetch/ftp.errors [new file with mode: 0644]
lib/libfetch/ftperr.h [new file with mode: 0644]
lib/libfetch/http.c [new file with mode: 0644]
lib/libfetch/http.errors [new file with mode: 0644]
lib/libfetch/httperr.h [new file with mode: 0644]

index 5f7ad6681a3d9bb773576f35ba6cfaf9049afadd..620ea112336ce36b4ec3e563ba406da5ecacd40c 100644 (file)
@@ -1,7 +1,7 @@
 .include <bsd.own.mk>
 
 SUBDIR= csu libc libcurses libdriver libnetdriver libend libedit libm libsys \
-       libtimers libutil libbz2 libl libhgfs libz
+       libtimers libutil libbz2 libl libhgfs libz libfetch
 
 .if ${COMPILER_TYPE} == "ack"
 SUBDIR+= ack/libd ack/libe ack/libfp ack/liby
diff --git a/lib/libfetch/Makefile b/lib/libfetch/Makefile
new file mode 100644 (file)
index 0000000..c2cd739
--- /dev/null
@@ -0,0 +1,44 @@
+# $NetBSD: Makefile,v 1.5 2010/02/24 22:00:51 joerg Exp $
+
+LIB=           fetch
+SRCS=          fetch.c common.c ftp.c http.c file.c
+#DPSRCS=       ftperr.h httperr.h
+INCSDIR=       /usr/include
+INCS=          fetch.h
+MAN=           fetch.3
+# CLEANFILES=  ftperr.h httperr.h
+MKLINT=                no
+MKPIC=         no
+MKPROFILE=     no
+
+.include <bsd.own.mk>
+
+CPPFLAGS+=  -D_POSIX_SOURCE
+CPPFLAGS+=     -I.
+CPPFLAGS+=     -D_LARGEFILE_SOURCE -D_LARGE_FILES
+
+FETCH_WITH_INET6?=     no
+FETCH_WITH_OPENSSL?=   no
+
+.if !empty(FETCH_WITH_INET6:M[yY][eE][sS])
+CPPFLAGS+=     -DINET6
+.endif
+
+.if !empty(FETCH_WITH_OPENSSL:M[yY][eE][sS])
+CPPFLAGS+=     -DWITH_SSL
+LDADD=         -lssl -lcrypto
+.endif
+
+CPPFLAGS+=     -DFTP_COMBINE_CWDS
+
+# WARNS?=              4
+
+#ftperr.h: ${.CURDIR}/ftp.errors ${.CURDIR}/Makefile ${.CURDIR}/errlist.sh
+#      ${.CURDIR}/errlist.sh ftp_errlist FTP \
+#          ${.CURDIR}/ftp.errors > ${.TARGET}
+#
+#httperr.h: ${.CURDIR}/http.errors ${.CURDIR}/Makefile ${.CURDIR}/errlist.sh
+#      ${.CURDIR}/errlist.sh http_errlist HTTP \
+#          ${.CURDIR}/http.errors > ${.TARGET}
+
+.include <bsd.lib.mk>
diff --git a/lib/libfetch/common.c b/lib/libfetch/common.c
new file mode 100644 (file)
index 0000000..b3552a8
--- /dev/null
@@ -0,0 +1,1053 @@
+/*     $NetBSD: common.c,v 1.27 2010/06/13 21:38:09 joerg Exp $        */
+/*-
+ * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2008, 2010 Joerg Sonnenberger <joerg@NetBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: common.c,v 1.53 2007/12/19 00:26:36 des Exp $
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#if defined(HAVE_INTTYPES_H) || defined(NETBSD)
+#include <inttypes.h>
+#endif
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat/netdb.h>
+#else
+#include <netdb.h>
+#endif
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef MSG_NOSIGNAL
+#include <signal.h>
+#endif
+
+#include "fetch.h"
+#include "common.h"
+
+/*** Local data **************************************************************/
+
+/*
+ * Error messages for resolver errors
+ */
+static struct fetcherr netdb_errlist[] = {
+#ifdef EAI_NODATA
+       { EAI_NODATA,   FETCH_RESOLV,   "Host not found" },
+#endif
+       { EAI_AGAIN,    FETCH_TEMP,     "Transient resolver failure" },
+       { EAI_FAIL,     FETCH_RESOLV,   "Non-recoverable resolver failure" },
+       { EAI_NONAME,   FETCH_RESOLV,   "No address record" },
+       { -1,           FETCH_UNKNOWN,  "Unknown resolver error" }
+};
+
+/*** Error-reporting functions ***********************************************/
+
+/*
+ * Map error code to string
+ */
+static struct fetcherr *
+fetch_finderr(struct fetcherr *p, int e)
+{
+       while (p->num != -1 && p->num != e)
+               p++;
+       return (p);
+}
+
+/*
+ * Set error code
+ */
+void
+fetch_seterr(struct fetcherr *p, int e)
+{
+       p = fetch_finderr(p, e);
+       fetchLastErrCode = p->cat;
+       snprintf(fetchLastErrString, MAXERRSTRING, "%s", p->string);
+}
+
+/*
+ * Set error code according to errno
+ */
+void
+fetch_syserr(void)
+{
+       switch (errno) {
+       case 0:
+               fetchLastErrCode = FETCH_OK;
+               break;
+       case EPERM:
+       case EACCES:
+       case EROFS:
+#ifdef EAUTH
+       case EAUTH:
+#endif
+#ifdef ENEEDAUTH
+       case ENEEDAUTH:
+#endif
+               fetchLastErrCode = FETCH_AUTH;
+               break;
+       case ENOENT:
+       case EISDIR: /* XXX */
+               fetchLastErrCode = FETCH_UNAVAIL;
+               break;
+       case ENOMEM:
+               fetchLastErrCode = FETCH_MEMORY;
+               break;
+       case EBUSY:
+       case EAGAIN:
+               fetchLastErrCode = FETCH_TEMP;
+               break;
+       case EEXIST:
+               fetchLastErrCode = FETCH_EXISTS;
+               break;
+       case ENOSPC:
+               fetchLastErrCode = FETCH_FULL;
+               break;
+       case EADDRINUSE:
+       case EADDRNOTAVAIL:
+       case ENETDOWN:
+       case ENETUNREACH:
+#if defined(ENETRESET)
+       case ENETRESET:
+#endif
+       case EHOSTUNREACH:
+               fetchLastErrCode = FETCH_NETWORK;
+               break;
+#if defined(ECONNABORTED)
+       case ECONNABORTED:
+#endif
+       case ECONNRESET:
+               fetchLastErrCode = FETCH_ABORT;
+               break;
+       case ETIMEDOUT:
+               fetchLastErrCode = FETCH_TIMEOUT;
+               break;
+       case ECONNREFUSED:
+#if defined(EHOSTDOWN)
+       case EHOSTDOWN:
+#endif
+               fetchLastErrCode = FETCH_DOWN;
+               break;
+default:
+               fetchLastErrCode = FETCH_UNKNOWN;
+       }
+       snprintf(fetchLastErrString, MAXERRSTRING, "%s", strerror(errno));
+}
+
+
+/*
+ * Emit status message
+ */
+void
+fetch_info(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+       fputc('\n', stderr);
+}
+
+
+/*** Network-related utility functions ***************************************/
+
+/*
+ * Return the default port for a scheme
+ */
+int
+fetch_default_port(const char *scheme)
+{
+       struct servent *se;
+
+       if ((se = getservbyname(scheme, "tcp")) != NULL)
+               return (ntohs(se->s_port));
+       if (strcasecmp(scheme, SCHEME_FTP) == 0)
+               return (FTP_DEFAULT_PORT);
+       if (strcasecmp(scheme, SCHEME_HTTP) == 0)
+               return (HTTP_DEFAULT_PORT);
+       return (0);
+}
+
+/*
+ * Return the default proxy port for a scheme
+ */
+int
+fetch_default_proxy_port(const char *scheme)
+{
+       if (strcasecmp(scheme, SCHEME_FTP) == 0)
+               return (FTP_DEFAULT_PROXY_PORT);
+       if (strcasecmp(scheme, SCHEME_HTTP) == 0)
+               return (HTTP_DEFAULT_PROXY_PORT);
+       return (0);
+}
+
+
+/*
+ * Create a connection for an existing descriptor.
+ */
+conn_t *
+fetch_reopen(int sd)
+{
+       conn_t *conn;
+
+       /* allocate and fill connection structure */
+       if ((conn = calloc(1, sizeof(*conn))) == NULL)
+               return (NULL);
+       conn->ftp_home = NULL;
+       conn->cache_url = NULL;
+       conn->next_buf = NULL;
+       conn->next_len = 0;
+       conn->sd = sd;
+       return (conn);
+}
+
+
+/*
+ * Bind a socket to a specific local address
+ */
+int
+fetch_bind(int sd, int af, const char *addr)
+{
+       struct addrinfo hints, *res, *res0;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = af;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = 0;
+       if (getaddrinfo(addr, NULL, &hints, &res0))
+               return (-1);
+       for (res = res0; res; res = res->ai_next) {
+               if (bind(sd, res->ai_addr, res->ai_addrlen) == 0)
+                       return (0);
+       }
+       return (-1);
+}
+
+
+/*
+ * Establish a TCP connection to the specified port on the specified host.
+ */
+conn_t *
+fetch_connect(struct url *url, int af, int verbose)
+{
+       conn_t *conn;
+       char pbuf[10];
+       const char *bindaddr;
+       struct addrinfo hints, *res, *res0;
+       int sd, error;
+
+       if (verbose)
+               fetch_info("looking up %s", url->host);
+
+       /* look up host name and set up socket address structure */
+       snprintf(pbuf, sizeof(pbuf), "%d", url->port);
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = af;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = 0;
+       if ((error = getaddrinfo(url->host, pbuf, &hints, &res0)) != 0) {
+               netdb_seterr(error);
+               return (NULL);
+       }
+       bindaddr = getenv("FETCH_BIND_ADDRESS");
+
+       if (verbose)
+               fetch_info("connecting to %s:%d", url->host, url->port);
+
+       /* try to connect */
+       for (sd = -1, res = res0; res; sd = -1, res = res->ai_next) {
+               if ((sd = socket(res->ai_family, res->ai_socktype,
+                        res->ai_protocol)) == -1)
+                       continue;
+               if (bindaddr != NULL && *bindaddr != '\0' &&
+                   fetch_bind(sd, res->ai_family, bindaddr) != 0) {
+                       fetch_info("failed to bind to '%s'", bindaddr);
+                       close(sd);
+                       continue;
+               }
+               if (connect(sd, res->ai_addr, res->ai_addrlen) == 0)
+                       break;
+               close(sd);
+       }
+       freeaddrinfo(res0);
+       if (sd == -1) {
+               fetch_syserr();
+               return (NULL);
+       }
+
+       if ((conn = fetch_reopen(sd)) == NULL) {
+               fetch_syserr();
+               close(sd);
+               return (NULL);
+       }
+       conn->cache_url = fetchCopyURL(url);
+       conn->cache_af = af;
+       return (conn);
+}
+
+static conn_t *connection_cache;
+static int cache_global_limit = 0;
+static int cache_per_host_limit = 0;
+
+/*
+ * Initialise cache with the given limits.
+ */
+void
+fetchConnectionCacheInit(int global_limit, int per_host_limit)
+{
+
+       if (global_limit < 0)
+               cache_global_limit = INT_MAX;
+       else if (per_host_limit > global_limit)
+               cache_global_limit = per_host_limit;
+       else
+               cache_global_limit = global_limit;
+       if (per_host_limit < 0)
+               cache_per_host_limit = INT_MAX;
+       else
+               cache_per_host_limit = per_host_limit;
+}
+
+/*
+ * Flush cache and free all associated resources.
+ */
+void
+fetchConnectionCacheClose(void)
+{
+       conn_t *conn;
+
+       while ((conn = connection_cache) != NULL) {
+               connection_cache = conn->next_cached;
+               (*conn->cache_close)(conn);
+       }
+}
+
+/*
+ * Check connection cache for an existing entry matching
+ * protocol/host/port/user/password/family.
+ */
+conn_t *
+fetch_cache_get(const struct url *url, int af)
+{
+       conn_t *conn, *last_conn = NULL;
+
+       for (conn = connection_cache; conn; conn = conn->next_cached) {
+               if (conn->cache_url->port == url->port &&
+                   strcmp(conn->cache_url->scheme, url->scheme) == 0 &&
+                   strcmp(conn->cache_url->host, url->host) == 0 &&
+                   strcmp(conn->cache_url->user, url->user) == 0 &&
+                   strcmp(conn->cache_url->pwd, url->pwd) == 0 &&
+                   (conn->cache_af == AF_UNSPEC || af == AF_UNSPEC ||
+                    conn->cache_af == af)) {
+                       if (last_conn != NULL)
+                               last_conn->next_cached = conn->next_cached;
+                       else
+                               connection_cache = conn->next_cached;
+                       return conn;
+               }
+       }
+
+       return NULL;
+}
+
+/*
+ * Put the connection back into the cache for reuse.
+ * If the connection is freed due to LRU or if the cache
+ * is explicitly closed, the given callback is called.
+ */
+void
+fetch_cache_put(conn_t *conn, int (*closecb)(conn_t *))
+{
+       conn_t *iter, *last;
+       int global_count, host_count;
+
+       if (conn->cache_url == NULL || cache_global_limit == 0) {
+               (*closecb)(conn);
+               return;
+       }
+
+       global_count = host_count = 0;
+       last = NULL;
+       for (iter = connection_cache; iter;
+           last = iter, iter = iter->next_cached) {
+               ++global_count;
+               if (strcmp(conn->cache_url->host, iter->cache_url->host) == 0)
+                       ++host_count;
+               if (global_count < cache_global_limit &&
+                   host_count < cache_per_host_limit)
+                       continue;
+               --global_count;
+               if (last != NULL)
+                       last->next_cached = iter->next_cached;
+               else
+                       connection_cache = iter->next_cached;
+               (*iter->cache_close)(iter);
+       }
+
+       conn->cache_close = closecb;
+       conn->next_cached = connection_cache;
+       connection_cache = conn;
+}
+
+/*
+ * Enable SSL on a connection.
+ */
+int
+fetch_ssl(conn_t *conn, int verbose)
+{
+
+#ifdef WITH_SSL
+       /* Init the SSL library and context */
+       if (!SSL_library_init()){
+               fprintf(stderr, "SSL library init failed\n");
+               return (-1);
+       }
+
+       SSL_load_error_strings();
+
+       conn->ssl_meth = SSLv23_client_method();
+       conn->ssl_ctx = SSL_CTX_new(conn->ssl_meth);
+       SSL_CTX_set_mode(conn->ssl_ctx, SSL_MODE_AUTO_RETRY);
+
+       conn->ssl = SSL_new(conn->ssl_ctx);
+       if (conn->ssl == NULL){
+               fprintf(stderr, "SSL context creation failed\n");
+               return (-1);
+       }
+       SSL_set_fd(conn->ssl, conn->sd);
+       if (SSL_connect(conn->ssl) == -1){
+               ERR_print_errors_fp(stderr);
+               return (-1);
+       }
+
+       if (verbose) {
+               X509_NAME *name;
+               char *str;
+
+               fprintf(stderr, "SSL connection established using %s\n",
+                   SSL_get_cipher(conn->ssl));
+               conn->ssl_cert = SSL_get_peer_certificate(conn->ssl);
+               name = X509_get_subject_name(conn->ssl_cert);
+               str = X509_NAME_oneline(name, 0, 0);
+               printf("Certificate subject: %s\n", str);
+               free(str);
+               name = X509_get_issuer_name(conn->ssl_cert);
+               str = X509_NAME_oneline(name, 0, 0);
+               printf("Certificate issuer: %s\n", str);
+               free(str);
+       }
+
+       return (0);
+#else
+       (void)conn;
+       (void)verbose;
+       fprintf(stderr, "SSL support disabled\n");
+       return (-1);
+#endif
+}
+
+
+/*
+ * Read a character from a connection w/ timeout
+ */
+ssize_t
+fetch_read(conn_t *conn, char *buf, size_t len)
+{
+       struct timeval now, timeout, waittv;
+       fd_set readfds;
+       ssize_t rlen;
+       int r;
+
+       if (len == 0)
+               return 0;
+
+       if (conn->next_len != 0) {
+               if (conn->next_len < len)
+                       len = conn->next_len;
+               memmove(buf, conn->next_buf, len);
+               conn->next_len -= len;
+               conn->next_buf += len;
+               return len;
+       }
+
+       if (fetchTimeout) {
+               FD_ZERO(&readfds);
+               gettimeofday(&timeout, NULL);
+               timeout.tv_sec += fetchTimeout;
+       }
+
+       for (;;) {
+               while (fetchTimeout && !FD_ISSET(conn->sd, &readfds)) {
+                       FD_SET(conn->sd, &readfds);
+                       gettimeofday(&now, NULL);
+                       waittv.tv_sec = timeout.tv_sec - now.tv_sec;
+                       waittv.tv_usec = timeout.tv_usec - now.tv_usec;
+                       if (waittv.tv_usec < 0) {
+                               waittv.tv_usec += 1000000;
+                               waittv.tv_sec--;
+                       }
+                       if (waittv.tv_sec < 0) {
+                               errno = ETIMEDOUT;
+                               fetch_syserr();
+                               return (-1);
+                       }
+                       errno = 0;
+                       r = select(conn->sd + 1, &readfds, NULL, NULL, &waittv);
+                       if (r == -1) {
+                               if (errno == EINTR && fetchRestartCalls)
+                                       continue;
+                               fetch_syserr();
+                               return (-1);
+                       }
+               }
+#ifdef WITH_SSL
+               if (conn->ssl != NULL)
+                       rlen = SSL_read(conn->ssl, buf, len);
+               else
+#endif
+                       rlen = read(conn->sd, buf, len);
+               if (rlen >= 0)
+                       break;
+       
+               if (errno != EINTR || !fetchRestartCalls)
+                       return (-1);
+       }
+       return (rlen);
+}
+
+
+/*
+ * Read a line of text from a connection w/ timeout
+ */
+#define MIN_BUF_SIZE 1024
+
+int
+fetch_getln(conn_t *conn)
+{
+       char *tmp, *next;
+       size_t tmpsize;
+       ssize_t len;
+
+       if (conn->buf == NULL) {
+               if ((conn->buf = malloc(MIN_BUF_SIZE)) == NULL) {
+                       errno = ENOMEM;
+                       return (-1);
+               }
+               conn->bufsize = MIN_BUF_SIZE;
+       }
+
+       conn->buflen = 0;
+       next = NULL;
+
+       do {
+               /*
+                * conn->bufsize != conn->buflen at this point,
+                * so the buffer can be NUL-terminated below for
+                * the case of len == 0.
+                */
+               len = fetch_read(conn, conn->buf + conn->buflen,
+                   conn->bufsize - conn->buflen);
+               if (len == -1)
+                       return (-1);
+               if (len == 0)
+                       break;
+               next = memchr(conn->buf + conn->buflen, '\n', len);
+               conn->buflen += len;
+               if (conn->buflen == conn->bufsize && next == NULL) {
+                       tmp = conn->buf;
+                       tmpsize = conn->bufsize * 2;
+                       if (tmpsize < conn->bufsize) {
+                               errno = ENOMEM;
+                               return (-1);
+                       }
+                       if ((tmp = realloc(tmp, tmpsize)) == NULL) {
+                               errno = ENOMEM;
+                               return (-1);
+                       }
+                       conn->buf = tmp;
+                       conn->bufsize = tmpsize;
+               }
+       } while (next == NULL);
+
+       if (next != NULL) {
+               *next = '\0';
+               conn->next_buf = next + 1;
+               conn->next_len = conn->buflen - (conn->next_buf - conn->buf);
+               conn->buflen = next - conn->buf;
+       } else {
+               conn->buf[conn->buflen] = '\0';
+               conn->next_len = 0;
+       }
+       return (0);
+}
+
+/*
+ * Write a vector to a connection w/ timeout
+ * Note: can modify the iovec.
+ */
+ssize_t
+fetch_write(conn_t *conn, const void *buf, size_t len)
+{
+       struct timeval now, timeout, waittv;
+       fd_set writefds;
+       ssize_t wlen, total;
+       int r;
+#ifndef MSG_NOSIGNAL
+       static int killed_sigpipe;
+#endif
+
+#ifndef MSG_NOSIGNAL
+       if (!killed_sigpipe) {
+               signal(SIGPIPE, SIG_IGN);
+               killed_sigpipe = 1;
+       }
+#endif
+
+
+       if (fetchTimeout) {
+               FD_ZERO(&writefds);
+               gettimeofday(&timeout, NULL);
+               timeout.tv_sec += fetchTimeout;
+       }
+
+       total = 0;
+       while (len) {
+               while (fetchTimeout && !FD_ISSET(conn->sd, &writefds)) {
+                       FD_SET(conn->sd, &writefds);
+                       gettimeofday(&now, NULL);
+                       waittv.tv_sec = timeout.tv_sec - now.tv_sec;
+                       waittv.tv_usec = timeout.tv_usec - now.tv_usec;
+                       if (waittv.tv_usec < 0) {
+                               waittv.tv_usec += 1000000;
+                               waittv.tv_sec--;
+                       }
+                       if (waittv.tv_sec < 0) {
+                               errno = ETIMEDOUT;
+                               fetch_syserr();
+                               return (-1);
+                       }
+                       errno = 0;
+                       r = select(conn->sd + 1, NULL, &writefds, NULL, &waittv);
+                       if (r == -1) {
+                               if (errno == EINTR && fetchRestartCalls)
+                                       continue;
+                               return (-1);
+                       }
+               }
+               errno = 0;
+#ifdef WITH_SSL
+               if (conn->ssl != NULL)
+                       wlen = SSL_write(conn->ssl, buf, len);
+               else
+#endif
+#ifndef MSG_NOSIGNAL
+                       wlen = send(conn->sd, buf, len, 0);
+#else
+                       wlen = send(conn->sd, buf, len, MSG_NOSIGNAL);
+#endif
+               if (wlen == 0) {
+                       /* we consider a short write a failure */
+                       errno = EPIPE;
+                       fetch_syserr();
+                       return (-1);
+               }
+               if (wlen < 0) {
+                       if (errno == EINTR && fetchRestartCalls)
+                               continue;
+                       return (-1);
+               }
+               total += wlen;
+               buf = (const char *)buf + wlen;
+               len -= wlen;
+       }
+       return (total);
+}
+
+
+/*
+ * Close connection
+ */
+int
+fetch_close(conn_t *conn)
+{
+       int ret;
+
+       ret = close(conn->sd);
+       if (conn->cache_url)
+               fetchFreeURL(conn->cache_url);
+       free(conn->ftp_home);
+       free(conn->buf);
+       free(conn);
+       return (ret);
+}
+
+
+/*** Directory-related utility functions *************************************/
+
+int
+fetch_add_entry(struct url_list *ue, struct url *base, const char *name,
+    int pre_quoted)
+{
+       struct url *tmp;
+       char *tmp_name;
+       size_t base_doc_len, name_len, i;
+       unsigned char c;
+
+       if (strchr(name, '/') != NULL ||
+           strcmp(name, "..") == 0 ||
+           strcmp(name, ".") == 0)
+               return 0;
+
+       if (strcmp(base->doc, "/") == 0)
+               base_doc_len = 0;
+       else
+               base_doc_len = strlen(base->doc);
+
+       name_len = 1;
+       for (i = 0; name[i] != '\0'; ++i) {
+               if ((!pre_quoted && name[i] == '%') ||
+                   !fetch_urlpath_safe(name[i]))
+                       name_len += 3;
+               else
+                       ++name_len;
+       }
+
+       tmp_name = malloc( base_doc_len + name_len + 1);
+       if (tmp_name == NULL) {
+               errno = ENOMEM;
+               fetch_syserr();
+               return (-1);
+       }
+
+       if (ue->length + 1 >= ue->alloc_size) {
+               tmp = realloc(ue->urls, (ue->alloc_size * 2 + 1) * sizeof(*tmp));
+               if (tmp == NULL) {
+                       free(tmp_name);
+                       errno = ENOMEM;
+                       fetch_syserr();
+                       return (-1);
+               }
+               ue->alloc_size = ue->alloc_size * 2 + 1;
+               ue->urls = tmp;
+       }
+
+       tmp = ue->urls + ue->length;
+       strcpy(tmp->scheme, base->scheme);
+       strcpy(tmp->user, base->user);
+       strcpy(tmp->pwd, base->pwd);
+       strcpy(tmp->host, base->host);
+       tmp->port = base->port;
+       tmp->doc = tmp_name;
+       memcpy(tmp->doc, base->doc, base_doc_len);
+       tmp->doc[base_doc_len] = '/';
+
+       for (i = base_doc_len + 1; *name != '\0'; ++name) {
+               if ((!pre_quoted && *name == '%') ||
+                   !fetch_urlpath_safe(*name)) {
+                       tmp->doc[i++] = '%';
+                       c = (unsigned char)*name / 16;
+                       if (c < 10)
+                               tmp->doc[i++] = '0' + c;
+                       else
+                               tmp->doc[i++] = 'a' - 10 + c;
+                       c = (unsigned char)*name % 16;
+                       if (c < 10)
+                               tmp->doc[i++] = '0' + c;
+                       else
+                               tmp->doc[i++] = 'a' - 10 + c;
+               } else {
+                       tmp->doc[i++] = *name;
+               }
+       }
+       tmp->doc[i] = '\0';
+
+       tmp->offset = 0;
+       tmp->length = 0;
+       tmp->last_modified = -1;
+
+       ++ue->length;
+
+       return (0);
+}
+
+void
+fetchInitURLList(struct url_list *ue)
+{
+       ue->length = ue->alloc_size = 0;
+       ue->urls = NULL;
+}
+
+int
+fetchAppendURLList(struct url_list *dst, const struct url_list *src)
+{
+       size_t i, j, len;
+
+       len = dst->length + src->length;
+       if (len > dst->alloc_size) {
+               struct url *tmp;
+
+               tmp = realloc(dst->urls, len * sizeof(*tmp));
+               if (tmp == NULL) {
+                       errno = ENOMEM;
+                       fetch_syserr();
+                       return (-1);
+               }
+               dst->alloc_size = len;
+               dst->urls = tmp;
+       }
+
+       for (i = 0, j = dst->length; i < src->length; ++i, ++j) {
+               dst->urls[j] = src->urls[i];
+               dst->urls[j].doc = strdup(src->urls[i].doc);
+               if (dst->urls[j].doc == NULL) {
+                       while (i-- > 0)
+                               free(dst->urls[j].doc);
+                       fetch_syserr();
+                       return -1;
+               }
+       }
+       dst->length = len;
+
+       return 0;
+}
+
+void
+fetchFreeURLList(struct url_list *ue)
+{
+       size_t i;
+
+       for (i = 0; i < ue->length; ++i)
+               free(ue->urls[i].doc);
+       free(ue->urls);
+       ue->length = ue->alloc_size = 0;
+}
+
+
+/*** Authentication-related utility functions ********************************/
+
+static const char *
+fetch_read_word(FILE *f)
+{
+       static char word[1024];
+
+       if (fscanf(f, " %1023s ", word) != 1)
+               return (NULL);
+       return (word);
+}
+
+/*
+ * Get authentication data for a URL from .netrc
+ */
+int
+fetch_netrc_auth(struct url *url)
+{
+       char fn[PATH_MAX];
+       const char *word;
+       char *p;
+       FILE *f;
+
+       if ((p = getenv("NETRC")) != NULL) {
+               if (snprintf(fn, sizeof(fn), "%s", p) >= (int)sizeof(fn)) {
+                       fetch_info("$NETRC specifies a file name "
+                           "longer than PATH_MAX");
+                       return (-1);
+               }
+       } else {
+               if ((p = getenv("HOME")) != NULL) {
+                       struct passwd *pwd;
+
+                       if ((pwd = getpwuid(getuid())) == NULL ||
+                           (p = pwd->pw_dir) == NULL)
+                               return (-1);
+               }
+               if (snprintf(fn, sizeof(fn), "%s/.netrc", p) >= (int)sizeof(fn))
+                       return (-1);
+       }
+
+       if ((f = fopen(fn, "r")) == NULL)
+               return (-1);
+       while ((word = fetch_read_word(f)) != NULL) {
+               if (strcmp(word, "default") == 0)
+                       break;
+               if (strcmp(word, "machine") == 0 &&
+                   (word = fetch_read_word(f)) != NULL &&
+                   strcasecmp(word, url->host) == 0) {
+                       break;
+               }
+       }
+       if (word == NULL)
+               goto ferr;
+       while ((word = fetch_read_word(f)) != NULL) {
+               if (strcmp(word, "login") == 0) {
+                       if ((word = fetch_read_word(f)) == NULL)
+                               goto ferr;
+                       if (snprintf(url->user, sizeof(url->user),
+                               "%s", word) > (int)sizeof(url->user)) {
+                               fetch_info("login name in .netrc is too long");
+                               url->user[0] = '\0';
+                       }
+               } else if (strcmp(word, "password") == 0) {
+                       if ((word = fetch_read_word(f)) == NULL)
+                               goto ferr;
+                       if (snprintf(url->pwd, sizeof(url->pwd),
+                               "%s", word) > (int)sizeof(url->pwd)) {
+                               fetch_info("password in .netrc is too long");
+                               url->pwd[0] = '\0';
+                       }
+               } else if (strcmp(word, "account") == 0) {
+                       if ((word = fetch_read_word(f)) == NULL)
+                               goto ferr;
+                       /* XXX not supported! */
+               } else {
+                       break;
+               }
+       }
+       fclose(f);
+       return (0);
+ ferr:
+       fclose(f);
+       return (-1);
+}
+
+/*
+ * The no_proxy environment variable specifies a set of domains for
+ * which the proxy should not be consulted; the contents is a comma-,
+ * or space-separated list of domain names.  A single asterisk will
+ * override all proxy variables and no transactions will be proxied
+ * (for compatability with lynx and curl, see the discussion at
+ * <http://curl.haxx.se/mail/archive_pre_oct_99/0009.html>).
+ */
+int
+fetch_no_proxy_match(const char *host)
+{
+       const char *no_proxy, *p, *q;
+       size_t h_len, d_len;
+
+       if ((no_proxy = getenv("NO_PROXY")) == NULL &&
+           (no_proxy = getenv("no_proxy")) == NULL)
+               return (0);
+
+       /* asterisk matches any hostname */
+       if (strcmp(no_proxy, "*") == 0)
+               return (1);
+
+       h_len = strlen(host);
+       p = no_proxy;
+       do {
+               /* position p at the beginning of a domain suffix */
+               while (*p == ',' || isspace((unsigned char)*p))
+                       p++;
+
+               /* position q at the first separator character */
+               for (q = p; *q; ++q)
+                       if (*q == ',' || isspace((unsigned char)*q))
+                               break;
+
+               d_len = q - p;
+               if (d_len > 0 && h_len > d_len &&
+                   strncasecmp(host + h_len - d_len,
+                       p, d_len) == 0) {
+                       /* domain name matches */
+                       return (1);
+               }
+
+               p = q + 1;
+       } while (*q);
+
+       return (0);
+}
+
+struct fetchIO {
+       void *io_cookie;
+       ssize_t (*io_read)(void *, void *, size_t);
+       ssize_t (*io_write)(void *, const void *, size_t);
+       void (*io_close)(void *);
+};
+
+void
+fetchIO_close(fetchIO *f)
+{
+       if (f->io_close != NULL)
+               (*f->io_close)(f->io_cookie);
+
+       free(f);
+}
+
+fetchIO *
+fetchIO_unopen(void *io_cookie, ssize_t (*io_read)(void *, void *, size_t),
+    ssize_t (*io_write)(void *, const void *, size_t),
+    void (*io_close)(void *))
+{
+       fetchIO *f;
+
+       f = malloc(sizeof(*f));
+       if (f == NULL)
+               return f;
+
+       f->io_cookie = io_cookie;
+       f->io_read = io_read;
+       f->io_write = io_write;
+       f->io_close = io_close;
+
+       return f;
+}
+
+ssize_t
+fetchIO_read(fetchIO *f, void *buf, size_t len)
+{
+       if (f->io_read == NULL)
+               return EBADF;
+       return (*f->io_read)(f->io_cookie, buf, len);
+}
+
+ssize_t
+fetchIO_write(fetchIO *f, const void *buf, size_t len)
+{
+       if (f->io_read == NULL)
+               return EBADF;
+       return (*f->io_write)(f->io_cookie, buf, len);
+}
diff --git a/lib/libfetch/common.h b/lib/libfetch/common.h
new file mode 100644 (file)
index 0000000..d9ce647
--- /dev/null
@@ -0,0 +1,139 @@
+/*     $NetBSD: common.h,v 1.16 2010/03/21 16:48:43 joerg Exp $        */
+/*-
+ * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: common.h,v 1.30 2007/12/18 11:03:07 des Exp $
+ */
+
+#ifndef _COMMON_H_INCLUDED
+#define _COMMON_H_INCLUDED
+
+#define FTP_DEFAULT_PORT       21
+#define HTTP_DEFAULT_PORT      80
+#define FTP_DEFAULT_PROXY_PORT 21
+#define HTTP_DEFAULT_PROXY_PORT        3128
+
+#ifdef WITH_SSL
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif
+
+#if !defined(__sun) && !defined(__hpux) && !defined(__INTERIX) && \
+    !defined(__digital__) && !defined(__linux) && !defined(__MINT__) && \
+    !defined(__sgi) && !defined(__minix)
+#define HAVE_SA_LEN
+#endif
+
+/* Connection */
+typedef struct fetchconn conn_t;
+
+struct fetchconn {
+       int              sd;            /* socket descriptor */
+       char            *buf;           /* buffer */
+       size_t           bufsize;       /* buffer size */
+       size_t           buflen;        /* length of buffer contents */
+       char            *next_buf;      /* pending buffer, e.g. after getln */
+       size_t           next_len;      /* size of pending buffer */
+       int              err;           /* last protocol reply code */
+#ifdef WITH_SSL
+       SSL             *ssl;           /* SSL handle */
+       SSL_CTX         *ssl_ctx;       /* SSL context */
+       X509            *ssl_cert;      /* server certificate */
+#  if OPENSSL_VERSION_NUMBER < 0x00909000L
+       SSL_METHOD *ssl_meth;           /* SSL method */
+#  else
+       const SSL_METHOD *ssl_meth;     /* SSL method */
+#  endif
+#endif
+
+       char            *ftp_home;
+
+       struct url      *cache_url;
+       int             cache_af;
+       int             (*cache_close)(conn_t *);
+       conn_t          *next_cached;
+};
+
+/* Structure used for error message lists */
+struct fetcherr {
+       const int        num;
+       const int        cat;
+       const char      *string;
+};
+
+void            fetch_seterr(struct fetcherr *, int);
+void            fetch_syserr(void);
+void            fetch_info(const char *, ...);
+int             fetch_default_port(const char *);
+int             fetch_default_proxy_port(const char *);
+int             fetch_bind(int, int, const char *);
+conn_t         *fetch_cache_get(const struct url *, int);
+void            fetch_cache_put(conn_t *, int (*)(conn_t *));
+conn_t         *fetch_connect(struct url *, int, int);
+conn_t         *fetch_reopen(int);
+int             fetch_ssl(conn_t *, int);
+ssize_t                 fetch_read(conn_t *, char *, size_t);
+int             fetch_getln(conn_t *);
+ssize_t                 fetch_write(conn_t *, const void *, size_t);
+int             fetch_close(conn_t *);
+int             fetch_add_entry(struct url_list *, struct url *, const char *, int);
+int             fetch_netrc_auth(struct url *url);
+int             fetch_no_proxy_match(const char *);
+int             fetch_urlpath_safe(char);
+
+#define ftp_seterr(n)   fetch_seterr(ftp_errlist, n)
+#define http_seterr(n)  fetch_seterr(http_errlist, n)
+#define netdb_seterr(n)         fetch_seterr(netdb_errlist, n)
+#define url_seterr(n)   fetch_seterr(url_errlist, n)
+
+fetchIO                *fetchIO_unopen(void *, ssize_t (*)(void *, void *, size_t),
+    ssize_t (*)(void *, const void *, size_t), void (*)(void *));
+
+/*
+ * I don't really like exporting http_request() and ftp_request(),
+ * but the HTTP and FTP code occasionally needs to cross-call
+ * eachother, and this saves me from adding a lot of special-case code
+ * to handle those cases.
+ *
+ * Note that _*_request() free purl, which is way ugly but saves us a
+ * whole lot of trouble.
+ */
+fetchIO                *http_request(struct url *, const char *,
+                    struct url_stat *, struct url *, const char *);
+fetchIO                *ftp_request(struct url *, const char *, const char *,
+                    struct url_stat *, struct url *, const char *);
+
+
+/*
+ * Check whether a particular flag is set
+ */
+#define CHECK_FLAG(x)  (flags && strchr(flags, (x)))
+
+#endif
diff --git a/lib/libfetch/errlist.sh b/lib/libfetch/errlist.sh
new file mode 100755 (executable)
index 0000000..84779fe
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+# $NetBSD: errlist.sh,v 1.2 2008/10/06 12:58:29 joerg Exp $
+
+printf "static struct fetcherr $1[] = {\n"
+while read code type msg; do
+       [ "${code}" = "#" ] && continue
+       printf "\t{ ${code}, FETCH_${type}, \"${msg}\" },\n"
+done < $3
+
+printf "\t{ -1, FETCH_UNKNOWN, \"Unknown $2 error\" }\n"
+printf "};\n"
diff --git a/lib/libfetch/fetch.3 b/lib/libfetch/fetch.3
new file mode 100644 (file)
index 0000000..9b9d955
--- /dev/null
@@ -0,0 +1,781 @@
+.\"-
+.\" Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
+.\" Copyright (c) 2010 Joerg Sonnenberger <joerg@NetBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD: fetch.3,v 1.64 2007/12/18 11:03:26 des Exp $
+.\" $NetBSD: fetch.3,v 1.15 2010/01/22 13:56:45 wiz Exp $
+.\"
+.Dd January 22, 2010
+.Dt FETCH 3
+.Os
+.Sh NAME
+.Nm fetchMakeURL ,
+.Nm fetchParseURL ,
+.Nm fetchCopyURL ,
+.Nm fetchFreeURL ,
+.Nm fetchXGetURL ,
+.Nm fetchGetURL ,
+.Nm fetchPutURL ,
+.Nm fetchStatURL ,
+.Nm fetchListURL ,
+.Nm fetchXGet ,
+.Nm fetchGet ,
+.Nm fetchPut ,
+.Nm fetchStat ,
+.Nm fetchList ,
+.Nm fetchXGetFile ,
+.Nm fetchGetFile ,
+.Nm fetchPutFile ,
+.Nm fetchStatFile ,
+.Nm fetchListFile ,
+.Nm fetchXGetHTTP ,
+.Nm fetchGetHTTP ,
+.Nm fetchPutHTTP ,
+.Nm fetchStatHTTP ,
+.Nm fetchListHTTP ,
+.Nm fetchXGetFTP ,
+.Nm fetchGetFTP ,
+.Nm fetchPutFTP ,
+.Nm fetchStatFTP ,
+.Nm fetchListFTP
+.Nm fetchInitURLList ,
+.Nm fetchFreeURLList ,
+.Nm fetchUnquotePath ,
+.Nm fetchUnquoteFilename ,
+.Nm fetchStringifyURL ,
+.Nm fetchConnectionCacheInit ,
+.Nm fetchConnectionCacheClose ,
+.Nm fetch
+.Nd file transfer functions
+.Sh LIBRARY
+.Lb libfetch
+.Sh SYNOPSIS
+.In stdio.h
+.In fetch.h
+.Ft struct url *
+.Fn fetchMakeURL "const char *scheme" "const char *host" "int port" "const char *doc" "const char *user" "const char *pwd"
+.Ft struct url *
+.Fn fetchParseURL "const char *URL"
+.Ft struct url *
+.Fn fetchCopyURL "const struct url *u"
+.Ft void
+.Fn fetchFreeURL "struct url *u"
+.Ft fetchIO *
+.Fn fetchXGetURL "const char *URL" "struct url_stat *us" "const char *flags"
+.Ft fetchIO *
+.Fn fetchGetURL "const char *URL" "const char *flags"
+.Ft fetchIO *
+.Fn fetchPutURL "const char *URL" "const char *flags"
+.Ft int
+.Fn fetchStatURL "const char *URL" "struct url_stat *us" "const char *flags"
+.Ft int
+.Fn fetchListURL "struct url_list *list" "const char *URL" "const char *flags"
+.Ft fetchIO *
+.Fn fetchXGet "struct url *u" "struct url_stat *us" "const char *flags"
+.Ft fetchIO *
+.Fn fetchGet "struct url *u" "const char *flags"
+.Ft fetchIO *
+.Fn fetchPut "struct url *u" "const char *flags"
+.Ft int
+.Fn fetchStat "struct url *u" "struct url_stat *us" "const char *flags"
+.Ft int
+.Fn fetchList "struct url_list *list" "struct url *u" "const char *flags"
+.Ft fetchIO *
+.Fn fetchXGetFile "struct url *u" "struct url_stat *us" "const char *flags"
+.Ft fetchIO *
+.Fn fetchGetFile "struct url *u" "const char *flags"
+.Ft fetchIO *
+.Fn fetchPutFile "struct url *u" "const char *flags"
+.Ft int
+.Fn fetchStatFile "struct url *u" "struct url_stat *us" "const char *flags"
+.Ft int
+.Fn fetchListFile "struct url_list *list" "struct url *u" "const char *flags"
+.Ft fetchIO *
+.Fn fetchXGetHTTP "struct url *u" "struct url_stat *us" "const char *flags"
+.Ft fetchIO *
+.Fn fetchGetHTTP "struct url *u" "const char *flags"
+.Ft fetchIO *
+.Fn fetchPutHTTP "struct url *u" "const char *flags"
+.Ft int
+.Fn fetchStatHTTP "struct url *u" "struct url_stat *us" "const char *flags"
+.Ft int
+.Fn fetchListHTTP "struct url_list *list" "struct url *u" "const char *flags"
+.Ft fetchIO *
+.Fn fetchXGetFTP "struct url *u" "struct url_stat *us" "const char *flags"
+.Ft fetchIO *
+.Fn fetchGetFTP "struct url *u" "const char *flags"
+.Ft fetchIO *
+.Fn fetchPutFTP "struct url *u" "const char *flags"
+.Ft int
+.Fn fetchStatFTP "struct url *u" "struct url_stat *us" "const char *flags"
+.Ft int
+.Fn fetchListFTP "struct url_list *list" "struct url *u" "const char *flags"
+.Ft void
+.Fn fetchInitURLList "struct url_list *ul"
+.Ft int
+.Fn fetchAppendURLList "struct url_list *dst" "const struct url_list *src"
+.Ft void
+.Fn fetchFreeURLList "struct url_list *ul"
+.Ft char *
+.Fn fetchUnquotePath "struct url *u"
+.Ft char *
+.Fn fetchUnquoteFilename "struct url *u"
+.Ft char *
+.Fn fetchStringifyURL "const struct url *u"
+.Ft void
+.Fn fetchConnectionCacheInit "int global" "int per_host"
+.Ft void
+.Fn fetchConnectionCacheClose "void"
+.Sh DESCRIPTION
+These functions implement a high-level library for retrieving and
+uploading files using Uniform Resource Locators (URLs).
+.Pp
+.Fn fetchParseURL
+takes a URL in the form of a null-terminated string and splits it into
+its components function according to the Common Internet Scheme Syntax
+detailed in RFC 1738.
+A regular expression which produces this syntax is:
+.Bd -literal -offset indent
+\*[Lt]scheme\*[Gt]:(//(\*[Lt]user\*[Gt](:\*[Lt]pwd\*[Gt])?@)?\*[Lt]host\*[Gt](:\*[Lt]port\*[Gt])?)?/(\*[Lt]document\*[Gt])?
+.Ed
+.Pp
+If the URL does not seem to begin with a scheme name, it is assumed to be a local path.
+Only absolute path names are accepted.
+.Pp
+Note that some components of the URL are not necessarily relevant to
+all URL schemes.
+For instance, the file scheme only needs the
+.Aq scheme
+and
+.Aq document
+components.
+.Fn fetchParseURL
+quotes any unsafe character in the URL automatically.
+This is not done by
+.Fn fetchMakeURL .
+.Fn fetchCopyURL
+copies an existing
+.Vt url
+structure.
+.Pp
+.Fn fetchMakeURL ,
+.Fn fetchParseURL ,
+and
+.Fn fetchCopyURL
+return a pointer to a
+.Vt url
+structure, which is defined as follows in
+.In fetch.h :
+.Bd -literal
+#define URL_SCHEMELEN 16
+#define URL_USERLEN 256
+#define URL_PWDLEN 256
+#define URL_HOSTLEN 255
+
+struct url {
+    char        scheme[URL_SCHEMELEN + 1];
+    char        user[URL_USERLEN + 1];
+    char        pwd[URL_PWDLEN + 1];
+    char        host[URL_HOSTLEN + 1];
+    int                 port;
+    char       *doc;
+    off_t       offset;
+    size_t      length;
+    time_t      last_modified;
+};
+.Ed
+.Pp
+The pointer returned by
+.Fn fetchMakeURL ,
+.Fn fetchCopyURL ,
+and
+.Fn fetchParseURL
+should be freed using
+.Fn fetchFreeURL .
+The size of
+.Vt struct URL
+is not part of the ABI.
+.Pp
+.Fn fetchXGetURL ,
+.Fn fetchGetURL ,
+and
+.Fn fetchPutURL
+constitute the recommended interface to the
+.Nm fetch
+library.
+They examine the URL passed to them to determine the transfer
+method, and call the appropriate lower-level functions to perform the
+actual transfer.
+.Fn fetchXGetURL
+also returns the remote document's metadata in the
+.Vt url_stat
+structure pointed to by the
+.Fa us
+argument.
+.Pp
+The
+.Fa flags
+argument is a string of characters which specify transfer options.
+The
+meaning of the individual flags is scheme-dependent, and is detailed
+in the appropriate section below.
+.Pp
+.Fn fetchStatURL
+attempts to obtain the requested document's metadata and fill in the
+structure pointed to by its second argument.
+The
+.Vt url_stat
+structure is defined as follows in
+.In fetch.h :
+.Bd -literal
+struct url_stat {
+    off_t       size;
+    time_t      atime;
+    time_t      mtime;
+};
+.Ed
+.Pp
+If the size could not be obtained from the server, the
+.Fa size
+field is set to \-1.
+If the modification time could not be obtained from the server, the
+.Fa mtime
+field is set to the epoch.
+If the access time could not be obtained from the server, the
+.Fa atime
+field is set to the modification time.
+.Pp
+.Fn fetchListURL
+attempts to list the contents of the directory pointed to by the URL provided.
+The pattern can be a simple glob-like expression as hint.
+Callers should not depend on the server to filter names.
+If successful, it appends the list of entries to the
+.Vt url_list
+structure.
+The
+.Vt url_list
+structure is defined as follows in
+.In fetch.h :
+.Bd -literal
+struct url_list {
+    size_t     length;
+    size_t     alloc_size;
+    struct url *urls;
+};
+.Ed
+.Pp
+The list should be initialized by calling
+.Fn fetchInitURLList
+and the entries be freed by calling
+.Fn fetchFreeURLList .
+The function
+.Fn fetchAppendURLList
+can be used to append one URL lists to another.
+If the
+.Ql c
+(cache result) flag is specified, the library is allowed to internally
+cache the result.
+.Pp
+.Fn fetchStringifyURL
+returns the URL as string.
+.Fn fetchUnquotePath
+returns the path name part of the URL with any quoting undone.
+Query arguments and fragment identifiers are not included.
+.Fn fetchUnquoteFilename
+returns the last component of the path name as returned by
+.Fn fetchUnquotePath .
+.Fn fetchStringifyURL ,
+.Fn fetchUnquotePath ,
+and
+.Fn fetchUnquoteFilename
+return a string that should be deallocated with
+.Fn free
+after use.
+.Pp
+.Fn fetchConnectionCacheInit
+enables the connection cache.
+The first argument specifies the global limit on cached connections.
+The second argument specifies the host limit.
+Entries are considered to specify the same host, if the host name
+from the URL is identical, indepent of the address or address family.
+.Fn fetchConnectionCacheClose
+flushed the connection cache and closes all cached connections.
+.Pp
+.Fn fetchXGet ,
+.Fn fetchGet ,
+.Fn fetchPut ,
+and
+.Fn fetchStat
+are similar to
+.Fn fetchXGetURL ,
+.Fn fetchGetURL ,
+.Fn fetchPutURL ,
+and
+.Fn fetchStatURL ,
+except that they expect a pre-parsed URL in the form of a pointer to
+a
+.Vt struct url
+rather than a string.
+.Pp
+All of the
+.Fn fetchXGetXXX ,
+.Fn fetchGetXXX ,
+and
+.Fn fetchPutXXX
+functions return a pointer to a stream which can be used to read or
+write data from or to the requested document, respectively.
+Note that
+although the implementation details of the individual access methods
+vary, it can generally be assumed that a stream returned by one of the
+.Fn fetchXGetXXX
+or
+.Fn fetchGetXXX
+functions is read-only, and that a stream returned by one of the
+.Fn fetchPutXXX
+functions is write-only.
+.Sh PROTOCOL INDEPENDENT FLAGS
+If the
+.Ql i
+(if-modified-since) flag is specified, the library will try to fetch
+the content only if it is newer than
+.Va last_modified .
+For HTTP an
+.Li If-Modified-Since
+HTTP header is sent.
+For FTP a
+.Li MTDM
+command is sent first and compared locally.
+For FILE the source file is compared.
+.Sh FILE SCHEME
+.Fn fetchXGetFile ,
+.Fn fetchGetFile ,
+and
+.Fn fetchPutFile
+provide access to documents which are files in a locally mounted file
+system.
+Only the
+.Aq document
+component of the URL is used.
+.Pp
+.Fn fetchXGetFile
+and
+.Fn fetchGetFile
+do not accept any flags.
+.Pp
+.Fn fetchPutFile
+accepts the
+.Ql a
+(append to file) flag.
+If that flag is specified, the data written to
+the stream returned by
+.Fn fetchPutFile
+will be appended to the previous contents of the file, instead of
+replacing them.
+.Sh FTP SCHEME
+.Fn fetchXGetFTP ,
+.Fn fetchGetFTP ,
+and
+.Fn fetchPutFTP
+implement the FTP protocol as described in RFC 959.
+.Pp
+By default
+.Nm libfetch
+will attempt to use passive mode first and only fallback to active mode
+if the server reports a syntax error.
+If the
+.Ql a
+(active) flag is specified, a passive connection is not tried and active mode
+is used directly.
+.Pp
+If the
+.Ql l
+(low) flag is specified, data sockets will be allocated in the low (or
+default) port range instead of the high port range (see
+.Xr ip 4 ) .
+.Pp
+If the
+.Ql d
+(direct) flag is specified,
+.Fn fetchXGetFTP ,
+.Fn fetchGetFTP ,
+and
+.Fn fetchPutFTP
+will use a direct connection even if a proxy server is defined.
+.Pp
+If no user name or password is given, the
+.Nm fetch
+library will attempt an anonymous login, with user name "anonymous"
+and password "anonymous@\*[Lt]hostname\*[Gt]".
+.Sh HTTP SCHEME
+The
+.Fn fetchXGetHTTP ,
+.Fn fetchGetHTTP ,
+and
+.Fn fetchPutHTTP
+functions implement the HTTP/1.1 protocol.
+With a little luck, there is
+even a chance that they comply with RFC 2616 and RFC 2617.
+.Pp
+If the
+.Ql d
+(direct) flag is specified,
+.Fn fetchXGetHTTP ,
+.Fn fetchGetHTTP ,
+and
+.Fn fetchPutHTTP
+will use a direct connection even if a proxy server is defined.
+.Pp
+Since there seems to be no good way of implementing the HTTP PUT
+method in a manner consistent with the rest of the
+.Nm fetch
+library,
+.Fn fetchPutHTTP
+is currently unimplemented.
+.Sh AUTHENTICATION
+Apart from setting the appropriate environment variables and
+specifying the user name and password in the URL or the
+.Vt struct url ,
+the calling program has the option of defining an authentication
+function with the following prototype:
+.Pp
+.Ft int
+.Fn myAuthMethod "struct url *u"
+.Pp
+The callback function should fill in the
+.Fa user
+and
+.Fa pwd
+fields in the provided
+.Vt struct url
+and return 0 on success, or any other value to indicate failure.
+.Pp
+To register the authentication callback, simply set
+.Va fetchAuthMethod
+to point at it.
+The callback will be used whenever a site requires authentication and
+the appropriate environment variables are not set.
+.Pp
+This interface is experimental and may be subject to change.
+.Sh RETURN VALUES
+.Fn fetchParseURL
+returns a pointer to a
+.Vt struct url
+containing the individual components of the URL.
+If it is
+unable to allocate memory, or the URL is syntactically incorrect,
+.Fn fetchParseURL
+returns a
+.Dv NULL
+pointer.
+.Pp
+The
+.Fn fetchStat
+functions return 0 on success and \-1 on failure.
+.Pp
+All other functions return a stream pointer which may be used to
+access the requested document, or
+.Dv NULL
+if an error occurred.
+.Pp
+The following error codes are defined in
+.In fetch.h :
+.Bl -tag -width 18n
+.It Bq Er FETCH_ABORT
+Operation aborted
+.It Bq Er FETCH_AUTH
+Authentication failed
+.It Bq Er FETCH_DOWN
+Service unavailable
+.It Bq Er FETCH_EXISTS
+File exists
+.It Bq Er FETCH_FULL
+File system full
+.It Bq Er FETCH_INFO
+Informational response
+.It Bq Er FETCH_MEMORY
+Insufficient memory
+.It Bq Er FETCH_MOVED
+File has moved
+.It Bq Er FETCH_NETWORK
+Network error
+.It Bq Er FETCH_OK
+No error
+.It Bq Er FETCH_PROTO
+Protocol error
+.It Bq Er FETCH_RESOLV
+Resolver error
+.It Bq Er FETCH_SERVER
+Server error
+.It Bq Er FETCH_TEMP
+Temporary error
+.It Bq Er FETCH_TIMEOUT
+Operation timed out
+.It Bq Er FETCH_UNAVAIL
+File is not available
+.It Bq Er FETCH_UNKNOWN
+Unknown error
+.It Bq Er FETCH_URL
+Invalid URL
+.El
+.Pp
+The accompanying error message includes a protocol-specific error code
+and message, e.g.\& "File is not available (404 Not Found)"
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev FETCH_BIND_ADDRESS"
+.It Ev FETCH_BIND_ADDRESS
+Specifies a host name or IP address to which sockets used for outgoing
+connections will be bound.
+.It Ev FTP_LOGIN
+Default FTP login if none was provided in the URL.
+.It Ev FTP_PASSIVE_MODE
+If set to anything but
+.Ql no ,
+forces the FTP code to use passive mode.
+.It Ev FTP_PASSWORD
+Default FTP password if the remote server requests one and none was
+provided in the URL.
+.It Ev FTP_PROXY
+URL of the proxy to use for FTP requests.
+The document part is ignored.
+FTP and HTTP proxies are supported; if no scheme is specified, FTP is
+assumed.
+If the proxy is an FTP proxy,
+.Nm libfetch
+will send
+.Ql user@host
+as user name to the proxy, where
+.Ql user
+is the real user name, and
+.Ql host
+is the name of the FTP server.
+.Pp
+If this variable is set to an empty string, no proxy will be used for
+FTP requests, even if the
+.Ev HTTP_PROXY
+variable is set.
+.It Ev ftp_proxy
+Same as
+.Ev FTP_PROXY ,
+for compatibility.
+.It Ev HTTP_AUTH
+Specifies HTTP authorization parameters as a colon-separated list of
+items.
+The first and second item are the authorization scheme and realm
+respectively; further items are scheme-dependent.
+Currently, only basic authorization is supported.
+.Pp
+Basic authorization requires two parameters: the user name and
+password, in that order.
+.Pp
+This variable is only used if the server requires authorization and
+no user name or password was specified in the URL.
+.It Ev HTTP_PROXY
+URL of the proxy to use for HTTP requests.
+The document part is ignored.
+Only HTTP proxies are supported for HTTP requests.
+If no port number is specified, the default is 3128.
+.Pp
+Note that this proxy will also be used for FTP documents, unless the
+.Ev FTP_PROXY
+variable is set.
+.It Ev http_proxy
+Same as
+.Ev HTTP_PROXY ,
+for compatibility.
+.It Ev HTTP_PROXY_AUTH
+Specifies authorization parameters for the HTTP proxy in the same
+format as the
+.Ev HTTP_AUTH
+variable.
+.Pp
+This variable is used if and only if connected to an HTTP proxy, and
+is ignored if a user and/or a password were specified in the proxy
+URL.
+.It Ev HTTP_REFERER
+Specifies the referrer URL to use for HTTP requests.
+If set to
+.Dq auto ,
+the document URL will be used as referrer URL.
+.It Ev HTTP_USER_AGENT
+Specifies the User-Agent string to use for HTTP requests.
+This can be useful when working with HTTP origin or proxy servers that
+differentiate between user agents.
+.It Ev NETRC
+Specifies a file to use instead of
+.Pa ~/.netrc
+to look up login names and passwords for FTP sites.
+See
+.Xr ftp 1
+for a description of the file format.
+This feature is experimental.
+.It Ev NO_PROXY
+Either a single asterisk, which disables the use of proxies
+altogether, or a comma- or whitespace-separated list of hosts for
+which proxies should not be used.
+.It Ev no_proxy
+Same as
+.Ev NO_PROXY ,
+for compatibility.
+.El
+.Sh EXAMPLES
+To access a proxy server on
+.Pa proxy.example.com
+port 8080, set the
+.Ev HTTP_PROXY
+environment variable in a manner similar to this:
+.Pp
+.Dl HTTP_PROXY=http://proxy.example.com:8080
+.Pp
+If the proxy server requires authentication, there are
+two options available for passing the authentication data.
+The first method is by using the proxy URL:
+.Pp
+.Dl HTTP_PROXY=http://\*[Lt]user\*[Gt]:\*[Lt]pwd\*[Gt]@proxy.example.com:8080
+.Pp
+The second method is by using the
+.Ev HTTP_PROXY_AUTH
+environment variable:
+.Bd -literal -offset indent
+HTTP_PROXY=http://proxy.example.com:8080
+HTTP_PROXY_AUTH=basic:*:\*[Lt]user\*[Gt]:\*[Lt]pwd\*[Gt]
+.Ed
+.Pp
+To disable the use of a proxy for an HTTP server running on the local
+host, define
+.Ev NO_PROXY
+as follows:
+.Bd -literal -offset indent
+NO_PROXY=localhost,127.0.0.1
+.Ed
+.Sh SEE ALSO
+.\" .Xr fetch 1 ,
+.\" .Xr ftpio 3 ,
+.Xr ftp 1 ,
+.Xr ip 4
+.Rs
+.%A J. Postel
+.%A J. K. Reynolds
+.%D October 1985
+.%B File Transfer Protocol
+.%O RFC 959
+.Re
+.Rs
+.%A P. Deutsch
+.%A A. Emtage
+.%A A. Marine
+.%D May 1994
+.%T How to Use Anonymous FTP
+.%O RFC 1635
+.Re
+.Rs
+.%A T. Berners-Lee
+.%A L. Masinter
+.%A M. McCahill
+.%D December 1994
+.%T Uniform Resource Locators (URL)
+.%O RFC 1738
+.Re
+.Rs
+.%A R. Fielding
+.%A J. Gettys
+.%A J. Mogul
+.%A H. Frystyk
+.%A L. Masinter
+.%A P. Leach
+.%A T. Berners-Lee
+.%D January 1999
+.%B Hypertext Transfer Protocol -- HTTP/1.1
+.%O RFC 2616
+.Re
+.Rs
+.%A J. Franks
+.%A P. Hallam-Baker
+.%A J. Hostetler
+.%A S. Lawrence
+.%A P. Leach
+.%A A. Luotonen
+.%A L. Stewart
+.%D June 1999
+.%B HTTP Authentication: Basic and Digest Access Authentication
+.%O RFC 2617
+.Re
+.Sh HISTORY
+The
+.Nm fetch
+library first appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm fetch
+library was mostly written by
+.An Dag-Erling Sm\(/orgrav Aq des@FreeBSD.org
+with numerous suggestions from
+.An Jordan K. Hubbard Aq jkh@FreeBSD.org ,
+.An Eugene Skepner Aq eu@qub.com
+and other
+.Fx
+developers.
+It replaces the older
+.Nm ftpio
+library written by
+.An Poul-Henning Kamp Aq phk@FreeBSD.org
+and
+.An Jordan K. Hubbard Aq jkh@FreeBSD.org .
+.Pp
+This manual page was written by
+.An Dag-Erling Sm\(/orgrav Aq des@FreeBSD.org .
+.Sh BUGS
+Some parts of the library are not yet implemented.
+The most notable
+examples of this are
+.Fn fetchPutHTTP
+and FTP proxy support.
+.Pp
+There is no way to select a proxy at run-time other than setting the
+.Ev HTTP_PROXY
+or
+.Ev FTP_PROXY
+environment variables as appropriate.
+.Pp
+.Nm libfetch
+does not understand or obey 305 (Use Proxy) replies.
+.Pp
+Error numbers are unique only within a certain context; the error
+codes used for FTP and HTTP overlap, as do those used for resolver and
+system errors.
+For instance, error code 202 means "Command not
+implemented, superfluous at this site" in an FTP context and
+"Accepted" in an HTTP context.
+.Pp
+.Fn fetchStatFTP
+does not check that the result of an MDTM command is a valid date.
+.Pp
+The man page is incomplete, poorly written and produces badly
+formatted text.
+.Pp
+The error reporting mechanism is unsatisfactory.
+.Pp
+Some parts of the code are not fully reentrant.
diff --git a/lib/libfetch/fetch.c b/lib/libfetch/fetch.c
new file mode 100644 (file)
index 0000000..b18bb2c
--- /dev/null
@@ -0,0 +1,627 @@
+/*     $NetBSD: fetch.c,v 1.19 2009/08/11 20:48:06 joerg Exp $ */
+/*-
+ * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2008 Joerg Sonnenberger <joerg@NetBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: fetch.c,v 1.41 2007/12/19 00:26:36 des Exp $
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fetch.h"
+#include "common.h"
+
+auth_t  fetchAuthMethod;
+int     fetchLastErrCode;
+char    fetchLastErrString[MAXERRSTRING];
+int     fetchTimeout;
+volatile int    fetchRestartCalls = 1;
+int     fetchDebug;
+
+
+/*** Local data **************************************************************/
+
+/*
+ * Error messages for parser errors
+ */
+#define URL_MALFORMED          1
+#define URL_BAD_SCHEME         2
+#define URL_BAD_PORT           3
+static struct fetcherr url_errlist[] = {
+       { URL_MALFORMED,        FETCH_URL,      "Malformed URL" },
+       { URL_BAD_SCHEME,       FETCH_URL,      "Invalid URL scheme" },
+       { URL_BAD_PORT,         FETCH_URL,      "Invalid server port" },
+       { -1,                   FETCH_UNKNOWN,  "Unknown parser error" }
+};
+
+
+/*** Public API **************************************************************/
+
+/*
+ * Select the appropriate protocol for the URL scheme, and return a
+ * read-only stream connected to the document referenced by the URL.
+ * Also fill out the struct url_stat.
+ */
+fetchIO *
+fetchXGet(struct url *URL, struct url_stat *us, const char *flags)
+{
+
+       if (us != NULL) {
+               us->size = -1;
+               us->atime = us->mtime = 0;
+       }
+       if (strcasecmp(URL->scheme, SCHEME_FILE) == 0)
+               return (fetchXGetFile(URL, us, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0)
+               return (fetchXGetFTP(URL, us, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_HTTP) == 0)
+               return (fetchXGetHTTP(URL, us, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0)
+               return (fetchXGetHTTP(URL, us, flags));
+       url_seterr(URL_BAD_SCHEME);
+       return (NULL);
+}
+
+/*
+ * Select the appropriate protocol for the URL scheme, and return a
+ * read-only stream connected to the document referenced by the URL.
+ */
+fetchIO *
+fetchGet(struct url *URL, const char *flags)
+{
+       return (fetchXGet(URL, NULL, flags));
+}
+
+/*
+ * Select the appropriate protocol for the URL scheme, and return a
+ * write-only stream connected to the document referenced by the URL.
+ */
+fetchIO *
+fetchPut(struct url *URL, const char *flags)
+{
+
+       if (strcasecmp(URL->scheme, SCHEME_FILE) == 0)
+               return (fetchPutFile(URL, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0)
+               return (fetchPutFTP(URL, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_HTTP) == 0)
+               return (fetchPutHTTP(URL, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0)
+               return (fetchPutHTTP(URL, flags));
+       url_seterr(URL_BAD_SCHEME);
+       return (NULL);
+}
+
+/*
+ * Select the appropriate protocol for the URL scheme, and return the
+ * size of the document referenced by the URL if it exists.
+ */
+int
+fetchStat(struct url *URL, struct url_stat *us, const char *flags)
+{
+
+       if (us != NULL) {
+               us->size = -1;
+               us->atime = us->mtime = 0;
+       }
+       if (strcasecmp(URL->scheme, SCHEME_FILE) == 0)
+               return (fetchStatFile(URL, us, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0)
+               return (fetchStatFTP(URL, us, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_HTTP) == 0)
+               return (fetchStatHTTP(URL, us, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0)
+               return (fetchStatHTTP(URL, us, flags));
+       url_seterr(URL_BAD_SCHEME);
+       return (-1);
+}
+
+/*
+ * Select the appropriate protocol for the URL scheme, and return a
+ * list of files in the directory pointed to by the URL.
+ */
+int
+fetchList(struct url_list *ue, struct url *URL, const char *pattern,
+    const char *flags)
+{
+
+       if (strcasecmp(URL->scheme, SCHEME_FILE) == 0)
+               return (fetchListFile(ue, URL, pattern, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0)
+               return (fetchListFTP(ue, URL, pattern, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_HTTP) == 0)
+               return (fetchListHTTP(ue, URL, pattern, flags));
+       else if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0)
+               return (fetchListHTTP(ue, URL, pattern, flags));
+       url_seterr(URL_BAD_SCHEME);
+       return -1;
+}
+
+/*
+ * Attempt to parse the given URL; if successful, call fetchXGet().
+ */
+fetchIO *
+fetchXGetURL(const char *URL, struct url_stat *us, const char *flags)
+{
+       struct url *u;
+       fetchIO *f;
+
+       if ((u = fetchParseURL(URL)) == NULL)
+               return (NULL);
+
+       f = fetchXGet(u, us, flags);
+
+       fetchFreeURL(u);
+       return (f);
+}
+
+/*
+ * Attempt to parse the given URL; if successful, call fetchGet().
+ */
+fetchIO *
+fetchGetURL(const char *URL, const char *flags)
+{
+       return (fetchXGetURL(URL, NULL, flags));
+}
+
+/*
+ * Attempt to parse the given URL; if successful, call fetchPut().
+ */
+fetchIO *
+fetchPutURL(const char *URL, const char *flags)
+{
+       struct url *u;
+       fetchIO *f;
+
+       if ((u = fetchParseURL(URL)) == NULL)
+               return (NULL);
+
+       f = fetchPut(u, flags);
+
+       fetchFreeURL(u);
+       return (f);
+}
+
+/*
+ * Attempt to parse the given URL; if successful, call fetchStat().
+ */
+int
+fetchStatURL(const char *URL, struct url_stat *us, const char *flags)
+{
+       struct url *u;
+       int s;
+
+       if ((u = fetchParseURL(URL)) == NULL)
+               return (-1);
+
+       s = fetchStat(u, us, flags);
+
+       fetchFreeURL(u);
+       return (s);
+}
+
+/*
+ * Attempt to parse the given URL; if successful, call fetchList().
+ */
+int
+fetchListURL(struct url_list *ue, const char *URL, const char *pattern,
+    const char *flags)
+{
+       struct url *u;
+       int rv;
+
+       if ((u = fetchParseURL(URL)) == NULL)
+               return -1;
+
+       rv = fetchList(ue, u, pattern, flags);
+
+       fetchFreeURL(u);
+       return rv;
+}
+
+/*
+ * Make a URL
+ */
+struct url *
+fetchMakeURL(const char *scheme, const char *host, int port, const char *doc,
+    const char *user, const char *pwd)
+{
+       struct url *u;
+
+       if (!scheme || (!host && !doc)) {
+               url_seterr(URL_MALFORMED);
+               return (NULL);
+       }
+
+       if (port < 0 || port > 65535) {
+               url_seterr(URL_BAD_PORT);
+               return (NULL);
+       }
+
+       /* allocate struct url */
+       if ((u = calloc(1, sizeof(*u))) == NULL) {
+               fetch_syserr();
+               return (NULL);
+       }
+
+       if ((u->doc = strdup(doc ? doc : "/")) == NULL) {
+               fetch_syserr();
+               free(u);
+               return (NULL);
+       }
+
+#define seturl(x) snprintf(u->x, sizeof(u->x), "%s", x)
+       seturl(scheme);
+       seturl(host);
+       seturl(user);
+       seturl(pwd);
+#undef seturl
+       u->port = port;
+
+       return (u);
+}
+
+int
+fetch_urlpath_safe(char x)
+{
+       if ((x >= '0' && x <= '9') || (x >= 'A' && x <= 'Z') ||
+           (x >= 'a' && x <= 'z'))
+               return 1;
+
+       switch (x) {
+       case '$':
+       case '-':
+       case '_':
+       case '.':
+       case '+':
+       case '!':
+       case '*':
+       case '\'':
+       case '(':
+       case ')':
+       case ',':
+       /* The following are allowed in segment and path components: */
+       case '?':
+       case ':':
+       case '@':
+       case '&':
+       case '=':
+       case '/':
+       case ';':
+       /* If something is already quoted... */
+       case '%':
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+/*
+ * Copy an existing URL.
+ */
+struct url *
+fetchCopyURL(const struct url *src)
+{
+       struct url *dst;
+       char *doc;
+
+       /* allocate struct url */
+       if ((dst = malloc(sizeof(*dst))) == NULL) {
+               fetch_syserr();
+               return (NULL);
+       }
+       if ((doc = strdup(src->doc)) == NULL) {
+               fetch_syserr();
+               free(dst);
+               return (NULL);
+       }
+       *dst = *src;
+       dst->doc = doc;
+
+       return dst;
+}
+
+/*
+ * Split an URL into components. URL syntax is:
+ * [method:/][/[user[:pwd]@]host[:port]/][document]
+ * This almost, but not quite, RFC1738 URL syntax.
+ */
+struct url *
+fetchParseURL(const char *URL)
+{
+       const char *p, *q;
+       struct url *u;
+       size_t i, count;
+       int pre_quoted;
+
+       /* allocate struct url */
+       if ((u = calloc(1, sizeof(*u))) == NULL) {
+               fetch_syserr();
+               return (NULL);
+       }
+
+       if (*URL == '/') {
+               pre_quoted = 0;
+               strcpy(u->scheme, SCHEME_FILE);
+               p = URL;
+               goto quote_doc;
+       }
+       if (strncmp(URL, "file:", 5) == 0) {
+               pre_quoted = 1;
+               strcpy(u->scheme, SCHEME_FILE);
+               URL += 5;
+               if (URL[0] != '/' || URL[1] != '/' || URL[2] != '/') {
+                       url_seterr(URL_MALFORMED);
+                       goto ouch;
+               }
+               p = URL + 2;
+               goto quote_doc;
+       }
+       if (strncmp(URL, "http:", 5) == 0 ||
+           strncmp(URL, "https:", 6) == 0) {
+               pre_quoted = 1;
+               if (URL[4] == ':') {
+                       strcpy(u->scheme, SCHEME_HTTP);
+                       URL += 5;
+               } else {
+                       strcpy(u->scheme, SCHEME_HTTPS);
+                       URL += 6;
+               }
+
+               if (URL[0] != '/' || URL[1] != '/') {
+                       url_seterr(URL_MALFORMED);
+                       goto ouch;
+               }
+               URL += 2;
+               p = URL;
+               goto find_user;
+       }
+       if (strncmp(URL, "ftp:", 4) == 0) {
+               pre_quoted = 1;
+               strcpy(u->scheme, SCHEME_FTP);
+               URL += 4;
+               if (URL[0] != '/' || URL[1] != '/') {
+                       url_seterr(URL_MALFORMED);
+                       goto ouch;
+               }
+               URL += 2;
+               p = URL;
+               goto find_user;                 
+       }
+
+       url_seterr(URL_BAD_SCHEME);
+       goto ouch;
+
+find_user:
+       p = strpbrk(URL, "/@");
+       if (p != NULL && *p == '@') {
+               /* username */
+               for (q = URL, i = 0; (*q != ':') && (*q != '@'); q++) {
+                       if (i < URL_USERLEN)
+                               u->user[i++] = *q;
+               }
+
+               /* password */
+               if (*q == ':') {
+                       for (q++, i = 0; (*q != '@'); q++)
+                               if (i < URL_PWDLEN)
+                                       u->pwd[i++] = *q;
+               }
+
+               p++;
+       } else {
+               p = URL;
+       }
+
+       /* hostname */
+#ifdef INET6
+       if (*p == '[' && (q = strchr(p + 1, ']')) != NULL &&
+           (*++q == '\0' || *q == '/' || *q == ':')) {
+               if ((i = q - p - 2) > URL_HOSTLEN)
+                       i = URL_HOSTLEN;
+               strncpy(u->host, ++p, i);
+               p = q;
+       } else
+#endif
+               for (i = 0; *p && (*p != '/') && (*p != ':'); p++)
+                       if (i < URL_HOSTLEN)
+                               u->host[i++] = *p;
+
+       /* port */
+       if (*p == ':') {
+               for (q = ++p; *q && (*q != '/'); q++)
+                       if (isdigit((unsigned char)*q))
+                               u->port = u->port * 10 + (*q - '0');
+                       else {
+                               /* invalid port */
+                               url_seterr(URL_BAD_PORT);
+                               goto ouch;
+                       }
+               p = q;
+       }
+
+       /* document */
+       if (!*p)
+               p = "/";
+
+quote_doc:
+       count = 1;
+       for (i = 0; p[i] != '\0'; ++i) {
+               if ((!pre_quoted && p[i] == '%') ||
+                   !fetch_urlpath_safe(p[i]))
+                       count += 3;
+               else
+                       ++count;
+       }
+
+       if ((u->doc = malloc(count)) == NULL) {
+               fetch_syserr();
+               goto ouch;
+       }
+       for (i = 0; *p != '\0'; ++p) {
+               if ((!pre_quoted && *p == '%') ||
+                   !fetch_urlpath_safe(*p)) {
+                       u->doc[i++] = '%';
+                       if ((unsigned char)*p < 160)
+                               u->doc[i++] = '0' + ((unsigned char)*p) / 16;
+                       else
+                               u->doc[i++] = 'a' - 10 + ((unsigned char)*p) / 16;
+                       if ((unsigned char)*p % 16 < 10)
+                               u->doc[i++] = '0' + ((unsigned char)*p) % 16;
+                       else
+                               u->doc[i++] = 'a' - 10 + ((unsigned char)*p) % 16;
+               } else
+                       u->doc[i++] = *p;
+       }
+       u->doc[i] = '\0';
+
+       return (u);
+
+ouch:
+       free(u);
+       return (NULL);
+}
+
+/*
+ * Free a URL
+ */
+void
+fetchFreeURL(struct url *u)
+{
+       free(u->doc);
+       free(u);
+}
+
+static char
+xdigit2digit(char digit)
+{
+       digit = tolower((unsigned char)digit);
+       if (digit >= 'a' && digit <= 'f')
+               digit = digit - 'a' + 10;
+       else
+               digit = digit - '0';
+
+       return digit;
+}
+
+/*
+ * Unquote whole URL.
+ * Skips optional parts like query or fragment identifier.
+ */ 
+char *
+fetchUnquotePath(struct url *url)
+{
+       char *unquoted;
+       const char *iter;
+       size_t i;
+
+       if ((unquoted = malloc(strlen(url->doc) + 1)) == NULL)
+               return NULL;
+
+       for (i = 0, iter = url->doc; *iter != '\0'; ++iter) {
+               if (*iter == '#' || *iter == '?')
+                       break;
+               if (iter[0] != '%' ||
+                   !isxdigit((unsigned char)iter[1]) ||
+                   !isxdigit((unsigned char)iter[2])) {
+                       unquoted[i++] = *iter;
+                       continue;
+               }
+               unquoted[i++] = xdigit2digit(iter[1]) * 16 +
+                   xdigit2digit(iter[2]);
+               iter += 2;
+       }
+       unquoted[i] = '\0';
+       return unquoted;
+}
+
+
+/*
+ * Extract the file name component of a URL.
+ */
+char *
+fetchUnquoteFilename(struct url *url)
+{
+       char *unquoted, *filename;
+       const char *last_slash;
+
+       if ((unquoted = fetchUnquotePath(url)) == NULL)
+               return NULL;
+
+       if ((last_slash = strrchr(unquoted, '/')) == NULL)
+               return unquoted;
+       filename = strdup(last_slash + 1);
+       free(unquoted);
+       return filename;
+}
+
+char *
+fetchStringifyURL(const struct url *url)
+{
+       size_t total;
+       char *doc;
+
+       /* scheme :// user : pwd @ host :port doc */
+       total = strlen(url->scheme) + 3 + strlen(url->user) + 1 +
+           strlen(url->pwd) + 1 + strlen(url->host) + 6 + strlen(url->doc) + 1;
+       if ((doc = malloc(total)) == NULL)
+               return NULL;
+       if (url->port != 0)
+               snprintf(doc, total, "%s%s%s%s%s%s%s:%d%s",
+                   url->scheme,
+                   url->scheme[0] != '\0' ? "://" : "",
+                   url->user,
+                   url->pwd[0] != '\0' ? ":" : "",
+                   url->pwd,
+                   url->user[0] != '\0' || url->pwd[0] != '\0' ? "@" : "",
+                   url->host,
+                   (int)url->port,
+                   url->doc);
+       else {
+               snprintf(doc, total, "%s%s%s%s%s%s%s%s",
+                   url->scheme,
+                   url->scheme[0] != '\0' ? "://" : "",
+                   url->user,
+                   url->pwd[0] != '\0' ? ":" : "",
+                   url->pwd,
+                   url->user[0] != '\0' || url->pwd[0] != '\0' ? "@" : "",
+                   url->host,
+                   url->doc);
+       }
+       return doc;
+}
diff --git a/lib/libfetch/fetch.cat3 b/lib/libfetch/fetch.cat3
new file mode 100644 (file)
index 0000000..f112d6a
--- /dev/null
@@ -0,0 +1,526 @@
+FETCH(3)                NetBSD Library Functions Manual               FETCH(3)
+
+N\bNA\bAM\bME\bE
+     f\bfe\bet\btc\bch\bhM\bMa\bak\bke\beU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhP\bPa\bar\brs\bse\beU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhC\bCo\bop\bpy\byU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhF\bFr\bre\bee\beU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btU\bUR\bRL\bL,
+     f\bfe\bet\btc\bch\bhG\bGe\bet\btU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhP\bPu\but\btU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\bt,
+     f\bfe\bet\btc\bch\bhG\bGe\bet\bt, f\bfe\bet\btc\bch\bhP\bPu\but\bt, f\bfe\bet\btc\bch\bhS\bSt\bta\bat\bt, f\bfe\bet\btc\bch\bhL\bLi\bis\bst\bt, f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btF\bFi\bil\ble\be, f\bfe\bet\btc\bch\bhG\bGe\bet\btF\bFi\bil\ble\be,
+     f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFi\bil\ble\be, f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btF\bFi\bil\ble\be, f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btF\bFi\bil\ble\be, f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btH\bHT\bTT\bTP\bP, f\bfe\bet\btc\bch\bhG\bGe\bet\btH\bHT\bTT\bTP\bP,
+     f\bfe\bet\btc\bch\bhP\bPu\but\btH\bHT\bTT\bTP\bP, f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btH\bHT\bTT\bTP\bP, f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btH\bHT\bTT\bTP\bP, f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btF\bFT\bTP\bP, f\bfe\bet\btc\bch\bhG\bGe\bet\btF\bFT\bTP\bP,
+     f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFT\bTP\bP, f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btF\bFT\bTP\bP, f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btF\bFT\bTP\bP f\bfe\bet\btc\bch\bhI\bIn\bni\bit\btU\bUR\bRL\bLL\bLi\bis\bst\bt,
+     f\bfe\bet\btc\bch\bhF\bFr\bre\bee\beU\bUR\bRL\bLL\bLi\bis\bst\bt, f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beP\bPa\bat\bth\bh, f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beF\bFi\bil\ble\ben\bna\bam\bme\be,
+     f\bfe\bet\btc\bch\bhS\bSt\btr\bri\bin\bng\bgi\bif\bfy\byU\bUR\bRL\bL, f\bfe\bet\btc\bch\bhC\bCo\bon\bnn\bne\bec\bct\bti\bio\bon\bnC\bCa\bac\bch\bhe\beI\bIn\bni\bit\bt, f\bfe\bet\btc\bch\bhC\bCo\bon\bnn\bne\bec\bct\bti\bio\bon\bnC\bCa\bac\bch\bhe\beC\bCl\blo\bos\bse\be,
+     f\bfe\bet\btc\bch\bh -- file transfer functions
+
+L\bLI\bIB\bBR\bRA\bAR\bRY\bY
+               File Transfer Library for URLs (libfetch, -lfetch)
+
+S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS
+     #\b#i\bin\bnc\bcl\blu\bud\bde\be <\b<s\bst\btd\bdi\bio\bo.\b.h\bh>\b>
+     #\b#i\bin\bnc\bcl\blu\bud\bde\be <\b<f\bfe\bet\btc\bch\bh.\b.h\bh>\b>
+
+     _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*
+     f\bfe\bet\btc\bch\bhM\bMa\bak\bke\beU\bUR\bRL\bL(_\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bs_\bc_\bh_\be_\bm_\be, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bh_\bo_\bs_\bt, _\bi_\bn_\bt _\bp_\bo_\br_\bt,
+         _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bd_\bo_\bc, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bu_\bs_\be_\br, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bp_\bw_\bd);
+
+     _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*
+     f\bfe\bet\btc\bch\bhP\bPa\bar\brs\bse\beU\bUR\bRL\bL(_\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bU_\bR_\bL);
+
+     _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*
+     f\bfe\bet\btc\bch\bhC\bCo\bop\bpy\byU\bUR\bRL\bL(_\bc_\bo_\bn_\bs_\bt _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu);
+
+     _\bv_\bo_\bi_\bd
+     f\bfe\bet\btc\bch\bhF\bFr\bre\bee\beU\bUR\bRL\bL(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btU\bUR\bRL\bL(_\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bU_\bR_\bL, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhG\bGe\bet\btU\bUR\bRL\bL(_\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bU_\bR_\bL, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhP\bPu\but\btU\bUR\bRL\bL(_\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bU_\bR_\bL, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btU\bUR\bRL\bL(_\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bU_\bR_\bL, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btU\bUR\bRL\bL(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bl_\bi_\bs_\bt, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bU_\bR_\bL, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\bt(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhG\bGe\bet\bt(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhP\bPu\but\bt(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhS\bSt\bta\bat\bt(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhL\bLi\bis\bst\bt(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bl_\bi_\bs_\bt, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btF\bFi\bil\ble\be(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhG\bGe\bet\btF\bFi\bil\ble\be(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFi\bil\ble\be(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btF\bFi\bil\ble\be(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btF\bFi\bil\ble\be(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bl_\bi_\bs_\bt, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btH\bHT\bTT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhG\bGe\bet\btH\bHT\bTT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhP\bPu\but\btH\bHT\bTT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btH\bHT\bTT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btH\bHT\bTT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bl_\bi_\bs_\bt, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btF\bFT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhG\bGe\bet\btF\bFT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bf_\be_\bt_\bc_\bh_\bI_\bO _\b*
+     f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btF\bFT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt _\b*_\bu_\bs, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btF\bFT\bTP\bP(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bl_\bi_\bs_\bt, _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu, _\bc_\bo_\bn_\bs_\bt _\bc_\bh_\ba_\br _\b*_\bf_\bl_\ba_\bg_\bs);
+
+     _\bv_\bo_\bi_\bd
+     f\bfe\bet\btc\bch\bhI\bIn\bni\bit\btU\bUR\bRL\bLL\bLi\bis\bst\bt(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bu_\bl);
+
+     _\bi_\bn_\bt
+     f\bfe\bet\btc\bch\bhA\bAp\bpp\bpe\ben\bnd\bdU\bUR\bRL\bLL\bLi\bis\bst\bt(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bd_\bs_\bt, _\bc_\bo_\bn_\bs_\bt _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bs_\br_\bc);
+
+     _\bv_\bo_\bi_\bd
+     f\bfe\bet\btc\bch\bhF\bFr\bre\bee\beU\bUR\bRL\bLL\bLi\bis\bst\bt(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt _\b*_\bu_\bl);
+
+     _\bc_\bh_\ba_\br _\b*
+     f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beP\bPa\bat\bth\bh(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu);
+
+     _\bc_\bh_\ba_\br _\b*
+     f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beF\bFi\bil\ble\ben\bna\bam\bme\be(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu);
+
+     _\bc_\bh_\ba_\br _\b*
+     f\bfe\bet\btc\bch\bhS\bSt\btr\bri\bin\bng\bgi\bif\bfy\byU\bUR\bRL\bL(_\bc_\bo_\bn_\bs_\bt _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu);
+
+     _\bv_\bo_\bi_\bd
+     f\bfe\bet\btc\bch\bhC\bCo\bon\bnn\bne\bec\bct\bti\bio\bon\bnC\bCa\bac\bch\bhe\beI\bIn\bni\bit\bt(_\bi_\bn_\bt _\bg_\bl_\bo_\bb_\ba_\bl, _\bi_\bn_\bt _\bp_\be_\br_\b__\bh_\bo_\bs_\bt);
+
+     _\bv_\bo_\bi_\bd
+     f\bfe\bet\btc\bch\bhC\bCo\bon\bnn\bne\bec\bct\bti\bio\bon\bnC\bCa\bac\bch\bhe\beC\bCl\blo\bos\bse\be(_\bv_\bo_\bi_\bd);
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     These functions implement a high-level library for retrieving and upload-
+     ing files using Uniform Resource Locators (URLs).
+
+     f\bfe\bet\btc\bch\bhP\bPa\bar\brs\bse\beU\bUR\bRL\bL() takes a URL in the form of a null-terminated string and
+     splits it into its components function according to the Common Internet
+     Scheme Syntax detailed in RFC 1738.  A regular expression which produces
+     this syntax is:
+
+           <scheme>:(//(<user>(:<pwd>)?@)?<host>(:<port>)?)?/(<document>)?
+
+     If the URL does not seem to begin with a scheme name, it is assumed to be
+     a local path.  Only absolute path names are accepted.
+
+     Note that some components of the URL are not necessarily relevant to all
+     URL schemes.  For instance, the file scheme only needs the <scheme> and
+     <document> components.  f\bfe\bet\btc\bch\bhP\bPa\bar\brs\bse\beU\bUR\bRL\bL() quotes any unsafe character in
+     the URL automatically.  This is not done by f\bfe\bet\btc\bch\bhM\bMa\bak\bke\beU\bUR\bRL\bL().
+     f\bfe\bet\btc\bch\bhC\bCo\bop\bpy\byU\bUR\bRL\bL() copies an existing _\bu_\br_\bl structure.
+
+     f\bfe\bet\btc\bch\bhM\bMa\bak\bke\beU\bUR\bRL\bL(), f\bfe\bet\btc\bch\bhP\bPa\bar\brs\bse\beU\bUR\bRL\bL(), and f\bfe\bet\btc\bch\bhC\bCo\bop\bpy\byU\bUR\bRL\bL() return a pointer to a
+     _\bu_\br_\bl structure, which is defined as follows in <_\bf_\be_\bt_\bc_\bh_\b._\bh>:
+
+     #define URL_SCHEMELEN 16
+     #define URL_USERLEN 256
+     #define URL_PWDLEN 256
+     #define URL_HOSTLEN 255
+
+     struct url {
+         char         scheme[URL_SCHEMELEN + 1];
+         char         user[URL_USERLEN + 1];
+         char         pwd[URL_PWDLEN + 1];
+         char         host[URL_HOSTLEN + 1];
+         int          port;
+         char        *doc;
+         off_t        offset;
+         size_t       length;
+         time_t       last_modified;
+     };
+
+     The pointer returned by f\bfe\bet\btc\bch\bhM\bMa\bak\bke\beU\bUR\bRL\bL(), f\bfe\bet\btc\bch\bhC\bCo\bop\bpy\byU\bUR\bRL\bL(), and
+     f\bfe\bet\btc\bch\bhP\bPa\bar\brs\bse\beU\bUR\bRL\bL() should be freed using f\bfe\bet\btc\bch\bhF\bFr\bre\bee\beU\bUR\bRL\bL().  The size of _\bs_\bt_\br_\bu_\bc_\bt
+     _\bU_\bR_\bL is not part of the ABI.
+
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btU\bUR\bRL\bL(), f\bfe\bet\btc\bch\bhG\bGe\bet\btU\bUR\bRL\bL(), and f\bfe\bet\btc\bch\bhP\bPu\but\btU\bUR\bRL\bL() constitute the recom-
+     mended interface to the f\bfe\bet\btc\bch\bh library.  They examine the URL passed to
+     them to determine the transfer method, and call the appropriate lower-
+     level functions to perform the actual transfer.  f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btU\bUR\bRL\bL() also
+     returns the remote document's metadata in the _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt structure pointed
+     to by the _\bu_\bs argument.
+
+     The _\bf_\bl_\ba_\bg_\bs argument is a string of characters which specify transfer
+     options.  The meaning of the individual flags is scheme-dependent, and is
+     detailed in the appropriate section below.
+
+     f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btU\bUR\bRL\bL() attempts to obtain the requested document's metadata and
+     fill in the structure pointed to by its second argument.  The _\bu_\br_\bl_\b__\bs_\bt_\ba_\bt
+     structure is defined as follows in <_\bf_\be_\bt_\bc_\bh_\b._\bh>:
+
+     struct url_stat {
+         off_t        size;
+         time_t       atime;
+         time_t       mtime;
+     };
+
+     If the size could not be obtained from the server, the _\bs_\bi_\bz_\be field is set
+     to -1.  If the modification time could not be obtained from the server,
+     the _\bm_\bt_\bi_\bm_\be field is set to the epoch.  If the access time could not be
+     obtained from the server, the _\ba_\bt_\bi_\bm_\be field is set to the modification
+     time.
+
+     f\bfe\bet\btc\bch\bhL\bLi\bis\bst\btU\bUR\bRL\bL() attempts to list the contents of the directory pointed to
+     by the URL provided.  The pattern can be a simple glob-like expression as
+     hint.  Callers should not depend on the server to filter names.  If suc-
+     cessful, it appends the list of entries to the _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt structure.  The
+     _\bu_\br_\bl_\b__\bl_\bi_\bs_\bt structure is defined as follows in <_\bf_\be_\bt_\bc_\bh_\b._\bh>:
+
+     struct url_list {
+         size_t      length;
+         size_t      alloc_size;
+         struct url  *urls;
+     };
+
+     The list should be initialized by calling f\bfe\bet\btc\bch\bhI\bIn\bni\bit\btU\bUR\bRL\bLL\bLi\bis\bst\bt() and the
+     entries be freed by calling f\bfe\bet\btc\bch\bhF\bFr\bre\bee\beU\bUR\bRL\bLL\bLi\bis\bst\bt().  The function
+     f\bfe\bet\btc\bch\bhA\bAp\bpp\bpe\ben\bnd\bdU\bUR\bRL\bLL\bLi\bis\bst\bt() can be used to append one URL lists to another.  If
+     the `c' (cache result) flag is specified, the library is allowed to
+     internally cache the result.
+
+     f\bfe\bet\btc\bch\bhS\bSt\btr\bri\bin\bng\bgi\bif\bfy\byU\bUR\bRL\bL() returns the URL as string.  f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beP\bPa\bat\bth\bh()
+     returns the path name part of the URL with any quoting undone.  Query
+     arguments and fragment identifiers are not included.
+     f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beF\bFi\bil\ble\ben\bna\bam\bme\be() returns the last component of the path name as
+     returned by f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beP\bPa\bat\bth\bh().  f\bfe\bet\btc\bch\bhS\bSt\btr\bri\bin\bng\bgi\bif\bfy\byU\bUR\bRL\bL(), f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beP\bPa\bat\bth\bh(),
+     and f\bfe\bet\btc\bch\bhU\bUn\bnq\bqu\buo\bot\bte\beF\bFi\bil\ble\ben\bna\bam\bme\be() return a string that should be deallocated
+     with f\bfr\bre\bee\be() after use.
+
+     f\bfe\bet\btc\bch\bhC\bCo\bon\bnn\bne\bec\bct\bti\bio\bon\bnC\bCa\bac\bch\bhe\beI\bIn\bni\bit\bt() enables the connection cache.  The first argu-
+     ment specifies the global limit on cached connections.  The second argu-
+     ment specifies the host limit.  Entries are considered to specify the
+     same host, if the host name from the URL is identical, indepent of the
+     address or address family.  f\bfe\bet\btc\bch\bhC\bCo\bon\bnn\bne\bec\bct\bti\bio\bon\bnC\bCa\bac\bch\bhe\beC\bCl\blo\bos\bse\be() flushed the con-
+     nection cache and closes all cached connections.
+
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\bt(), f\bfe\bet\btc\bch\bhG\bGe\bet\bt(), f\bfe\bet\btc\bch\bhP\bPu\but\bt(), and f\bfe\bet\btc\bch\bhS\bSt\bta\bat\bt() are similar to
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btU\bUR\bRL\bL(), f\bfe\bet\btc\bch\bhG\bGe\bet\btU\bUR\bRL\bL(), f\bfe\bet\btc\bch\bhP\bPu\but\btU\bUR\bRL\bL(), and f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btU\bUR\bRL\bL(), except
+     that they expect a pre-parsed URL in the form of a pointer to a _\bs_\bt_\br_\bu_\bc_\bt
+     _\bu_\br_\bl rather than a string.
+
+     All of the f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btX\bXX\bXX\bX(), f\bfe\bet\btc\bch\bhG\bGe\bet\btX\bXX\bXX\bX(), and f\bfe\bet\btc\bch\bhP\bPu\but\btX\bXX\bXX\bX() functions
+     return a pointer to a stream which can be used to read or write data from
+     or to the requested document, respectively.  Note that although the
+     implementation details of the individual access methods vary, it can gen-
+     erally be assumed that a stream returned by one of the f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btX\bXX\bXX\bX() or
+     f\bfe\bet\btc\bch\bhG\bGe\bet\btX\bXX\bXX\bX() functions is read-only, and that a stream returned by one
+     of the f\bfe\bet\btc\bch\bhP\bPu\but\btX\bXX\bXX\bX() functions is write-only.
+
+P\bPR\bRO\bOT\bTO\bOC\bCO\bOL\bL I\bIN\bND\bDE\bEP\bPE\bEN\bND\bDE\bEN\bNT\bT F\bFL\bLA\bAG\bGS\bS
+     If the `i' (if-modified-since) flag is specified, the library will try to
+     fetch the content only if it is newer than _\bl_\ba_\bs_\bt_\b__\bm_\bo_\bd_\bi_\bf_\bi_\be_\bd.  For HTTP an
+     If-Modified-Since HTTP header is sent.  For FTP a MTDM command is sent
+     first and compared locally.  For FILE the source file is compared.
+
+F\bFI\bIL\bLE\bE S\bSC\bCH\bHE\bEM\bME\bE
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btF\bFi\bil\ble\be(), f\bfe\bet\btc\bch\bhG\bGe\bet\btF\bFi\bil\ble\be(), and f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFi\bil\ble\be() provide access to
+     documents which are files in a locally mounted file system.  Only the
+     <document> component of the URL is used.
+
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btF\bFi\bil\ble\be() and f\bfe\bet\btc\bch\bhG\bGe\bet\btF\bFi\bil\ble\be() do not accept any flags.
+
+     f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFi\bil\ble\be() accepts the `a' (append to file) flag.  If that flag is
+     specified, the data written to the stream returned by f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFi\bil\ble\be() will
+     be appended to the previous contents of the file, instead of replacing
+     them.
+
+F\bFT\bTP\bP S\bSC\bCH\bHE\bEM\bME\bE
+     f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btF\bFT\bTP\bP(), f\bfe\bet\btc\bch\bhG\bGe\bet\btF\bFT\bTP\bP(), and f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFT\bTP\bP() implement the FTP proto-
+     col as described in RFC 959.
+
+     By default l\bli\bib\bbf\bfe\bet\btc\bch\bh will attempt to use passive mode first and only fall-
+     back to active mode if the server reports a syntax error.  If the `a'
+     (active) flag is specified, a passive connection is not tried and active
+     mode is used directly.
+
+     If the `l' (low) flag is specified, data sockets will be allocated in the
+     low (or default) port range instead of the high port range (see ip(4)).
+
+     If the `d' (direct) flag is specified, f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btF\bFT\bTP\bP(), f\bfe\bet\btc\bch\bhG\bGe\bet\btF\bFT\bTP\bP(), and
+     f\bfe\bet\btc\bch\bhP\bPu\but\btF\bFT\bTP\bP() will use a direct connection even if a proxy server is
+     defined.
+
+     If no user name or password is given, the f\bfe\bet\btc\bch\bh library will attempt an
+     anonymous login, with user name "anonymous" and password "anony-
+     mous@<hostname>".
+
+H\bHT\bTT\bTP\bP S\bSC\bCH\bHE\bEM\bME\bE
+     The f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btH\bHT\bTT\bTP\bP(), f\bfe\bet\btc\bch\bhG\bGe\bet\btH\bHT\bTT\bTP\bP(), and f\bfe\bet\btc\bch\bhP\bPu\but\btH\bHT\bTT\bTP\bP() functions imple-
+     ment the HTTP/1.1 protocol.  With a little luck, there is even a chance
+     that they comply with RFC 2616 and RFC 2617.
+
+     If the `d' (direct) flag is specified, f\bfe\bet\btc\bch\bhX\bXG\bGe\bet\btH\bHT\bTT\bTP\bP(), f\bfe\bet\btc\bch\bhG\bGe\bet\btH\bHT\bTT\bTP\bP(),
+     and f\bfe\bet\btc\bch\bhP\bPu\but\btH\bHT\bTT\bTP\bP() will use a direct connection even if a proxy server is
+     defined.
+
+     Since there seems to be no good way of implementing the HTTP PUT method
+     in a manner consistent with the rest of the f\bfe\bet\btc\bch\bh library, f\bfe\bet\btc\bch\bhP\bPu\but\btH\bHT\bTT\bTP\bP()
+     is currently unimplemented.
+
+A\bAU\bUT\bTH\bHE\bEN\bNT\bTI\bIC\bCA\bAT\bTI\bIO\bON\bN
+     Apart from setting the appropriate environment variables and specifying
+     the user name and password in the URL or the _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl, the calling pro-
+     gram has the option of defining an authentication function with the fol-
+     lowing prototype:
+
+     _\bi_\bn_\bt m\bmy\byA\bAu\but\bth\bhM\bMe\bet\bth\bho\bod\bd(_\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl _\b*_\bu)
+
+     The callback function should fill in the _\bu_\bs_\be_\br and _\bp_\bw_\bd fields in the pro-
+     vided _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl and return 0 on success, or any other value to indicate
+     failure.
+
+     To register the authentication callback, simply set _\bf_\be_\bt_\bc_\bh_\bA_\bu_\bt_\bh_\bM_\be_\bt_\bh_\bo_\bd to
+     point at it.  The callback will be used whenever a site requires authen-
+     tication and the appropriate environment variables are not set.
+
+     This interface is experimental and may be subject to change.
+
+R\bRE\bET\bTU\bUR\bRN\bN V\bVA\bAL\bLU\bUE\bES\bS
+     f\bfe\bet\btc\bch\bhP\bPa\bar\brs\bse\beU\bUR\bRL\bL() returns a pointer to a _\bs_\bt_\br_\bu_\bc_\bt _\bu_\br_\bl containing the individ-
+     ual components of the URL.  If it is unable to allocate memory, or the
+     URL is syntactically incorrect, f\bfe\bet\btc\bch\bhP\bPa\bar\brs\bse\beU\bUR\bRL\bL() returns a NULL pointer.
+
+     The f\bfe\bet\btc\bch\bhS\bSt\bta\bat\bt() functions return 0 on success and -1 on failure.
+
+     All other functions return a stream pointer which may be used to access
+     the requested document, or NULL if an error occurred.
+
+     The following error codes are defined in <_\bf_\be_\bt_\bc_\bh_\b._\bh>:
+
+     [FETCH_ABORT]       Operation aborted
+
+     [FETCH_AUTH]        Authentication failed
+
+     [FETCH_DOWN]        Service unavailable
+
+     [FETCH_EXISTS]      File exists
+
+     [FETCH_FULL]        File system full
+
+     [FETCH_INFO]        Informational response
+
+     [FETCH_MEMORY]      Insufficient memory
+
+     [FETCH_MOVED]       File has moved
+
+     [FETCH_NETWORK]     Network error
+
+     [FETCH_OK]          No error
+
+     [FETCH_PROTO]       Protocol error
+
+     [FETCH_RESOLV]      Resolver error
+
+     [FETCH_SERVER]      Server error
+
+     [FETCH_TEMP]        Temporary error
+
+     [FETCH_TIMEOUT]     Operation timed out
+
+     [FETCH_UNAVAIL]     File is not available
+
+     [FETCH_UNKNOWN]     Unknown error
+
+     [FETCH_URL]         Invalid URL
+
+     The accompanying error message includes a protocol-specific error code
+     and message, e.g. "File is not available (404 Not Found)"
+
+E\bEN\bNV\bVI\bIR\bRO\bON\bNM\bME\bEN\bNT\bT
+     FETCH_BIND_ADDRESS  Specifies a host name or IP address to which sockets
+                         used for outgoing connections will be bound.
+
+     FTP_LOGIN           Default FTP login if none was provided in the URL.
+
+     FTP_PASSIVE_MODE    If set to anything but `no', forces the FTP code to
+                         use passive mode.
+
+     FTP_PASSWORD        Default FTP password if the remote server requests
+                         one and none was provided in the URL.
+
+     FTP_PROXY           URL of the proxy to use for FTP requests.  The docu-
+                         ment part is ignored.  FTP and HTTP proxies are sup-
+                         ported; if no scheme is specified, FTP is assumed.
+                         If the proxy is an FTP proxy, l\bli\bib\bbf\bfe\bet\btc\bch\bh will send
+                         `user@host' as user name to the proxy, where `user'
+                         is the real user name, and `host' is the name of the
+                         FTP server.
+
+                         If this variable is set to an empty string, no proxy
+                         will be used for FTP requests, even if the HTTP_PROXY
+                         variable is set.
+
+     ftp_proxy           Same as FTP_PROXY, for compatibility.
+
+     HTTP_AUTH           Specifies HTTP authorization parameters as a colon-
+                         separated list of items.  The first and second item
+                         are the authorization scheme and realm respectively;
+                         further items are scheme-dependent.  Currently, only
+                         basic authorization is supported.
+
+                         Basic authorization requires two parameters: the user
+                         name and password, in that order.
+
+                         This variable is only used if the server requires
+                         authorization and no user name or password was speci-
+                         fied in the URL.
+
+     HTTP_PROXY          URL of the proxy to use for HTTP requests.  The docu-
+                         ment part is ignored.  Only HTTP proxies are sup-
+                         ported for HTTP requests.  If no port number is spec-
+                         ified, the default is 3128.
+
+                         Note that this proxy will also be used for FTP docu-
+                         ments, unless the FTP_PROXY variable is set.
+
+     http_proxy          Same as HTTP_PROXY, for compatibility.
+
+     HTTP_PROXY_AUTH     Specifies authorization parameters for the HTTP proxy
+                         in the same format as the HTTP_AUTH variable.
+
+                         This variable is used if and only if connected to an
+                         HTTP proxy, and is ignored if a user and/or a pass-
+                         word were specified in the proxy URL.
+
+     HTTP_REFERER        Specifies the referrer URL to use for HTTP requests.
+                         If set to ``auto'', the document URL will be used as
+                         referrer URL.
+
+     HTTP_USER_AGENT     Specifies the User-Agent string to use for HTTP
+                         requests.  This can be useful when working with HTTP
+                         origin or proxy servers that differentiate between
+                         user agents.
+
+     NETRC               Specifies a file to use instead of _\b~_\b/_\b._\bn_\be_\bt_\br_\bc to look
+                         up login names and passwords for FTP sites.  See
+                         ftp(1) for a description of the file format.  This
+                         feature is experimental.
+
+     NO_PROXY            Either a single asterisk, which disables the use of
+                         proxies altogether, or a comma- or whitespace-sepa-
+                         rated list of hosts for which proxies should not be
+                         used.
+
+     no_proxy            Same as NO_PROXY, for compatibility.
+
+E\bEX\bXA\bAM\bMP\bPL\bLE\bES\bS
+     To access a proxy server on _\bp_\br_\bo_\bx_\by_\b._\be_\bx_\ba_\bm_\bp_\bl_\be_\b._\bc_\bo_\bm port 8080, set the
+     HTTP_PROXY environment variable in a manner similar to this:
+
+           HTTP_PROXY=http://proxy.example.com:8080
+
+     If the proxy server requires authentication, there are two options avail-
+     able for passing the authentication data.  The first method is by using
+     the proxy URL:
+
+           HTTP_PROXY=http://<user>:<pwd>@proxy.example.com:8080
+
+     The second method is by using the HTTP_PROXY_AUTH environment variable:
+
+           HTTP_PROXY=http://proxy.example.com:8080
+           HTTP_PROXY_AUTH=basic:*:<user>:<pwd>
+
+     To disable the use of a proxy for an HTTP server running on the local
+     host, define NO_PROXY as follows:
+
+           NO_PROXY=localhost,127.0.0.1
+
+S\bSE\bEE\bE A\bAL\bLS\bSO\bO
+     ftp(1), ip(4)
+
+     J. Postel and J. K. Reynolds, _\bF_\bi_\bl_\be _\bT_\br_\ba_\bn_\bs_\bf_\be_\br _\bP_\br_\bo_\bt_\bo_\bc_\bo_\bl, October 1985, RFC
+     959.
+
+     P. Deutsch, A. Emtage, and A. Marine, _\bH_\bo_\bw _\bt_\bo _\bU_\bs_\be _\bA_\bn_\bo_\bn_\by_\bm_\bo_\bu_\bs _\bF_\bT_\bP, May 1994,
+     RFC 1635.
+
+     T. Berners-Lee, L. Masinter, and M. McCahill, _\bU_\bn_\bi_\bf_\bo_\br_\bm _\bR_\be_\bs_\bo_\bu_\br_\bc_\be _\bL_\bo_\bc_\ba_\bt_\bo_\br_\bs
+     _\b(_\bU_\bR_\bL_\b), December 1994, RFC 1738.
+
+     R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, and
+     T. Berners-Lee, _\bH_\by_\bp_\be_\br_\bt_\be_\bx_\bt _\bT_\br_\ba_\bn_\bs_\bf_\be_\br _\bP_\br_\bo_\bt_\bo_\bc_\bo_\bl _\b-_\b- _\bH_\bT_\bT_\bP_\b/_\b1_\b._\b1, January 1999,
+     RFC 2616.
+
+     J. Franks, P. Hallam-Baker, J. Hostetler, S. Lawrence, P. Leach, A.
+     Luotonen, and L. Stewart, _\bH_\bT_\bT_\bP _\bA_\bu_\bt_\bh_\be_\bn_\bt_\bi_\bc_\ba_\bt_\bi_\bo_\bn_\b: _\bB_\ba_\bs_\bi_\bc _\ba_\bn_\bd _\bD_\bi_\bg_\be_\bs_\bt _\bA_\bc_\bc_\be_\bs_\bs
+     _\bA_\bu_\bt_\bh_\be_\bn_\bt_\bi_\bc_\ba_\bt_\bi_\bo_\bn, June 1999, RFC 2617.
+
+H\bHI\bIS\bST\bTO\bOR\bRY\bY
+     The f\bfe\bet\btc\bch\bh library first appeared in FreeBSD 3.0.
+
+A\bAU\bUT\bTH\bHO\bOR\bRS\bS
+     The f\bfe\bet\btc\bch\bh library was mostly written by Dag-Erling Smørgrav
+     <des@FreeBSD.org> with numerous suggestions from Jordan K. Hubbard
+     <jkh@FreeBSD.org>, Eugene Skepner <eu@qub.com> and other FreeBSD develop-
+     ers.  It replaces the older f\bft\btp\bpi\bio\bo library written by Poul-Henning Kamp
+     <phk@FreeBSD.org> and Jordan K. Hubbard <jkh@FreeBSD.org>.
+
+     This manual page was written by Dag-Erling Smørgrav <des@FreeBSD.org>.
+
+B\bBU\bUG\bGS\bS
+     Some parts of the library are not yet implemented.  The most notable
+     examples of this are f\bfe\bet\btc\bch\bhP\bPu\but\btH\bHT\bTT\bTP\bP() and FTP proxy support.
+
+     There is no way to select a proxy at run-time other than setting the
+     HTTP_PROXY or FTP_PROXY environment variables as appropriate.
+
+     l\bli\bib\bbf\bfe\bet\btc\bch\bh does not understand or obey 305 (Use Proxy) replies.
+
+     Error numbers are unique only within a certain context; the error codes
+     used for FTP and HTTP overlap, as do those used for resolver and system
+     errors.  For instance, error code 202 means "Command not implemented,
+     superfluous at this site" in an FTP context and "Accepted" in an HTTP
+     context.
+
+     f\bfe\bet\btc\bch\bhS\bSt\bta\bat\btF\bFT\bTP\bP() does not check that the result of an MDTM command is a
+     valid date.
+
+     The man page is incomplete, poorly written and produces badly formatted
+     text.
+
+     The error reporting mechanism is unsatisfactory.
+
+     Some parts of the code are not fully reentrant.
+
+NetBSD 5.0                     January 22, 2010                     NetBSD 5.0
diff --git a/lib/libfetch/fetch.h b/lib/libfetch/fetch.h
new file mode 100644 (file)
index 0000000..3075486
--- /dev/null
@@ -0,0 +1,187 @@
+/*     $NetBSD: fetch.h,v 1.16 2010/01/22 13:21:09 joerg Exp $ */
+/*-
+ * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: fetch.h,v 1.26 2004/09/21 18:35:20 des Exp $
+ */
+
+#ifndef _FETCH_H_INCLUDED
+#define _FETCH_H_INCLUDED
+
+#include <sys/types.h>
+#include <limits.h>
+#include <stdio.h>
+
+#define _LIBFETCH_VER "libfetch/2.0"
+
+#define URL_HOSTLEN 255
+#define URL_SCHEMELEN 16
+#define URL_USERLEN 256
+#define URL_PWDLEN 256
+
+typedef struct fetchIO fetchIO;
+
+struct url {
+       char             scheme[URL_SCHEMELEN + 1];
+       char             user[URL_USERLEN + 1];
+       char             pwd[URL_PWDLEN + 1];
+       char             host[URL_HOSTLEN + 1];
+       int              port;
+       char            *doc;
+       off_t            offset;
+       size_t           length;
+       time_t           last_modified;
+};
+
+struct url_stat {
+       off_t            size;
+       time_t           atime;
+       time_t           mtime;
+};
+
+struct url_list {
+       size_t           length;
+       size_t           alloc_size;
+       struct url      *urls;
+};
+
+/* Recognized schemes */
+#define SCHEME_FTP     "ftp"
+#define SCHEME_HTTP    "http"
+#define SCHEME_HTTPS   "https"
+#define SCHEME_FILE    "file"
+
+/* Error codes */
+#define        FETCH_ABORT      1
+#define        FETCH_AUTH       2
+#define        FETCH_DOWN       3
+#define        FETCH_EXISTS     4
+#define        FETCH_FULL       5
+#define        FETCH_INFO       6
+#define        FETCH_MEMORY     7
+#define        FETCH_MOVED      8
+#define        FETCH_NETWORK    9
+#define        FETCH_OK        10
+#define        FETCH_PROTO     11
+#define        FETCH_RESOLV    12
+#define        FETCH_SERVER    13
+#define        FETCH_TEMP      14
+#define        FETCH_TIMEOUT   15
+#define        FETCH_UNAVAIL   16
+#define        FETCH_UNKNOWN   17
+#define        FETCH_URL       18
+#define        FETCH_VERBOSE   19
+#define        FETCH_UNCHANGED 20
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void           fetchIO_close(fetchIO *);
+ssize_t                fetchIO_read(fetchIO *, void *, size_t);
+ssize_t                fetchIO_write(fetchIO *, const void *, size_t);
+
+/* fetchIO-specific functions */
+fetchIO                *fetchXGetFile(struct url *, struct url_stat *, const char *);
+fetchIO                *fetchGetFile(struct url *, const char *);
+fetchIO                *fetchPutFile(struct url *, const char *);
+int             fetchStatFile(struct url *, struct url_stat *, const char *);
+int             fetchListFile(struct url_list *, struct url *, const char *,
+                   const char *);
+
+/* HTTP-specific functions */
+fetchIO                *fetchXGetHTTP(struct url *, struct url_stat *, const char *);
+fetchIO                *fetchGetHTTP(struct url *, const char *);
+fetchIO                *fetchPutHTTP(struct url *, const char *);
+int             fetchStatHTTP(struct url *, struct url_stat *, const char *);
+int             fetchListHTTP(struct url_list *, struct url *, const char *,
+                   const char *);
+
+/* FTP-specific functions */
+fetchIO                *fetchXGetFTP(struct url *, struct url_stat *, const char *);
+fetchIO                *fetchGetFTP(struct url *, const char *);
+fetchIO                *fetchPutFTP(struct url *, const char *);
+int             fetchStatFTP(struct url *, struct url_stat *, const char *);
+int             fetchListFTP(struct url_list *, struct url *, const char *,
+                   const char *);
+
+/* Generic functions */
+fetchIO                *fetchXGetURL(const char *, struct url_stat *, const char *);
+fetchIO                *fetchGetURL(const char *, const char *);
+fetchIO                *fetchPutURL(const char *, const char *);
+int             fetchStatURL(const char *, struct url_stat *, const char *);
+int             fetchListURL(struct url_list *, const char *, const char *,
+                   const char *);
+fetchIO                *fetchXGet(struct url *, struct url_stat *, const char *);
+fetchIO                *fetchGet(struct url *, const char *);
+fetchIO                *fetchPut(struct url *, const char *);
+int             fetchStat(struct url *, struct url_stat *, const char *);
+int             fetchList(struct url_list *, struct url *, const char *,
+                   const char *);
+
+/* URL parsing */
+struct url     *fetchMakeURL(const char *, const char *, int,
+                    const char *, const char *, const char *);
+struct url     *fetchParseURL(const char *);
+struct url     *fetchCopyURL(const struct url *);
+char           *fetchStringifyURL(const struct url *);
+void            fetchFreeURL(struct url *);
+
+/* URL listening */
+void            fetchInitURLList(struct url_list *);
+int             fetchAppendURLList(struct url_list *, const struct url_list *);
+void            fetchFreeURLList(struct url_list *);
+char           *fetchUnquotePath(struct url *);
+char           *fetchUnquoteFilename(struct url *);
+
+/* Connection caching */
+void            fetchConnectionCacheInit(int, int);
+void            fetchConnectionCacheClose(void);
+
+/* Authentication */
+typedef int (*auth_t)(struct url *);
+extern auth_t           fetchAuthMethod;
+
+/* Last error code */
+extern int              fetchLastErrCode;
+#define MAXERRSTRING 256
+extern char             fetchLastErrString[MAXERRSTRING];
+
+/* I/O timeout */
+extern int              fetchTimeout;
+
+/* Restart interrupted syscalls */
+extern volatile int     fetchRestartCalls;
+
+/* Extra verbosity */
+extern int              fetchDebug;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/lib/libfetch/file.c b/lib/libfetch/file.c
new file mode 100644 (file)
index 0000000..553464f
--- /dev/null
@@ -0,0 +1,265 @@
+/*     $NetBSD: file.c,v 1.15 2009/10/15 12:36:57 joerg Exp $  */
+/*-
+ * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2008, 2009 Joerg Sonnenberger <joerg@NetBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: file.c,v 1.18 2007/12/14 10:26:58 des Exp $
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat.h>
+#endif
+
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "fetch.h"
+#include "common.h"
+
+static int     fetch_stat_file(int, struct url_stat *);
+
+static ssize_t
+fetchFile_read(void *cookie, void *buf, size_t len)
+{
+       return read(*(int *)cookie, buf, len);
+}
+
+static ssize_t
+fetchFile_write(void *cookie, const void *buf, size_t len)
+{
+       return write(*(int *)cookie, buf, len);
+}
+
+static void
+fetchFile_close(void *cookie)
+{
+       int fd = *(int *)cookie;
+
+       free(cookie);
+
+       close(fd);
+}
+
+fetchIO *
+fetchXGetFile(struct url *u, struct url_stat *us, const char *flags)
+{
+       char *path;
+       fetchIO *f;
+       struct url_stat local_us;
+       int if_modified_since, fd, *cookie;
+
+       if_modified_since = CHECK_FLAG('i');
+       if (if_modified_since && us == NULL)
+               us = &local_us;
+
+       if ((path = fetchUnquotePath(u)) == NULL) {
+               fetch_syserr();
+               return NULL;
+       }
+
+       fd = open(path, O_RDONLY);
+       free(path);
+       if (fd == -1) {
+               fetch_syserr();
+               return NULL;
+       }
+
+       if (us && fetch_stat_file(fd, us) == -1) {
+               close(fd);
+               fetch_syserr();
+               return NULL;
+       }
+
+       if (if_modified_since && u->last_modified > 0 &&
+           u->last_modified >= us->mtime) {
+               close(fd);
+               fetchLastErrCode = FETCH_UNCHANGED;
+               snprintf(fetchLastErrString, MAXERRSTRING, "Unchanged");
+               return NULL;
+       }
+
+       if (u->offset && lseek(fd, u->offset, SEEK_SET) == -1) {
+               close(fd);
+               fetch_syserr();
+               return NULL;
+       }
+
+       cookie = malloc(sizeof(int));
+       if (cookie == NULL) {
+               close(fd);
+               fetch_syserr();
+               return NULL;
+       }
+
+       *cookie = fd;
+       f = fetchIO_unopen(cookie, fetchFile_read, fetchFile_write, fetchFile_close);
+       if (f == NULL) {
+               close(fd);
+               free(cookie);
+       }
+       return f;
+}
+
+fetchIO *
+fetchGetFile(struct url *u, const char *flags)
+{
+       return (fetchXGetFile(u, NULL, flags));
+}
+
+fetchIO *
+fetchPutFile(struct url *u, const char *flags)
+{
+       char *path;
+       fetchIO *f;
+       int fd, *cookie;
+
+       if ((path = fetchUnquotePath(u)) == NULL) {
+               fetch_syserr();
+               return NULL;
+       }
+
+       if (CHECK_FLAG('a'))
+               fd = open(path, O_WRONLY | O_APPEND);
+       else
+               fd = open(path, O_WRONLY);
+
+       free(path);
+
+       if (fd == -1) {
+               fetch_syserr();
+               return NULL;
+       }
+
+       if (u->offset && lseek(fd, u->offset, SEEK_SET) == -1) {
+               close(fd);
+               fetch_syserr();
+               return NULL;
+       }
+
+       cookie = malloc(sizeof(int));
+       if (cookie == NULL) {
+               close(fd);
+               fetch_syserr();
+               return NULL;
+       }
+
+       *cookie = fd;
+       f = fetchIO_unopen(cookie, fetchFile_read, fetchFile_write, fetchFile_close);
+       if (f == NULL) {
+               close(fd);
+               free(cookie);
+       }
+       return f;
+}
+
+static int
+fetch_stat_file(int fd, struct url_stat *us)
+{
+       struct stat sb;
+
+       us->size = -1;
+       us->atime = us->mtime = 0;
+       if (fstat(fd, &sb) == -1) {
+               fetch_syserr();
+               return (-1);
+       }
+       us->size = sb.st_size;
+       us->atime = sb.st_atime;
+       us->mtime = sb.st_mtime;
+       return (0);
+}
+
+int
+fetchStatFile(struct url *u, struct url_stat *us, const char *flags)
+{
+       char *path;
+       int fd, rv;
+
+       if ((path = fetchUnquotePath(u)) == NULL) {
+               fetch_syserr();
+               return -1;
+       }
+
+       fd = open(path, O_RDONLY);
+       free(path);
+
+       if (fd == -1) {
+               fetch_syserr();
+               return -1;
+       }
+
+       rv = fetch_stat_file(fd, us);
+       close(fd);
+
+       return rv;
+}
+
+int
+fetchListFile(struct url_list *ue, struct url *u, const char *pattern, const char *flags)
+{
+       char *path;
+       struct dirent *de;
+       DIR *dir;
+       int ret;
+
+       if ((path = fetchUnquotePath(u)) == NULL) {
+               fetch_syserr();
+               return -1;
+       }
+               
+       dir = opendir(path);
+       free(path);
+
+       if (dir == NULL) {
+               fetch_syserr();
+               return -1;
+       }
+
+       ret = 0;
+
+       while ((de = readdir(dir)) != NULL) {
+               if (pattern && fnmatch(pattern, de->d_name, 0) != 0)
+                       continue;
+               ret = fetch_add_entry(ue, u, de->d_name, 0);
+               if (ret)
+                       break;
+       }
+
+       closedir(dir);
+
+       return ret;
+}
diff --git a/lib/libfetch/ftp.c b/lib/libfetch/ftp.c
new file mode 100644 (file)
index 0000000..3bc995d
--- /dev/null
@@ -0,0 +1,1363 @@
+/*     $NetBSD: ftp.c,v 1.35 2010/03/21 16:48:43 joerg Exp $   */
+/*-
+ * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2008, 2009, 2010 Joerg Sonnenberger <joerg@NetBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: ftp.c,v 1.101 2008/01/23 20:57:59 des Exp $
+ */
+
+/*
+ * Portions of this code were taken from or based on ftpio.c:
+ *
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * Major Changelog:
+ *
+ * Dag-Erling Coïdan Smørgrav
+ * 9 Jun 1998
+ *
+ * Incorporated into libfetch
+ *
+ * Jordan K. Hubbard
+ * 17 Jan 1996
+ *
+ * Turned inside out. Now returns xfers as new file ids, not as a special
+ * `state' of FTP_t
+ *
+ * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
+ *
+ */
+
+#ifdef __linux__
+/* Keep this down to Linux, it can create surprises else where. */
+#define _GNU_SOURCE
+#endif
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#if defined(HAVE_INTTYPES_H) || defined(NETBSD)
+#include <inttypes.h>
+#endif
+#include <stdarg.h>
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat/netdb.h>
+#include <nbcompat/stdio.h>
+#else
+#include <netdb.h>
+#include <stdio.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "fetch.h"
+#include "common.h"
+#include "ftperr.h"
+
+#define FTP_ANONYMOUS_USER     "anonymous"
+
+#define FTP_CONNECTION_ALREADY_OPEN    125
+#define FTP_OPEN_DATA_CONNECTION       150
+#define FTP_OK                         200
+#define FTP_FILE_STATUS                        213
+#define FTP_SERVICE_READY              220
+#define FTP_TRANSFER_COMPLETE          226
+#define FTP_PASSIVE_MODE               227
+#define FTP_LPASSIVE_MODE              228
+#define FTP_EPASSIVE_MODE              229
+#define FTP_LOGGED_IN                  230
+#define FTP_FILE_ACTION_OK             250
+#define FTP_DIRECTORY_CREATED          257 /* multiple meanings */
+#define FTP_FILE_CREATED               257 /* multiple meanings */
+#define FTP_WORKING_DIRECTORY          257 /* multiple meanings */
+#define FTP_NEED_PASSWORD              331
+#define FTP_NEED_ACCOUNT               332
+#define FTP_FILE_OK                    350
+#define FTP_SYNTAX_ERROR               500
+#define FTP_PROTOCOL_ERROR             999
+
+#define isftpreply(foo)                                \
+       (isdigit((unsigned char)foo[0]) &&      \
+           isdigit((unsigned char)foo[1]) &&   \
+           isdigit((unsigned char)foo[2]) &&   \
+           (foo[3] == ' ' || foo[3] == '\0'))
+#define isftpinfo(foo) \
+       (isdigit((unsigned char)foo[0]) &&      \
+           isdigit((unsigned char)foo[1]) &&   \
+           isdigit((unsigned char)foo[2]) &&   \
+           foo[3] == '-')
+
+#define MINBUFSIZE 4096
+
+#ifdef INET6
+/*
+ * Translate IPv4 mapped IPv6 address to IPv4 address
+ */
+static void
+unmappedaddr(struct sockaddr_in6 *sin6, socklen_t *len)
+{
+       struct sockaddr_in *sin4;
+       uint32_t addr;
+       int port;
+
+       if (sin6->sin6_family != AF_INET6 ||
+           !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
+               return;
+       sin4 = (struct sockaddr_in *)sin6;
+       addr = *(uint32_t *)&sin6->sin6_addr.s6_addr[12];
+       port = sin6->sin6_port;
+       memset(sin4, 0, sizeof(struct sockaddr_in));
+       sin4->sin_addr.s_addr = addr;
+       sin4->sin_port = port;
+       sin4->sin_family = AF_INET;
+       *len = sizeof(struct sockaddr_in);
+#ifdef HAVE_SA_LEN
+       sin4->sin_len = sizeof(struct sockaddr_in);
+#endif
+}
+#endif
+
+/*
+ * Get server response
+ */
+static int
+ftp_chkerr(conn_t *conn)
+{
+       if (fetch_getln(conn) == -1) {
+               fetch_syserr();
+               return (-1);
+       }
+       if (isftpinfo(conn->buf)) {
+               while (conn->buflen && !isftpreply(conn->buf)) {
+                       if (fetch_getln(conn) == -1) {
+                               fetch_syserr();
+                               return (-1);
+                       }
+               }
+       }
+
+       while (conn->buflen &&
+           isspace((unsigned char)conn->buf[conn->buflen - 1]))
+               conn->buflen--;
+       conn->buf[conn->buflen] = '\0';
+
+       if (!isftpreply(conn->buf)) {
+               ftp_seterr(FTP_PROTOCOL_ERROR);
+               return (-1);
+       }
+
+       conn->err = (conn->buf[0] - '0') * 100
+           + (conn->buf[1] - '0') * 10
+           + (conn->buf[2] - '0');
+
+       return (conn->err);
+}
+
+/*
+ * Send a command and check reply
+ */
+#ifndef __minix
+static int
+ftp_cmd(conn_t *conn, const char *fmt, ...)
+{
+       va_list ap;
+       size_t len;
+       char *msg;
+       int r;
+
+       va_start(ap, fmt);
+       len = vasprintf(&msg, fmt, ap);
+       va_end(ap);
+
+       if (msg == NULL) {
+               errno = ENOMEM;
+               fetch_syserr();
+               return (-1);
+       }
+
+       r = fetch_write(conn, msg, len);
+       free(msg);
+
+       if (r == -1) {
+               fetch_syserr();
+               return (-1);
+       }
+
+       return (ftp_chkerr(conn));
+}
+#else
+static int
+ftp_cmd(conn_t *conn, const char *fmt, ...)
+{
+       va_list ap;
+       size_t len;
+       char msg[MINBUFSIZE];
+       int r;
+
+       va_start(ap, fmt);
+       len = vsnprintf(&msg[0], MINBUFSIZE, fmt, ap);
+       va_end(ap);
+
+       if (len >= MINBUFSIZE) {
+               errno = ENOMEM;
+               fetch_syserr();
+               return (-1);
+       }
+
+       r = fetch_write(conn, msg, len);
+
+       if (r == -1) {
+               fetch_syserr();
+               return (-1);
+       }
+
+       return (ftp_chkerr(conn));
+}
+#endif
+/*
+ * Return a pointer to the filename part of a path
+ */
+static const char *
+ftp_filename(const char *file, int *len, int *type, int subdir)
+{
+       const char *s;
+
+       if ((s = strrchr(file, '/')) == NULL || subdir)
+               s = file;
+       else
+               s = s + 1;
+       *len = strlen(s);
+       if (*len > 7 && strncmp(s + *len - 7, ";type=", 6) == 0) {
+               *type = s[*len - 1];
+               *len -= 7;
+       } else {
+               *type = '\0';
+       }
+       return (s);
+}
+
+/*
+ * Get current working directory from the reply to a CWD, PWD or CDUP
+ * command.
+ */
+static int
+ftp_pwd(conn_t *conn, char **pwd)
+{
+       char *src, *dst, *end;
+       int q;
+
+       if (conn->err != FTP_WORKING_DIRECTORY &&
+           conn->err != FTP_FILE_ACTION_OK)
+               return (FTP_PROTOCOL_ERROR);
+       end = conn->buf + conn->buflen;
+       src = conn->buf + 4;
+       if (src >= end || *src++ != '"')
+               return (FTP_PROTOCOL_ERROR);
+       *pwd = malloc(end - src + 1);
+       if (*pwd == NULL)
+               return (FTP_PROTOCOL_ERROR);
+       for (q = 0, dst = *pwd; src < end; ++src) {
+               if (!q && *src == '"')
+                       q = 1;
+               else if (q && *src != '"')
+                       break;
+               else if (q)
+                       *dst++ = '"', q = 0;
+               else
+                       *dst++ = *src;
+       }
+       *dst = '\0';
+       if (**pwd != '/') {
+               free(*pwd);
+               *pwd = NULL;
+               return (FTP_PROTOCOL_ERROR);
+       }
+       return (FTP_OK);
+}
+
+/*
+ * Change working directory to the directory that contains the specified
+ * file.
+ */
+static int
+ftp_cwd(conn_t *conn, const char *path, int subdir)
+{
+       const char *beg, *end;
+       char *pwd, *dst;
+       int e, i, len;
+
+       if (*path != '/') {
+               ftp_seterr(501);
+               return (-1);
+       }
+       ++path;
+
+       /* Simple case: still in the home directory and no directory change. */
+       if (conn->ftp_home == NULL && strchr(path, '/') == NULL &&
+           (!subdir || *path == '\0'))
+               return 0;
+
+       if ((e = ftp_cmd(conn, "PWD\r\n")) != FTP_WORKING_DIRECTORY ||
+           (e = ftp_pwd(conn, &pwd)) != FTP_OK) {
+               ftp_seterr(e);
+               return (-1);
+       }
+       if (conn->ftp_home == NULL && (conn->ftp_home = strdup(pwd)) == NULL) {
+               fetch_syserr();
+               free(pwd);
+               return (-1);
+       }
+       if (*path == '/') {
+               while (path[1] == '/')
+                       ++path;
+               dst = strdup(path);
+       } else if (strcmp(conn->ftp_home, "/") == 0) {
+               dst = strdup(path - 1);
+       } else {
+#ifndef __minix
+               asprintf(&dst, "%s/%s", conn->ftp_home, path);
+#else
+               if((dst = malloc(sizeof(char)*MINBUFSIZE)) != NULL) {
+                       len = snprintf(dst, MINBUFSIZE, "%s/%s", conn->ftp_home, path);
+                       
+                       if(len >= MINBUFSIZE) {
+                               free(dst);
+                               dst = NULL;
+                       }
+               }
+#endif
+       }
+       if (dst == NULL) {
+               fetch_syserr();
+               free(pwd);
+               return (-1);
+       }
+
+       if (subdir)
+               end = dst + strlen(dst);
+       else
+               end = strrchr(dst, '/');
+
+       for (;;) {
+               len = strlen(pwd);
+
+               /* Look for a common prefix between PWD and dir to fetch. */
+               for (i = 0; i <= len && i <= end - dst; ++i)
+                       if (pwd[i] != dst[i])
+                               break;
+               /* Keep going up a dir until we have a matching prefix. */
+               if (strcmp(pwd, "/") == 0)
+                       break;
+               if (pwd[i] == '\0' && (dst[i - 1] == '/' || dst[i] == '/'))
+                       break;
+               free(pwd);
+               if ((e = ftp_cmd(conn, "CDUP\r\n")) != FTP_FILE_ACTION_OK ||
+                   (e = ftp_cmd(conn, "PWD\r\n")) != FTP_WORKING_DIRECTORY ||
+                   (e = ftp_pwd(conn, &pwd)) != FTP_OK) {
+                       ftp_seterr(e);
+                       free(dst);
+                       return (-1);
+               }
+       }
+       free(pwd);
+
+#ifdef FTP_COMBINE_CWDS
+       /* Skip leading slashes, even "////". */
+       for (beg = dst + i; beg < end && *beg == '/'; ++beg, ++i)
+               /* nothing */ ;
+
+       /* If there is no trailing dir, we're already there. */
+       if (beg >= end) {
+               free(dst);
+               return (0);
+       }
+
+       /* Change to the directory all in one chunk (e.g., foo/bar/baz). */
+       e = ftp_cmd(conn, "CWD %.*s\r\n", (int)(end - beg), beg);
+       if (e == FTP_FILE_ACTION_OK) {
+               free(dst);
+               return (0);
+       }
+#endif /* FTP_COMBINE_CWDS */
+
+       /* That didn't work so go back to legacy behavior (multiple CWDs). */
+       for (beg = dst + i; beg < end; beg = dst + i + 1) {
+               while (*beg == '/')
+                       ++beg, ++i;
+               for (++i; dst + i < end && dst[i] != '/'; ++i)
+                       /* nothing */ ;
+               e = ftp_cmd(conn, "CWD %.*s\r\n", dst + i - beg, beg);
+               if (e != FTP_FILE_ACTION_OK) {
+                       free(dst);
+                       ftp_seterr(e);
+                       return (-1);
+               }
+       }
+       free(dst);
+       return (0);
+}
+
+/*
+ * Set transfer mode and data type
+ */
+static int
+ftp_mode_type(conn_t *conn, int mode, int type)
+{
+       int e;
+
+       switch (mode) {
+       case 0:
+       case 's':
+               mode = 'S';
+       case 'S':
+               break;
+       default:
+               return (FTP_PROTOCOL_ERROR);
+       }
+       if ((e = ftp_cmd(conn, "MODE %c\r\n", mode)) != FTP_OK) {
+               if (mode == 'S') {
+                       /*
+                        * Stream mode is supposed to be the default - so
+                        * much so that some servers not only do not
+                        * support any other mode, but do not support the
+                        * MODE command at all.
+                        *
+                        * If "MODE S" fails, it is unlikely that we
+                        * previously succeeded in setting a different
+                        * mode.  Therefore, we simply hope that the
+                        * server is already in the correct mode, and
+                        * silently ignore the failure.
+                        */
+               } else {
+                       return (e);
+               }
+       }
+
+       switch (type) {
+       case 0:
+       case 'i':
+               type = 'I';
+       case 'I':
+               break;
+       case 'a':
+               type = 'A';
+       case 'A':
+               break;
+       case 'd':
+               type = 'D';
+       case 'D':
+               /* can't handle yet */
+       default:
+               return (FTP_PROTOCOL_ERROR);
+       }
+       if ((e = ftp_cmd(conn, "TYPE %c\r\n", type)) != FTP_OK)
+               return (e);
+
+       return (FTP_OK);
+}
+
+/*
+ * Request and parse file stats
+ */
+static int
+ftp_stat(conn_t *conn, const char *file, struct url_stat *us)
+{
+       char *ln;
+       const char *filename;
+       int filenamelen, type;
+       struct tm tm;
+       time_t t;
+       int e;
+
+       us->size = -1;
+       us->atime = us->mtime = 0;
+
+       filename = ftp_filename(file, &filenamelen, &type, 0);
+
+       if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK) {
+               ftp_seterr(e);
+               return (-1);
+       }
+
+       e = ftp_cmd(conn, "SIZE %.*s\r\n", filenamelen, filename);
+       if (e != FTP_FILE_STATUS) {
+               ftp_seterr(e);
+               return (-1);
+       }
+       for (ln = conn->buf + 4; *ln && isspace((unsigned char)*ln); ln++)
+               /* nothing */ ;
+       for (us->size = 0; *ln && isdigit((unsigned char)*ln); ln++)
+               us->size = us->size * 10 + *ln - '0';
+       if (*ln && !isspace((unsigned char)*ln)) {
+               ftp_seterr(FTP_PROTOCOL_ERROR);
+               us->size = -1;
+               return (-1);
+       }
+       if (us->size == 0)
+               us->size = -1;
+
+       e = ftp_cmd(conn, "MDTM %.*s\r\n", filenamelen, filename);
+       if (e != FTP_FILE_STATUS) {
+               ftp_seterr(e);
+               return (-1);
+       }
+       for (ln = conn->buf + 4; *ln && isspace((unsigned char)*ln); ln++)
+               /* nothing */ ;
+       switch (strspn(ln, "0123456789")) {
+       case 14:
+               break;
+       case 15:
+               ln++;
+               ln[0] = '2';
+               ln[1] = '0';
+               break;
+       default:
+               ftp_seterr(FTP_PROTOCOL_ERROR);
+               return (-1);
+       }
+       if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
+           &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+           &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
+               ftp_seterr(FTP_PROTOCOL_ERROR);
+               return (-1);
+       }
+       tm.tm_mon--;
+       tm.tm_year -= 1900;
+       tm.tm_isdst = -1;
+       t = timegm(&tm);
+       if (t == (time_t)-1)
+               t = time(NULL);
+       us->mtime = t;
+       us->atime = t;
+
+       return (0);
+}
+
+/*
+ * I/O functions for FTP
+ */
+struct ftpio {
+       conn_t  *cconn;         /* Control connection */
+       conn_t  *dconn;         /* Data connection */
+       int      dir;           /* Direction */
+       int      eof;           /* EOF reached */
+       int      err;           /* Error code */
+};
+
+static ssize_t  ftp_readfn(void *, void *, size_t);
+static ssize_t  ftp_writefn(void *, const void *, size_t);
+static void     ftp_closefn(void *);
+
+static ssize_t
+ftp_readfn(void *v, void *buf, size_t len)
+{
+       struct ftpio *io;
+       int r;
+
+       io = (struct ftpio *)v;
+       if (io == NULL) {
+               errno = EBADF;
+               return (-1);
+       }
+       if (io->cconn == NULL || io->dconn == NULL || io->dir == O_WRONLY) {
+               errno = EBADF;
+               return (-1);
+       }
+       if (io->err) {
+               errno = io->err;
+               return (-1);
+       }
+       if (io->eof)
+               return (0);
+       r = fetch_read(io->dconn, buf, len);
+       if (r > 0)
+               return (r);
+       if (r == 0) {
+               io->eof = 1;
+               return (0);
+       }
+       if (errno != EINTR)
+               io->err = errno;
+       return (-1);
+}
+
+static ssize_t
+ftp_writefn(void *v, const void *buf, size_t len)
+{
+       struct ftpio *io;
+       int w;
+
+       io = (struct ftpio *)v;
+       if (io == NULL) {
+               errno = EBADF;
+               return (-1);
+       }
+       if (io->cconn == NULL || io->dconn == NULL || io->dir == O_RDONLY) {
+               errno = EBADF;
+               return (-1);
+       }
+       if (io->err) {
+               errno = io->err;
+               return (-1);
+       }
+       w = fetch_write(io->dconn, buf, len);
+       if (w >= 0)
+               return (w);
+       if (errno != EINTR)
+               io->err = errno;
+       return (-1);
+}
+
+static int
+ftp_disconnect(conn_t *conn)
+{
+       ftp_cmd(conn, "QUIT\r\n");
+       return fetch_close(conn);
+}
+
+static void
+ftp_closefn(void *v)
+{
+       struct ftpio *io;
+       int r;
+
+       io = (struct ftpio *)v;
+       if (io == NULL) {
+               errno = EBADF;
+               return;
+       }
+       if (io->dir == -1)
+               return;
+       if (io->cconn == NULL || io->dconn == NULL) {
+               errno = EBADF;
+               return;
+       }
+       fetch_close(io->dconn);
+       io->dconn = NULL;
+       io->dir = -1;
+       r = ftp_chkerr(io->cconn);
+       fetch_cache_put(io->cconn, ftp_disconnect);
+       free(io);
+       return;
+}
+
+static fetchIO *
+ftp_setup(conn_t *cconn, conn_t *dconn, int mode)
+{
+       struct ftpio *io;
+       fetchIO *f;
+
+       if (cconn == NULL || dconn == NULL)
+               return (NULL);
+       if ((io = malloc(sizeof(*io))) == NULL)
+               return (NULL);
+       io->cconn = cconn;
+       io->dconn = dconn;
+       io->dir = mode;
+       io->eof = io->err = 0;
+       f = fetchIO_unopen(io, ftp_readfn, ftp_writefn, ftp_closefn);
+       if (f == NULL)
+               free(io);
+       return (f);
+}
+
+/*
+ * Transfer file
+ */
+static fetchIO *
+ftp_transfer(conn_t *conn, const char *oper, const char *file, const char *op_arg,
+    int mode, off_t offset, const char *flags)
+{
+       union anonymous {
+               struct sockaddr_storage ss;
+               struct sockaddr sa;
+               struct sockaddr_in6 sin6;
+               struct sockaddr_in sin4;
+       } u;
+       const char *bindaddr;
+       const char *filename;
+       int filenamelen, type;
+       int low, pasv, verbose;
+       int e, sd = -1;
+       socklen_t l;
+       char *s;
+       fetchIO *df;
+
+       /* check flags */
+       low = CHECK_FLAG('l');
+       pasv = !CHECK_FLAG('a');
+       verbose = CHECK_FLAG('v');
+
+       /* passive mode */
+       if (!pasv)
+               pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
+                   strncasecmp(s, "no", 2) != 0);
+
+       /* isolate filename */
+       filename = ftp_filename(file, &filenamelen, &type, op_arg != NULL);
+
+       /* set transfer mode and data type */
+       if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK)
+               goto ouch;
+
+       /* find our own address, bind, and listen */
+       l = sizeof(u.ss);
+       if (getsockname(conn->sd, &u.sa, &l) == -1)
+               goto sysouch;
+#ifdef INET6
+       if (u.ss.ss_family == AF_INET6)
+               unmappedaddr(&u.sin6, &l);
+#endif
+
+retry_mode:
+
+       /* open data socket */
+       if ((sd = socket(u.ss.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+               fetch_syserr();
+               return (NULL);
+       }
+
+       if (pasv) {
+               unsigned char addr[64];
+               char *ln, *p;
+               unsigned int i;
+               int port;
+
+               /* send PASV command */
+               if (verbose)
+                       fetch_info("setting passive mode");
+               switch (u.ss.ss_family) {
+               case AF_INET:
+                       if ((e = ftp_cmd(conn, "PASV\r\n")) != FTP_PASSIVE_MODE)
+                               goto ouch;
+                       break;
+#ifdef INET6
+               case AF_INET6:
+                       if ((e = ftp_cmd(conn, "EPSV\r\n")) != FTP_EPASSIVE_MODE) {
+                               if (e == -1)
+                                       goto ouch;
+                               if ((e = ftp_cmd(conn, "LPSV\r\n")) !=
+                                   FTP_LPASSIVE_MODE)
+                                       goto ouch;
+                       }
+                       break;
+#endif
+               default:
+                       e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
+                       goto ouch;
+               }
+
+               /*
+                * Find address and port number. The reply to the PASV command
+                * is IMHO the one and only weak point in the FTP protocol.
+                */
+               ln = conn->buf;
+               switch (e) {
+               case FTP_PASSIVE_MODE:
+               case FTP_LPASSIVE_MODE:
+                       for (p = ln + 3; *p && !isdigit((unsigned char)*p); p++)
+                               /* nothing */ ;
+                       if (!*p) {
+                               e = FTP_PROTOCOL_ERROR;
+                               goto ouch;
+                       }
+                       l = (e == FTP_PASSIVE_MODE ? 6 : 21);
+                       for (i = 0; *p && i < l; i++, p++)
+                               addr[i] = strtol(p, &p, 10);
+                       if (i < l) {
+                               e = FTP_PROTOCOL_ERROR;
+                               goto ouch;
+                       }
+                       break;
+               case FTP_EPASSIVE_MODE:
+                       for (p = ln + 3; *p && *p != '('; p++)
+                               /* nothing */ ;
+                       if (!*p) {
+                               e = FTP_PROTOCOL_ERROR;
+                               goto ouch;
+                       }
+                       ++p;
+                       if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
+                               &port, &addr[3]) != 5 ||
+                           addr[0] != addr[1] ||
+                           addr[0] != addr[2] || addr[0] != addr[3]) {
+                               e = FTP_PROTOCOL_ERROR;
+                               goto ouch;
+                       }
+                       break;
+               case FTP_SYNTAX_ERROR:
+                       if (verbose)
+                               fetch_info("passive mode failed");
+                       /* Close socket and retry with passive mode. */
+                       pasv = 0;
+                       close(sd);
+                       sd = -1;
+                       goto retry_mode;
+               }
+
+               /* seek to required offset */
+               if (offset)
+                       if (ftp_cmd(conn, "REST %lu\r\n", (unsigned long)offset) != FTP_FILE_OK)
+                               goto sysouch;
+
+               /* construct sockaddr for data socket */
+               l = sizeof(u.ss);
+               if (getpeername(conn->sd, &u.sa, &l) == -1)
+                       goto sysouch;
+#ifdef INET6
+               if (u.ss.ss_family == AF_INET6)
+                       unmappedaddr(&u.sin6, &l);
+#endif
+               switch (u.ss.ss_family) {
+#ifdef INET6
+               case AF_INET6:
+                       if (e == FTP_EPASSIVE_MODE)
+                               u.sin6.sin6_port = htons(port);
+                       else {
+                               memcpy(&u.sin6.sin6_addr, addr + 2, 16);
+                               memcpy(&u.sin6.sin6_port, addr + 19, 2);
+                       }
+                       break;
+#endif
+               case AF_INET:
+                       if (e == FTP_EPASSIVE_MODE)
+                               u.sin4.sin_port = htons(port);
+                       else {
+                               memcpy(&u.sin4.sin_addr, addr, 4);
+                               memcpy(&u.sin4.sin_port, addr + 4, 2);
+                       }
+                       break;
+               default:
+                       e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
+                       break;
+               }
+
+               /* connect to data port */
+               if (verbose)
+                       fetch_info("opening data connection");
+               bindaddr = getenv("FETCH_BIND_ADDRESS");
+               if (bindaddr != NULL && *bindaddr != '\0' &&
+                   fetch_bind(sd, u.ss.ss_family, bindaddr) != 0)
+                       goto sysouch;
+               if (connect(sd, &u.sa, l) == -1)
+                       goto sysouch;
+
+               /* make the server initiate the transfer */
+               if (verbose)
+                       fetch_info("initiating transfer");
+               if (op_arg)
+                       e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
+               else
+                       e = ftp_cmd(conn, "%s %.*s\r\n", oper,
+                           filenamelen, filename);
+               if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
+                       goto ouch;
+
+       } else {
+               uint32_t a;
+               uint16_t p;
+#if defined(IPV6_PORTRANGE) || defined(IP_PORTRANGE)
+               int arg;
+#endif
+               int d;
+               char *ap;
+#ifdef INET6
+               char hname[INET6_ADDRSTRLEN];
+#endif
+
+               switch (u.ss.ss_family) {
+#ifdef INET6
+               case AF_INET6:
+                       u.sin6.sin6_port = 0;
+#ifdef IPV6_PORTRANGE
+                       arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
+                       if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
+                               (char *)&arg, sizeof(arg)) == -1)
+                               goto sysouch;
+#endif
+                       break;
+#endif
+               case AF_INET:
+                       u.sin4.sin_port = 0;
+#ifdef IP_PORTRANGE
+                       arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
+                       if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
+                               (char *)&arg, sizeof(arg)) == -1)
+                               goto sysouch;
+#endif
+                       break;
+               }
+               if (verbose)
+                       fetch_info("binding data socket");
+               if (bind(sd, &u.sa, l) == -1)
+                       goto sysouch;
+               if (listen(sd, 1) == -1)
+                       goto sysouch;
+
+               /* find what port we're on and tell the server */
+               if (getsockname(sd, &u.sa, &l) == -1)
+                       goto sysouch;
+               switch (u.ss.ss_family) {
+               case AF_INET:
+                       a = ntohl(u.sin4.sin_addr.s_addr);
+                       p = ntohs(u.sin4.sin_port);
+                       e = ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d\r\n",
+                           (a >> 24) & 0xff, (a >> 16) & 0xff,
+                           (a >> 8) & 0xff, a & 0xff,
+                           (p >> 8) & 0xff, p & 0xff);
+                       break;
+#ifdef INET6
+               case AF_INET6:
+#define UC(b)  (((int)b)&0xff)
+                       e = -1;
+                       u.sin6.sin6_scope_id = 0;
+                       if (getnameinfo(&u.sa, l,
+                               hname, sizeof(hname),
+                               NULL, 0, NI_NUMERICHOST) == 0) {
+                               e = ftp_cmd(conn, "EPRT |%d|%s|%d|\r\n", 2, hname,
+                                   htons(u.sin6.sin6_port));
+                               if (e == -1)
+                                       goto ouch;
+                       }
+                       if (e != FTP_OK) {
+                               ap = (char *)&u.sin6.sin6_addr;
+                               e = ftp_cmd(conn,
+                                   "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\r\n",
+                                   6, 16,
+                                   UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
+                                   UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
+                                   UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
+                                   UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
+                                   2,
+                                   (ntohs(u.sin6.sin6_port) >> 8) & 0xff,
+                                   ntohs(u.sin6.sin6_port)        & 0xff);
+                       }
+                       break;
+#endif
+               default:
+                       e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
+                       goto ouch;
+               }
+               if (e != FTP_OK)
+                       goto ouch;
+
+#ifndef __minix
+               /* seek to required offset */
+               if (offset)
+                       if (ftp_cmd(conn, "REST %llu\r\n", (unsigned long long)offset) != FTP_FILE_OK)
+                               goto sysouch;
+#else
+/* seek to required offset */
+               if (offset)
+                       if (ftp_cmd(conn, "REST %lu\r\n", (unsigned long)offset) != FTP_FILE_OK)
+                               goto sysouch;
+#endif
+
+               /* make the server initiate the transfer */
+               if (verbose)
+                       fetch_info("initiating transfer");
+               if (op_arg)
+                       e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
+               else
+                       e = ftp_cmd(conn, "%s %.*s\r\n", oper,
+                           filenamelen, filename);
+               if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
+                       goto ouch;
+
+               /* accept the incoming connection and go to town */
+               if ((d = accept(sd, NULL, NULL)) == -1)
+                       goto sysouch;
+               close(sd);
+               sd = d;
+       }
+
+       if ((df = ftp_setup(conn, fetch_reopen(sd), mode)) == NULL)
+               goto sysouch;
+       return (df);
+
+sysouch:
+       fetch_syserr();
+       if (sd >= 0)
+               close(sd);
+       return (NULL);
+
+ouch:
+       if (e != -1)
+               ftp_seterr(e);
+       if (sd >= 0)
+               close(sd);
+       return (NULL);
+}
+
+/*
+ * Authenticate
+ */
+static int
+ftp_authenticate(conn_t *conn, struct url *url, struct url *purl)
+{
+       const char *user, *pwd, *login_name;
+       char pbuf[URL_USERLEN + 1 + URL_HOSTLEN + 1];
+       int e, len;
+
+       /* XXX FTP_AUTH, and maybe .netrc */
+
+       /* send user name and password */
+       if (url->user[0] == '\0')
+               fetch_netrc_auth(url);
+       user = url->user;
+       if (*user == '\0')
+               user = getenv("FTP_LOGIN");
+       if (user == NULL || *user == '\0')
+               user = FTP_ANONYMOUS_USER;
+       if (purl && url->port == fetch_default_port(url->scheme))
+               e = ftp_cmd(conn, "USER %s@%s\r\n", user, url->host);
+       else if (purl)
+               e = ftp_cmd(conn, "USER %s@%s@%d\r\n", user, url->host, url->port);
+       else
+               e = ftp_cmd(conn, "USER %s\r\n", user);
+
+       /* did the server request a password? */
+       if (e == FTP_NEED_PASSWORD) {
+               pwd = url->pwd;
+               if (*pwd == '\0')
+                       pwd = getenv("FTP_PASSWORD");
+               if (pwd == NULL || *pwd == '\0') {
+                       if ((login_name = getlogin()) == 0)
+                               login_name = FTP_ANONYMOUS_USER;
+                       if ((len = snprintf(pbuf, URL_USERLEN + 2, "%s@", login_name)) < 0)
+                               len = 0;
+                       else if (len > URL_USERLEN + 1)
+                               len = URL_USERLEN + 1;
+                       gethostname(pbuf + len, sizeof(pbuf) - len);
+                       /* MAXHOSTNAMELEN can differ from URL_HOSTLEN + 1 */
+                       pbuf[sizeof(pbuf) - 1] = '\0';
+                       pwd = pbuf;
+               }
+               e = ftp_cmd(conn, "PASS %s\r\n", pwd);
+       }
+
+       return (e);
+}
+
+/*
+ * Log on to FTP server
+ */
+static conn_t *
+ftp_connect(struct url *url, struct url *purl, const char *flags)
+{
+       conn_t *conn;
+       int e, direct, verbose;
+#ifdef INET6
+       int af = AF_UNSPEC;
+#else
+       int af = AF_INET;
+#endif
+
+       direct = CHECK_FLAG('d');
+       verbose = CHECK_FLAG('v');
+       if (CHECK_FLAG('4'))
+               af = AF_INET;
+#ifdef INET6
+       else if (CHECK_FLAG('6'))
+               af = AF_INET6;
+#endif
+       if (direct)
+               purl = NULL;
+
+       /* check for proxy */
+       if (purl) {
+               /* XXX proxy authentication! */
+               /* XXX connetion caching */
+               if (!purl->port)
+                       purl->port = fetch_default_port(purl->scheme);
+
+               conn = fetch_connect(purl, af, verbose);
+       } else {
+               /* no proxy, go straight to target */
+               if (!url->port)
+                       url->port = fetch_default_port(url->scheme);
+
+               while ((conn = fetch_cache_get(url, af)) != NULL) {
+                       e = ftp_cmd(conn, "NOOP\r\n");
+                       if (e == FTP_OK)
+                               return conn;
+                       fetch_close(conn);
+               }
+               conn = fetch_connect(url, af, verbose);
+               purl = NULL;
+       }
+
+       /* check connection */
+       if (conn == NULL)
+               /* fetch_connect() has already set an error code */
+               return (NULL);
+
+       /* expect welcome message */
+       if ((e = ftp_chkerr(conn)) != FTP_SERVICE_READY)
+               goto fouch;
+
+       /* authenticate */
+       if ((e = ftp_authenticate(conn, url, purl)) != FTP_LOGGED_IN)
+               goto fouch;
+
+       /* TODO: Request extended features supported, if any (RFC 3659). */
+
+       /* done */
+       return (conn);
+
+fouch:
+       if (e != -1)
+               ftp_seterr(e);
+       fetch_close(conn);
+       return (NULL);
+}
+
+/*
+ * Check the proxy settings
+ */
+static struct url *
+ftp_get_proxy(struct url * url, const char *flags)
+{
+       struct url *purl;
+       char *p;
+
+       if (flags != NULL && strchr(flags, 'd') != NULL)
+               return (NULL);
+       if (fetch_no_proxy_match(url->host))
+               return (NULL);
+       if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) ||
+               (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
+           *p && (purl = fetchParseURL(p)) != NULL) {
+               if (!*purl->scheme) {
+                       if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
+                               strcpy(purl->scheme, SCHEME_FTP);
+                       else
+                               strcpy(purl->scheme, SCHEME_HTTP);
+               }
+               if (!purl->port)
+                       purl->port = fetch_default_proxy_port(purl->scheme);
+               if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
+                   strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
+                       return (purl);
+               fetchFreeURL(purl);
+       }
+       return (NULL);
+}
+
+/*
+ * Process an FTP request
+ */
+fetchIO *
+ftp_request(struct url *url, const char *op, const char *op_arg,
+    struct url_stat *us, struct url *purl, const char *flags)
+{
+       fetchIO *f;
+       char *path;
+       conn_t *conn;
+       int if_modified_since, oflag;
+       struct url_stat local_us;
+
+       /* check if we should use HTTP instead */
+       if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
+               if (strcmp(op, "STAT") == 0)
+                       return (http_request(url, "HEAD", us, purl, flags));
+               else if (strcmp(op, "RETR") == 0)
+                       return (http_request(url, "GET", us, purl, flags));
+               /*
+                * Our HTTP code doesn't support PUT requests yet, so try
+                * a direct connection.
+                */
+       }
+
+       /* connect to server */
+       conn = ftp_connect(url, purl, flags);
+       if (purl)
+               fetchFreeURL(purl);
+       if (conn == NULL)
+               return (NULL);
+
+       if ((path = fetchUnquotePath(url)) == NULL) {
+               fetch_syserr();
+               return NULL;
+       }
+
+       /* change directory */
+       if (ftp_cwd(conn, path, op_arg != NULL) == -1) {
+               free(path);
+               return (NULL);
+       }
+
+       if_modified_since = CHECK_FLAG('i');
+       if (if_modified_since && us == NULL)
+               us = &local_us;
+
+       /* stat file */
+       if (us && ftp_stat(conn, path, us) == -1
+           && fetchLastErrCode != FETCH_PROTO
+           && fetchLastErrCode != FETCH_UNAVAIL) {
+               free(path);
+               return (NULL);
+       }
+
+       if (if_modified_since && url->last_modified > 0 &&
+           url->last_modified >= us->mtime) {
+               free(path);
+               fetchLastErrCode = FETCH_UNCHANGED;
+               snprintf(fetchLastErrString, MAXERRSTRING, "Unchanged");
+               return NULL;
+       }
+
+       /* just a stat */
+       if (strcmp(op, "STAT") == 0) {
+               free(path);
+               return fetchIO_unopen(NULL, NULL, NULL, NULL);
+       }
+       if (strcmp(op, "STOR") == 0 || strcmp(op, "APPE") == 0)
+               oflag = O_WRONLY;
+       else
+               oflag = O_RDONLY;
+
+       /* initiate the transfer */
+       f = (ftp_transfer(conn, op, path, op_arg, oflag, url->offset, flags));
+       free(path);
+       return f;
+}
+
+/*
+ * Get and stat file
+ */
+fetchIO *
+fetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
+{
+       return (ftp_request(url, "RETR", NULL, us, ftp_get_proxy(url, flags), flags));
+}
+
+/*
+ * Get file
+ */
+fetchIO *
+fetchGetFTP(struct url *url, const char *flags)
+{
+       return (fetchXGetFTP(url, NULL, flags));
+}
+
+/*
+ * Put file
+ */
+fetchIO *
+fetchPutFTP(struct url *url, const char *flags)
+{
+       return (ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL, NULL,
+           ftp_get_proxy(url, flags), flags));
+}
+
+/*
+ * Get file stats
+ */
+int
+fetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
+{
+       fetchIO *f;
+
+       f = ftp_request(url, "STAT", NULL, us, ftp_get_proxy(url, flags), flags);
+       if (f == NULL)
+               return (-1);
+       fetchIO_close(f);
+       return (0);
+}
+
+/*
+ * List a directory
+ */
+int
+fetchListFTP(struct url_list *ue, struct url *url, const char *pattern, const char *flags)
+{
+       fetchIO *f;
+       char buf[2 * PATH_MAX], *eol, *eos;
+       ssize_t len;
+       size_t cur_off;
+       int ret;
+
+       /* XXX What about proxies? */
+       if (pattern == NULL || strcmp(pattern, "*") == 0)
+               pattern = "";
+       f = ftp_request(url, "NLST", pattern, NULL, ftp_get_proxy(url, flags), flags);
+       if (f == NULL)
+               return -1;
+
+       cur_off = 0;
+       ret = 0;
+
+       while ((len = fetchIO_read(f, buf + cur_off, sizeof(buf) - cur_off)) > 0) {
+               cur_off += len;
+               while ((eol = memchr(buf, '\n', cur_off)) != NULL) {
+                       if (len == eol - buf)
+                               break;
+                       if (eol != buf) {
+                               if (eol[-1] == '\r')
+                                       eos = eol - 1;
+                               else
+                                       eos = eol;
+                               *eos = '\0';
+                               ret = fetch_add_entry(ue, url, buf, 0);
+                               if (ret)
+                                       break;
+                               cur_off -= eol - buf + 1;
+                               memmove(buf, eol + 1, cur_off);
+                       }
+               }
+               if (ret)
+                       break;
+       }
+       if (cur_off != 0 || len < 0) {
+               /* Not RFC conform, bail out. */
+               fetchIO_close(f);
+               return -1;
+       }
+       fetchIO_close(f);
+       return ret;
+}
diff --git a/lib/libfetch/ftp.errors b/lib/libfetch/ftp.errors
new file mode 100644 (file)
index 0000000..e9c4950
--- /dev/null
@@ -0,0 +1,48 @@
+# $NetBSD: ftp.errors,v 1.2 2008/10/06 12:58:29 joerg Exp $
+# $FreeBSD: ftp.errors,v 1.6 2002/10/30 06:06:16 des Exp $
+#
+# This list is taken from RFC 959.
+# It probably needs a going over.
+#
+110 OK         Restart marker reply
+120 TEMP       Service ready in a few minutes
+125 OK         Data connection already open; transfer starting
+150 OK         File status okay; about to open data connection
+200 OK         Command okay
+202 PROTO      Command not implemented, superfluous at this site
+211 INFO       System status, or system help reply
+212 INFO       Directory status
+213 INFO       File status
+214 INFO       Help message
+215 INFO       Set system type
+220 OK         Service ready for new user
+221 OK         Service closing control connection
+225 OK         Data connection open; no transfer in progress
+226 OK         Requested file action successful
+227 OK         Entering Passive Mode
+229 OK         Entering Extended Passive Mode
+230 OK         User logged in, proceed
+250 OK         Requested file action okay, completed
+257 OK         File/directory created
+331 AUTH       User name okay, need password
+332 AUTH       Need account for login
+350 OK         Requested file action pending further information
+421 DOWN       Service not available, closing control connection
+425 NETWORK    Can't open data connection
+426 ABORT      Connection closed; transfer aborted
+450 UNAVAIL    File unavailable (e.g., file busy)
+451 SERVER     Requested action aborted: local error in processing
+452 FULL       Insufficient storage space in system
+500 PROTO      Syntax error, command unrecognized
+501 PROTO      Syntax error in parameters or arguments
+502 PROTO      Command not implemented
+503 PROTO      Bad sequence of commands
+504 PROTO      Command not implemented for that parameter
+530 AUTH       Not logged in
+532 AUTH       Need account for storing files
+535 PROTO      Bug in MediaHawk Video Kernel FTP server
+550 UNAVAIL    File unavailable (e.g., file not found, no access)
+551 PROTO      Requested action aborted. Page type unknown
+552 FULL       Exceeded storage allocation
+553 EXISTS     File name not allowed
+999 PROTO      Protocol error
diff --git a/lib/libfetch/ftperr.h b/lib/libfetch/ftperr.h
new file mode 100644 (file)
index 0000000..272add6
--- /dev/null
@@ -0,0 +1,45 @@
+static struct fetcherr ftp_errlist[] = {
+       { 110, FETCH_OK, "Restart marker reply" },
+       { 120, FETCH_TEMP, "Service ready in a few minutes" },
+       { 125, FETCH_OK, "Data connection already open; transfer starting" },
+       { 150, FETCH_OK, "File status okay; about to open data connection" },
+       { 200, FETCH_OK, "Command okay" },
+       { 202, FETCH_PROTO, "Command not implemented, superfluous at this site" },
+       { 211, FETCH_INFO, "System status, or system help reply" },
+       { 212, FETCH_INFO, "Directory status" },
+       { 213, FETCH_INFO, "File status" },
+       { 214, FETCH_INFO, "Help message" },
+       { 215, FETCH_INFO, "Set system type" },
+       { 220, FETCH_OK, "Service ready for new user" },
+       { 221, FETCH_OK, "Service closing control connection" },
+       { 225, FETCH_OK, "Data connection open; no transfer in progress" },
+       { 226, FETCH_OK, "Requested file action successful" },
+       { 227, FETCH_OK, "Entering Passive Mode" },
+       { 229, FETCH_OK, "Entering Extended Passive Mode" },
+       { 230, FETCH_OK, "User logged in, proceed" },
+       { 250, FETCH_OK, "Requested file action okay, completed" },
+       { 257, FETCH_OK, "File/directory created" },
+       { 331, FETCH_AUTH, "User name okay, need password" },
+       { 332, FETCH_AUTH, "Need account for login" },
+       { 350, FETCH_OK, "Requested file action pending further information" },
+       { 421, FETCH_DOWN, "Service not available, closing control connection" },
+       { 425, FETCH_NETWORK, "Can't open data connection" },
+       { 426, FETCH_ABORT, "Connection closed; transfer aborted" },
+       { 450, FETCH_UNAVAIL, "File unavailable (e.g., file busy)" },
+       { 451, FETCH_SERVER, "Requested action aborted: local error in processing" },
+       { 452, FETCH_FULL, "Insufficient storage space in system" },
+       { 500, FETCH_PROTO, "Syntax error, command unrecognized" },
+       { 501, FETCH_PROTO, "Syntax error in parameters or arguments" },
+       { 502, FETCH_PROTO, "Command not implemented" },
+       { 503, FETCH_PROTO, "Bad sequence of commands" },
+       { 504, FETCH_PROTO, "Command not implemented for that parameter" },
+       { 530, FETCH_AUTH, "Not logged in" },
+       { 532, FETCH_AUTH, "Need account for storing files" },
+       { 535, FETCH_PROTO, "Bug in MediaHawk Video Kernel FTP server" },
+       { 550, FETCH_UNAVAIL, "File unavailable (e.g., file not found, no access)" },
+       { 551, FETCH_PROTO, "Requested action aborted. Page type unknown" },
+       { 552, FETCH_FULL, "Exceeded storage allocation" },
+       { 553, FETCH_EXISTS, "File name not allowed" },
+       { 999, FETCH_PROTO, "Protocol error" },
+       { -1, FETCH_UNKNOWN, "Unknown FTP error" }
+};
diff --git a/lib/libfetch/http.c b/lib/libfetch/http.c
new file mode 100644 (file)
index 0000000..3c47287
--- /dev/null
@@ -0,0 +1,1572 @@
+/*     $NetBSD: http.c,v 1.29 2010/01/24 19:10:35 joerg Exp $  */
+/*-
+ * Copyright (c) 2000-2004 Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2003 Thomas Klausner <wiz@NetBSD.org>
+ * Copyright (c) 2008, 2009 Joerg Sonnenberger <joerg@NetBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: http.c,v 1.83 2008/02/06 11:39:55 des Exp $
+ */
+
+/*
+ * The following copyright applies to the base64 code:
+ *
+ *-
+ * Copyright 1997 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
+ * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(__linux__) || defined(__MINT__)
+/* Keep this down to Linux or MiNT, it can create surprises elsewhere. */
+#define _GNU_SOURCE
+#endif
+
+/* Needed for gmtime_r on Interix */
+#define _REENTRANT
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdarg.h>
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat/stdio.h>
+#else
+#include <stdio.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#if !defined(NETBSD) && !defined(__minix)
+#include <nbcompat/netdb.h>
+#else
+#include <netdb.h>
+#endif
+
+#include <arpa/inet.h>
+
+#include "fetch.h"
+#include "common.h"
+#include "httperr.h"
+
+/* Maximum number of redirects to follow */
+#define MAX_REDIRECT 5
+
+/* Symbolic names for reply codes we care about */
+#define HTTP_OK                        200
+#define HTTP_PARTIAL           206
+#define HTTP_MOVED_PERM                301
+#define HTTP_MOVED_TEMP                302
+#define HTTP_SEE_OTHER         303
+#define HTTP_NOT_MODIFIED      304
+#define HTTP_TEMP_REDIRECT     307
+#define HTTP_NEED_AUTH         401
+#define HTTP_NEED_PROXY_AUTH   407
+#define HTTP_BAD_RANGE         416
+#define HTTP_PROTOCOL_ERROR    999
+
+#define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
+                           || (xyz) == HTTP_MOVED_TEMP \
+                           || (xyz) == HTTP_TEMP_REDIRECT \
+                           || (xyz) == HTTP_SEE_OTHER)
+
+#define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
+
+#define MINBUFSIZE 4096
+
+/*****************************************************************************
+ * I/O functions for decoding chunked streams
+ */
+
+struct httpio
+{
+       conn_t          *conn;          /* connection */
+       int              chunked;       /* chunked mode */
+       int              keep_alive;    /* keep-alive mode */
+       char            *buf;           /* chunk buffer */
+       size_t           bufsize;       /* size of chunk buffer */
+       ssize_t          buflen;        /* amount of data currently in buffer */
+       int              bufpos;        /* current read offset in buffer */
+       int              eof;           /* end-of-file flag */
+       int              error;         /* error flag */
+       size_t           chunksize;     /* remaining size of current chunk */
+       off_t            contentlength; /* remaining size of the content */
+};
+
+/*
+ * Get next chunk header
+ */
+static int
+http_new_chunk(struct httpio *io)
+{
+       char *p;
+
+       if (fetch_getln(io->conn) == -1)
+               return (-1);
+
+       if (io->conn->buflen < 2 || !isxdigit((unsigned char)*io->conn->buf))
+               return (-1);
+
+       for (p = io->conn->buf; *p && !isspace((unsigned char)*p); ++p) {
+               if (*p == ';')
+                       break;
+               if (!isxdigit((unsigned char)*p))
+                       return (-1);
+               if (isdigit((unsigned char)*p)) {
+                       io->chunksize = io->chunksize * 16 +
+                           *p - '0';
+               } else {
+                       io->chunksize = io->chunksize * 16 +
+                           10 + tolower((unsigned char)*p) - 'a';
+               }
+       }
+
+       return (io->chunksize);
+}
+
+/*
+ * Grow the input buffer to at least len bytes
+ */
+static int
+http_growbuf(struct httpio *io, size_t len)
+{
+       char *tmp;
+
+       if (io->bufsize >= len)
+               return (0);
+
+       if ((tmp = realloc(io->buf, len)) == NULL)
+               return (-1);
+       io->buf = tmp;
+       io->bufsize = len;
+       return (0);
+}
+
+/*
+ * Fill the input buffer, do chunk decoding on the fly
+ */
+static int
+http_fillbuf(struct httpio *io, size_t len)
+{
+       if (io->error)
+               return (-1);
+       if (io->eof)
+               return (0);
+
+       if (io->contentlength >= 0 && (off_t)len > io->contentlength)
+               len = io->contentlength;
+
+       if (io->chunked == 0) {
+               if (http_growbuf(io, len) == -1)
+                       return (-1);
+               if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
+                       io->error = 1;
+                       return (-1);
+               }
+               if (io->contentlength)
+                       io->contentlength -= io->buflen;
+               io->bufpos = 0;
+               return (io->buflen);
+       }
+
+       if (io->chunksize == 0) {
+               switch (http_new_chunk(io)) {
+               case -1:
+                       io->error = 1;
+                       return (-1);
+               case 0:
+                       io->eof = 1;
+                       if (fetch_getln(io->conn) == -1)
+                               return (-1);
+                       return (0);
+               }
+       }
+
+       if (len > io->chunksize)
+               len = io->chunksize;
+       if (http_growbuf(io, len) == -1)
+               return (-1);
+       if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
+               io->error = 1;
+               return (-1);
+       }
+       io->chunksize -= io->buflen;
+       if (io->contentlength >= 0)
+               io->contentlength -= io->buflen;
+
+       if (io->chunksize == 0) {
+               char endl[2];
+               ssize_t len2;
+
+               len2 = fetch_read(io->conn, endl, 2);
+               if (len2 == 1 && fetch_read(io->conn, endl + 1, 1) != 1)
+                       return (-1);
+               if (len2 == -1 || endl[0] != '\r' || endl[1] != '\n')
+                       return (-1);
+       }
+
+       io->bufpos = 0;
+
+       return (io->buflen);
+}
+
+/*
+ * Read function
+ */
+static ssize_t
+http_readfn(void *v, void *buf, size_t len)
+{
+       struct httpio *io = (struct httpio *)v;
+       size_t l, pos;
+
+       if (io->error)
+               return (-1);
+       if (io->eof)
+               return (0);
+
+       for (pos = 0; len > 0; pos += l, len -= l) {
+               /* empty buffer */
+               if (!io->buf || io->bufpos == io->buflen)
+                       if (http_fillbuf(io, len) < 1)
+                               break;
+               l = io->buflen - io->bufpos;
+               if (len < l)
+                       l = len;
+               memcpy((char *)buf + pos, io->buf + io->bufpos, l);
+               io->bufpos += l;
+       }
+
+       if (!pos && io->error)
+               return (-1);
+       return (pos);
+}
+
+/*
+ * Write function
+ */
+static ssize_t
+http_writefn(void *v, const void *buf, size_t len)
+{
+       struct httpio *io = (struct httpio *)v;
+
+       return (fetch_write(io->conn, buf, len));
+}
+
+/*
+ * Close function
+ */
+static void
+http_closefn(void *v)
+{
+       struct httpio *io = (struct httpio *)v;
+
+       if (io->keep_alive) {
+               int val;
+
+               val = 0;
+               setsockopt(io->conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
+                          sizeof(val));
+                         fetch_cache_put(io->conn, fetch_close);
+#ifdef TCP_NOPUSH
+               val = 1;
+               setsockopt(io->conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
+                   sizeof(val));
+#endif
+       } else {
+               fetch_close(io->conn);
+       }
+
+       free(io->buf);
+       free(io);
+}
+
+/*
+ * Wrap a file descriptor up
+ */
+static fetchIO *
+http_funopen(conn_t *conn, int chunked, int keep_alive, off_t clength)
+{
+       struct httpio *io;
+       fetchIO *f;
+
+       if ((io = calloc(1, sizeof(*io))) == NULL) {
+               fetch_syserr();
+               return (NULL);
+       }
+       io->conn = conn;
+       io->chunked = chunked;
+       io->contentlength = clength;
+       io->keep_alive = keep_alive;
+       f = fetchIO_unopen(io, http_readfn, http_writefn, http_closefn);
+       if (f == NULL) {
+               fetch_syserr();
+               free(io);
+               return (NULL);
+       }
+       return (f);
+}
+
+
+/*****************************************************************************
+ * Helper functions for talking to the server and parsing its replies
+ */
+
+/* Header types */
+typedef enum {
+       hdr_syserror = -2,
+       hdr_error = -1,
+       hdr_end = 0,
+       hdr_unknown = 1,
+       hdr_connection,
+       hdr_content_length,
+       hdr_content_range,
+       hdr_last_modified,
+       hdr_location,
+       hdr_transfer_encoding,
+       hdr_www_authenticate
+} hdr_t;
+
+/* Names of interesting headers */
+static struct {
+       hdr_t            num;
+       const char      *name;
+} hdr_names[] = {
+       { hdr_connection,               "Connection" },
+       { hdr_content_length,           "Content-Length" },
+       { hdr_content_range,            "Content-Range" },
+       { hdr_last_modified,            "Last-Modified" },
+       { hdr_location,                 "Location" },
+       { hdr_transfer_encoding,        "Transfer-Encoding" },
+       { hdr_www_authenticate,         "WWW-Authenticate" },
+       { hdr_unknown,                  NULL },
+};
+
+/*
+ * Send a formatted line; optionally echo to terminal
+ */
+#ifndef __minix
+static int
+http_cmd(conn_t *conn, const char *fmt, ...)
+{
+       va_list ap;
+       size_t len;
+       char *msg;
+       int r;
+
+       va_start(ap, fmt);
+       len = vasprintf(&msg, fmt, ap);
+       va_end(ap);
+
+       if (msg == NULL) {
+               errno = ENOMEM;
+               fetch_syserr();
+               return (-1);
+       }
+
+       r = fetch_write(conn, msg, len);
+       free(msg);
+
+       if (r == -1) {
+               fetch_syserr();
+               return (-1);
+       }
+
+       return (0);
+}
+#else
+static int
+http_cmd(conn_t *conn, const char *fmt, ...)
+{
+       va_list ap;
+       size_t len;
+       char msg[MINBUFSIZE];
+       int r;
+
+       va_start(ap, fmt);
+       len = vsnprintf(&msg[0], MINBUFSIZE, fmt, ap);
+       va_end(ap);
+
+       if (len >= MINBUFSIZE) {
+               errno = ENOMEM;
+               fetch_syserr();
+               return (-1);
+       }
+
+       r = fetch_write(conn, &msg[0], len);
+
+       if (r == -1) {
+               fetch_syserr();
+               return (-1);
+       }
+
+       return (0);
+}
+#endif
+/*
+ * Get and parse status line
+ */
+static int
+http_get_reply(conn_t *conn)
+{
+       char *p;
+
+       if (fetch_getln(conn) == -1)
+               return (-1);
+       /*
+        * A valid status line looks like "HTTP/m.n xyz reason" where m
+        * and n are the major and minor protocol version numbers and xyz
+        * is the reply code.
+        * Unfortunately, there are servers out there (NCSA 1.5.1, to name
+        * just one) that do not send a version number, so we can't rely
+        * on finding one, but if we do, insist on it being 1.0 or 1.1.
+        * We don't care about the reason phrase.
+        */
+       if (strncmp(conn->buf, "HTTP", 4) != 0)
+               return (HTTP_PROTOCOL_ERROR);
+       p = conn->buf + 4;
+       if (*p == '/') {
+               if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1'))
+                       return (HTTP_PROTOCOL_ERROR);
+               p += 4;
+       }
+       if (*p != ' ' ||
+           !isdigit((unsigned char)p[1]) ||
+           !isdigit((unsigned char)p[2]) ||
+           !isdigit((unsigned char)p[3]))
+               return (HTTP_PROTOCOL_ERROR);
+
+       conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0');
+       return (conn->err);
+}
+
+/*
+ * Check a header; if the type matches the given string, return a pointer
+ * to the beginning of the value.
+ */
+static const char *
+http_match(const char *str, const char *hdr)
+{
+       while (*str && *hdr &&
+           tolower((unsigned char)*str++) == tolower((unsigned char)*hdr++))
+               /* nothing */;
+       if (*str || *hdr != ':')
+               return (NULL);
+       while (*hdr && isspace((unsigned char)*++hdr))
+               /* nothing */;
+       return (hdr);
+}
+
+/*
+ * Get the next header and return the appropriate symbolic code.
+ */
+static hdr_t
+http_next_header(conn_t *conn, const char **p)
+{
+       int i;
+
+       if (fetch_getln(conn) == -1)
+               return (hdr_syserror);
+       while (conn->buflen && isspace((unsigned char)conn->buf[conn->buflen - 1]))
+               conn->buflen--;
+       conn->buf[conn->buflen] = '\0';
+       if (conn->buflen == 0)
+               return (hdr_end);
+       /*
+        * We could check for malformed headers but we don't really care.
+        * A valid header starts with a token immediately followed by a
+        * colon; a token is any sequence of non-control, non-whitespace
+        * characters except "()<>@,;:\\\"{}".
+        */
+       for (i = 0; hdr_names[i].num != hdr_unknown; i++)
+               if ((*p = http_match(hdr_names[i].name, conn->buf)) != NULL)
+                       return (hdr_names[i].num);
+       return (hdr_unknown);
+}
+
+/*
+ * Parse a last-modified header
+ */
+static int
+http_parse_mtime(const char *p, time_t *mtime)
+{
+       char locale[64], *r;
+       struct tm tm;
+
+       strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale));
+       setlocale(LC_TIME, "C");
+       r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
+       /* XXX should add support for date-2 and date-3 */
+       setlocale(LC_TIME, locale);
+       if (r == NULL)
+               return (-1);
+       *mtime = timegm(&tm);
+       return (0);
+}
+
+/*
+ * Parse a content-length header
+ */
+static int
+http_parse_length(const char *p, off_t *length)
+{
+       off_t len;
+
+       for (len = 0; *p && isdigit((unsigned char)*p); ++p)
+               len = len * 10 + (*p - '0');
+       if (*p)
+               return (-1);
+       *length = len;
+       return (0);
+}
+
+/*
+ * Parse a content-range header
+ */
+static int
+http_parse_range(const char *p, off_t *offset, off_t *length, off_t *size)
+{
+       off_t first, last, len;
+
+       if (strncasecmp(p, "bytes ", 6) != 0)
+               return (-1);
+       p += 6;
+       if (*p == '*') {
+               first = last = -1;
+               ++p;
+       } else {
+               for (first = 0; *p && isdigit((unsigned char)*p); ++p)
+                       first = first * 10 + *p - '0';
+               if (*p != '-')
+                       return (-1);
+               for (last = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
+                       last = last * 10 + *p - '0';
+       }
+       if (first > last || *p != '/')
+               return (-1);
+       for (len = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
+               len = len * 10 + *p - '0';
+       if (*p || len < last - first + 1)
+               return (-1);
+       if (first == -1)
+               *length = 0;
+       else
+               *length = last - first + 1;
+       *offset = first;
+       *size = len;
+       return (0);
+}
+
+
+/*****************************************************************************
+ * Helper functions for authorization
+ */
+
+/*
+ * Base64 encoding
+ */
+static char *
+http_base64(const char *src)
+{
+       static const char base64[] =
+           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+           "abcdefghijklmnopqrstuvwxyz"
+           "0123456789+/";
+       char *str, *dst;
+       size_t l;
+       int t, r;
+
+       l = strlen(src);
+       if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL)
+               return (NULL);
+       dst = str;
+       r = 0;
+
+       while (l >= 3) {
+               t = (src[0] << 16) | (src[1] << 8) | src[2];
+               dst[0] = base64[(t >> 18) & 0x3f];
+               dst[1] = base64[(t >> 12) & 0x3f];
+               dst[2] = base64[(t >> 6) & 0x3f];
+               dst[3] = base64[(t >> 0) & 0x3f];
+               src += 3; l -= 3;
+               dst += 4; r += 4;
+       }
+
+       switch (l) {
+       case 2:
+               t = (src[0] << 16) | (src[1] << 8);
+               dst[0] = base64[(t >> 18) & 0x3f];
+               dst[1] = base64[(t >> 12) & 0x3f];
+               dst[2] = base64[(t >> 6) & 0x3f];
+               dst[3] = '=';
+               dst += 4;
+               r += 4;
+               break;
+       case 1:
+               t = src[0] << 16;
+               dst[0] = base64[(t >> 18) & 0x3f];
+               dst[1] = base64[(t >> 12) & 0x3f];
+               dst[2] = dst[3] = '=';
+               dst += 4;
+               r += 4;
+               break;
+       case 0:
+               break;
+       }
+
+       *dst = 0;
+       return (str);
+}
+
+/*
+ * Encode username and password
+ */
+#ifndef __minix
+static int
+http_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
+{
+       char *upw, *auth;
+       int r;
+
+       if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
+               return (-1);
+       auth = http_base64(upw);
+       free(upw);
+       if (auth == NULL)
+               return (-1);
+       r = http_cmd(conn, "%s: Basic %s\r\n", hdr, auth);
+       free(auth);
+       return (r);
+}
+#else
+static int
+http_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
+{
+       char upw[MINBUFSIZE], *auth;
+       int len, r;
+
+       len = snprintf(&upw[0], MINBUFSIZE, "%s:%s", usr, pwd);
+       if (len >= MINBUFSIZE)
+               return -1;
+       auth = http_base64(&upw[0]);
+       if (auth == NULL)
+               return (-1);
+       r = http_cmd(conn, "%s: Basic %s\r\n", hdr, auth);
+       free(auth);
+       return (r);
+}
+#endif
+/*
+ * Send an authorization header
+ */
+static int
+http_authorize(conn_t *conn, const char *hdr, const char *p)
+{
+       /* basic authorization */
+       if (strncasecmp(p, "basic:", 6) == 0) {
+               char *user, *pwd, *str;
+               int r;
+
+               /* skip realm */
+               for (p += 6; *p && *p != ':'; ++p)
+                       /* nothing */ ;
+               if (!*p || strchr(++p, ':') == NULL)
+                       return (-1);
+               if ((str = strdup(p)) == NULL)
+                       return (-1); /* XXX */
+               user = str;
+               pwd = strchr(str, ':');
+               *pwd++ = '\0';
+               r = http_basic_auth(conn, hdr, user, pwd);
+               free(str);
+               return (r);
+       }
+       return (-1);
+}
+
+
+/*****************************************************************************
+ * Helper functions for connecting to a server or proxy
+ */
+
+/*
+ * Connect to the correct HTTP server or proxy.
+ */
+static conn_t *
+http_connect(struct url *URL, struct url *purl, const char *flags, int *cached)
+{
+       conn_t *conn;
+       int af, verbose;
+#ifdef TCP_NOPUSH
+       int val;
+#endif
+
+       *cached = 1;
+
+#ifdef INET6
+       af = AF_UNSPEC;
+#else
+       af = AF_INET;
+#endif
+
+       verbose = CHECK_FLAG('v');
+       if (CHECK_FLAG('4'))
+               af = AF_INET;
+#ifdef INET6
+       else if (CHECK_FLAG('6'))
+               af = AF_INET6;
+#endif
+
+       if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) {
+               URL = purl;
+       } else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) {
+               /* can't talk http to an ftp server */
+               /* XXX should set an error code */
+               return (NULL);
+       }
+
+       if ((conn = fetch_cache_get(URL, af)) != NULL) {
+               *cached = 1;
+               return (conn);
+       }
+
+       if ((conn = fetch_connect(URL, af, verbose)) == NULL)
+               /* fetch_connect() has already set an error code */
+               return (NULL);
+       if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 &&
+           fetch_ssl(conn, verbose) == -1) {
+               fetch_close(conn);
+               /* grrr */
+#ifdef EAUTH
+               errno = EAUTH;
+#else
+               errno = EPERM;
+#endif
+               fetch_syserr();
+               return (NULL);
+       }
+
+#ifdef TCP_NOPUSH
+       val = 1;
+       setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
+#endif
+
+       return (conn);
+}
+
+static struct url *
+http_get_proxy(struct url * url, const char *flags)
+{
+       struct url *purl;
+       char *p;
+
+       if (flags != NULL && strchr(flags, 'd') != NULL)
+               return (NULL);
+       if (fetch_no_proxy_match(url->host))
+               return (NULL);
+       if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
+           *p && (purl = fetchParseURL(p))) {
+               if (!*purl->scheme)
+                       strcpy(purl->scheme, SCHEME_HTTP);
+               if (!purl->port)
+                       purl->port = fetch_default_proxy_port(purl->scheme);
+               if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
+                       return (purl);
+               fetchFreeURL(purl);
+       }
+       return (NULL);
+}
+
+static void
+set_if_modified_since(conn_t *conn, time_t last_modified)
+{
+       static const char weekdays[] = "SunMonTueWedThuFriSat";
+       static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
+       struct tm tm;
+       char buf[80];
+       gmtime_r(&last_modified, &tm);
+       snprintf(buf, sizeof(buf), "%.3s, %02d %.3s %4d %02d:%02d:%02d GMT",
+           weekdays + tm.tm_wday * 3, tm.tm_mday, months + tm.tm_mon * 3,
+           tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
+       http_cmd(conn, "If-Modified-Since: %s\r\n", buf);
+}
+
+
+/*****************************************************************************
+ * Core
+ */
+
+/*
+ * Send a request and process the reply
+ *
+ * XXX This function is way too long, the do..while loop should be split
+ * XXX off into a separate function.
+ */
+fetchIO *
+http_request(struct url *URL, const char *op, struct url_stat *us,
+    struct url *purl, const char *flags)
+{
+       conn_t *conn;
+       struct url *url, *new;
+       int chunked, direct, if_modified_since, need_auth, noredirect;
+       int keep_alive, verbose, cached;
+       int e, i, n, val;
+       off_t offset, clength, length, size;
+       time_t mtime;
+       const char *p;
+       fetchIO *f;
+       hdr_t h;
+       char hbuf[URL_HOSTLEN + 7], *host;
+
+       direct = CHECK_FLAG('d');
+       noredirect = CHECK_FLAG('A');
+       verbose = CHECK_FLAG('v');
+       if_modified_since = CHECK_FLAG('i');
+       keep_alive = 0;
+
+       if (direct && purl) {
+               fetchFreeURL(purl);
+               purl = NULL;
+       }
+
+       /* try the provided URL first */
+       url = URL;
+
+       /* if the A flag is set, we only get one try */
+       n = noredirect ? 1 : MAX_REDIRECT;
+       i = 0;
+
+       e = HTTP_PROTOCOL_ERROR;
+       need_auth = 0;
+       do {
+               new = NULL;
+               chunked = 0;
+               offset = 0;
+               clength = -1;
+               length = -1;
+               size = -1;
+               mtime = 0;
+
+               /* check port */
+               if (!url->port)
+                       url->port = fetch_default_port(url->scheme);
+
+               /* were we redirected to an FTP URL? */
+               if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) {
+                       if (strcmp(op, "GET") == 0)
+                               return (ftp_request(url, "RETR", NULL, us, purl, flags));
+                       else if (strcmp(op, "HEAD") == 0)
+                               return (ftp_request(url, "STAT", NULL, us, purl, flags));
+               }
+
+               /* connect to server or proxy */
+               if ((conn = http_connect(url, purl, flags, &cached)) == NULL)
+                       goto ouch;
+
+               host = url->host;
+#ifdef INET6
+               if (strchr(url->host, ':')) {
+                       snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
+                       host = hbuf;
+               }
+#endif
+               if (url->port != fetch_default_port(url->scheme)) {
+                       if (host != hbuf) {
+                               strcpy(hbuf, host);
+                               host = hbuf;
+                       }
+                       snprintf(hbuf + strlen(hbuf),
+                           sizeof(hbuf) - strlen(hbuf), ":%d", url->port);
+               }
+
+               /* send request */
+               if (verbose)
+                       fetch_info("requesting %s://%s%s",
+                           url->scheme, host, url->doc);
+               if (purl) {
+                       http_cmd(conn, "%s %s://%s%s HTTP/1.1\r\n",
+                           op, url->scheme, host, url->doc);
+               } else {
+                       http_cmd(conn, "%s %s HTTP/1.1\r\n",
+                           op, url->doc);
+               }
+
+               if (if_modified_since && url->last_modified > 0)
+                       set_if_modified_since(conn, url->last_modified);
+
+               /* virtual host */
+               http_cmd(conn, "Host: %s\r\n", host);
+
+               /* proxy authorization */
+               if (purl) {
+                       if (*purl->user || *purl->pwd)
+                               http_basic_auth(conn, "Proxy-Authorization",
+                                   purl->user, purl->pwd);
+                       else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && *p != '\0')
+                               http_authorize(conn, "Proxy-Authorization", p);
+               }
+
+               /* server authorization */
+               if (need_auth || *url->user || *url->pwd) {
+                       if (*url->user || *url->pwd)
+                               http_basic_auth(conn, "Authorization", url->user, url->pwd);
+                       else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0')
+                               http_authorize(conn, "Authorization", p);
+                       else if (fetchAuthMethod && fetchAuthMethod(url) == 0) {
+                               http_basic_auth(conn, "Authorization", url->user, url->pwd);
+                       } else {
+                               http_seterr(HTTP_NEED_AUTH);
+                               goto ouch;
+                       }
+               }
+
+               /* other headers */
+               if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
+                       if (strcasecmp(p, "auto") == 0)
+                               http_cmd(conn, "Referer: %s://%s%s\r\n",
+                                   url->scheme, host, url->doc);
+                       else
+                               http_cmd(conn, "Referer: %s\r\n", p);
+               }
+               if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0')
+                       http_cmd(conn, "User-Agent: %s\r\n", p);
+               else
+                       http_cmd(conn, "User-Agent: %s\r\n", _LIBFETCH_VER);
+#ifndef __minix
+               if (url->offset > 0)
+                       http_cmd(conn, "Range: bytes=%lld-\r\n", (long long)url->offset);
+#else
+               if (url->offset > 0)
+                       http_cmd(conn, "Range: bytes=%ld-\r\n", (long)url->offset);
+#endif
+               http_cmd(conn, "\r\n");
+
+               /*
+                * Force the queued request to be dispatched.  Normally, one
+                * would do this with shutdown(2) but squid proxies can be
+                * configured to disallow such half-closed connections.  To
+                * be compatible with such configurations, fiddle with socket
+                * options to force the pending data to be written.
+                */
+#ifdef TCP_NOPUSH
+               val = 0;
+               setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
+                          sizeof(val));
+#endif
+               val = 1;
+               setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
+                          sizeof(val));
+
+               /* get reply */
+               switch (http_get_reply(conn)) {
+               case HTTP_OK:
+               case HTTP_PARTIAL:
+               case HTTP_NOT_MODIFIED:
+                       /* fine */
+                       break;
+               case HTTP_MOVED_PERM:
+               case HTTP_MOVED_TEMP:
+               case HTTP_SEE_OTHER:
+                       /*
+                        * Not so fine, but we still have to read the
+                        * headers to get the new location.
+                        */
+                       break;
+               case HTTP_NEED_AUTH:
+                       if (need_auth) {
+                               /*
+                                * We already sent out authorization code,
+                                * so there's nothing more we can do.
+                                */
+                               http_seterr(conn->err);
+                               goto ouch;
+                       }
+                       /* try again, but send the password this time */
+                       if (verbose)
+                               fetch_info("server requires authorization");
+                       break;
+               case HTTP_NEED_PROXY_AUTH:
+                       /*
+                        * If we're talking to a proxy, we already sent
+                        * our proxy authorization code, so there's
+                        * nothing more we can do.
+                        */
+                       http_seterr(conn->err);
+                       goto ouch;
+               case HTTP_BAD_RANGE:
+                       /*
+                        * This can happen if we ask for 0 bytes because
+                        * we already have the whole file.  Consider this
+                        * a success for now, and check sizes later.
+                        */
+                       break;
+               case HTTP_PROTOCOL_ERROR:
+                       /* fall through */
+               case -1:
+                       --i;
+                       if (cached)
+                               continue;
+                       fetch_syserr();
+                       goto ouch;
+               default:
+                       http_seterr(conn->err);
+                       if (!verbose)
+                               goto ouch;
+                       /* fall through so we can get the full error message */
+               }
+
+               /* get headers */
+               do {
+                       switch ((h = http_next_header(conn, &p))) {
+                       case hdr_syserror:
+                               fetch_syserr();
+                               goto ouch;
+                       case hdr_error:
+                               http_seterr(HTTP_PROTOCOL_ERROR);
+                               goto ouch;
+                       case hdr_connection:
+                               /* XXX too weak? */
+                               keep_alive = (strcasecmp(p, "keep-alive") == 0);
+                               break;
+                       case hdr_content_length:
+                               http_parse_length(p, &clength);
+                               break;
+                       case hdr_content_range:
+                               http_parse_range(p, &offset, &length, &size);
+                               break;
+                       case hdr_last_modified:
+                               http_parse_mtime(p, &mtime);
+                               break;
+                       case hdr_location:
+                               if (!HTTP_REDIRECT(conn->err))
+                                       break;
+                               if (new)
+                                       free(new);
+                               if (verbose)
+                                       fetch_info("%d redirect to %s", conn->err, p);
+                               if (*p == '/')
+                                       /* absolute path */
+                                       new = fetchMakeURL(url->scheme, url->host, url->port, p,
+                                           url->user, url->pwd);
+                               else
+                                       new = fetchParseURL(p);
+                               if (new == NULL) {
+                                       /* XXX should set an error code */
+                                       goto ouch;
+                               }
+                               if (!*new->user && !*new->pwd) {
+                                       strcpy(new->user, url->user);
+                                       strcpy(new->pwd, url->pwd);
+                               }
+                               new->offset = url->offset;
+                               new->length = url->length;
+                               break;
+                       case hdr_transfer_encoding:
+                               /* XXX weak test*/
+                               chunked = (strcasecmp(p, "chunked") == 0);
+                               break;
+                       case hdr_www_authenticate:
+                               if (conn->err != HTTP_NEED_AUTH)
+                                       break;
+                               /* if we were smarter, we'd check the method and realm */
+                               break;
+                       case hdr_end:
+                               /* fall through */
+                       case hdr_unknown:
+                               /* ignore */
+                               break;
+                       }
+               } while (h > hdr_end);
+
+               /* we need to provide authentication */
+               if (conn->err == HTTP_NEED_AUTH) {
+                       e = conn->err;
+                       need_auth = 1;
+                       fetch_close(conn);
+                       conn = NULL;
+                       continue;
+               }
+
+               /* requested range not satisfiable */
+               if (conn->err == HTTP_BAD_RANGE) {
+                       if (url->offset == size && url->length == 0) {
+                               /* asked for 0 bytes; fake it */
+                               offset = url->offset;
+                               conn->err = HTTP_OK;
+                               break;
+                       } else {
+                               http_seterr(conn->err);
+                               goto ouch;
+                       }
+               }
+
+               /* we have a hit or an error */
+               if (conn->err == HTTP_OK ||
+                   conn->err == HTTP_PARTIAL ||
+                   conn->err == HTTP_NOT_MODIFIED ||
+                   HTTP_ERROR(conn->err))
+                       break;
+
+               /* all other cases: we got a redirect */
+               e = conn->err;
+               need_auth = 0;
+               fetch_close(conn);
+               conn = NULL;
+               if (!new)
+                       break;
+               if (url != URL)
+                       fetchFreeURL(url);
+               url = new;
+       } while (++i < n);
+
+       /* we failed, or ran out of retries */
+       if (conn == NULL) {
+               http_seterr(e);
+               goto ouch;
+       }
+
+       /* check for inconsistencies */
+       if (clength != -1 && length != -1 && clength != length) {
+               http_seterr(HTTP_PROTOCOL_ERROR);
+               goto ouch;
+       }
+       if (clength == -1)
+               clength = length;
+       if (clength != -1)
+               length = offset + clength;
+       if (length != -1 && size != -1 && length != size) {
+               http_seterr(HTTP_PROTOCOL_ERROR);
+               goto ouch;
+       }
+       if (size == -1)
+               size = length;
+
+       /* fill in stats */
+       if (us) {
+               us->size = size;
+               us->atime = us->mtime = mtime;
+       }
+
+       /* too far? */
+       if (URL->offset > 0 && offset > URL->offset) {
+               http_seterr(HTTP_PROTOCOL_ERROR);
+               goto ouch;
+       }
+
+       /* report back real offset and size */
+       URL->offset = offset;
+       URL->length = clength;
+
+       if (clength == -1 && !chunked)
+               keep_alive = 0;
+
+       if (conn->err == HTTP_NOT_MODIFIED) {
+               http_seterr(HTTP_NOT_MODIFIED);
+               if (keep_alive) {
+                       fetch_cache_put(conn, fetch_close);
+                       conn = NULL;
+               }
+               goto ouch;
+       }
+
+       /* wrap it up in a fetchIO */
+       if ((f = http_funopen(conn, chunked, keep_alive, clength)) == NULL) {
+               fetch_syserr();
+               goto ouch;
+       }
+
+       if (url != URL)
+               fetchFreeURL(url);
+       if (purl)
+               fetchFreeURL(purl);
+
+       if (HTTP_ERROR(conn->err)) {
+
+               if (keep_alive) {
+                       char buf[512];
+                       do {
+                       } while (fetchIO_read(f, buf, sizeof(buf)) > 0);
+               }
+
+               fetchIO_close(f);
+               f = NULL;
+       }
+
+       return (f);
+
+ouch:
+       if (url != URL)
+               fetchFreeURL(url);
+       if (purl)
+               fetchFreeURL(purl);
+       if (conn != NULL)
+               fetch_close(conn);
+       return (NULL);
+}
+
+
+/*****************************************************************************
+ * Entry points
+ */
+
+/*
+ * Retrieve and stat a file by HTTP
+ */
+fetchIO *
+fetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags)
+{
+       return (http_request(URL, "GET", us, http_get_proxy(URL, flags), flags));
+}
+
+/*
+ * Retrieve a file by HTTP
+ */
+fetchIO *
+fetchGetHTTP(struct url *URL, const char *flags)
+{
+       return (fetchXGetHTTP(URL, NULL, flags));
+}
+
+/*
+ * Store a file by HTTP
+ */
+fetchIO *
+fetchPutHTTP(struct url *URL, const char *flags)
+{
+       fprintf(stderr, "fetchPutHTTP(): not implemented\n");
+       return (NULL);
+}
+
+/*
+ * Get an HTTP document's metadata
+ */
+int
+fetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags)
+{
+       fetchIO *f;
+
+       f = http_request(URL, "HEAD", us, http_get_proxy(URL, flags), flags);
+       if (f == NULL)
+               return (-1);
+       fetchIO_close(f);
+       return (0);
+}
+
+enum http_states {
+       ST_NONE,
+       ST_LT,
+       ST_LTA,
+       ST_TAGA,
+       ST_H,
+       ST_R,
+       ST_E,
+       ST_F,
+       ST_HREF,
+       ST_HREFQ,
+       ST_TAG,
+       ST_TAGAX,
+       ST_TAGAQ
+};
+
+struct index_parser {
+       struct url_list *ue;
+       struct url *url;
+       enum http_states state;
+};
+
+static ssize_t
+parse_index(struct index_parser *parser, const char *buf, size_t len)
+{
+       char *end_attr, p = *buf;
+
+       switch (parser->state) {
+       case ST_NONE:
+               /* Plain text, not in markup */
+               if (p == '<')
+                       parser->state = ST_LT;
+               return 1;
+       case ST_LT:
+               /* In tag -- "<" already found */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == 'a' || p == 'A')
+                       parser->state = ST_LTA;
+               else if (!isspace((unsigned char)p))
+                       parser->state = ST_TAG;
+               return 1;
+       case ST_LTA:
+               /* In tag -- "<a" already found */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_TAGAQ;
+               else if (isspace((unsigned char)p))
+                       parser->state = ST_TAGA;
+               else
+                       parser->state = ST_TAG;
+               return 1;
+       case ST_TAG:
+               /* In tag, but not "<a" -- disregard */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               return 1;
+       case ST_TAGA:
+               /* In a-tag -- "<a " already found */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_TAGAQ;
+               else if (p == 'h' || p == 'H')
+                       parser->state = ST_H;
+               else if (!isspace((unsigned char)p))
+                       parser->state = ST_TAGAX;
+               return 1;
+       case ST_TAGAX:
+               /* In unknown keyword in a-tag */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_TAGAQ;
+               else if (isspace((unsigned char)p))
+                       parser->state = ST_TAGA;
+               return 1;
+       case ST_TAGAQ:
+               /* In a-tag, unknown argument for keys. */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_TAGA;
+               return 1;
+       case ST_H:
+               /* In a-tag -- "<a h" already found */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_TAGAQ;
+               else if (p == 'r' || p == 'R')
+                       parser->state = ST_R;
+               else if (isspace((unsigned char)p))
+                       parser->state = ST_TAGA;
+               else
+                       parser->state = ST_TAGAX;
+               return 1;
+       case ST_R:
+               /* In a-tag -- "<a hr" already found */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_TAGAQ;
+               else if (p == 'e' || p == 'E')
+                       parser->state = ST_E;
+               else if (isspace((unsigned char)p))
+                       parser->state = ST_TAGA;
+               else
+                       parser->state = ST_TAGAX;
+               return 1;
+       case ST_E:
+               /* In a-tag -- "<a hre" already found */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_TAGAQ;
+               else if (p == 'f' || p == 'F')
+                       parser->state = ST_F;
+               else if (isspace((unsigned char)p))
+                       parser->state = ST_TAGA;
+               else
+                       parser->state = ST_TAGAX;
+               return 1;
+       case ST_F:
+               /* In a-tag -- "<a href" already found */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_TAGAQ;
+               else if (p == '=')
+                       parser->state = ST_HREF;
+               else if (!isspace((unsigned char)p))
+                       parser->state = ST_TAGAX;
+               return 1;
+       case ST_HREF:
+               /* In a-tag -- "<a href=" already found */
+               if (p == '>')
+                       parser->state = ST_NONE;
+               else if (p == '"')
+                       parser->state = ST_HREFQ;
+               else if (!isspace((unsigned char)p))
+                       parser->state = ST_TAGA;
+               return 1;
+       case ST_HREFQ:
+               /* In href of the a-tag */
+               end_attr = memchr(buf, '"', len);
+               if (end_attr == NULL)
+                       return 0;
+               *end_attr = '\0';
+               parser->state = ST_TAGA;
+               if (fetch_add_entry(parser->ue, parser->url, buf, 1))
+                       return -1;
+               return end_attr + 1 - buf;
+       }
+       /* NOTREACHED */
+       abort();
+}
+
+struct http_index_cache {
+       struct http_index_cache *next;
+       struct url *location;
+       struct url_list ue;
+};
+
+static struct http_index_cache *index_cache;
+
+/*
+ * List a directory
+ */
+int
+fetchListHTTP(struct url_list *ue, struct url *url, const char *pattern, const char *flags)
+{
+       fetchIO *f;
+       char buf[2 * PATH_MAX];
+       size_t buf_len, sum_processed;
+       ssize_t read_len, processed;
+       struct index_parser state;
+       struct http_index_cache *cache = NULL;
+       int do_cache, ret;
+
+       do_cache = CHECK_FLAG('c');
+
+       if (do_cache) {
+               for (cache = index_cache; cache != NULL; cache = cache->next) {
+                       if (strcmp(cache->location->scheme, url->scheme))
+                               continue;
+                       if (strcmp(cache->location->user, url->user))
+                               continue;
+                       if (strcmp(cache->location->pwd, url->pwd))
+                               continue;
+                       if (strcmp(cache->location->host, url->host))
+                               continue;
+                       if (cache->location->port != url->port)
+                               continue;
+                       if (strcmp(cache->location->doc, url->doc))
+                               continue;
+                       return fetchAppendURLList(ue, &cache->ue);
+               }
+
+               cache = malloc(sizeof(*cache));
+               fetchInitURLList(&cache->ue);
+               cache->location = fetchCopyURL(url);
+       }
+
+       f = fetchGetHTTP(url, flags);
+       if (f == NULL) {
+               if (do_cache) {
+                       fetchFreeURLList(&cache->ue);
+                       fetchFreeURL(cache->location);
+                       free(cache);
+               }
+               return -1;
+       }
+
+       state.url = url;
+       state.state = ST_NONE;
+       if (do_cache) {
+               state.ue = &cache->ue;
+       } else {
+               state.ue = ue;
+       }
+
+       buf_len = 0;
+
+       while ((read_len = fetchIO_read(f, buf + buf_len, sizeof(buf) - buf_len)) > 0) {
+               buf_len += read_len;
+               sum_processed = 0;
+               do {
+                       processed = parse_index(&state, buf + sum_processed, buf_len);
+                       if (processed == -1)
+                               break;
+                       buf_len -= processed;
+                       sum_processed += processed;
+               } while (processed != 0 && buf_len > 0);
+               if (processed == -1) {
+                       read_len = -1;
+                       break;
+               }
+               memmove(buf, buf + sum_processed, buf_len);
+       }
+
+       fetchIO_close(f);
+
+       ret = read_len < 0 ? -1 : 0;
+
+       if (do_cache) {
+               if (ret == 0) {
+                       cache->next = index_cache;
+                       index_cache = cache;
+               }
+
+               if (fetchAppendURLList(ue, &cache->ue))
+                       ret = -1;
+       }
+
+       return ret;
+}
diff --git a/lib/libfetch/http.errors b/lib/libfetch/http.errors
new file mode 100644 (file)
index 0000000..004aac2
--- /dev/null
@@ -0,0 +1,46 @@
+# $FreeBSD: http.errors,v 1.5 2001/05/23 18:52:02 des Exp $
+# $NetBSD: http.errors,v 1.3 2009/02/05 16:59:45 joerg Exp $
+#
+# This list is taken from RFC 2068.
+#
+100 OK         Continue
+101 OK         Switching Protocols
+200 OK         OK
+201 OK         Created
+202 OK         Accepted
+203 INFO       Non-Authoritative Information
+204 OK         No Content
+205 OK         Reset Content
+206 OK         Partial Content
+300 MOVED      Multiple Choices
+301 MOVED      Moved Permanently
+302 MOVED      Moved Temporarily
+303 MOVED      See Other
+304 UNCHANGED  Not Modified
+305 INFO       Use Proxy
+307 MOVED      Temporary Redirect
+400 PROTO      Bad Request
+401 AUTH       Unauthorized
+402 AUTH       Payment Required
+403 AUTH       Forbidden
+404 UNAVAIL    Not Found
+405 PROTO      Method Not Allowed
+406 PROTO      Not Acceptable
+407 AUTH       Proxy Authentication Required
+408 TIMEOUT    Request Time-out
+409 EXISTS     Conflict
+410 UNAVAIL    Gone
+411 PROTO      Length Required
+412 SERVER     Precondition Failed
+413 PROTO      Request Entity Too Large
+414 PROTO      Request-URI Too Large
+415 PROTO      Unsupported Media Type
+416 UNAVAIL    Requested Range Not Satisfiable
+417 SERVER     Expectation Failed
+500 SERVER     Internal Server Error
+501 PROTO      Not Implemented
+502 SERVER     Bad Gateway
+503 TEMP       Service Unavailable
+504 TIMEOUT    Gateway Time-out
+505 PROTO      HTTP Version not supported
+999 PROTO      Protocol error
diff --git a/lib/libfetch/httperr.h b/lib/libfetch/httperr.h
new file mode 100644 (file)
index 0000000..6a00dbe
--- /dev/null
@@ -0,0 +1,44 @@
+static struct fetcherr http_errlist[] = {
+       { 100, FETCH_OK, "Continue" },
+       { 101, FETCH_OK, "Switching Protocols" },
+       { 200, FETCH_OK, "OK" },
+       { 201, FETCH_OK, "Created" },
+       { 202, FETCH_OK, "Accepted" },
+       { 203, FETCH_INFO, "Non-Authoritative Information" },
+       { 204, FETCH_OK, "No Content" },
+       { 205, FETCH_OK, "Reset Content" },
+       { 206, FETCH_OK, "Partial Content" },
+       { 300, FETCH_MOVED, "Multiple Choices" },
+       { 301, FETCH_MOVED, "Moved Permanently" },
+       { 302, FETCH_MOVED, "Moved Temporarily" },
+       { 303, FETCH_MOVED, "See Other" },
+       { 304, FETCH_UNCHANGED, "Not Modified" },
+       { 305, FETCH_INFO, "Use Proxy" },
+       { 307, FETCH_MOVED, "Temporary Redirect" },
+       { 400, FETCH_PROTO, "Bad Request" },
+       { 401, FETCH_AUTH, "Unauthorized" },
+       { 402, FETCH_AUTH, "Payment Required" },
+       { 403, FETCH_AUTH, "Forbidden" },
+       { 404, FETCH_UNAVAIL, "Not Found" },
+       { 405, FETCH_PROTO, "Method Not Allowed" },
+       { 406, FETCH_PROTO, "Not Acceptable" },
+       { 407, FETCH_AUTH, "Proxy Authentication Required" },
+       { 408, FETCH_TIMEOUT, "Request Time-out" },
+       { 409, FETCH_EXISTS, "Conflict" },
+       { 410, FETCH_UNAVAIL, "Gone" },
+       { 411, FETCH_PROTO, "Length Required" },
+       { 412, FETCH_SERVER, "Precondition Failed" },
+       { 413, FETCH_PROTO, "Request Entity Too Large" },
+       { 414, FETCH_PROTO, "Request-URI Too Large" },
+       { 415, FETCH_PROTO, "Unsupported Media Type" },
+       { 416, FETCH_UNAVAIL, "Requested Range Not Satisfiable" },
+       { 417, FETCH_SERVER, "Expectation Failed" },
+       { 500, FETCH_SERVER, "Internal Server Error" },
+       { 501, FETCH_PROTO, "Not Implemented" },
+       { 502, FETCH_SERVER, "Bad Gateway" },
+       { 503, FETCH_TEMP, "Service Unavailable" },
+       { 504, FETCH_TIMEOUT, "Gateway Time-out" },
+       { 505, FETCH_PROTO, "HTTP Version not supported" },
+       { 999, FETCH_PROTO, "Protocol error" },
+       { -1, FETCH_UNKNOWN, "Unknown HTTP error" }
+};