send_request (http_t hd, const char *httphost, const char *auth,
	      const char *proxy, const char *srvtag, unsigned int timeout,
              strlist_t headers)
{
  gpg_error_t err;
  const char *server;
  char *request, *p;
  unsigned short port;
  const char *http_proxy = NULL;
  char *proxy_authstr = NULL;
  char *authstr = NULL;
  assuan_fd_t sock;
#ifdef USE_TLS
  int have_http_proxy = 0;
#endif

  if (hd->uri->use_tls && !hd->session)
    {
      log_error ("TLS requested but no session object provided\n");
      return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
    }
#ifdef USE_TLS
  if (hd->uri->use_tls && !hd->session->tls_session)
    {
      log_error ("TLS requested but no TLS context available\n");
      return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
    }
  if (opt_debug)
    log_debug ("Using TLS library: %s %s\n",
# if HTTP_USE_NTBTLS
               "NTBTLS", ntbtls_check_version (NULL)
# elif HTTP_USE_GNUTLS
               "GNUTLS", gnutls_check_version (NULL)
# else
               "?", "?"
# endif /*HTTP_USE_*TLS*/
               );
#endif /*USE_TLS*/

  if ((hd->flags & HTTP_FLAG_FORCE_TOR))
    {
      int mode;

      if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
        {
          log_error ("Tor support is not available\n");
          return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
        }
      /* Non-blocking connects do not work with our Tor proxy because
       * we can't continue the Socks protocol after the EINPROGRESS.
       * Disable the timeout to use a blocking connect.  */
      timeout = 0;
    }

  server = *hd->uri->host ? hd->uri->host : "localhost";
  port = hd->uri->port ? hd->uri->port : 80;

  /* Try to use SNI.  */
#ifdef USE_TLS
  if (hd->uri->use_tls)
    {
# if HTTP_USE_GNUTLS
      int rc;
# endif

      xfree (hd->session->servername);
      hd->session->servername = xtrystrdup (httphost? httphost : server);
      if (!hd->session->servername)
        {
          err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
          return err;
        }

# if HTTP_USE_NTBTLS
      err = ntbtls_set_hostname (hd->session->tls_session,
                                 hd->session->servername);
      if (err)
        {
          log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err));
          return err;
        }
# elif HTTP_USE_GNUTLS
      rc = gnutls_server_name_set (hd->session->tls_session,
                                   GNUTLS_NAME_DNS,
                                   hd->session->servername,
                                   strlen (hd->session->servername));
      if (rc < 0)
        log_info ("gnutls_server_name_set failed: %s\n", gnutls_strerror (rc));
# endif /*HTTP_USE_GNUTLS*/
    }
#endif /*USE_TLS*/

  if ( (proxy && *proxy)
       || ( (hd->flags & HTTP_FLAG_TRY_PROXY)
            && (http_proxy = getenv (HTTP_PROXY_ENV))
            && *http_proxy ))
    {
      parsed_uri_t uri;

      if (proxy)
	http_proxy = proxy;

      err = parse_uri (&uri, http_proxy, 0, 0);
      if (gpg_err_code (err) == GPG_ERR_INV_URI
          && is_hostname_port (http_proxy))
        {
          /* Retry assuming a "hostname:port" string.  */
          char *tmpname = strconcat ("http://", http_proxy, NULL);
          if (tmpname && !parse_uri (&uri, tmpname, 0, 0))
            err = 0;
          xfree (tmpname);
        }

      if (err)
        ;
#ifdef USE_TLS
      else if (!strcmp (uri->scheme, "http"))
        have_http_proxy = 1;
#endif
      else if (!strcmp (uri->scheme, "socks4")
               || !strcmp (uri->scheme, "socks5h"))
        err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
      else
        err = gpg_err_make (default_errsource, GPG_ERR_INV_URI);

      if (err)
	{
	  log_error ("invalid HTTP proxy (%s): %s\n",
		     http_proxy, gpg_strerror (err));
	  return gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
	}

      if (uri->auth)
        {
          remove_escapes (uri->auth);
          proxy_authstr = make_header_line ("Proxy-Authorization: Basic ",
                                            "\r\n",
                                            uri->auth, strlen(uri->auth));
          if (!proxy_authstr)
            {
              err = gpg_err_make (default_errsource,
                                  gpg_err_code_from_syserror ());
              http_release_parsed_uri (uri);
              return err;
            }
        }

      err = connect_server (*uri->host ? uri->host : "localhost",
                            uri->port ? uri->port : 80,
                            hd->flags, NULL, timeout, &sock);
      http_release_parsed_uri (uri);
    }
  else
    {
      err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
    }

  if (err)
    {
      xfree (proxy_authstr);
      return err;
    }
  hd->sock = my_socket_new (sock);
  if (!hd->sock)
    {
      xfree (proxy_authstr);
      return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
    }

#if USE_TLS
  if (have_http_proxy && hd->uri->use_tls)
    {
      int saved_flags;
      cookie_t cookie;

      /* Try to use the CONNECT method to proxy our TLS stream.  */
      request = es_bsprintf
        ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s",
         httphost ? httphost : server,
         port,
         httphost ? httphost : server,
         port,
         proxy_authstr ? proxy_authstr : "");
      xfree (proxy_authstr);
      proxy_authstr = NULL;

      if (! request)
        return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());

      if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
        log_debug_with_string (request, "http.c:request:");

      cookie = xtrycalloc (1, sizeof *cookie);
      if (! cookie)
        {
          err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
          xfree (request);
          return err;
        }
      cookie->sock = my_socket_ref (hd->sock);
      hd->write_cookie = cookie;

      hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
      if (! hd->fp_write)
        {
          err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
          my_socket_unref (cookie->sock, NULL, NULL);
          xfree (cookie);
          xfree (request);
          hd->write_cookie = NULL;
          return err;
        }
      else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());

      xfree (request);
      request = NULL;

      /* Make sure http_wait_response doesn't close the stream.  */
      saved_flags = hd->flags;
      hd->flags &= ~HTTP_FLAG_SHUTDOWN;

      /* Get the response.  */
      err = http_wait_response (hd);

      /* Restore flags, destroy stream.  */
      hd->flags = saved_flags;
      es_fclose (hd->fp_read);
      hd->fp_read = NULL;
      hd->read_cookie = NULL;

      /* Reset state.  */
      hd->in_data = 0;

      if (err)
        return err;

      if (hd->status_code != 200)
        {
          request = es_bsprintf
            ("CONNECT %s:%hu",
             httphost ? httphost : server,
             port);

          log_error (_("error accessing '%s': http status %u\n"),
                     request ? request : "out of core",
                     http_get_status_code (hd));

          xfree (request);
          return gpg_error (GPG_ERR_NO_DATA);
        }

      /* We are done with the proxy, the code below will establish a
       * TLS session and talk directly to the target server.  */
      http_proxy = NULL;
    }
#endif	/* USE_TLS */

#if HTTP_USE_NTBTLS
  if (hd->uri->use_tls)
    {
      estream_t in, out;

      my_socket_ref (hd->sock);

      /* Until we support send/recv in estream under Windows we need
       * to use es_fopencookie.  */
#ifdef HAVE_W32_SYSTEM
      in = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "rb",
                           simple_cookie_functions);
#else
      in = es_fdopen_nc (hd->sock->fd, "rb");
#endif
      if (!in)
        {
          err = gpg_error_from_syserror ();
          xfree (proxy_authstr);
          return err;
        }

#ifdef HAVE_W32_SYSTEM
      out = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "wb",
                            simple_cookie_functions);
#else
      out = es_fdopen_nc (hd->sock->fd, "wb");
#endif
      if (!out)
        {
          err = gpg_error_from_syserror ();
          es_fclose (in);
          xfree (proxy_authstr);
          return err;
        }

      err = ntbtls_set_transport (hd->session->tls_session, in, out);
      if (err)
        {
          log_info ("TLS set_transport failed: %s <%s>\n",
                    gpg_strerror (err), gpg_strsource (err));
          xfree (proxy_authstr);
          return err;
        }

#ifdef HTTP_USE_NTBTLS
      if (hd->session->verify_cb)
        {
          err = ntbtls_set_verify_cb (hd->session->tls_session,
                                      my_ntbtls_verify_cb, hd);
          if (err)
            {
              log_error ("ntbtls_set_verify_cb failed: %s\n",
                         gpg_strerror (err));
              xfree (proxy_authstr);
              return err;
            }
        }
#endif /*HTTP_USE_NTBTLS*/

      while ((err = ntbtls_handshake (hd->session->tls_session)))
        {
          switch (err)
            {
            default:
              log_info ("TLS handshake failed: %s <%s>\n",
                        gpg_strerror (err), gpg_strsource (err));
              xfree (proxy_authstr);
              return err;
            }
        }

      hd->session->verify.done = 0;

      /* Try the available verify callbacks until one returns success
       * or a real error.  Note that NTBTLS does the verification
       * during the handshake via   */
#ifdef HTTP_USE_NTBTLS
      err = 0; /* Fixme check that the CB has been called.  */
#else
      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif

      if (hd->session->verify_cb
          && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
          && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
        err = hd->session->verify_cb (hd->session->verify_cb_value,
                                      hd, hd->session,
                                      (hd->flags | hd->session->flags),
                                      hd->session->tls_session);

      if (tls_callback
          && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
          && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
        err = tls_callback (hd, hd->session, 0);

      if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
          && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
        err = http_verify_server_credentials (hd->session);

      if (err)
        {
          log_info ("TLS connection authentication failed: %s <%s>\n",
                    gpg_strerror (err), gpg_strsource (err));
          xfree (proxy_authstr);
          return err;
        }

    }
#elif HTTP_USE_GNUTLS
  if (hd->uri->use_tls)
    {
      int rc;

      my_socket_ref (hd->sock);
      gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
      gnutls_transport_set_pull_function (hd->session->tls_session,
                                          my_gnutls_read);
      gnutls_transport_set_push_function (hd->session->tls_session,
                                          my_gnutls_write);

    handshake_again:
      do
        {
          rc = gnutls_handshake (hd->session->tls_session);
        }
      while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
      if (rc < 0)
        {
          if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED
              || rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
            {
              gnutls_alert_description_t alertno;
              const char *alertstr;

              alertno = gnutls_alert_get (hd->session->tls_session);
              alertstr = gnutls_alert_get_name (alertno);
              log_info ("TLS handshake %s: %s (alert %d)\n",
                        rc == GNUTLS_E_WARNING_ALERT_RECEIVED
                        ? "warning" : "failed",
                        alertstr, (int)alertno);
              if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
                log_info ("  (sent server name '%s')\n", server);

              if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED)
                goto handshake_again;
            }
          else
            log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
          xfree (proxy_authstr);
          return gpg_err_make (default_errsource, GPG_ERR_NETWORK);
        }

      hd->session->verify.done = 0;
      if (tls_callback)
        err = tls_callback (hd, hd->session, 0);
      else
        err = http_verify_server_credentials (hd->session);
      if (err)
        {
          log_info ("TLS connection authentication failed: %s\n",
                    gpg_strerror (err));
          xfree (proxy_authstr);
          return err;
        }
    }
#endif /*HTTP_USE_GNUTLS*/

  if (auth || hd->uri->auth)
    {
      char *myauth;

      if (auth)
        {
          myauth = xtrystrdup (auth);
          if (!myauth)
            {
              xfree (proxy_authstr);
              return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
            }
          remove_escapes (myauth);
        }
      else
        {
          remove_escapes (hd->uri->auth);
          myauth = hd->uri->auth;
        }

      authstr = make_header_line ("Authorization: Basic ", "\r\n",
                                  myauth, strlen (myauth));
      if (auth)
        xfree (myauth);

      if (!authstr)
        {
          xfree (proxy_authstr);
          return gpg_err_make (default_errsource,
                               gpg_err_code_from_syserror ());
        }
    }

  p = build_rel_path (hd->uri);
  if (!p)
    return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());

  if (http_proxy && *http_proxy)
    {
      request = es_bsprintf
        ("%s %s://%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",
         hd->uri->use_tls? "https" : "http",
         httphost? httphost : server,
         port, *p == '/' ? "" : "/", p,
         authstr ? authstr : "",
         proxy_authstr ? proxy_authstr : "");
    }
  else
    {
      char portstr[35];

      if (port == (hd->uri->use_tls? 443 : 80))
        *portstr = 0;
      else
        snprintf (portstr, sizeof portstr, ":%u", port);

      request = es_bsprintf
        ("%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,
         httphost? httphost : server,
         portstr,
         authstr? authstr:"");
    }
  xfree (p);
  if (!request)
    {
      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
      xfree (authstr);
      xfree (proxy_authstr);
      return err;
    }

  if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
    log_debug_with_string (request, "http.c:request:");

  /* First setup estream so that we can write even the first line
     using estream.  This is also required for the sake of gnutls. */
  {
    cookie_t cookie;

    cookie = xtrycalloc (1, sizeof *cookie);
    if (!cookie)
      {
        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
        goto leave;
      }
    cookie->sock = my_socket_ref (hd->sock);
    hd->write_cookie = cookie;
    cookie->use_tls = hd->uri->use_tls;
    cookie->session = http_session_ref (hd->session);

    hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
    if (!hd->fp_write)
      {
        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
        my_socket_unref (cookie->sock, NULL, NULL);
        xfree (cookie);
        hd->write_cookie = NULL;
      }
    else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
    else
      err = 0;

  if (!err)
    {
      for (;headers; headers=headers->next)
        {
          if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
            log_debug_with_string (headers->d, "http.c:request-header:");
          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 (default_errsource,
                                  gpg_err_code_from_syserror ());
              break;
            }
        }
    }
  }

 leave:
  es_free (request);
  xfree (authstr);
  xfree (proxy_authstr);

  return err;
}