.
[gnupg.git] / common / http.c
index 7898d86..1e3e897 100644 (file)
@@ -1,12 +1,12 @@
 /* http.c  -  HTTP protocol handler
- * Copyright (C) 1999, 2001, 2002, 2003, 2004,
- *               2006 Free Software Foundation, Inc.
+ * Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006,
+ *               2009, 2010 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
  * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * GnuPG is distributed in the hope that it will be useful,
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 /* Simple HTTP client implementation.  We try to keep the code as
    self-contained as possible.  There are some contraints however:
 
+  - estream is required.  We now require estream because it provides a
+    very useful and portable asprintf implementation and the fopencookie
+    function.
   - stpcpy is required
   - fixme: list other requirements.
 
 
-  - With HTTP_USE_ESTREAM defined, all I/O is done through estream.
   - With HTTP_USE_GNUTLS support for https is provided (this also
     requires estream).
+  - With HTTP_NO_WSASTARTUP the socket initialization is not done
+    under Windows.  This is useful if the socket layer has already
+    been initialized elsewhere.  This also avoids the installation of
+    an exit handler to cleanup the socket layer.
 */
 
 #ifdef HAVE_CONFIG_H
 #include <string.h>
 #include <ctype.h>
 #include <errno.h>
+#include <unistd.h>
 
 #ifdef HAVE_W32_SYSTEM
 # include <windows.h>
 #else /*!HAVE_W32_SYSTEM*/
-# include <unistd.h>
 # include <sys/types.h>
 # include <sys/socket.h>
 # include <sys/time.h>
 # include <netdb.h>
 #endif /*!HAVE_W32_SYSTEM*/
 
+#ifdef WITHOUT_GNU_PTH /* Give the Makefile a chance to build without Pth.  */
+# undef HAVE_PTH
+# undef USE_GNU_PTH
+#endif
+
+#ifdef HAVE_PTH
+# include <pth.h>
+#endif
+
+
 #ifdef HTTP_USE_GNUTLS
 # include <gnutls/gnutls.h>
 /* For non-understandable reasons GNUTLS dropped the _t suffix from
@@ -65,15 +79,21 @@ typedef gnutls_session gnutls_session_t;
 typedef gnutls_transport_ptr gnutls_transport_ptr_t;
 #endif /*HTTP_USE_GNUTLS*/
 
+#ifdef TEST
+#undef USE_DNS_SRV
+#endif
+
 #include "util.h"
+#include "i18n.h"
 #include "http.h"
-
-/* If we are not compiling with SRV record support we provide stub
-   data structures. */
-#ifndef USE_DNS_SRV
-#ifndef MAXDNAME
-#define MAXDNAME 1025
-#endif
+#ifdef USE_DNS_SRV
+# include "srv.h"
+#else /*!USE_DNS_SRV*/
+  /* If we are not compiling with SRV record support we provide stub
+     data structures. */
+# ifndef MAXDNAME
+#  define MAXDNAME 1025
+# endif
 struct srventry
 {
   unsigned short priority;
@@ -102,26 +122,27 @@ struct srventry
                         "01234567890@"                 \
                         "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
 
-/* Define a prefix to map stream functions to the estream library. */
-#ifdef HTTP_USE_ESTREAM
-#define P_ES(a)  es_ ## a
+/* A long counter type.  */
+#ifdef HAVE_STRTOULL
+typedef unsigned long long longcounter_t;
+# define counter_strtoul(a) strtoull ((a), NULL, 10)
 #else
-#define P_ES(a)  a
+typedef unsigned long longcounter_t;
+# define counter_strtoul(a) strtoul ((a), NULL, 10)
 #endif
+
 #ifndef HTTP_USE_GNUTLS
 typedef void * gnutls_session_t;
 #endif
-#if defined(HTTP_USE_GNUTLS) && !defined(HTTP_USE_ESTREAM)
-#error Use of GNUTLS also requires support for Estream
-#endif
 
-static gpg_error_t do_parse_uri (parsed_uri_t uri, int only_local_part);
+static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part);
 static int remove_escapes (char *string);
 static int insert_escapes (char *buffer, const char *string,
                            const char *special);
 static uri_tuple_t parse_tuple (char *string);
-static gpg_error_t send_request (http_t hd,
-                                 const char *auth, const char *proxy);
+static gpg_error_t send_request (http_t hd, const char *auth,const char *proxy,
+                                const char *srvtag,strlist_t headers,
+                                 gpg_err_source_t errsource);
 static char *build_rel_path (parsed_uri_t uri);
 static gpg_error_t parse_response (http_t hd);
 
@@ -129,7 +150,6 @@ static int connect_server (const char *server, unsigned short port,
                            unsigned int flags, const char *srvtag);
 static gpg_error_t write_server (int sock, const char *data, size_t length);
 
-#ifdef HTTP_USE_ESTREAM
 static ssize_t cookie_read (void *cookie, void *buffer, size_t size);
 static ssize_t cookie_write (void *cookie, const void *buffer, size_t size);
 static int cookie_close (void *cookie);
@@ -144,21 +164,71 @@ static es_cookie_io_functions_t cookie_functions =
 
 struct cookie_s 
 {
-  int fd;  /* File descriptor or -1 if already closed. */
-  gnutls_session_t tls_session;  /* TLS session context or NULL if not used. */
-  int keep_socket; /* Flag to communicate with teh close handler. */
+  /* File descriptor or -1 if already closed. */
+  int fd;
+
+  /* TLS session context or NULL if not used. */
+  gnutls_session_t tls_session; 
+
+  /* The remaining content length and a flag telling whether to use
+     the content length.  */
+  longcounter_t content_length;  
+  unsigned int content_length_valid:1;
+
+  /* Flag to communicate with the close handler. */
+  unsigned int keep_socket:1; 
 };
 typedef struct cookie_s *cookie_t;
 
-#endif /*HTTP_USE_ESTREAM*/
-
 #ifdef HTTP_USE_GNUTLS
 static gpg_error_t (*tls_callback) (http_t, gnutls_session_t, int);
 #endif /*HTTP_USE_GNUTLS*/
 
 
+/* An object to save header lines. */
+struct header_s
+{
+  struct header_s *next;
+  char *value;    /* The value of the header (malloced).  */
+  char name[1];   /* The name of the header (canonicalized). */
+};
+typedef struct header_s *header_t;
+
+
+/* Our handle context. */
+struct http_context_s 
+{
+  unsigned int status_code;
+  int sock;
+  unsigned int in_data:1;
+  unsigned int is_http_0_9:1;
+  estream_t fp_read;
+  estream_t fp_write;
+  void *write_cookie;
+  void *read_cookie;
+  void *tls_context;
+  parsed_uri_t uri;
+  http_req_t req_type;
+  char *buffer;          /* Line buffer. */
+  size_t buffer_size;
+  unsigned int flags;
+  header_t headers;      /* Received headers. */
+};
+
+
+
+\f
+#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
+
+#if GNUPG_MAJOR_VERSION == 1
+#define REQ_WINSOCK_MAJOR  1
+#define REQ_WINSOCK_MINOR  1
+#else
+#define REQ_WINSOCK_MAJOR  2
+#define REQ_WINSOCK_MINOR  2
+#endif
+
 
-#ifdef HAVE_W32_SYSTEM
 static void
 deinit_sockets (void)
 {
@@ -174,23 +244,25 @@ init_sockets (void)
   if (initialized)
     return;
 
-  if ( WSAStartup( 0x0101, &wsdata ) ) 
+  if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) ) 
     {
       log_error ("error initializing socket library: ec=%d\n", 
                  (int)WSAGetLastError () );
       return;
     }
-  if ( wsdata.wVersion < 0x0001 ) 
+  if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR  
+       || HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR ) 
     {
-      log_error ("socket library version is %x.%x - but 1.1 needed\n",
-                 LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion));
+      log_error ("socket library version is %x.%x - but %d.%d needed\n",
+                 LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion),
+                 REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR);
       WSACleanup();
       return;
     }
   atexit ( deinit_sockets );
   initialized = 1;
 }
-#endif /*HAVE_W32_SYSTEM*/
+#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
 
 
 
@@ -248,44 +320,56 @@ http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) )
 {
 #ifdef HTTP_USE_GNUTLS
   tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb;
+#else
+  (void)cb;
 #endif  
 }
 
 
 
+/* Start a HTTP retrieval and return on success in R_HD a context
+   pointer for completing the the request and to wait for the
+   response. */
 gpg_error_t
-http_open (http_t hd, http_req_t reqtype, const char *url, 
-           const char *auth, unsigned int flags, const char *proxy,
-           void *tls_context)
+_http_open (http_t *r_hd, http_req_t reqtype, const char *url, 
+            const char *auth, unsigned int flags, const char *proxy,
+            void *tls_context, const char *srvtag, strlist_t headers,
+            gpg_err_source_t errsource)
 {
   gpg_error_t err;
+  http_t hd;
+  
+  *r_hd = NULL;
 
   if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST))
-    return gpg_error (GPG_ERR_INV_ARG);
+    return gpg_err_make (errsource, GPG_ERR_INV_ARG);
 
-  /* Initialize the handle. */
-  memset (hd, 0, sizeof *hd);
+  /* Create the handle. */
+  hd = xtrycalloc (1, sizeof *hd);
+  if (!hd)
+    return gpg_error_from_syserror ();
   hd->sock = -1;
-  hd->initialized = 1;
   hd->req_type = reqtype;
   hd->flags = flags;
   hd->tls_context = tls_context;
 
-  err = http_parse_uri (&hd->uri, url);
+  err = _http_parse_uri (&hd->uri, url, errsource);
   if (!err)
-    err = send_request (hd, auth, proxy);
+    err = send_request (hd, auth, proxy, srvtag, headers, errsource);
   
   if (err)
     {
       if (!hd->fp_read && !hd->fp_write && hd->sock != -1)
         sock_close (hd->sock);
       if (hd->fp_read)
-        P_ES(fclose) (hd->fp_read);
+        es_fclose (hd->fp_read);
       if (hd->fp_write)
-        P_ES(fclose) (hd->fp_write);
+        es_fclose (hd->fp_write);
       http_release_parsed_uri (hd->uri);
-      hd->initialized = 0;
+      xfree (hd);
     }
+  else
+    *r_hd = hd;
   return err;
 }
 
@@ -295,84 +379,60 @@ http_start_data (http_t hd)
 {
   if (!hd->in_data)
     {
-#ifdef HTTP_USE_ESTREAM
       es_fputs ("\r\n", hd->fp_write);
       es_fflush (hd->fp_write);
-#else
-      fflush (hd->fp_write);
-      write_server (hd->sock, "\r\n", 2);
-#endif
       hd->in_data = 1;
     }
   else
-    P_ES(fflush) (hd->fp_write);
+    es_fflush (hd->fp_write);
 }
 
 
 gpg_error_t
-http_wait_response (http_t hd, unsigned int *ret_status)
+_http_wait_response (http_t hd, gpg_err_source_t errsource)
 {
   gpg_error_t err;
+  cookie_t cookie;
 
   /* Make sure that we are in the data. */
   http_start_data (hd);        
 
-  /* We dup the socket, to cope with the fact that fclose closes the
-     underlying socket. In TLS mode we don't do that because we can't
-     close the socket gnutls is working on; instead we make sure that
-     the fclose won't close the socket in this case. */
-#ifdef HTTP_USE_ESTREAM
-  if (hd->write_cookie)
-    {
-      /* The write cookie is only set in the TLS case. */
-      cookie_t cookie = hd->write_cookie;
-      cookie->keep_socket = 1;
-    }
-  else
-#endif /*HTTP_USE_ESTREAM*/
-    {
-      hd->sock = dup (hd->sock);
-      if (hd->sock == -1)
-        return gpg_error_from_errno (errno);
-    }
-  P_ES(fclose) (hd->fp_write);
+  /* Close the write stream but keep the socket open.  */
+  cookie = hd->write_cookie;
+  if (!cookie)
+    return gpg_err_make (errsource, GPG_ERR_INTERNAL);
+
+  cookie->keep_socket = 1;
+  es_fclose (hd->fp_write);
   hd->fp_write = NULL;
-#ifdef HTTP_USE_ESTREAM
+  /* The close has released the cookie and thus we better set it to NULL.  */
   hd->write_cookie = NULL;
-#endif
 
-  if (!(hd->flags & HTTP_FLAG_NO_SHUTDOWN))
+  /* Shutdown one end of the socket is desired.  As per HTTP/1.0 this
+     is not required but some very old servers (e.g. the original pksd
+     key server didn't worked without it.  */
+  if ((hd->flags & HTTP_FLAG_SHUTDOWN))
     shutdown (hd->sock, 1);
   hd->in_data = 0;
 
-#ifdef HTTP_USE_ESTREAM
-  {
-    cookie_t cookie;
-
-    cookie = xtrycalloc (1, sizeof *cookie);
-    if (!cookie)
-      return gpg_error_from_errno (errno);
-    cookie->fd = hd->sock;
-    if (hd->uri->use_tls)
-      cookie->tls_session = hd->tls_context;
+  /* Create a new cookie and a stream for reading.  */
+  cookie = xtrycalloc (1, sizeof *cookie);
+  if (!cookie)
+    return gpg_err_make (errsource, gpg_err_code_from_syserror ());
+  cookie->fd = hd->sock;
+  if (hd->uri->use_tls)
+    cookie->tls_session = hd->tls_context;
 
-    hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
-    if (!hd->fp_read)
-      {
-        xfree (cookie);
-        return gpg_error_from_errno (errno);
-      }
-  }
-#else /*!HTTP_USE_ESTREAM*/
-  hd->fp_read = fdopen (hd->sock, "r");
+  hd->read_cookie = cookie;
+  hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
   if (!hd->fp_read)
-    return gpg_error_from_errno (errno);
-#endif /*!HTTP_USE_ESTREAM*/
+    {
+      xfree (cookie);
+      hd->read_cookie = NULL;
+      return gpg_err_make (errsource, gpg_err_code_from_syserror ());
+    }
 
   err = parse_response (hd);
-  if (!err && ret_status)
-    *ret_status = hd->status_code;
-
   return err;
 }
 
@@ -382,19 +442,21 @@ http_wait_response (http_t hd, unsigned int *ret_status)
    be used as an HTTP proxy and any enabled $http_proxy gets
    ignored. */
 gpg_error_t
-http_open_document (http_t hd, const char *document, 
-                    const char *auth, unsigned int flags, const char *proxy,
-                    void *tls_context)
+_http_open_document (http_t *r_hd, const char *document, 
+                     const char *auth, unsigned int flags, const char *proxy,
+                     void *tls_context, const char *srvtag, strlist_t headers,
+                     gpg_err_source_t errsource)
 {
   gpg_error_t err;
 
-  err = http_open (hd, HTTP_REQ_GET, document, auth, flags, proxy,tls_context);
+  err = _http_open (r_hd, HTTP_REQ_GET, document, auth, flags,
+                    proxy, tls_context, srvtag, headers, errsource);
   if (err)
     return err;
 
-  err = http_wait_response (hd, NULL);
+  err = _http_wait_response (*r_hd, errsource);
   if (err)
-    http_close (hd, 0);
+    http_close (*r_hd, 0);
 
   return err;
 }
@@ -403,32 +465,61 @@ http_open_document (http_t hd, const char *document,
 void
 http_close (http_t hd, int keep_read_stream)
 {
-  if (!hd || !hd->initialized)
+  if (!hd)
     return;
   if (!hd->fp_read && !hd->fp_write && hd->sock != -1)
     sock_close (hd->sock);
   if (hd->fp_read && !keep_read_stream)
-    P_ES(fclose) (hd->fp_read);
+    es_fclose (hd->fp_read);
   if (hd->fp_write)
-    P_ES(fclose) (hd->fp_write);
+    es_fclose (hd->fp_write);
   http_release_parsed_uri (hd->uri);
+  while (hd->headers)
+    {
+      header_t tmp = hd->headers->next;
+      xfree (hd->headers->value);
+      xfree (hd->headers);
+      hd->headers = tmp;
+    }
   xfree (hd->buffer);
-  hd->initialized = 0;
+  xfree (hd);
+}
+
+
+estream_t
+http_get_read_ptr (http_t hd)
+{
+  return hd?hd->fp_read:NULL;
+}
+
+estream_t
+http_get_write_ptr (http_t hd)
+{
+  return hd?hd->fp_write:NULL;
 }
 
+unsigned int
+http_get_status_code (http_t hd)
+{
+  return hd?hd->status_code:0;
+}
 
 
+\f
 /*
  * Parse an URI and put the result into the newly allocated RET_URI.
  * The caller must always use release_parsed_uri() to releases the
  * resources (even on error).
  */
 gpg_error_t
-http_parse_uri (parsed_uri_t * ret_uri, const char *uri)
+_http_parse_uri (parsed_uri_t * ret_uri, const char *uri,
+                 gpg_err_source_t errsource)
 {
-  *ret_uri = xcalloc (1, sizeof **ret_uri + strlen (uri));
+  *ret_uri = xtrycalloc (1, sizeof **ret_uri + strlen (uri));
+  if (!*ret_uri)
+    return gpg_err_make (errsource, gpg_err_code_from_syserror ());
   strcpy ((*ret_uri)->buffer, uri);
-  return do_parse_uri (*ret_uri, 0);
+  return gpg_err_make (errsource, do_parse_uri (*ret_uri, 0));
 }
 
 void
@@ -448,11 +539,11 @@ http_release_parsed_uri (parsed_uri_t uri)
 }
 
 
-static gpg_error_t
+static gpg_err_code_t
 do_parse_uri (parsed_uri_t uri, int only_local_part)
 {
   uri_tuple_t *tail;
-  char *p, *p2, *p3;
+  char *p, *p2, *p3, *pp;
   int n;
 
   p = uri->buffer;
@@ -466,15 +557,16 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
 
   /* A quick validity check. */
   if (strspn (p, VALID_URI_CHARS) != n)
-    return gpg_error (GPG_ERR_BAD_URI);        /* Invalid characters found. */
+    return GPG_ERR_BAD_URI;    /* Invalid characters found. */
 
   if (!only_local_part)
     {
       /* Find the scheme. */
       if (!(p2 = strchr (p, ':')) || p2 == p)
-       return gpg_error (GPG_ERR_BAD_URI); /* No scheme. */
+       return GPG_ERR_BAD_URI; /* No scheme. */
       *p2++ = 0;
-      strlwr (p);
+      for (pp=p; *pp; pp++)
+       *pp = tolower (*(unsigned char*)pp);
       uri->scheme = p;
       if (!strcmp (uri->scheme, "http"))
         uri->port = 80;
@@ -485,16 +577,14 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
           uri->use_tls = 1;
         }
 #endif
-      else if (!strcmp (uri->scheme, "hkp"))
-        uri->port = 11371;
       else
-       return gpg_error (GPG_ERR_INV_URI); /* Unsupported scheme */
+       return GPG_ERR_INV_URI; /* Unsupported scheme */
 
       p = p2;
 
       /* Find the hostname */
       if (*p != '/')
-       return gpg_error (GPG_ERR_INV_URI); /* Does not start with a slash. */
+       return GPG_ERR_INV_URI; /* Does not start with a slash. */
 
       p++;
       if (*p == '/') /* There seems to be a hostname. */
@@ -511,19 +601,30 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
               p = p3;
             }
 
-         strlwr (p);
-         uri->host = p;
+          for (pp=p; *pp; pp++)
+            *pp = tolower (*(unsigned char*)pp);
+
+         /* Handle an IPv6 literal */
+         if( *p == '[' && (p3=strchr( p, ']' )) )
+           {
+             *p3++ = '\0';
+             /* worst case, uri->host should have length 0, points to \0 */
+             uri->host = p + 1;
+             p = p3;
+           }
+         else
+           uri->host = p;
+
          if ((p3 = strchr (p, ':')))
            {
-             *p3++ = 0;
+             *p3++ = '\0';
              uri->port = atoi (p3);
            }
 
-         uri->host = p;
          if ((n = remove_escapes (uri->host)) < 0)
-           return gpg_error (GPG_ERR_BAD_URI);
-         if (n != strlen (p))
-           return gpg_error (GPG_ERR_BAD_URI); /* Hostname incudes a Nul. */
+           return GPG_ERR_BAD_URI;
+         if (n != strlen (uri->host))
+           return GPG_ERR_BAD_URI;     /* Hostname incudes a Nul. */
          p = p2 ? p2 : NULL;
        }
     } /* End global URI part. */
@@ -540,9 +641,9 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
 
   uri->path = p;
   if ((n = remove_escapes (p)) < 0)
-    return gpg_error (GPG_ERR_BAD_URI);
+    return GPG_ERR_BAD_URI;
   if (n != strlen (p))
-    return gpg_error (GPG_ERR_BAD_URI);        /* Path includes a Nul. */
+    return GPG_ERR_BAD_URI;    /* Path includes a Nul. */
   p = p2 ? p2 : NULL;
 
   if (!p || !*p)       
@@ -557,7 +658,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
       if ((p2 = strchr (p, '&')))
        *p2++ = 0;
       if (!(elem = parse_tuple (p)))
-       return gpg_error (GPG_ERR_BAD_URI);
+       return GPG_ERR_BAD_URI;
       *tail = elem;
       tail = &elem->next;
 
@@ -638,7 +739,7 @@ insert_escapes (char *buffer, const char *string,
        {
          if (buffer)
            {
-             sprintf (buffer, "%%%02X", *s);
+             snprintf (buffer, 4, "%%%02X", *s);
              buffer += 3;
            }
          n += 3;
@@ -648,6 +749,29 @@ insert_escapes (char *buffer, const char *string,
 }
 
 
+/* Allocate a new string from STRING using standard HTTP escaping as
+   well as escaping of characters given in SPECIALS.  A common pattern
+   for SPECIALS is "%;?&=". However it depends on the needs, for
+   example "+" and "/: often needs to be escaped too.  Returns NULL on
+   failure and sets ERRNO. */
+char *
+http_escape_string (const char *string, const char *specials)
+{
+  int n;
+  char *buf;
+
+  n = insert_escapes (NULL, string, specials);
+  buf = xtrymalloc (n+1);
+  if (buf)
+    {
+      insert_escapes (buf, string, specials);
+      buf[n] = 0;
+    }
+  return buf;
+}
+
+
+
 static uri_tuple_t
 parse_tuple (char *string)
 {
@@ -691,7 +815,9 @@ parse_tuple (char *string)
  * Returns 0 if the request was successful
  */
 static gpg_error_t
-send_request (http_t hd, const char *auth, const char *proxy)
+send_request (http_t hd, const char *auth,
+             const char *proxy, const char *srvtag, strlist_t headers,
+              gpg_err_source_t errsource)
 {
   gnutls_session_t tls_session;
   gpg_error_t err;
@@ -707,7 +833,7 @@ send_request (http_t hd, const char *auth, const char *proxy)
   if (hd->uri->use_tls && !tls_session)
     {
       log_error ("TLS requested but no GNUTLS context provided\n");
-      return gpg_error (GPG_ERR_INTERNAL);
+      return gpg_err_make (errsource, GPG_ERR_INTERNAL);
     }
 
   server = *hd->uri->host ? hd->uri->host : "localhost";
@@ -723,14 +849,13 @@ send_request (http_t hd, const char *auth, const char *proxy)
       if (proxy)
        http_proxy = proxy;
 
-      err = http_parse_uri (&uri, http_proxy);
+      err = _http_parse_uri (&uri, http_proxy, errsource);
       if (err)
        {
          log_error ("invalid HTTP proxy (%s): %s\n",
                     http_proxy, gpg_strerror (err));
          http_release_parsed_uri (uri);
-         return gpg_error (GPG_ERR_CONFIGURATION);
-
+         return gpg_err_make (errsource, GPG_ERR_CONFIGURATION);
        }
 
       if (uri->auth)
@@ -741,7 +866,7 @@ send_request (http_t hd, const char *auth, const char *proxy)
                                             uri->auth, strlen(uri->auth));
           if (!proxy_authstr)
             {
-              err = gpg_error_from_errno (errno);
+              err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
               http_release_parsed_uri (uri);
               return err;
             }
@@ -749,20 +874,22 @@ send_request (http_t hd, const char *auth, const char *proxy)
 
       hd->sock = connect_server (*uri->host ? uri->host : "localhost",
                                 uri->port ? uri->port : 80,
-                                 hd->flags, hd->uri->scheme);
+                                 hd->flags, srvtag);
       save_errno = errno;
       http_release_parsed_uri (uri);
     }
   else
     {
-      hd->sock = connect_server (server, port, hd->flags, hd->uri->scheme);
+      hd->sock = connect_server (server, port, hd->flags, srvtag);
       save_errno = errno;
     }
 
   if (hd->sock == -1)
     {
       xfree (proxy_authstr);
-      return gpg_error_from_errno (save_errno);
+      return gpg_err_make (errsource, (save_errno 
+                                       ? gpg_err_code_from_errno (save_errno)
+                                       : GPG_ERR_NOT_FOUND));
     }
 
 #ifdef HTTP_USE_GNUTLS
@@ -780,7 +907,7 @@ send_request (http_t hd, const char *auth, const char *proxy)
         {
           log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
           xfree (proxy_authstr);
-          return gpg_error (GPG_ERR_NETWORK);
+          return gpg_err_make (errsource, GPG_ERR_NETWORK);
         }
 
       if (tls_callback)
@@ -807,7 +934,7 @@ send_request (http_t hd, const char *auth, const char *proxy)
           if (!myauth)
             {
               xfree (proxy_authstr);
-              return gpg_error_from_errno (errno);
+              return gpg_err_make (errsource, gpg_err_code_from_syserror ());
             }
           remove_escapes (myauth);
         }
@@ -825,37 +952,24 @@ send_request (http_t hd, const char *auth, const char *proxy)
       if (!authstr)
         {
           xfree (proxy_authstr);
-          return gpg_error_from_errno (errno);
+          return gpg_err_make (errsource, gpg_err_code_from_syserror ());
         }
     }
   
   p = build_rel_path (hd->uri);
   if (!p)
-    return gpg_error_from_errno (errno);
-
-  request = xtrymalloc (2 * strlen (server) 
-                        + strlen (p)
-                        + (authstr?strlen(authstr):0)
-                        + (proxy_authstr?strlen(proxy_authstr):0)
-                        + 100);
-  if (!request)
-    {
-      err = gpg_error_from_errno (errno);
-      xfree (p);
-      xfree (authstr);
-      xfree (proxy_authstr);
-      return err;
-    }
+    return gpg_err_make (errsource, gpg_err_code_from_syserror ());
 
   if (http_proxy && *http_proxy)
     {
-      sprintf (request, "%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s",
-              hd->req_type == HTTP_REQ_GET ? "GET" :
-              hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
-              hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
-              server, port, *p == '/' ? "" : "/", p,
-              authstr ? authstr : "",
-               proxy_authstr ? proxy_authstr : "");
+      request = es_asprintf 
+        ("%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s",
+         hd->req_type == HTTP_REQ_GET ? "GET" :
+         hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
+         hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
+         server, port, *p == '/' ? "" : "/", p,
+         authstr ? authstr : "",
+         proxy_authstr ? proxy_authstr : "");
     }
   else
     {
@@ -864,19 +978,26 @@ send_request (http_t hd, const char *auth, const char *proxy)
       if (port == 80)
         *portstr = 0;
       else
-        sprintf (portstr, ":%u", port);
-
-      sprintf (request, "%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
-              hd->req_type == HTTP_REQ_GET ? "GET" :
-              hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
-              hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
-              *p == '/' ? "" : "/", p, server, portstr,
-               authstr? authstr:"");
+        snprintf (portstr, sizeof portstr, ":%u", port);
+
+      request = es_asprintf 
+        ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
+         hd->req_type == HTTP_REQ_GET ? "GET" :
+         hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
+         hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
+         *p == '/' ? "" : "/", p, server, portstr,
+         authstr? authstr:"");
     }
   xfree (p);
+  if (!request)
+    {
+      err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+      xfree (authstr);
+      xfree (proxy_authstr);
+      return err;
+    }
 
 
-#ifdef HTTP_USE_ESTREAM
   /* First setup estream so that we can write even the first line
      using estream.  This is also required for the sake of gnutls. */
   {
@@ -885,44 +1006,42 @@ send_request (http_t hd, const char *auth, const char *proxy)
     cookie = xtrycalloc (1, sizeof *cookie);
     if (!cookie)
       {
-        err = gpg_error_from_errno (errno);
+        err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
         goto leave;
       }
     cookie->fd = hd->sock;
+    hd->write_cookie = cookie;
     if (hd->uri->use_tls)
-      {
-        cookie->tls_session = tls_session;
-        hd->write_cookie = cookie;
-      }
+      cookie->tls_session = tls_session;
 
     hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
     if (!hd->fp_write)
       {
+        err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
         xfree (cookie);
-        err = gpg_error_from_errno (errno);
+        hd->write_cookie = NULL;
       }
     else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
-      err = gpg_error_from_errno (errno);
+      err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
     else
       err = 0;
-  }
-
- leave:
 
-#else /*!HTTP_USE_ESTREAM*/
-  /* We send out the start of the request through our own send
-     function and only then assign a stdio stream.  This allows for
-     better error reporting that through standard stdio means. */
-  err = write_server (hd->sock, request, strlen (request));
   if (!err)
     {
-      hd->fp_write = fdopen (hd->sock, "w");
-      if (!hd->fp_write)
-        err = gpg_error_from_errno (errno);
+      for (;headers; headers=headers->next)
+        {
+          if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
+              || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
+            {
+              err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+              break;
+            }
+        }
     }
-#endif /*!HTTP_USE_ESTREAM*/
-
-  xfree (request);
+  }
+  
+ leave:
+  es_free (request);
   xfree (authstr);
   xfree (proxy_authstr);
 
@@ -982,119 +1101,164 @@ build_rel_path (parsed_uri_t uri)
 }
 
 
+/* Transform a header name into a standard capitalized format; e.g.
+   "Content-Type".  Conversion stops at the colon.  As usual we don't
+   use the localized versions of ctype.h. */
+static void
+capitalize_header_name (char *name)
+{
+  int first = 1;
+
+  for (; *name && *name != ':'; name++)
+    {
+      if (*name == '-')
+        first = 1;
+      else if (first)
+        {
+          if (*name >= 'a' && *name <= 'z')
+            *name = *name - 'a' + 'A';
+          first = 0;
+        }
+      else if (*name >= 'A' && *name <= 'Z')
+        *name = *name - 'A' + 'a';
+    }
+}
+
 
-/*
-   Same as fgets() but if the buffer is too short a larger one will be
-   allocated up to some limit *MAX_LENGTH.  A line is considered a
-   byte stream ending in a LF.  Returns the length of the line. EOF is
-   indicated by a line of length zero. The last LF may be missing due
-   to an EOF.  If MAX_LENGTH is zero on return, the line has been
-   truncated.  If the returned buffer is NULL, not enough memory was
-   enable to increase it, the return value will also be 0 and some
-   bytes might have been lost which should be no problem becuase
-   out-of-memory is pretty fatal for most applications.
-
-   If a line has been truncated, the file pointer is internally moved
-   forward to the end of the line.
-
-   Note: The returned buffer is allocated with enough extra space to
-   append a CR,LF,Nul
- */
-static size_t
-my_read_line (
-#ifdef HTTP_USE_ESTREAM
-              estream_t fp,
-#else
-              FILE *fp,
-#endif
-              char **addr_of_buffer,
-              size_t *length_of_buffer, size_t *max_length)
+/* Store an HTTP header line in LINE away.  Line continuation is
+   supported as well as merging of headers with the same name. This
+   function may modify LINE. */
+static gpg_err_code_t
+store_header (http_t hd, char *line)
 {
-  int c;
-  char *buffer = *addr_of_buffer;
-  size_t length = *length_of_buffer;
-  size_t nbytes = 0;
-  size_t maxlen = *max_length;
-  char *p;
-
-  if (!buffer) /* Must allocate a new buffer. */
-    {          
-      length = 256;
-      buffer = xtrymalloc (length);
-      *addr_of_buffer = buffer;
-      if (!buffer)
-       {
-         *length_of_buffer = *max_length = 0;
-         return 0;
-       }
-      *length_of_buffer = length;
+  size_t n;
+  char *p, *value;
+  header_t h;
+
+  n = strlen (line);
+  if (n && line[n-1] == '\n')
+    {
+      line[--n] = 0;
+      if (n && line[n-1] == '\r')
+        line[--n] = 0;
+    }
+  if (!n)  /* we are never called to hit this. */
+    return GPG_ERR_BUG;
+  if (*line == ' ' || *line == '\t')
+    {
+      /* Continuation. This won't happen too often as it is not
+         recommended.  We use a straightforward implementaion. */
+      if (!hd->headers)
+        return GPG_ERR_PROTOCOL_VIOLATION;
+      n += strlen (hd->headers->value);
+      p = xtrymalloc (n+1);
+      if (!p)
+        return gpg_err_code_from_syserror ();
+      strcpy (stpcpy (p, hd->headers->value), line);
+      xfree (hd->headers->value);
+      hd->headers->value = p;
+      return 0;
     }
 
-  length -= 3; /* Reserve 3 bytes (cr,lf,eol). */
-  p = buffer;
-  while ((c = P_ES(getc) (fp)) != EOF)
+  capitalize_header_name (line);
+  p = strchr (line, ':');
+  if (!p)
+    return GPG_ERR_PROTOCOL_VIOLATION;
+  *p++ = 0;
+  while (*p == ' ' || *p == '\t')
+    p++;
+  value = p;
+  
+  for (h=hd->headers; h; h = h->next)
+    if ( !strcmp (h->name, line) )
+      break;
+  if (h)
     {
-      if (nbytes == length) /* Increase the buffer. */
-       {                       
-         if (length > maxlen) /* Limit reached. */
-           {
-             /* Skip the rest of the line. */
-             while (c != '\n' && (c = P_ES(getc) (fp)) != EOF)
-               ;
-             *p++ = '\n'; /* Always append a LF (we reserved some space). */
-             nbytes++;
-             *max_length = 0; /* Indicate truncation */
-             break; /*(the while loop)*/
-           }
-         length += 3; /* Adjust for the reserved bytes. */
-         length += length < 1024 ? 256 : 1024;
-         *addr_of_buffer = xtryrealloc (buffer, length);
-         if (!*addr_of_buffer)
-           {
-             int save_errno = errno;
-             xfree (buffer);
-             *length_of_buffer = *max_length = 0;
-             errno = save_errno;
-             return 0;
-           }
-         buffer = *addr_of_buffer;
-         *length_of_buffer = length;
-         length -= 3; /* And re-adjust for the reservation. */
-         p = buffer + nbytes;
-       }
-      *p++ = c;
-      nbytes++;
-      if (c == '\n')
-       break;
+      /* We have already seen a line with that name.  Thus we assume
+         it is a comma separated list and merge them.  */
+      p = xtrymalloc (strlen (h->value) + 1 + strlen (value)+ 1);
+      if (!p)
+        return gpg_err_code_from_syserror ();
+      strcpy (stpcpy (stpcpy (p, h->value), ","), value);
+      xfree (h->value);
+      h->value = p;
+      return 0;
+    }
+
+  /* Append a new header. */
+  h = xtrymalloc (sizeof *h + strlen (line));
+  if (!h)
+    return gpg_err_code_from_syserror ();
+  strcpy (h->name, line);
+  h->value = xtrymalloc (strlen (value)+1);
+  if (!h->value)
+    {
+      xfree (h);
+      return gpg_err_code_from_syserror ();
     }
-  *p = 0; /* Make sure the line is a string. */
+  strcpy (h->value, value);
+  h->next = hd->headers;
+  hd->headers = h;
+
+  return 0;
+}
+
+
+/* Return the header NAME from the last response.  The returned value
+   is valid as along as HD has not been closed and no othe request has
+   been send. If the header was not found, NULL is returned.  Name
+   must be canonicalized, that is the first letter of each dash
+   delimited part must be uppercase and all other letters lowercase.  */
+const char *
+http_get_header (http_t hd, const char *name)
+{
+  header_t h;
 
-  return nbytes;
+  for (h=hd->headers; h; h = h->next)
+    if ( !strcmp (h->name, name) )
+      return h->value;
+  return NULL;
 }
 
 
+
 /*
  * Parse the response from a server.
  * Returns: Errorcode and sets some files in the handle
  */
-static gpg_error_t
+static gpg_err_code_t
 parse_response (http_t hd)
 {
   char *line, *p, *p2;
   size_t maxlen, len;
+  cookie_t cookie = hd->read_cookie;
+  const char *s;
+
+  /* Delete old header lines.  */
+  while (hd->headers)
+    {
+      header_t tmp = hd->headers->next;
+      xfree (hd->headers->value);
+      xfree (hd->headers);
+      hd->headers = tmp;
+    }
 
   /* Wait for the status line. */
   do
     {
       maxlen = MAX_LINELEN;
-      len = my_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
+      len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
       line = hd->buffer;
       if (!line)
-       return gpg_error_from_errno (errno); /* Out of core. */
+       return gpg_err_code_from_syserror (); /* Out of core. */
       if (!maxlen)
-       return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */
+       return GPG_ERR_TRUNCATED; /* Line has been truncated. */
       if (!len)
-       return gpg_error (GPG_ERR_EOF);
+       return GPG_ERR_EOF;
+
+      if ((hd->flags & HTTP_FLAG_LOG_RESP))
+        log_info ("RESP: `%.*s'\n",
+                  (int)strlen(line)-(*line&&line[1]?2:0),line);
     }
   while (!*line);
 
@@ -1128,19 +1292,39 @@ parse_response (http_t hd)
   do
     {
       maxlen = MAX_LINELEN;
-      len = my_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
+      len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
       line = hd->buffer;
       if (!line)
-       return gpg_error_from_errno (errno); /* Out of core. */
+       return gpg_err_code_from_syserror (); /* Out of core. */
       /* Note, that we can silently ignore truncated lines. */
       if (!len)
-       return gpg_error (GPG_ERR_EOF);
+       return GPG_ERR_EOF;
       /* Trim line endings of empty lines. */
       if ((*line == '\r' && line[1] == '\n') || *line == '\n')
        *line = 0;
+      if ((hd->flags & HTTP_FLAG_LOG_RESP))
+        log_info ("RESP: `%.*s'\n",
+                  (int)strlen(line)-(*line&&line[1]?2:0),line);
+      if (*line)
+        {
+          gpg_err_code_t ec = store_header (hd, line);
+          if (ec)
+            return ec;
+        }
     }
   while (len && *line);
 
+  cookie->content_length_valid = 0;
+  if (!(hd->flags & HTTP_FLAG_IGNORE_CL))
+    {
+      s = http_get_header (hd, "Content-Length");
+      if (s)
+        {
+          cookie->content_length_valid = 1;
+          cookie->content_length = counter_strtoul (s);
+        }
+    }
+
   return 0;
 }
 
@@ -1221,7 +1405,7 @@ start_server ()
 }
 #endif
 
-/* Actually connect to a server.  Returns the file descripto or -1 on
+/* Actually connect to a server.  Returns the file descriptor or -1 on
    error.  ERRNO is set on error. */
 static int
 connect_server (const char *server, unsigned short port,
@@ -1233,11 +1417,17 @@ connect_server (const char *server, unsigned short port,
   int srv, connected;
   int last_errno = 0;
   struct srventry *serverlist = NULL;
-
 #ifdef HAVE_W32_SYSTEM
   unsigned long inaddr;
+#endif
+  /* Not currently using the flags */
+  (void)flags;
 
-  init_sockets();
+#ifdef HAVE_W32_SYSTEM
+
+#ifndef HTTP_NO_WSASTARTUP
+  init_sockets ();
+#endif
   /* Win32 gethostbyname doesn't handle IP addresses internally, so we
      try inet_addr first on that platform only. */
   inaddr = inet_addr(server);
@@ -1267,18 +1457,21 @@ connect_server (const char *server, unsigned short port,
 
 #ifdef USE_DNS_SRV
   /* Do the SRV thing */
-  if ((flags & HTTP_FLAG_TRY_SRV) && srvtag)
+  if (srvtag)
     {
       /* We're using SRV, so append the tags. */
       if (1+strlen (srvtag) + 6 + strlen (server) + 1 <= MAXDNAME)
        {
          char srvname[MAXDNAME];
 
-         stprcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
+         stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
                            "._tcp."), server);
          srvcount = getsrv (srvname, &serverlist);
        }
     }
+#else
+  (void)flags;
+  (void)srvtag;
 #endif /*USE_DNS_SRV*/
 
   if (!serverlist)
@@ -1301,7 +1494,7 @@ connect_server (const char *server, unsigned short port,
       struct addrinfo hints, *res, *ai;
       char portstr[35];
 
-      sprintf (portstr, "%hu", port);
+      snprintf (portstr, sizeof portstr, "%hu", port);
       memset (&hints, 0, sizeof (hints));
       hints.ai_socktype = SOCK_STREAM;
       if (getaddrinfo (serverlist[srv].target, portstr, &hints, &res))
@@ -1404,7 +1597,7 @@ connect_server (const char *server, unsigned short port,
 #endif
       if (sock != -1)
        sock_close (sock);
-      errno = last_errno;
+      gpg_err_set_errno (last_errno);
       return -1;
     }
   return sock;
@@ -1415,21 +1608,25 @@ static gpg_error_t
 write_server (int sock, const char *data, size_t length)
 {
   int nleft;
+  int nwritten;
 
+  /* FIXME: We would better use pth I/O functions.  */
   nleft = length;
   while (nleft > 0)
     {
-#ifdef HAVE_W32_SYSTEM
-      int nwritten;
-      
+#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_PTH)
       nwritten = send (sock, data, nleft, 0);
       if ( nwritten == SOCKET_ERROR ) 
         {
           log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
-          return G10ERR_NETWORK;
+          return gpg_error (GPG_ERR_NETWORK);
         }
-#else /*!HAVE_W32_SYSTEM*/
-      int nwritten = write (sock, data, nleft);
+#else /*!HAVE_W32_SYSTEM || HAVE_PTH*/
+# ifdef HAVE_PTH
+      nwritten = pth_write (sock, data, nleft);
+# else
+      nwritten = write (sock, data, nleft);
+# endif
       if (nwritten == -1)
        {
          if (errno == EINTR)
@@ -1444,9 +1641,9 @@ write_server (int sock, const char *data, size_t length)
              continue;
            }
          log_info ("network write failed: %s\n", strerror (errno));
-         return gpg_error_from_errno (errno);
+         return gpg_error_from_syserror ();
        }
-#endif /*!HAVE_W32_SYSTEM*/
+#endif /*!HAVE_W32_SYSTEM || HAVE_PTH*/
       nleft -= nwritten;
       data += nwritten;
     }
@@ -1456,7 +1653,6 @@ write_server (int sock, const char *data, size_t length)
 
 
 \f
-#ifdef HTTP_USE_ESTREAM
 /* Read handler for estream.  */
 static ssize_t
 cookie_read (void *cookie, void *buffer, size_t size)
@@ -1464,6 +1660,14 @@ cookie_read (void *cookie, void *buffer, size_t size)
   cookie_t c = cookie;
   int nread;
 
+  if (c->content_length_valid)
+    {
+      if (!c->content_length)
+        return 0; /* EOF */
+      if (c->content_length < size)
+        size = c->content_length;
+    }
+
 #ifdef HTTP_USE_GNUTLS
   if (c->tls_session)
     {
@@ -1485,7 +1689,7 @@ cookie_read (void *cookie, void *buffer, size_t size)
           if (nread == GNUTLS_E_REHANDSHAKE)
             goto again; /* A client is allowed to just ignore this request. */
           log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
-          errno = EIO;
+          gpg_err_set_errno (EIO);
           return -1;
         }
     }
@@ -1494,11 +1698,26 @@ cookie_read (void *cookie, void *buffer, size_t size)
     {
       do
         {
+#ifdef HAVE_PTH
+          nread = pth_read (c->fd, buffer, size);
+#elif defined(HAVE_W32_SYSTEM)
+          /* Under Windows we need to use recv for a socket.  */
+          nread = recv (c->fd, buffer, size, 0);
+#else          
           nread = read (c->fd, buffer, size);
+#endif
         }
       while (nread == -1 && errno == EINTR);
     }
 
+  if (c->content_length_valid && nread > 0)
+    {
+      if (nread < c->content_length)
+        c->content_length -= nread;
+      else
+        c->content_length = 0;          
+    }
+
   return nread;
 }
 
@@ -1531,7 +1750,7 @@ cookie_write (void *cookie, const void *buffer, size_t size)
                 }
               log_info ("TLS network write failed: %s\n",
                         gnutls_strerror (nwritten));
-              errno = EIO;
+              gpg_err_set_errno (EIO);
               return -1;
             }
           nleft -= nwritten;
@@ -1543,7 +1762,7 @@ cookie_write (void *cookie, const void *buffer, size_t size)
     {
       if ( write_server (c->fd, buffer, size) )
         {
-          errno = EIO;
+          gpg_err_set_errno (EIO);
           nwritten = -1;
         }
       else
@@ -1562,19 +1781,18 @@ cookie_close (void *cookie)
   if (!c)
     return 0;
 
- #ifdef HTTP_USE_GNUTLS
+#ifdef HTTP_USE_GNUTLS
   if (c->tls_session && !c->keep_socket)
     {
       gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR);
     }
 #endif /*HTTP_USE_GNUTLS*/
   if (c->fd != -1 && !c->keep_socket)
-    close (c->fd);
+    sock_close (c->fd);
 
   xfree (c);
   return 0;
 }
-#endif /*HTTP_USE_ESTREAM*/
 
 
 
@@ -1603,17 +1821,16 @@ main (int argc, char **argv)
   int rc;
   parsed_uri_t uri;
   uri_tuple_t r;
-  struct http_context_s hd;
+  http_t hd;
   int c;
   gnutls_session_t tls_session = NULL;
 #ifdef HTTP_USE_GNUTLS
   gnutls_certificate_credentials certcred;
   const int certprio[] = { GNUTLS_CRT_X509, 0 };
 #endif /*HTTP_USE_GNUTLS*/
+  header_t hdr;
 
-#ifdef HTTP_USE_ESTREAM
   es_init ();
-#endif
   log_set_prefix ("http-test", 1 | 4);
   if (argc == 1)
     {
@@ -1699,17 +1916,28 @@ main (int argc, char **argv)
   http_release_parsed_uri (uri);
   uri = NULL;
 
-  rc = http_open_document (&hd, *argv, NULL, HTTP_FLAG_NO_SHUTDOWN,
-                           NULL, tls_session);
+  rc = http_open_document (&hd, *argv, NULL, 0, NULL, tls_session);
   if (rc)
     {
       log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc));
       return 1;
     }
-  log_info ("open_http_document succeeded; status=%u\n", hd.status_code);
-  while ((c = P_ES(getc) (hd.fp_read)) != EOF)
-    putchar (c);
-  http_close (&hd, 0);
+  log_info ("open_http_document succeeded; status=%u\n",
+            http_get_status_code (hd));
+  for (hdr = hd->headers; hdr; hdr = hdr->next)
+    printf ("HDR: %s: %s\n", hdr->name, hdr->value);
+  switch (http_get_status_code (hd))
+    {
+    case 200:
+      while ((c = es_getc (http_get_read_ptr (hd))) != EOF)
+        putchar (c);
+      break;
+    case 301:
+    case 302:
+      printf ("Redirected to `%s'\n", http_get_header (hd, "Location"));
+      break;
+    }
+  http_close (hd, 0);
 
 #ifdef HTTP_USE_GNUTLS
   gnutls_deinit (tls_session);