Marked all unused args on non-W32 platforms.
[gnupg.git] / common / http.c
index 22ee5c2..96e2a9e 100644 (file)
@@ -6,7 +6,7 @@
  *
  * 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,
@@ -15,9 +15,7 @@
  * 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
   - 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>
@@ -65,12 +67,18 @@ 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"
-
+#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 USE_DNS_SRV
 #ifndef MAXDNAME
 #define MAXDNAME 1025
 #endif
@@ -156,6 +164,17 @@ typedef struct cookie_s *cookie_t;
 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 
 {
@@ -177,12 +196,23 @@ struct http_context_s
   char *buffer;          /* Line buffer. */
   size_t buffer_size;
   unsigned int flags;
+  header_t headers;      /* Received headers. */
 };
 
 
 
 \f
-#ifdef HAVE_W32_SYSTEM
+#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
+
+
 static void
 deinit_sockets (void)
 {
@@ -198,23 +228,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*/
 
 
 
@@ -272,6 +304,8 @@ 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  
 }
 
@@ -296,7 +330,7 @@ http_open (http_t *r_hd, http_req_t reqtype, const char *url,
   /* Create the handle. */
   hd = xtrycalloc (1, sizeof *hd);
   if (!hd)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
   hd->sock = -1;
   hd->req_type = reqtype;
   hd->flags = flags;
@@ -364,9 +398,18 @@ http_wait_response (http_t hd)
   else
 #endif /*HTTP_USE_ESTREAM*/
     {
+#ifdef HAVE_W32_SYSTEM
+      HANDLE handle = (HANDLE)hd->sock;
+      if (!DuplicateHandle (GetCurrentProcess(), handle,
+                           GetCurrentProcess(), &handle, 0,
+                           TRUE, DUPLICATE_SAME_ACCESS ))
+       return gpg_error_from_syserror ();
+      hd->sock = (int)handle;
+#else
       hd->sock = dup (hd->sock);
+#endif
       if (hd->sock == -1)
-        return gpg_error_from_errno (errno);
+        return gpg_error_from_syserror ();
     }
   P_ES(fclose) (hd->fp_write);
   hd->fp_write = NULL;
@@ -384,7 +427,7 @@ http_wait_response (http_t hd)
 
     cookie = xtrycalloc (1, sizeof *cookie);
     if (!cookie)
-      return gpg_error_from_errno (errno);
+      return gpg_error_from_syserror ();
     cookie->fd = hd->sock;
     if (hd->uri->use_tls)
       cookie->tls_session = hd->tls_context;
@@ -393,13 +436,13 @@ http_wait_response (http_t hd)
     if (!hd->fp_read)
       {
         xfree (cookie);
-        return gpg_error_from_errno (errno);
+        return gpg_error_from_syserror ();
       }
   }
 #else /*!HTTP_USE_ESTREAM*/
   hd->fp_read = fdopen (hd->sock, "r");
   if (!hd->fp_read)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 #endif /*!HTTP_USE_ESTREAM*/
 
   err = parse_response (hd);
@@ -443,6 +486,13 @@ http_close (http_t hd, int keep_read_stream)
   if (hd->fp_write)
     P_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);
   xfree (hd);
 }
@@ -547,8 +597,6 @@ 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 */
 
@@ -827,7 +875,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_error_from_syserror ();
               http_release_parsed_uri (uri);
               return err;
             }
@@ -848,7 +896,9 @@ send_request (http_t hd, const char *auth, const char *proxy)
   if (hd->sock == -1)
     {
       xfree (proxy_authstr);
-      return gpg_error_from_errno (save_errno);
+      return (save_errno 
+              ? gpg_error_from_errno (save_errno)
+              : gpg_error (GPG_ERR_NOT_FOUND));
     }
 
 #ifdef HTTP_USE_GNUTLS
@@ -893,7 +943,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_error_from_syserror ();
             }
           remove_escapes (myauth);
         }
@@ -911,13 +961,13 @@ send_request (http_t hd, const char *auth, const char *proxy)
       if (!authstr)
         {
           xfree (proxy_authstr);
-          return gpg_error_from_errno (errno);
+          return gpg_error_from_syserror ();
         }
     }
   
   p = build_rel_path (hd->uri);
   if (!p)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 
   request = xtrymalloc (2 * strlen (server) 
                         + strlen (p)
@@ -926,7 +976,7 @@ send_request (http_t hd, const char *auth, const char *proxy)
                         + 100);
   if (!request)
     {
-      err = gpg_error_from_errno (errno);
+      err = gpg_error_from_syserror ();
       xfree (p);
       xfree (authstr);
       xfree (proxy_authstr);
@@ -971,7 +1021,7 @@ 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_error_from_syserror ();
         goto leave;
       }
     cookie->fd = hd->sock;
@@ -985,10 +1035,10 @@ send_request (http_t hd, const char *auth, const char *proxy)
     if (!hd->fp_write)
       {
         xfree (cookie);
-        err = gpg_error_from_errno (errno);
+        err = gpg_error_from_syserror ();
       }
     else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
-      err = gpg_error_from_errno (errno);
+      err = gpg_error_from_syserror ();
     else
       err = 0;
   }
@@ -1004,7 +1054,7 @@ send_request (http_t hd, const char *auth, const char *proxy)
     {
       hd->fp_write = fdopen (hd->sock, "w");
       if (!hd->fp_write)
-        err = gpg_error_from_errno (errno);
+        err = gpg_error_from_syserror ();
     }
 #endif /*!HTTP_USE_ESTREAM*/
 
@@ -1159,6 +1209,129 @@ my_read_line (
 }
 
 
+/* 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';
+    }
+}
+
+
+/* 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_error_t
+store_header (http_t hd, char *line)
+{
+  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_error (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_error (GPG_ERR_PROTOCOL_VIOLATION);
+      n += strlen (hd->headers->value);
+      p = xtrymalloc (n+1);
+      if (!p)
+        return gpg_error_from_syserror ();
+      strcpy (stpcpy (p, hd->headers->value), line);
+      xfree (hd->headers->value);
+      hd->headers->value = p;
+      return 0;
+    }
+
+  capitalize_header_name (line);
+  p = strchr (line, ':');
+  if (!p)
+    return gpg_error (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)
+    {
+      /* 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_error_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_error_from_syserror ();
+  strcpy (h->name, line);
+  h->value = xtrymalloc (strlen (value)+1);
+  if (!h->value)
+    {
+      xfree (h);
+      return gpg_error_from_syserror ();
+    }
+  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.
+   Note that the context must have been opened with the
+   HTTP_FLAG_NEED_HEADER. */
+const char *
+http_get_header (http_t hd, const char *name)
+{
+  header_t h;
+
+  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
@@ -1169,6 +1342,15 @@ parse_response (http_t hd)
   char *line, *p, *p2;
   size_t maxlen, len;
 
+  /* 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
     {
@@ -1176,7 +1358,7 @@ parse_response (http_t hd)
       len = my_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_error_from_syserror (); /* Out of core. */
       if (!maxlen)
        return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */
       if (!len)
@@ -1220,7 +1402,7 @@ parse_response (http_t hd)
       len = my_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_error_from_syserror (); /* Out of core. */
       /* Note, that we can silently ignore truncated lines. */
       if (!len)
        return gpg_error (GPG_ERR_EOF);
@@ -1230,6 +1412,12 @@ parse_response (http_t hd)
       if ( (hd->flags & HTTP_FLAG_LOG_RESP) )
         log_info ("RESP: `%.*s'\n",
                   (int)strlen(line)-(*line&&line[1]?2:0),line);
+      if ( (hd->flags & HTTP_FLAG_NEED_HEADER) && *line )
+        {
+          gpg_error_t err = store_header (hd, line);
+          if (err)
+            return err;
+        }
     }
   while (len && *line);
 
@@ -1313,7 +1501,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,
@@ -1329,7 +1517,9 @@ connect_server (const char *server, unsigned short port,
 #ifdef HAVE_W32_SYSTEM
   unsigned long inaddr;
 
-  init_sockets();
+#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);
@@ -1366,7 +1556,7 @@ connect_server (const char *server, unsigned short port,
        {
          char srvname[MAXDNAME];
 
-         stprcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
+         stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
                            "._tcp."), server);
          srvcount = getsrv (srvname, &serverlist);
        }
@@ -1518,7 +1708,7 @@ write_server (int sock, const char *data, size_t length)
       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);
@@ -1536,7 +1726,7 @@ 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*/
       nleft -= nwritten;
@@ -1586,7 +1776,12 @@ cookie_read (void *cookie, void *buffer, size_t size)
     {
       do
         {
+#ifdef 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);
     }
@@ -1654,14 +1849,14 @@ 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;
@@ -1702,6 +1897,7 @@ main (int argc, char **argv)
   gnutls_certificate_credentials certcred;
   const int certprio[] = { GNUTLS_CRT_X509, 0 };
 #endif /*HTTP_USE_GNUTLS*/
+  header_t hdr;
 
 #ifdef HTTP_USE_ESTREAM
   es_init ();
@@ -1791,7 +1987,8 @@ main (int argc, char **argv)
   http_release_parsed_uri (uri);
   uri = NULL;
 
-  rc = http_open_document (&hd, *argv, NULL, HTTP_FLAG_NO_SHUTDOWN,
+  rc = http_open_document (&hd, *argv, NULL, 
+                           HTTP_FLAG_NO_SHUTDOWN | HTTP_FLAG_NEED_HEADER,
                            NULL, tls_session);
   if (rc)
     {
@@ -1800,8 +1997,19 @@ main (int argc, char **argv)
     }
   log_info ("open_http_document succeeded; status=%u\n",
             http_get_status_code (hd));
-  while ((c = P_ES(getc) (http_get_read_ptr (hd))) != EOF)
-    putchar (c);
+  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 = P_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