aboutsummaryrefslogtreecommitdiffstats
path: root/common/http.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--common/http.c359
1 files changed, 290 insertions, 69 deletions
diff --git a/common/http.c b/common/http.c
index f8628e622..7df84576f 100644
--- a/common/http.c
+++ b/common/http.c
@@ -161,13 +161,25 @@ static char *build_rel_path (parsed_uri_t uri);
static gpg_error_t parse_response (http_t hd);
static int connect_server (const char *server, unsigned short port,
- unsigned int flags, const char *srvtag);
+ unsigned int flags, const char *srvtag,
+ int *r_host_not_found);
static gpg_error_t write_server (int sock, const char *data, size_t length);
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);
+
+/* A socket object used to a allow ref counting of sockets. */
+struct my_socket_s
+{
+ int fd; /* The actual socket - shall never be -1. */
+ int refcount; /* Number of references to this socket. */
+};
+typedef struct my_socket_s *my_socket_t;
+
+
+/* Cookie function structure and cookie object. */
static es_cookie_io_functions_t cookie_functions =
{
cookie_read,
@@ -178,8 +190,8 @@ static es_cookie_io_functions_t cookie_functions =
struct cookie_s
{
- /* File descriptor or -1 if already closed. */
- int fd;
+ /* Socket object or NULL if already closed. */
+ my_socket_t sock;
/* TLS session context or NULL if not used. */
gnutls_session_t tls_session;
@@ -213,7 +225,7 @@ typedef struct header_s *header_t;
struct http_context_s
{
unsigned int status_code;
- int sock;
+ my_socket_t sock;
unsigned int in_data:1;
unsigned int is_http_0_9:1;
estream_t fp_read;
@@ -279,6 +291,77 @@ init_sockets (void)
#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
+/* Create a new socket object. Returns NULL and closes FD if not
+ enough memory is available. */
+static my_socket_t
+my_socket_new (int fd)
+{
+ my_socket_t so;
+
+ so = xtrymalloc (sizeof *so);
+ if (!so)
+ {
+ int save_errno = errno;
+ sock_close (fd);
+ gpg_err_set_errno (save_errno);
+ return NULL;
+ }
+ so->fd = fd;
+ so->refcount = 1;
+ /* log_debug ("my_socket_new(%d): object %p for fd %d created\n", */
+ /* lnr, so, so->fd); */
+ return so;
+}
+/* #define my_socket_new(a) _my_socket_new ((a),__LINE__) */
+
+/* Bump up the reference counter for the socket object SO. */
+static my_socket_t
+my_socket_ref (my_socket_t so)
+{
+ so->refcount++;
+ /* log_debug ("my_socket_ref(%d): object %p for fd %d refcount now %d\n", */
+ /* lnr, so, so->fd, so->refcount); */
+ return so;
+}
+/* #define my_socket_ref(a) _my_socket_ref ((a),__LINE__) */
+
+/* Bump down the reference counter for the socket object SO. If SO
+ has no more references, close the socket and release the
+ object. */
+static void
+my_socket_unref (my_socket_t so)
+{
+ if (so)
+ {
+ so->refcount--;
+ /* log_debug ("my_socket_unref(%d): object %p for fd %d ref now %d\n", */
+ /* lnr, so, so->fd, so->refcount); */
+ if (!so->refcount)
+ {
+ sock_close (so->fd);
+ xfree (so);
+ }
+ }
+}
+/* #define my_socket_unref(a) _my_socket_unref ((a),__LINE__) */
+
+
+/* This notification function is called by estream whenever stream is
+ closed. Its purpose is to mark the the closing in the handle so
+ that a http_close won't accidentally close the estream. The function
+ http_close removes this notification so that it won't be called if
+ http_close was used before an es_fclose. */
+static void
+fp_onclose_notification (estream_t stream, void *opaque)
+{
+ http_t hd = opaque;
+
+ if (hd->fp_read && hd->fp_read == stream)
+ hd->fp_read = NULL;
+ else if (hd->fp_write && hd->fp_write == stream)
+ hd->fp_write = NULL;
+}
+
/*
* Helper function to create an HTTP header with hex encoded data. A
@@ -343,7 +426,7 @@ 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. */
+ response. */
gpg_error_t
_http_open (http_t *r_hd, http_req_t reqtype, const char *url,
const char *auth, unsigned int flags, const char *proxy,
@@ -362,7 +445,6 @@ _http_open (http_t *r_hd, http_req_t reqtype, const char *url,
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
- hd->sock = -1;
hd->req_type = reqtype;
hd->flags = flags;
hd->tls_context = tls_context;
@@ -373,8 +455,7 @@ _http_open (http_t *r_hd, http_req_t reqtype, const char *url,
if (err)
{
- if (!hd->fp_read && !hd->fp_write && hd->sock != -1)
- sock_close (hd->sock);
+ my_socket_unref (hd->sock);
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
@@ -387,6 +468,105 @@ _http_open (http_t *r_hd, http_req_t reqtype, const char *url,
}
+/* This function is useful to connect to a generic TCP service using
+ this http abstraction layer. This has the advantage of providing
+ service tags and an estream interface. */
+gpg_error_t
+_http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
+ unsigned int flags, const char *srvtag,
+ gpg_err_source_t errsource)
+{
+ gpg_error_t err = 0;
+ int sock;
+ http_t hd;
+ cookie_t cookie;
+ int hnf;
+
+ *r_hd = NULL;
+
+ /* Create the handle. */
+ hd = xtrycalloc (1, sizeof *hd);
+ if (!hd)
+ return gpg_error_from_syserror ();
+ hd->req_type = HTTP_REQ_OPAQUE;
+ hd->flags = flags;
+
+ /* Connect. */
+ sock = connect_server (server, port, hd->flags, srvtag, &hnf);
+ if (sock == -1)
+ {
+ err = gpg_err_make (errsource, (hnf? GPG_ERR_UNKNOWN_HOST
+ :gpg_err_code_from_syserror ()));
+ xfree (hd);
+ return err;
+ }
+ hd->sock = my_socket_new (sock);
+ if (!hd->sock)
+ {
+ err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ xfree (hd);
+ return err;
+ }
+
+ /* Setup estreams for reading and writing. */
+ cookie = xtrycalloc (1, sizeof *cookie);
+ if (!cookie)
+ {
+ err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ goto leave;
+ }
+ cookie->sock = my_socket_ref (hd->sock);
+ hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
+ if (!hd->fp_write)
+ {
+ err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ my_socket_unref (cookie->sock);
+ xfree (cookie);
+ goto leave;
+ }
+ hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */
+
+ cookie = xtrycalloc (1, sizeof *cookie);
+ if (!cookie)
+ {
+ err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ goto leave;
+ }
+ cookie->sock = my_socket_ref (hd->sock);
+ hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
+ if (!hd->fp_read)
+ {
+ err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ my_socket_unref (cookie->sock);
+ xfree (cookie);
+ goto leave;
+ }
+ hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */
+
+ /* Register close notification to interlock the use of es_fclose in
+ http_close and in user code. */
+ err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd);
+ if (!err)
+ err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
+
+ leave:
+ if (err)
+ {
+ if (hd->fp_read)
+ es_fclose (hd->fp_read);
+ if (hd->fp_write)
+ es_fclose (hd->fp_write);
+ my_socket_unref (hd->sock);
+ xfree (hd);
+ }
+ else
+ *r_hd = hd;
+ return err;
+}
+
+
+
+
void
http_start_data (http_t hd)
{
@@ -410,12 +590,12 @@ _http_wait_response (http_t hd, gpg_err_source_t errsource)
/* Make sure that we are in the data. */
http_start_data (hd);
- /* Close the write stream but keep the socket open. */
+ /* Close the write stream. Note that the reference counted socket
+ object keeps the actual system 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;
/* The close has released the cookie and thus we better set it to NULL. */
@@ -425,14 +605,14 @@ _http_wait_response (http_t hd, gpg_err_source_t errsource)
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);
+ shutdown (hd->sock->fd, 1);
hd->in_data = 0;
/* 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;
+ cookie->sock = my_socket_ref (hd->sock);
if (hd->uri->use_tls)
cookie->tls_session = hd->tls_context;
@@ -440,12 +620,18 @@ _http_wait_response (http_t hd, gpg_err_source_t errsource)
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
+ err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ my_socket_unref (cookie->sock);
xfree (cookie);
hd->read_cookie = NULL;
- return gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ return err;
}
err = parse_response (hd);
+
+ if (!err)
+ err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
+
return err;
}
@@ -480,8 +666,15 @@ http_close (http_t hd, int keep_read_stream)
{
if (!hd)
return;
- if (!hd->fp_read && !hd->fp_write && hd->sock != -1)
- sock_close (hd->sock);
+
+ /* First remove the close notifications for the streams. */
+ if (hd->fp_read)
+ es_onclose (hd->fp_read, 0, fp_onclose_notification, hd);
+ if (hd->fp_write)
+ es_onclose (hd->fp_write, 0, fp_onclose_notification, hd);
+
+ /* Now we can close the streams. */
+ my_socket_unref (hd->sock);
if (hd->fp_read && !keep_read_stream)
es_fclose (hd->fp_read);
if (hd->fp_write)
@@ -577,6 +770,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check)
uri->params = uri->query = NULL;
uri->use_tls = 0;
uri->is_http = 0;
+ uri->opaque = 0;
/* A quick validity check. */
if (strspn (p, VALID_URI_CHARS) != n)
@@ -614,14 +808,9 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check)
p = p2;
- /* Find the hostname */
- if (*p != '/')
- return GPG_ERR_INV_URI; /* Does not start with a slash. */
-
- p++;
- if (*p == '/') /* There seems to be a hostname. */
+ if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */
{
- p++;
+ p += 2;
if ((p2 = strchr (p, '/')))
*p2++ = 0;
@@ -659,6 +848,15 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check)
return GPG_ERR_BAD_URI; /* Hostname incudes a Nul. */
p = p2 ? p2 : NULL;
}
+ else if (uri->is_http)
+ return GPG_ERR_INV_URI; /* No Leading double slash for HTTP. */
+ else
+ {
+ uri->opaque = 1;
+ uri->path = p;
+ return 0;
+ }
+
} /* End global URI part. */
/* Parse the pathname part */
@@ -888,7 +1086,8 @@ send_request (http_t hd, const char *auth,
const char *http_proxy = NULL;
char *proxy_authstr = NULL;
char *authstr = NULL;
- int save_errno;
+ int sock;
+ int hnf;
tls_session = hd->tls_context;
if (hd->uri->use_tls && !tls_session)
@@ -906,6 +1105,7 @@ send_request (http_t hd, const char *auth,
&& *http_proxy ))
{
parsed_uri_t uri;
+ int save_errno;
if (proxy)
http_proxy = proxy;
@@ -932,32 +1132,42 @@ send_request (http_t hd, const char *auth,
}
}
- hd->sock = connect_server (*uri->host ? uri->host : "localhost",
- uri->port ? uri->port : 80,
- hd->flags, srvtag);
+ sock = connect_server (*uri->host ? uri->host : "localhost",
+ uri->port ? uri->port : 80,
+ hd->flags, srvtag, &hnf);
save_errno = errno;
http_release_parsed_uri (uri);
+ if (sock == -1)
+ gpg_err_set_errno (save_errno);
}
else
{
- hd->sock = connect_server (server, port, hd->flags, srvtag);
- save_errno = errno;
+ sock = connect_server (server, port, hd->flags, srvtag, &hnf);
}
- if (hd->sock == -1)
+ if (sock == -1)
{
xfree (proxy_authstr);
- return gpg_err_make (errsource, (save_errno
- ? gpg_err_code_from_errno (save_errno)
- : GPG_ERR_NOT_FOUND));
+ return gpg_err_make (errsource, (hnf? GPG_ERR_UNKNOWN_HOST
+ : gpg_err_code_from_syserror ()));
}
+ hd->sock = my_socket_new (sock);
+ if (!hd->sock)
+ {
+ xfree (proxy_authstr);
+ return gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ }
+
+
#ifdef HTTP_USE_GNUTLS
if (hd->uri->use_tls)
{
int rc;
- gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock);
+ my_socket_ref (hd->sock);
+ gnutls_transport_set_ptr (tls_session,
+ (gnutls_transport_ptr_t)(hd->sock->fd));
do
{
rc = gnutls_handshake (tls_session);
@@ -1069,7 +1279,7 @@ send_request (http_t hd, const char *auth,
err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
goto leave;
}
- cookie->fd = hd->sock;
+ cookie->sock = my_socket_ref (hd->sock);
hd->write_cookie = cookie;
if (hd->uri->use_tls)
cookie->tls_session = tls_session;
@@ -1078,6 +1288,7 @@ send_request (http_t hd, const char *auth,
if (!hd->fp_write)
{
err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ my_socket_unref (cookie->sock);
xfree (cookie);
hd->write_cookie = NULL;
}
@@ -1469,7 +1680,7 @@ start_server ()
error. ERRNO is set on error. */
static int
connect_server (const char *server, unsigned short port,
- unsigned int flags, const char *srvtag)
+ unsigned int flags, const char *srvtag, int *r_host_not_found)
{
int sock = -1;
int srvcount = 0;
@@ -1483,6 +1694,7 @@ connect_server (const char *server, unsigned short port,
/* Not currently using the flags */
(void)flags;
+ *r_host_not_found = 0;
#ifdef HAVE_W32_SYSTEM
#ifndef HTTP_NO_WSASTARTUP
@@ -1655,6 +1867,8 @@ connect_server (const char *server, unsigned short port,
server,
hostfound? strerror (last_errno):"host not found");
#endif
+ if (!hostfound)
+ *r_host_not_found = 1;
if (sock != -1)
sock_close (sock);
gpg_err_set_errno (last_errno);
@@ -1758,12 +1972,12 @@ cookie_read (void *cookie, void *buffer, size_t size)
do
{
#ifdef HAVE_PTH
- nread = pth_read (c->fd, buffer, size);
+ nread = pth_read (c->sock->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);
+ nread = recv (c->sock->fd, buffer, size, 0);
#else
- nread = read (c->fd, buffer, size);
+ nread = read (c->sock->fd, buffer, size);
#endif
}
while (nread == -1 && errno == EINTR);
@@ -1819,7 +2033,7 @@ cookie_write (void *cookie, const void *buffer, size_t size)
else
#endif /*HTTP_USE_GNUTLS*/
{
- if ( write_server (c->fd, buffer, size) )
+ if ( write_server (c->sock->fd, buffer, size) )
{
gpg_err_set_errno (EIO);
nwritten = -1;
@@ -1844,28 +2058,29 @@ cookie_close (void *cookie)
if (c->tls_session && !c->keep_socket)
{
gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR);
+ my_socket_unref (c->sock);
}
#endif /*HTTP_USE_GNUTLS*/
- if (c->fd != -1 && !c->keep_socket)
- sock_close (c->fd);
+ if (c->sock && !c->keep_socket)
+ my_socket_unref (c->sock);
xfree (c);
return 0;
}
-
/**** Test code ****/
#ifdef TEST
+#ifdef HTTP_USE_GNUTLS
static gpg_error_t
verify_callback (http_t hd, void *tls_context, int reserved)
{
log_info ("verification of certificates skipped\n");
return 0;
}
-
+#endif /*HTTP_USE_GNUTLS*/
/* static void */
@@ -1938,7 +2153,7 @@ main (int argc, char **argv)
http_register_tls_callback (verify_callback);
#endif /*HTTP_USE_GNUTLS*/
- rc = http_parse_uri (&uri, *argv, 0);
+ rc = http_parse_uri (&uri, *argv, 1);
if (rc)
{
log_error ("`%s': %s\n", *argv, gpg_strerror (rc));
@@ -1946,35 +2161,41 @@ main (int argc, char **argv)
}
printf ("Scheme: %s\n", uri->scheme);
- printf ("Host : %s\n", uri->host);
- printf ("Port : %u\n", uri->port);
- printf ("Path : %s\n", uri->path);
- for (r = uri->params; r; r = r->next)
- {
- printf ("Params: %s", r->name);
- if (!r->no_value)
- {
- printf ("=%s", r->value);
- if (strlen (r->value) != r->valuelen)
- printf (" [real length=%d]", (int) r->valuelen);
- }
- putchar ('\n');
- }
- for (r = uri->query; r; r = r->next)
+ if (uri->opaque)
+ printf ("Value : %s\n", uri->path);
+ else
{
- printf ("Query : %s", r->name);
- if (!r->no_value)
- {
- printf ("=%s", r->value);
- if (strlen (r->value) != r->valuelen)
- printf (" [real length=%d]", (int) r->valuelen);
- }
- putchar ('\n');
+ printf ("Auth : %s\n", uri->auth? uri->auth:"[none]");
+ printf ("Host : %s\n", uri->host);
+ printf ("Port : %u\n", uri->port);
+ printf ("Path : %s\n", uri->path);
+ for (r = uri->params; r; r = r->next)
+ {
+ printf ("Params: %s", r->name);
+ if (!r->no_value)
+ {
+ printf ("=%s", r->value);
+ if (strlen (r->value) != r->valuelen)
+ printf (" [real length=%d]", (int) r->valuelen);
+ }
+ putchar ('\n');
+ }
+ for (r = uri->query; r; r = r->next)
+ {
+ printf ("Query : %s", r->name);
+ if (!r->no_value)
+ {
+ printf ("=%s", r->value);
+ if (strlen (r->value) != r->valuelen)
+ printf (" [real length=%d]", (int) r->valuelen);
+ }
+ putchar ('\n');
+ }
}
http_release_parsed_uri (uri);
uri = NULL;
- rc = http_open_document (&hd, *argv, NULL, 0, NULL, tls_session);
+ rc = http_open_document (&hd, *argv, NULL, 0, NULL, tls_session, NULL, NULL);
if (rc)
{
log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc));
@@ -2010,6 +2231,6 @@ main (int argc, char **argv)
/*
Local Variables:
-compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -L../jnlib -ljnlib -lgcrypt -lpth -lgnutls"
+compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -lgcrypt -lpth -lgnutls"
End:
*/