Added missing file
[gnupg.git] / common / http.c
index 7898d86..83b6216 100644 (file)
@@ -65,12 +65,17 @@ 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 "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
@@ -157,7 +162,43 @@ 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;
+  int in_data;
+#ifdef HTTP_USE_ESTREAM
+  estream_t fp_read;
+  estream_t fp_write;
+  void *write_cookie;
+#else /*!HTTP_USE_ESTREAM*/
+  FILE *fp_read;
+  FILE *fp_write;
+#endif /*!HTTP_USE_ESTREAM*/
+  void *tls_context;
+  int is_http_0_9;
+  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
 #ifdef HAVE_W32_SYSTEM
 static void
 deinit_sockets (void)
@@ -253,20 +294,27 @@ http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) )
 
 
 
+/* 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, 
+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)
 {
   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);
 
-  /* Initialize the handle. */
-  memset (hd, 0, sizeof *hd);
+  /* Create the handle. */
+  hd = xtrycalloc (1, sizeof *hd);
+  if (!hd)
+    return gpg_error_from_errno (errno);
   hd->sock = -1;
-  hd->initialized = 1;
   hd->req_type = reqtype;
   hd->flags = flags;
   hd->tls_context = tls_context;
@@ -284,8 +332,10 @@ http_open (http_t hd, http_req_t reqtype, const char *url,
       if (hd->fp_write)
         P_ES(fclose) (hd->fp_write);
       http_release_parsed_uri (hd->uri);
-      hd->initialized = 0;
+      xfree (hd);
     }
+  else
+    *r_hd = hd;
   return err;
 }
 
@@ -310,7 +360,7 @@ http_start_data (http_t hd)
 
 
 gpg_error_t
-http_wait_response (http_t hd, unsigned int *ret_status)
+http_wait_response (http_t hd)
 {
   gpg_error_t err;
 
@@ -370,9 +420,6 @@ http_wait_response (http_t hd, unsigned int *ret_status)
 #endif /*!HTTP_USE_ESTREAM*/
 
   err = parse_response (hd);
-  if (!err && ret_status)
-    *ret_status = hd->status_code;
-
   return err;
 }
 
@@ -382,19 +429,20 @@ 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, 
+http_open_document (http_t *r_hd, const char *document, 
                     const char *auth, unsigned int flags, const char *proxy,
                     void *tls_context)
 {
   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);
   if (err)
     return err;
 
-  err = http_wait_response (hd, NULL);
+  err = http_wait_response (*r_hd);
   if (err)
-    http_close (hd, 0);
+    http_close (*r_hd, 0);
 
   return err;
 }
@@ -403,7 +451,7 @@ 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);
@@ -412,12 +460,49 @@ 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);
-  hd->initialized = 0;
+  xfree (hd);
 }
 
 
+#ifdef HTTP_USE_ESTREAM
+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;
+}
+#else /*!HTTP_USE_ESTREAM*/
+FILE *
+http_get_read_ptr (http_t hd)
+{
+  return hd?hd->fp_read:NULL;
+}
+FILE *
+http_get_write_ptr (http_t hd)
+{
+  return hd?hd->fp_write:NULL;
+}
+#endif /*!HTTP_USE_ESTREAM*/
+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
@@ -452,7 +537,7 @@ static gpg_error_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;
@@ -474,7 +559,8 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
       if (!(p2 = strchr (p, ':')) || p2 == p)
        return gpg_error (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;
@@ -511,7 +597,8 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
               p = p3;
             }
 
-         strlwr (p);
+          for (pp=p; *pp; pp++)
+            *pp = tolower (*(unsigned char*)pp);
          uri->host = p;
          if ((p3 = strchr (p, ':')))
            {
@@ -648,6 +735,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)
 {
@@ -762,7 +872,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
@@ -1073,6 +1185,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_errno (errno);
+      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_errno (errno);
+      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_errno (errno);
+  strcpy (h->name, line);
+  h->value = xtrymalloc (strlen (value)+1);
+  if (!h->value)
+    {
+      xfree (h);
+      return gpg_error_from_errno (errno);
+    }
+  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
@@ -1083,6 +1318,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
     {
@@ -1095,6 +1339,9 @@ parse_response (http_t hd)
        return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */
       if (!len)
        return gpg_error (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);
 
@@ -1138,6 +1385,15 @@ parse_response (http_t hd)
       /* 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 ( (hd->flags & HTTP_FLAG_NEED_HEADER) && *line )
+        {
+          gpg_error_t err = store_header (hd, line);
+          if (err)
+            return err;
+        }
     }
   while (len && *line);
 
@@ -1274,7 +1530,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);
        }
@@ -1603,13 +1859,14 @@ 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 ();
@@ -1699,17 +1956,30 @@ 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)
     {
       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 = 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
   gnutls_deinit (tls_session);