#include <config.h>
#endif
+#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
-#include "soup-address.h"
#include "soup-socket.h"
+#include "soup-address.h"
+#include "soup-filter-input-stream.h"
#include "soup-marshal.h"
#include "soup-misc.h"
-#include "soup-ssl.h"
-
-#include <sys/time.h>
-#include <sys/types.h>
+#include "soup-misc-private.h"
/**
* SECTION:soup-socket
WRITABLE,
DISCONNECTED,
NEW_CONNECTION,
+ EVENT,
LAST_SIGNAL
};
PROP_NON_BLOCKING,
PROP_IS_SERVER,
PROP_SSL_CREDENTIALS,
+ PROP_SSL_STRICT,
+ PROP_SSL_FALLBACK,
PROP_ASYNC_CONTEXT,
+ PROP_USE_THREAD_CONTEXT,
PROP_TIMEOUT,
+ PROP_TRUSTED_CERTIFICATE,
+ PROP_CLEAN_DISPOSE,
+ PROP_TLS_CERTIFICATE,
+ PROP_TLS_ERRORS,
LAST_PROP
};
typedef struct {
- int sockfd;
SoupAddress *local_addr, *remote_addr;
- GIOChannel *iochannel;
+ GIOStream *conn;
+ GSocket *gsock;
+ GInputStream *istream;
+ GOutputStream *ostream;
+ GTlsCertificateFlags tls_errors;
guint non_blocking:1;
guint is_server:1;
+ guint ssl:1;
+ guint ssl_strict:1;
+ guint ssl_fallback:1;
+ guint clean_dispose:1;
+ guint use_thread_context:1;
gpointer ssl_creds;
GMainContext *async_context;
GSource *watch_src;
GSource *read_src, *write_src;
- GByteArray *read_buf;
- GMutex *iolock, *addrlock;
+ GMutex iolock, addrlock;
guint timeout;
+
+ GCancellable *connect_cancel;
} SoupSocketPrivate;
#define SOUP_SOCKET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SOCKET, SoupSocketPrivate))
-#ifdef HAVE_IPV6
-#define soup_sockaddr_max sockaddr_in6
-#else
-#define soup_sockaddr_max sockaddr_in
-#endif
-
static void set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec);
static void get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec);
-#ifdef G_OS_WIN32
-#define SOUP_IS_SOCKET_ERROR(status) ((status) == SOCKET_ERROR)
-#define SOUP_IS_INVALID_SOCKET(socket) ((socket) == INVALID_SOCKET)
-#define SOUP_IS_CONNECT_STATUS_INPROGRESS() (WSAGetLastError () == WSAEWOULDBLOCK)
-#define SHUT_RDWR SD_BOTH
-#else
-#define SOUP_IS_SOCKET_ERROR(status) ((status) == -1)
-#define SOUP_IS_INVALID_SOCKET(socket) ((socket) < 0)
-#define SOUP_IS_CONNECT_STATUS_INPROGRESS() (errno == EINPROGRESS)
-#endif
+static void soup_socket_peer_certificate_changed (GObject *conn,
+ GParamSpec *pspec,
+ gpointer user_data);
static void
soup_socket_init (SoupSocket *sock)
{
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
- priv->sockfd = -1;
priv->non_blocking = TRUE;
- priv->addrlock = g_mutex_new ();
- priv->iolock = g_mutex_new ();
- priv->timeout = 0;
+ g_mutex_init (&priv->addrlock);
+ g_mutex_init (&priv->iolock);
}
static void
-disconnect_internal (SoupSocketPrivate *priv)
+disconnect_internal (SoupSocket *sock, gboolean close)
{
- g_io_channel_unref (priv->iochannel);
- priv->iochannel = NULL;
- priv->sockfd = -1;
+ SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+ if (priv->gsock) {
+ if (close)
+ g_socket_close (priv->gsock, NULL);
+ g_object_unref (priv->gsock);
+ priv->gsock = NULL;
+ }
+ if (priv->conn) {
+ if (G_IS_TLS_CONNECTION (priv->conn))
+ g_signal_handlers_disconnect_by_func (priv->conn, soup_socket_peer_certificate_changed, sock);
+ g_clear_object (&priv->conn);
+ }
if (priv->read_src) {
g_source_destroy (priv->read_src);
{
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object);
- if (priv->iochannel)
- disconnect_internal (priv);
+ if (priv->connect_cancel) {
+ if (priv->clean_dispose)
+ g_warning ("Disposing socket %p during connect", object);
+ g_object_unref (priv->connect_cancel);
+ }
+ if (priv->conn) {
+ if (priv->clean_dispose)
+ g_warning ("Disposing socket %p while still connected", object);
+ disconnect_internal (SOUP_SOCKET (object), TRUE);
+ }
+
+ g_clear_object (&priv->istream);
+ g_clear_object (&priv->ostream);
if (priv->local_addr)
g_object_unref (priv->local_addr);
if (priv->remote_addr)
g_object_unref (priv->remote_addr);
- if (priv->watch_src)
+ if (priv->watch_src) {
+ if (priv->clean_dispose && !priv->is_server)
+ g_warning ("Disposing socket %p during async op", object);
g_source_destroy (priv->watch_src);
+ }
if (priv->async_context)
g_main_context_unref (priv->async_context);
- if (priv->read_buf)
- g_byte_array_free (priv->read_buf, TRUE);
-
- g_mutex_free (priv->addrlock);
- g_mutex_free (priv->iolock);
+ g_mutex_clear (&priv->addrlock);
+ g_mutex_clear (&priv->iolock);
G_OBJECT_CLASS (soup_socket_parent_class)->finalize (object);
}
{
GObjectClass *object_class = G_OBJECT_CLASS (socket_class);
-#ifdef SIGPIPE
- signal (SIGPIPE, SIG_IGN);
-#endif
-
g_type_class_add_private (socket_class, sizeof (SoupSocketPrivate));
/* virtual method override */
* @sock: the socket
*
* Emitted when an async socket is readable. See
- * soup_socket_read() and soup_socket_read_until().
+ * soup_socket_read(), soup_socket_read_until() and
+ * #SoupSocket:non-blocking.
**/
signals[READABLE] =
g_signal_new ("readable",
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (SoupSocketClass, readable),
NULL, NULL,
- soup_marshal_NONE__NONE,
+ _soup_marshal_NONE__NONE,
G_TYPE_NONE, 0);
/**
* @sock: the socket
*
* Emitted when an async socket is writable. See
- * soup_socket_write().
+ * soup_socket_write() and #SoupSocket:non-blocking.
**/
signals[WRITABLE] =
g_signal_new ("writable",
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (SoupSocketClass, writable),
NULL, NULL,
- soup_marshal_NONE__NONE,
+ _soup_marshal_NONE__NONE,
G_TYPE_NONE, 0);
/**
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (SoupSocketClass, disconnected),
NULL, NULL,
- soup_marshal_NONE__NONE,
+ _soup_marshal_NONE__NONE,
G_TYPE_NONE, 0);
/**
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (SoupSocketClass, new_connection),
NULL, NULL,
- soup_marshal_NONE__OBJECT,
+ _soup_marshal_NONE__OBJECT,
G_TYPE_NONE, 1,
SOUP_TYPE_SOCKET);
+ /**
+ * SoupSocket::event:
+ * @sock: the socket
+ * @event: the event that occurred
+ * @connection: the current connection state
+ *
+ * Emitted when a network-related event occurs. See
+ * #GSocketClient::event for more details.
+ *
+ * Since: 2.38
+ **/
+ signals[EVENT] =
+ g_signal_new ("event",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_SOCKET_CLIENT_EVENT,
+ G_TYPE_IO_STREAM);
+
/* properties */
+ /**
+ * SOUP_SOCKET_LOCAL_ADDRESS:
+ *
+ * Alias for the #SoupSocket:local-address property. (Address
+ * of local end of socket.)
+ **/
g_object_class_install_property (
object_class, PROP_LOCAL_ADDRESS,
g_param_spec_object (SOUP_SOCKET_LOCAL_ADDRESS,
"Address of local end of socket",
SOUP_TYPE_ADDRESS,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ /**
+ * SOUP_SOCKET_REMOTE_ADDRESS:
+ *
+ * Alias for the #SoupSocket:remote-address property. (Address
+ * of remote end of socket.)
+ **/
g_object_class_install_property (
object_class, PROP_REMOTE_ADDRESS,
g_param_spec_object (SOUP_SOCKET_REMOTE_ADDRESS,
"Address of remote end of socket",
SOUP_TYPE_ADDRESS,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ /**
+ * SoupSocket:non-blocking:
+ *
+ * Whether or not the socket uses non-blocking I/O.
+ *
+ * #SoupSocket's I/O methods are designed around the idea of
+ * using a single codepath for both synchronous and
+ * asynchronous I/O. If you want to read off a #SoupSocket,
+ * the "correct" way to do it is to call soup_socket_read() or
+ * soup_socket_read_until() repeatedly until you have read
+ * everything you want. If it returns %SOUP_SOCKET_WOULD_BLOCK
+ * at any point, stop reading and wait for it to emit the
+ * #SoupSocket::readable signal. Then go back to the
+ * reading-as-much-as-you-can loop. Likewise, for writing to a
+ * #SoupSocket, you should call soup_socket_write() either
+ * until you have written everything, or it returns
+ * %SOUP_SOCKET_WOULD_BLOCK (in which case you wait for
+ * #SoupSocket::writable and then go back into the loop).
+ *
+ * Code written this way will work correctly with both
+ * blocking and non-blocking sockets; blocking sockets will
+ * simply never return %SOUP_SOCKET_WOULD_BLOCK, and so the
+ * code that handles that case just won't get used for them.
+ **/
+ /**
+ * SOUP_SOCKET_FLAG_NONBLOCKING:
+ *
+ * Alias for the #SoupSocket:non-blocking property. (Whether
+ * or not the socket uses non-blocking I/O.)
+ **/
g_object_class_install_property (
object_class, PROP_NON_BLOCKING,
g_param_spec_boolean (SOUP_SOCKET_FLAG_NONBLOCKING,
"Whether or not the socket uses non-blocking I/O",
TRUE,
G_PARAM_READWRITE));
+ /**
+ * SOUP_SOCKET_IS_SERVER:
+ *
+ * Alias for the #SoupSocket:is-server property. (Whether or
+ * not the socket is a server socket.)
+ **/
g_object_class_install_property (
object_class, PROP_IS_SERVER,
g_param_spec_boolean (SOUP_SOCKET_IS_SERVER,
"Whether or not the socket is a server socket",
FALSE,
G_PARAM_READABLE));
+ /**
+ * SOUP_SOCKET_SSL_CREDENTIALS:
+ *
+ * Alias for the #SoupSocket:ssl-creds property.
+ * (SSL credential information.)
+ **/
+ /* For historical reasons, there's only a single property
+ * here, which is a GTlsDatabase for client sockets, and
+ * a GTlsCertificate for server sockets. Whee!
+ */
g_object_class_install_property (
object_class, PROP_SSL_CREDENTIALS,
g_param_spec_pointer (SOUP_SOCKET_SSL_CREDENTIALS,
"SSL credentials",
"SSL credential information, passed from the session to the SSL implementation",
G_PARAM_READWRITE));
+ /**
+ * SOUP_SOCKET_SSL_STRICT:
+ *
+ * Alias for the #SoupSocket:ssl-strict property.
+ **/
+ g_object_class_install_property (
+ object_class, PROP_SSL_STRICT,
+ g_param_spec_boolean (SOUP_SOCKET_SSL_STRICT,
+ "Strictly validate SSL certificates",
+ "Whether certificate errors should be considered a connection error",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ /**
+ * SOUP_SOCKET_SSL_FALLBACK:
+ *
+ * Alias for the #SoupSocket:ssl-fallback property.
+ **/
+ g_object_class_install_property (
+ object_class, PROP_SSL_FALLBACK,
+ g_param_spec_boolean (SOUP_SOCKET_SSL_FALLBACK,
+ "SSLv3 fallback",
+ "Use SSLv3 instead of TLS (client-side only)",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ /**
+ * SOUP_SOCKET_TRUSTED_CERTIFICATE:
+ *
+ * Alias for the #SoupSocket:trusted-certificate
+ * property.
+ **/
+ g_object_class_install_property (
+ object_class, PROP_TRUSTED_CERTIFICATE,
+ g_param_spec_boolean (SOUP_SOCKET_TRUSTED_CERTIFICATE,
+ "Trusted Certificate",
+ "Whether the server certificate is trusted, if this is an SSL socket",
+ FALSE,
+ G_PARAM_READABLE));
+ /**
+ * SOUP_SOCKET_ASYNC_CONTEXT:
+ *
+ * Alias for the #SoupSocket:async-context property. (The
+ * socket's #GMainContext.)
+ **/
g_object_class_install_property (
object_class, PROP_ASYNC_CONTEXT,
g_param_spec_pointer (SOUP_SOCKET_ASYNC_CONTEXT,
"The GMainContext to dispatch this socket's async I/O in",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ /**
+ * SOUP_SOCKET_USE_THREAD_CONTEXT:
+ *
+ * Alias for the #SoupSocket:use-thread-context property. (Use
+ * g_main_context_get_thread_default())
+ *
+ * Since: 2.36.1
+ */
+ /**
+ * SoupSocket:use-thread-context:
+ *
+ * Use g_main_context_get_thread_default().
+ *
+ * Since: 2.36.1
+ */
+ g_object_class_install_property (
+ object_class, PROP_USE_THREAD_CONTEXT,
+ g_param_spec_boolean (SOUP_SOCKET_USE_THREAD_CONTEXT,
+ "Use thread context",
+ "Use g_main_context_get_thread_default",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * SOUP_SOCKET_TIMEOUT:
+ *
+ * Alias for the #SoupSocket:timeout property. (The timeout
+ * in seconds for blocking socket I/O operations.)
+ **/
g_object_class_install_property (
object_class, PROP_TIMEOUT,
g_param_spec_uint (SOUP_SOCKET_TIMEOUT,
0, G_MAXUINT, 0,
G_PARAM_READWRITE));
-#ifdef G_OS_WIN32
- /* Make sure WSAStartup() gets called. */
- soup_address_get_type ();
-#endif
+ g_object_class_install_property (
+ object_class, PROP_CLEAN_DISPOSE,
+ g_param_spec_boolean ("clean-dispose",
+ "Clean dispose",
+ "Warn on unclean dispose",
+ FALSE,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+ /**
+ * SOUP_SOCKET_TLS_CERTIFICATE:
+ *
+ * Alias for the #SoupSocket:tls-certificate
+ * property. Note that this property's value is only useful
+ * if the socket is for a TLS connection, and only reliable
+ * after some data has been transferred to or from it.
+ *
+ * Since: 2.34
+ **/
+ g_object_class_install_property (
+ object_class, PROP_TLS_CERTIFICATE,
+ g_param_spec_object (SOUP_SOCKET_TLS_CERTIFICATE,
+ "TLS certificate",
+ "The peer's TLS certificate",
+ G_TYPE_TLS_CERTIFICATE,
+ G_PARAM_READABLE));
+ /**
+ * SOUP_SOCKET_TLS_ERRORS:
+ *
+ * Alias for the #SoupSocket:tls-errors
+ * property. Note that this property's value is only useful
+ * if the socket is for a TLS connection, and only reliable
+ * after some data has been transferred to or from it.
+ *
+ * Since: 2.34
+ **/
+ g_object_class_install_property (
+ object_class, PROP_TLS_ERRORS,
+ g_param_spec_flags (SOUP_SOCKET_TLS_ERRORS,
+ "TLS errors",
+ "Errors with the peer's TLS certificate",
+ G_TYPE_TLS_CERTIFICATE_FLAGS, 0,
+ G_PARAM_READABLE));
}
static void
-set_nonblocking (SoupSocketPrivate *priv)
-{
-#ifndef G_OS_WIN32
- int flags;
-#else
- u_long val;
-#endif
-
- if (priv->sockfd == -1)
- return;
-
-#ifndef G_OS_WIN32
- flags = fcntl (priv->sockfd, F_GETFL, 0);
- if (flags != -1) {
- if (priv->non_blocking)
- flags |= O_NONBLOCK;
- else
- flags &= ~O_NONBLOCK;
- fcntl (priv->sockfd, F_SETFL, flags);
- }
-#else
- val = priv->non_blocking ? 1 : 0;
- ioctlsocket (priv->sockfd, FIONBIO, &val);
-#endif
-}
-
-static void
-set_fdflags (SoupSocketPrivate *priv)
+finish_socket_setup (SoupSocketPrivate *priv)
{
- int opt;
- struct timeval timeout;
-#ifndef G_OS_WIN32
- int flags;
-#endif
-
- if (priv->sockfd == -1)
+ if (!priv->gsock)
return;
- set_nonblocking (priv);
+ if (!priv->conn)
+ priv->conn = (GIOStream *)g_socket_connection_factory_create_connection (priv->gsock);
+ if (!priv->istream)
+ priv->istream = soup_filter_input_stream_new (g_io_stream_get_input_stream (priv->conn));
+ if (!priv->ostream)
+ priv->ostream = g_object_ref (g_io_stream_get_output_stream (priv->conn));
-#ifndef G_OS_WIN32
- flags = fcntl (priv->sockfd, F_GETFD, 0);
- if (flags != -1) {
- flags |= FD_CLOEXEC;
- fcntl (priv->sockfd, F_SETFD, flags);
- }
-#endif
-
- opt = 1;
- setsockopt (priv->sockfd, IPPROTO_TCP,
- TCP_NODELAY, (void *) &opt, sizeof (opt));
- setsockopt (priv->sockfd, SOL_SOCKET,
- SO_REUSEADDR, (void *) &opt, sizeof (opt));
-
- timeout.tv_sec = priv->timeout;
- timeout.tv_usec = 0;
- setsockopt (priv->sockfd, SOL_SOCKET,
- SO_RCVTIMEO, (void *) &timeout, sizeof (timeout));
-
- timeout.tv_sec = priv->timeout;
- timeout.tv_usec = 0;
- setsockopt (priv->sockfd, SOL_SOCKET,
- SO_SNDTIMEO, (void *) &timeout, sizeof (timeout));
-
-#ifndef G_OS_WIN32
- priv->iochannel =
- g_io_channel_unix_new (priv->sockfd);
-#else
- priv->iochannel =
- g_io_channel_win32_new_socket (priv->sockfd);
-#endif
- g_io_channel_set_close_on_unref (priv->iochannel, TRUE);
- g_io_channel_set_encoding (priv->iochannel, NULL, NULL);
- g_io_channel_set_buffered (priv->iochannel, FALSE);
+ g_socket_set_timeout (priv->gsock, priv->timeout);
}
static void
break;
case PROP_NON_BLOCKING:
priv->non_blocking = g_value_get_boolean (value);
- set_nonblocking (priv);
break;
case PROP_SSL_CREDENTIALS:
priv->ssl_creds = g_value_get_pointer (value);
break;
+ case PROP_SSL_STRICT:
+ priv->ssl_strict = g_value_get_boolean (value);
+ break;
+ case PROP_SSL_FALLBACK:
+ priv->ssl_fallback = g_value_get_boolean (value);
+ break;
case PROP_ASYNC_CONTEXT:
priv->async_context = g_value_get_pointer (value);
if (priv->async_context)
g_main_context_ref (priv->async_context);
break;
+ case PROP_USE_THREAD_CONTEXT:
+ priv->use_thread_context = g_value_get_boolean (value);
+ break;
case PROP_TIMEOUT:
priv->timeout = g_value_get_uint (value);
+ if (priv->conn)
+ g_socket_set_timeout (priv->gsock, priv->timeout);
+ break;
+ case PROP_CLEAN_DISPOSE:
+ priv->clean_dispose = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
case PROP_SSL_CREDENTIALS:
g_value_set_pointer (value, priv->ssl_creds);
break;
+ case PROP_SSL_STRICT:
+ g_value_set_boolean (value, priv->ssl_strict);
+ break;
+ case PROP_SSL_FALLBACK:
+ g_value_set_boolean (value, priv->ssl_fallback);
+ break;
+ case PROP_TRUSTED_CERTIFICATE:
+ g_value_set_boolean (value, priv->tls_errors == 0);
+ break;
case PROP_ASYNC_CONTEXT:
g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
break;
+ case PROP_USE_THREAD_CONTEXT:
+ g_value_set_boolean (value, priv->use_thread_context);
+ break;
case PROP_TIMEOUT:
g_value_set_uint (value, priv->timeout);
break;
+ case PROP_TLS_CERTIFICATE:
+ if (G_IS_TLS_CONNECTION (priv->conn))
+ g_value_set_object (value, g_tls_connection_get_peer_certificate (G_TLS_CONNECTION (priv->conn)));
+ else
+ g_value_set_object (value, NULL);
+ break;
+ case PROP_TLS_ERRORS:
+ g_value_set_flags (value, priv->tls_errors);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
return sock;
}
-typedef struct {
- SoupSocket *sock;
- GCancellable *cancellable;
- guint cancel_id;
- SoupSocketCallback callback;
- gpointer user_data;
-} SoupSocketAsyncConnectData;
-
-static gboolean
-idle_connect_result (gpointer user_data)
-{
- SoupSocketAsyncConnectData *sacd = user_data;
- SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
- guint status;
-
- priv->watch_src = NULL;
- if (sacd->cancel_id)
- g_signal_handler_disconnect (sacd->cancellable, sacd->cancel_id);
-
- if (priv->sockfd == -1) {
- if (g_cancellable_is_cancelled (sacd->cancellable))
- status = SOUP_STATUS_CANCELLED;
- else
- status = SOUP_STATUS_CANT_CONNECT;
- } else
- status = SOUP_STATUS_OK;
-
- sacd->callback (sacd->sock, status, sacd->user_data);
- g_slice_free (SoupSocketAsyncConnectData, sacd);
- return FALSE;
-}
-
-static gboolean
-connect_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data)
-{
- SoupSocketAsyncConnectData *sacd = data;
- SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
- int error = 0;
- int len = sizeof (error);
-
- /* Remove the watch now in case we don't return immediately */
- g_source_destroy (priv->watch_src);
- priv->watch_src = NULL;
-
- if ((condition & ~(G_IO_IN | G_IO_OUT)) ||
- (getsockopt (priv->sockfd, SOL_SOCKET, SO_ERROR,
- (void *)&error, (void *)&len) != 0) ||
- error)
- disconnect_internal (priv);
-
- return idle_connect_result (sacd);
-}
-
static void
-got_address (SoupAddress *addr, guint status, gpointer user_data)
+proxy_socket_client_event (GSocketClient *client,
+ GSocketClientEvent event,
+ GSocketConnectable *connectable,
+ GIOStream *connection,
+ gpointer user_data)
{
- SoupSocketAsyncConnectData *sacd = user_data;
-
- if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
- sacd->callback (sacd->sock, status, sacd->user_data);
- g_slice_free (SoupSocketAsyncConnectData, sacd);
- return;
- }
+ SoupSocket *sock = user_data;
- soup_socket_connect_async (sacd->sock, sacd->cancellable,
- sacd->callback, sacd->user_data);
- g_slice_free (SoupSocketAsyncConnectData, sacd);
-}
-
-static void
-async_cancel (GCancellable *cancellable, gpointer user_data)
-{
- SoupSocketAsyncConnectData *sacd = user_data;
- SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
-
- if (priv->watch_src)
- g_source_destroy (priv->watch_src);
- disconnect_internal (priv);
- priv->watch_src = soup_add_idle (priv->async_context,
- idle_connect_result, sacd);
+ g_signal_emit (sock, signals[EVENT], 0,
+ event, connection);
}
static guint
-socket_connect_internal (SoupSocket *sock)
+socket_connected (SoupSocket *sock, GSocketConnection *conn, GError *error)
{
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
- struct sockaddr *sa;
- int len, status;
- sa = soup_address_get_sockaddr (priv->remote_addr, &len);
- if (!sa)
- return SOUP_STATUS_CANT_RESOLVE;
+ if (priv->connect_cancel) {
+ GCancellable *cancellable = priv->connect_cancel;
- priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0);
- if (SOUP_IS_INVALID_SOCKET (priv->sockfd))
- return SOUP_STATUS_CANT_CONNECT;
- set_fdflags (priv);
+ g_object_unref (priv->connect_cancel);
+ priv->connect_cancel = NULL;
+ if (g_cancellable_is_cancelled (cancellable))
+ return SOUP_STATUS_CANCELLED;
+ }
- status = connect (priv->sockfd, sa, len);
+ if (error) {
+ if (error->domain == G_RESOLVER_ERROR) {
+ g_error_free (error);
+ return SOUP_STATUS_CANT_RESOLVE;
+ } else {
+ g_error_free (error);
+ return SOUP_STATUS_CANT_CONNECT;
+ }
+ }
- if (SOUP_IS_SOCKET_ERROR (status)) {
- if (SOUP_IS_CONNECT_STATUS_INPROGRESS ())
- return SOUP_STATUS_CONTINUE;
+ priv->conn = (GIOStream *)conn;
+ priv->gsock = g_object_ref (g_socket_connection_get_socket (conn));
+ finish_socket_setup (priv);
- disconnect_internal (priv);
- return SOUP_STATUS_CANT_CONNECT;
- } else
- return SOUP_STATUS_OK;
+ return SOUP_STATUS_OK;
}
/**
* The callback function passed to soup_socket_connect_async().
**/
+typedef struct {
+ SoupSocket *sock;
+ SoupSocketCallback callback;
+ gpointer user_data;
+} SoupSocketAsyncConnectData;
+
+static void
+async_connected (GObject *client, GAsyncResult *result, gpointer data)
+{
+ SoupSocketAsyncConnectData *sacd = data;
+ SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
+ GError *error = NULL;
+ GSocketConnection *conn;
+ guint status;
+
+ if (priv->async_context && !priv->use_thread_context)
+ g_main_context_pop_thread_default (priv->async_context);
+
+ conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client),
+ result, &error);
+ status = socket_connected (sacd->sock, conn, error);
+
+ sacd->callback (sacd->sock, status, sacd->user_data);
+ g_object_unref (sacd->sock);
+ g_slice_free (SoupSocketAsyncConnectData, sacd);
+}
+
/**
* soup_socket_connect_async:
* @sock: a client #SoupSocket (which must not already be connected)
* @cancellable: a #GCancellable, or %NULL
- * @callback: callback to call after connecting
+ * @callback: (scope async): callback to call after connecting
* @user_data: data to pass to @callback
*
* Begins asynchronously connecting to @sock's remote address. The
{
SoupSocketPrivate *priv;
SoupSocketAsyncConnectData *sacd;
- guint status;
+ GSocketClient *client;
g_return_if_fail (SOUP_IS_SOCKET (sock));
priv = SOUP_SOCKET_GET_PRIVATE (sock);
g_return_if_fail (priv->remote_addr != NULL);
sacd = g_slice_new0 (SoupSocketAsyncConnectData);
- sacd->sock = sock;
- sacd->cancellable = cancellable;
+ sacd->sock = g_object_ref (sock);
sacd->callback = callback;
sacd->user_data = user_data;
- if (!soup_address_get_sockaddr (priv->remote_addr, NULL)) {
- soup_address_resolve_async (priv->remote_addr,
- priv->async_context,
- cancellable,
- got_address, sacd);
- return;
- }
-
- status = socket_connect_internal (sock);
- if (status == SOUP_STATUS_CONTINUE) {
- /* Wait for connect to succeed or fail */
- priv->watch_src =
- soup_add_io_watch (priv->async_context,
- priv->iochannel,
- G_IO_IN | G_IO_OUT |
- G_IO_PRI | G_IO_ERR |
- G_IO_HUP | G_IO_NVAL,
- connect_watch, sacd);
- if (cancellable) {
- sacd->cancel_id =
- g_signal_connect (cancellable, "cancelled",
- G_CALLBACK (async_cancel),
- sacd);
- }
- } else {
- priv->watch_src = soup_add_idle (priv->async_context,
- idle_connect_result, sacd);
- }
-}
-
-static void
-sync_cancel (GCancellable *cancellable, gpointer sock)
-{
- SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
-
- shutdown (priv->sockfd, SHUT_RDWR);
+ priv->connect_cancel = cancellable ? g_object_ref (cancellable) : g_cancellable_new ();
+
+ if (priv->async_context && !priv->use_thread_context)
+ g_main_context_push_thread_default (priv->async_context);
+
+ client = g_socket_client_new ();
+ g_signal_connect (client, "event",
+ G_CALLBACK (proxy_socket_client_event), sock);
+ if (priv->timeout)
+ g_socket_client_set_timeout (client, priv->timeout);
+ g_socket_client_connect_async (client,
+ G_SOCKET_CONNECTABLE (priv->remote_addr),
+ priv->connect_cancel,
+ async_connected, sacd);
+ g_object_unref (client);
}
/**
soup_socket_connect_sync (SoupSocket *sock, GCancellable *cancellable)
{
SoupSocketPrivate *priv;
- guint status, cancel_id;
+ GSocketClient *client;
+ GSocketConnection *conn;
+ GError *error = NULL;
g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_STATUS_MALFORMED);
priv = SOUP_SOCKET_GET_PRIVATE (sock);
g_return_val_if_fail (!priv->is_server, SOUP_STATUS_MALFORMED);
- g_return_val_if_fail (priv->sockfd == -1, SOUP_STATUS_MALFORMED);
+ g_return_val_if_fail (priv->gsock == NULL, SOUP_STATUS_MALFORMED);
g_return_val_if_fail (priv->remote_addr != NULL, SOUP_STATUS_MALFORMED);
- if (!soup_address_get_sockaddr (priv->remote_addr, NULL)) {
- status = soup_address_resolve_sync (priv->remote_addr,
- cancellable);
- if (!SOUP_STATUS_IS_SUCCESSFUL (status))
- return status;
- }
+ if (cancellable)
+ g_object_ref (cancellable);
+ else
+ cancellable = g_cancellable_new ();
+ priv->connect_cancel = cancellable;
+
+ client = g_socket_client_new ();
+ g_signal_connect (client, "event",
+ G_CALLBACK (proxy_socket_client_event), sock);
+ if (priv->timeout)
+ g_socket_client_set_timeout (client, priv->timeout);
+ conn = g_socket_client_connect (client,
+ G_SOCKET_CONNECTABLE (priv->remote_addr),
+ priv->connect_cancel, &error);
+ g_object_unref (client);
+
+ return socket_connected (sock, conn, error);
+}
- if (cancellable) {
- cancel_id = g_signal_connect (cancellable, "cancelled",
- G_CALLBACK (sync_cancel), sock);
- }
+int
+soup_socket_get_fd (SoupSocket *sock)
+{
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), -1);
- status = socket_connect_internal (sock);
+ return g_socket_get_fd (SOUP_SOCKET_GET_PRIVATE (sock)->gsock);
+}
- if (cancellable) {
- if (status != SOUP_STATUS_OK &&
- g_cancellable_is_cancelled (cancellable)) {
- status = SOUP_STATUS_CANCELLED;
- disconnect_internal (priv);
- }
- g_signal_handler_disconnect (cancellable, cancel_id);
- }
+GSocket *
+soup_socket_get_gsocket (SoupSocket *sock)
+{
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
- return status;
+ return SOUP_SOCKET_GET_PRIVATE (sock)->gsock;
+}
+
+GIOStream *
+soup_socket_get_iostream (SoupSocket *sock)
+{
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
+
+ return SOUP_SOCKET_GET_PRIVATE (sock)->conn;
+}
+
+static GSource *
+soup_socket_create_watch (SoupSocketPrivate *priv, GIOCondition cond,
+ GPollableSourceFunc callback, gpointer user_data,
+ GCancellable *cancellable)
+{
+ GSource *watch;
+ GMainContext *async_context;
+
+ if (cond == G_IO_IN)
+ watch = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (priv->istream), cancellable);
+ else
+ watch = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (priv->ostream), cancellable);
+ g_source_set_callback (watch, (GSourceFunc)callback, user_data, NULL);
+
+ if (priv->use_thread_context)
+ async_context = g_main_context_get_thread_default ();
+ else
+ async_context = priv->async_context;
+
+ g_source_attach (watch, async_context);
+ g_source_unref (watch);
+
+ return watch;
}
static gboolean
-listen_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data)
+listen_watch (GObject *pollable, gpointer data)
{
SoupSocket *sock = data, *new;
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock), *new_priv;
- struct soup_sockaddr_max sa;
- int sa_len, sockfd;
+ GSocket *new_gsock;
- if (condition & (G_IO_HUP | G_IO_ERR)) {
- g_source_destroy (priv->watch_src);
- priv->watch_src = NULL;
+ new_gsock = g_socket_accept (priv->gsock, NULL, NULL);
+ if (!new_gsock)
return FALSE;
- }
-
- sa_len = sizeof (sa);
- sockfd = accept (priv->sockfd, (struct sockaddr *)&sa, (void *)&sa_len);
- if (SOUP_IS_INVALID_SOCKET (sockfd))
- return TRUE;
new = g_object_new (SOUP_TYPE_SOCKET, NULL);
new_priv = SOUP_SOCKET_GET_PRIVATE (new);
- new_priv->sockfd = sockfd;
+ new_priv->gsock = new_gsock;
if (priv->async_context)
new_priv->async_context = g_main_context_ref (priv->async_context);
+ new_priv->use_thread_context = priv->use_thread_context;
new_priv->non_blocking = priv->non_blocking;
new_priv->is_server = TRUE;
- new_priv->ssl_creds = priv->ssl_creds;
- set_fdflags (new_priv);
-
- new_priv->remote_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&sa, sa_len);
+ new_priv->ssl = priv->ssl;
+ if (priv->ssl_creds)
+ new_priv->ssl_creds = priv->ssl_creds;
+ finish_socket_setup (new_priv);
if (new_priv->ssl_creds) {
- if (!soup_socket_start_ssl (new, NULL)) {
+ if (!soup_socket_start_proxy_ssl (new, NULL, NULL)) {
g_object_unref (new);
return TRUE;
}
* listening)
*
* Makes @sock start listening on its local address. When connections
- * come in, @sock will emit %new_connection.
+ * come in, @sock will emit #SoupSocket::new_connection.
*
* Return value: whether or not @sock is now listening.
**/
{
SoupSocketPrivate *priv;
- struct sockaddr *sa;
- int sa_len;
+ GSocketAddress *addr;
g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE);
priv = SOUP_SOCKET_GET_PRIVATE (sock);
- g_return_val_if_fail (priv->sockfd == -1, FALSE);
+ g_return_val_if_fail (priv->gsock == NULL, FALSE);
g_return_val_if_fail (priv->local_addr != NULL, FALSE);
priv->is_server = TRUE;
* have to make a new addr by calling getsockname(), which
* will have the right port number.
*/
- sa = soup_address_get_sockaddr (priv->local_addr, &sa_len);
- g_return_val_if_fail (sa != NULL, FALSE);
-
- priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0);
- if (SOUP_IS_INVALID_SOCKET (priv->sockfd))
+ addr = soup_address_get_gsockaddr (priv->local_addr);
+ g_return_val_if_fail (addr != NULL, FALSE);
+
+ priv->gsock = g_socket_new (g_socket_address_get_family (addr),
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ NULL);
+ if (!priv->gsock)
goto cant_listen;
- set_fdflags (priv);
+ finish_socket_setup (priv);
/* Bind */
- if (bind (priv->sockfd, sa, sa_len) != 0)
+ if (!g_socket_bind (priv->gsock, addr, TRUE, NULL))
goto cant_listen;
/* Force local_addr to be re-resolved now */
g_object_unref (priv->local_addr);
priv->local_addr = NULL;
/* Listen */
- if (listen (priv->sockfd, 10) != 0)
+ if (!g_socket_listen (priv->gsock, NULL))
goto cant_listen;
- priv->watch_src = soup_add_io_watch (priv->async_context,
- priv->iochannel,
- G_IO_IN | G_IO_ERR | G_IO_HUP,
- listen_watch, sock);
+ priv->watch_src = soup_socket_create_watch (priv, G_IO_IN,
+ listen_watch, sock,
+ NULL);
+ g_object_unref (addr);
return TRUE;
cant_listen:
- if (priv->iochannel)
- disconnect_internal (priv);
+ if (priv->conn)
+ disconnect_internal (sock, TRUE);
+ g_object_unref (addr);
return FALSE;
}
+static void
+soup_socket_peer_certificate_changed (GObject *conn, GParamSpec *pspec,
+ gpointer sock)
+{
+ SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+ priv->tls_errors = g_tls_connection_get_peer_certificate_errors (G_TLS_CONNECTION (priv->conn));
+
+ g_object_notify (sock, "tls-certificate");
+ g_object_notify (sock, "tls-errors");
+}
+
+static gboolean
+soup_socket_accept_certificate (GTlsConnection *conn, GTlsCertificate *cert,
+ GTlsCertificateFlags errors, gpointer sock)
+{
+ return TRUE;
+}
+
/**
* soup_socket_start_ssl:
* @sock: the socket
GCancellable *cancellable)
{
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
- GIOChannel *ssl_chan;
- GIOChannel *real_chan;
+ GTlsBackend *backend = g_tls_backend_get_default ();
- real_chan = priv->iochannel;
- ssl_chan = soup_ssl_wrap_iochannel (
- real_chan, priv->is_server ?
- SOUP_SSL_TYPE_SERVER : SOUP_SSL_TYPE_CLIENT,
- ssl_host, priv->ssl_creds);
+ if (G_IS_TLS_CONNECTION (priv->conn))
+ return TRUE;
- if (!ssl_chan)
+ if (g_cancellable_is_cancelled (cancellable))
return FALSE;
- priv->iochannel = ssl_chan;
- g_io_channel_unref (real_chan);
+ priv->ssl = TRUE;
+
+ if (!priv->is_server) {
+ GTlsClientConnection *conn;
+ GSocketConnectable *identity;
+
+ identity = g_network_address_new (ssl_host, 0);
+ conn = g_initable_new (g_tls_backend_get_client_connection_type (backend),
+ NULL, NULL,
+ "base-io-stream", priv->conn,
+ "server-identity", identity,
+ "database", priv->ssl_creds,
+ "require-close-notify", FALSE,
+ "use-ssl3", priv->ssl_fallback,
+ NULL);
+ g_object_unref (identity);
+
+ if (!conn)
+ return FALSE;
+
+ g_object_unref (priv->conn);
+ priv->conn = G_IO_STREAM (conn);
+
+ if (!priv->ssl_strict) {
+ g_signal_connect (conn, "accept-certificate",
+ G_CALLBACK (soup_socket_accept_certificate),
+ sock);
+ }
+ } else {
+ GTlsServerConnection *conn;
+
+ conn = g_initable_new (g_tls_backend_get_server_connection_type (backend),
+ NULL, NULL,
+ "base-io-stream", priv->conn,
+ "certificate", priv->ssl_creds,
+ "use-system-certdb", FALSE,
+ "require-close-notify", FALSE,
+ NULL);
+ if (!conn)
+ return FALSE;
+
+ g_object_unref (priv->conn);
+ priv->conn = G_IO_STREAM (conn);
+ }
+
+ g_signal_connect (priv->conn, "notify::peer-certificate",
+ G_CALLBACK (soup_socket_peer_certificate_changed), sock);
+
+ if (priv->istream)
+ g_object_unref (priv->istream);
+ if (priv->ostream)
+ g_object_unref (priv->ostream);
+ priv->istream = soup_filter_input_stream_new (g_io_stream_get_input_stream (priv->conn));
+ priv->ostream = g_object_ref (g_io_stream_get_output_stream (priv->conn));
return TRUE;
}
+guint
+soup_socket_handshake_sync (SoupSocket *sock,
+ GCancellable *cancellable)
+{
+ SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+ GError *error = NULL;
+
+ priv->ssl = TRUE;
+ if (g_tls_connection_handshake (G_TLS_CONNECTION (priv->conn),
+ cancellable, &error))
+ return SOUP_STATUS_OK;
+ else if (!priv->ssl_fallback &&
+ g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS)) {
+ g_error_free (error);
+ return SOUP_STATUS_TLS_FAILED;
+ } else {
+ g_error_free (error);
+ return SOUP_STATUS_SSL_FAILED;
+ }
+}
+
+static void
+handshake_async_ready (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ SoupSocketAsyncConnectData *data = user_data;
+ SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (data->sock);
+ GError *error = NULL;
+ guint status;
+
+ if (priv->async_context && !priv->use_thread_context)
+ g_main_context_pop_thread_default (priv->async_context);
+
+ if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (source),
+ result, &error))
+ status = SOUP_STATUS_OK;
+ else if (!priv->ssl_fallback &&
+ g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS))
+ status = SOUP_STATUS_TLS_FAILED;
+ else
+ status = SOUP_STATUS_SSL_FAILED;
+ g_clear_error (&error);
+
+ data->callback (data->sock, status, data->user_data);
+ g_object_unref (data->sock);
+ g_slice_free (SoupSocketAsyncConnectData, data);
+}
+
+void
+soup_socket_handshake_async (SoupSocket *sock,
+ GCancellable *cancellable,
+ SoupSocketCallback callback,
+ gpointer user_data)
+{
+ SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+ SoupSocketAsyncConnectData *data;
+
+ priv->ssl = TRUE;
+
+ data = g_slice_new (SoupSocketAsyncConnectData);
+ data->sock = g_object_ref (sock);
+ data->callback = callback;
+ data->user_data = user_data;
+
+ if (priv->async_context && !priv->use_thread_context)
+ g_main_context_push_thread_default (priv->async_context);
+ g_tls_connection_handshake_async (G_TLS_CONNECTION (priv->conn),
+ G_PRIORITY_DEFAULT,
+ cancellable, handshake_async_ready,
+ data);
+}
+
/**
* soup_socket_is_ssl:
* @sock: a #SoupSocket
*
- * Tests if @sock is set up to do SSL. Note that this simply means
- * that the %SOUP_SOCKET_SSL_CREDENTIALS property has been set; it
- * does not mean that soup_socket_start_ssl() has been called.
+ * Tests if @sock is doing (or has attempted to do) SSL.
*
* Return value: %TRUE if @sock has SSL credentials set
**/
{
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
- return priv->ssl_creds != NULL;
+ return priv->ssl;
}
/**
g_return_if_fail (SOUP_IS_SOCKET (sock));
priv = SOUP_SOCKET_GET_PRIVATE (sock);
- if (g_mutex_trylock (priv->iolock)) {
- if (priv->iochannel)
- disconnect_internal (priv);
+ if (priv->connect_cancel) {
+ disconnect_internal (sock, FALSE);
+ g_cancellable_cancel (priv->connect_cancel);
+ return;
+ } else if (g_mutex_trylock (&priv->iolock)) {
+ if (priv->conn)
+ disconnect_internal (sock, TRUE);
else
already_disconnected = TRUE;
- g_mutex_unlock (priv->iolock);
+ g_mutex_unlock (&priv->iolock);
} else {
- int sockfd;
-
/* Another thread is currently doing IO, so
- * we can't close the iochannel. So just shutdown
+ * we can't close the socket. So just shutdown
* the file descriptor to force the I/O to fail.
- * (It will actually be closed when the socket is
- * destroyed.)
+ * (It will actually be closed when the socket
+ * is destroyed.)
*/
- sockfd = priv->sockfd;
- priv->sockfd = -1;
-
- if (sockfd == -1)
- already_disconnected = TRUE;
- else
- shutdown (sockfd, SHUT_RDWR);
+ g_socket_shutdown (priv->gsock, TRUE, TRUE, NULL);
}
if (already_disconnected)
return;
+ /* Keep ref around signals in case the object is unreferenced
+ * in a handler
+ */
+ g_object_ref (sock);
+
/* Give all readers a chance to notice the connection close */
g_signal_emit (sock, signals[READABLE], 0);
/* Then let everyone know we're disconnected */
g_signal_emit (sock, signals[DISCONNECTED], 0);
+
+ g_object_unref (sock);
}
/**
g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE);
priv = SOUP_SOCKET_GET_PRIVATE (sock);
- return priv->iochannel != NULL;
+ return priv->conn != NULL;
}
/**
*
* Returns the #SoupAddress corresponding to the local end of @sock.
*
- * Return value: the #SoupAddress
+ * Return value: (transfer none): the #SoupAddress
**/
SoupAddress *
soup_socket_get_local_address (SoupSocket *sock)
g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
priv = SOUP_SOCKET_GET_PRIVATE (sock);
- g_mutex_lock (priv->addrlock);
+ g_mutex_lock (&priv->addrlock);
if (!priv->local_addr) {
- struct soup_sockaddr_max bound_sa;
- int sa_len;
-
- sa_len = sizeof (bound_sa);
- getsockname (priv->sockfd, (struct sockaddr *)&bound_sa, (void *)&sa_len);
- priv->local_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&bound_sa, sa_len);
+ GSocketAddress *addr;
+ struct sockaddr_storage sa;
+ gssize sa_len;
+
+ addr = g_socket_get_local_address (priv->gsock, NULL);
+ sa_len = g_socket_address_get_native_size (addr);
+ g_socket_address_to_native (addr, &sa, sa_len, NULL);
+ priv->local_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&sa, sa_len);
+ g_object_unref (addr);
}
- g_mutex_unlock (priv->addrlock);
+ g_mutex_unlock (&priv->addrlock);
return priv->local_addr;
}
*
* Returns the #SoupAddress corresponding to the remote end of @sock.
*
- * Return value: the #SoupAddress
+ * Return value: (transfer none): the #SoupAddress
**/
SoupAddress *
soup_socket_get_remote_address (SoupSocket *sock)
g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
priv = SOUP_SOCKET_GET_PRIVATE (sock);
- g_mutex_lock (priv->addrlock);
+ g_mutex_lock (&priv->addrlock);
if (!priv->remote_addr) {
- struct soup_sockaddr_max bound_sa;
- int sa_len;
-
- sa_len = sizeof (bound_sa);
- getpeername (priv->sockfd, (struct sockaddr *)&bound_sa, (void *)&sa_len);
- priv->remote_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&bound_sa, sa_len);
+ GSocketAddress *addr;
+ struct sockaddr_storage sa;
+ gssize sa_len;
+
+ addr = g_socket_get_remote_address (priv->gsock, NULL);
+ sa_len = g_socket_address_get_native_size (addr);
+ g_socket_address_to_native (addr, &sa, sa_len, NULL);
+ priv->remote_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&sa, sa_len);
+ g_object_unref (addr);
}
- g_mutex_unlock (priv->addrlock);
+ g_mutex_unlock (&priv->addrlock);
return priv->remote_addr;
}
+GInputStream *
+soup_socket_get_input_stream (SoupSocket *sock)
+{
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
+ return SOUP_SOCKET_GET_PRIVATE (sock)->istream;
+}
+
+GOutputStream *
+soup_socket_get_output_stream (SoupSocket *sock)
+{
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
+
+ return SOUP_SOCKET_GET_PRIVATE (sock)->ostream;
+}
static gboolean
-socket_read_watch (GIOChannel *chan, GIOCondition cond, gpointer user_data)
+socket_read_watch (GObject *pollable, gpointer user_data)
{
SoupSocket *sock = user_data;
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
priv->read_src = NULL;
-
- if (cond & (G_IO_ERR | G_IO_HUP))
- soup_socket_disconnect (sock);
- else
- g_signal_emit (sock, signals[READABLE], 0);
-
+ g_signal_emit (sock, signals[READABLE], 0);
return FALSE;
}
static SoupSocketIOStatus
-read_from_network (SoupSocket *sock, gpointer buffer, gsize len,
- gsize *nread, GError **error)
+translate_read_status (SoupSocket *sock, GCancellable *cancellable,
+ gssize my_nread, gsize *nread,
+ GError *my_err, GError **error)
{
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
- GIOStatus status;
- GIOCondition cond = G_IO_IN;
- GError *my_err = NULL;
- *nread = 0;
-
- if (!priv->iochannel)
+ if (my_nread > 0) {
+ g_assert_no_error (my_err);
+ *nread = my_nread;
+ return SOUP_SOCKET_OK;
+ } else if (my_nread == 0) {
+ g_assert_no_error (my_err);
+ *nread = my_nread;
return SOUP_SOCKET_EOF;
-
- status = g_io_channel_read_chars (priv->iochannel,
- buffer, len, nread, &my_err);
- if (my_err) {
- if (my_err->domain == SOUP_SSL_ERROR &&
- my_err->code == SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE)
- cond = G_IO_OUT;
- g_propagate_error (error, my_err);
- }
-
- switch (status) {
- case G_IO_STATUS_NORMAL:
- case G_IO_STATUS_AGAIN:
- if (*nread > 0) {
- g_clear_error (error);
- return SOUP_SOCKET_OK;
- }
-
- /* If the socket is sync and we get EAGAIN, then it is
- * a socket timeout and should be treated as an error
- * condition.
- */
- if (!priv->non_blocking)
- return SOUP_SOCKET_ERROR;
-
+ } else if (g_error_matches (my_err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+ g_clear_error (&my_err);
if (!priv->read_src) {
priv->read_src =
- soup_add_io_watch (priv->async_context,
- priv->iochannel,
- cond | G_IO_HUP | G_IO_ERR,
- socket_read_watch, sock);
+ soup_socket_create_watch (priv, G_IO_IN,
+ socket_read_watch, sock,
+ cancellable);
}
- g_clear_error (error);
return SOUP_SOCKET_WOULD_BLOCK;
-
- case G_IO_STATUS_EOF:
- g_clear_error (error);
- return SOUP_SOCKET_EOF;
-
- default:
- return SOUP_SOCKET_ERROR;
- }
-}
-
-static SoupSocketIOStatus
-read_from_buf (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread)
-{
- SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
- GByteArray *read_buf = priv->read_buf;
-
- *nread = MIN (read_buf->len, len);
- memcpy (buffer, read_buf->data, *nread);
-
- if (*nread == read_buf->len) {
- g_byte_array_free (read_buf, TRUE);
- priv->read_buf = NULL;
- } else {
- memmove (read_buf->data, read_buf->data + *nread,
- read_buf->len - *nread);
- g_byte_array_set_size (read_buf, read_buf->len - *nread);
}
- return SOUP_SOCKET_OK;
+ g_propagate_error (error, my_err);
+ return SOUP_SOCKET_ERROR;
}
/**
* @sock: the socket
* @buffer: buffer to read into
* @len: size of @buffer in bytes
- * @nread: on return, the number of bytes read into @buffer
+ * @nread: (out): on return, the number of bytes read into @buffer
* @cancellable: a #GCancellable, or %NULL
* @error: error pointer
*
* Attempts to read up to @len bytes from @sock into @buffer. If some
* data is successfully read, soup_socket_read() will return
* %SOUP_SOCKET_OK, and *@nread will contain the number of bytes
- * actually read.
+ * actually read (which may be less than @len).
*
* If @sock is non-blocking, and no data is available, the return
* value will be %SOUP_SOCKET_WOULD_BLOCK. In this case, the caller
- * can connect to the %readable signal to know when there is more data
- * to read. (NB: You MUST read all available data off the socket
- * first. The %readable signal will only be emitted after
- * soup_socket_read() has returned %SOUP_SOCKET_WOULD_BLOCK.)
+ * can connect to the #SoupSocket::readable signal to know when there
+ * is more data to read. (NB: You MUST read all available data off the
+ * socket first. #SoupSocket::readable is only emitted after
+ * soup_socket_read() returns %SOUP_SOCKET_WOULD_BLOCK, and it is only
+ * emitted once. See the documentation for #SoupSocket:non-blocking.)
*
* Return value: a #SoupSocketIOStatus, as described above (or
* %SOUP_SOCKET_EOF if the socket is no longer connected, or
{
SoupSocketPrivate *priv;
SoupSocketIOStatus status;
+ gssize my_nread;
+ GError *my_err = NULL;
g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR);
g_return_val_if_fail (nread != NULL, SOUP_SOCKET_ERROR);
priv = SOUP_SOCKET_GET_PRIVATE (sock);
- g_mutex_lock (priv->iolock);
- if (priv->read_buf)
- status = read_from_buf (sock, buffer, len, nread);
- else
- status = read_from_network (sock, buffer, len, nread, error);
- g_mutex_unlock (priv->iolock);
+ g_mutex_lock (&priv->iolock);
+
+ if (!priv->istream) {
+ status = SOUP_SOCKET_EOF;
+ goto out;
+ }
+
+ if (!priv->non_blocking) {
+ my_nread = g_input_stream_read (priv->istream, buffer, len,
+ cancellable, &my_err);
+ } else {
+ my_nread = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (priv->istream),
+ buffer, len,
+ cancellable, &my_err);
+ }
+ status = translate_read_status (sock, cancellable,
+ my_nread, nread, my_err, error);
+
+out:
+ g_mutex_unlock (&priv->iolock);
return status;
}
* @len: size of @buffer in bytes
* @boundary: boundary to read until
* @boundary_len: length of @boundary in bytes
- * @nread: on return, the number of bytes read into @buffer
+ * @nread: (out): on return, the number of bytes read into @buffer
* @got_boundary: on return, whether or not the data in @buffer
* ends with the boundary string
* @cancellable: a #GCancellable, or %NULL
* included in the returned data, and *@got_boundary will be set to
* %TRUE.) Any data after the boundary will returned in future reads.
*
+ * soup_socket_read_until() will almost always return fewer than @len
+ * bytes: if the boundary is found, then it will only return the bytes
+ * up until the end of the boundary, and if the boundary is not found,
+ * then it will leave the last <literal>(boundary_len - 1)</literal>
+ * bytes in its internal buffer, in case they form the start of the
+ * boundary string. Thus, @len normally needs to be at least 1 byte
+ * longer than @boundary_len if you want to make any progress at all.
+ *
* Return value: as for soup_socket_read()
**/
SoupSocketIOStatus
{
SoupSocketPrivate *priv;
SoupSocketIOStatus status;
- GByteArray *read_buf;
- guint match_len, prev_len;
- guint8 *p, *end;
+ gssize my_nread;
+ GError *my_err = NULL;
g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR);
g_return_val_if_fail (nread != NULL, SOUP_SOCKET_ERROR);
priv = SOUP_SOCKET_GET_PRIVATE (sock);
- g_mutex_lock (priv->iolock);
+ g_mutex_lock (&priv->iolock);
*got_boundary = FALSE;
- if (!priv->read_buf)
- priv->read_buf = g_byte_array_new ();
- read_buf = priv->read_buf;
-
- if (read_buf->len < boundary_len) {
- prev_len = read_buf->len;
- g_byte_array_set_size (read_buf, len);
- status = read_from_network (sock,
- read_buf->data + prev_len,
- len - prev_len, nread, error);
- read_buf->len = prev_len + *nread;
-
- if (status != SOUP_SOCKET_OK) {
- g_mutex_unlock (priv->iolock);
- return status;
- }
+ if (!priv->istream)
+ status = SOUP_SOCKET_EOF;
+ else {
+ my_nread = soup_filter_input_stream_read_until (
+ SOUP_FILTER_INPUT_STREAM (priv->istream),
+ buffer, len, boundary, boundary_len,
+ !priv->non_blocking,
+ got_boundary, cancellable, &my_err);
+ status = translate_read_status (sock, cancellable,
+ my_nread, nread, my_err, error);
}
- /* Scan for the boundary */
- end = read_buf->data + read_buf->len;
- for (p = read_buf->data; p <= end - boundary_len; p++) {
- if (!memcmp (p, boundary, boundary_len)) {
- p += boundary_len;
- *got_boundary = TRUE;
- break;
- }
- }
-
- /* Return everything up to 'p' (which is either just after the
- * boundary, or @boundary_len - 1 bytes before the end of the
- * buffer).
- */
- match_len = p - read_buf->data;
- status = read_from_buf (sock, buffer, MIN (len, match_len), nread);
-
- g_mutex_unlock (priv->iolock);
+ g_mutex_unlock (&priv->iolock);
return status;
}
static gboolean
-socket_write_watch (GIOChannel *chan, GIOCondition cond, gpointer user_data)
+socket_write_watch (GObject *pollable, gpointer user_data)
{
SoupSocket *sock = user_data;
SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
priv->write_src = NULL;
-
- if (cond & (G_IO_ERR | G_IO_HUP))
- soup_socket_disconnect (sock);
- else
- g_signal_emit (sock, signals[WRITABLE], 0);
-
+ g_signal_emit (sock, signals[WRITABLE], 0);
return FALSE;
}
* @sock: the socket
* @buffer: data to write
* @len: size of @buffer, in bytes
- * @nwrote: on return, number of bytes written
+ * @nwrote: (out): on return, number of bytes written
* @cancellable: a #GCancellable, or %NULL
* @error: error pointer
*
* Attempts to write @len bytes from @buffer to @sock. If some data is
- * successfully written, the resturn status will be
- * %SOUP_SOCKET_OK, and *@nwrote will contain the number of bytes
- * actually written.
+ * successfully written, the return status will be %SOUP_SOCKET_OK,
+ * and *@nwrote will contain the number of bytes actually written
+ * (which may be less than @len).
*
* If @sock is non-blocking, and no data could be written right away,
* the return value will be %SOUP_SOCKET_WOULD_BLOCK. In this case,
- * the caller can connect to the %writable signal to know when more
- * data can be written. (NB: %writable is only emitted after a
- * %SOUP_SOCKET_WOULD_BLOCK.)
+ * the caller can connect to the #SoupSocket::writable signal to know
+ * when more data can be written. (NB: #SoupSocket::writable is only
+ * emitted after soup_socket_write() returns %SOUP_SOCKET_WOULD_BLOCK,
+ * and it is only emitted once. See the documentation for
+ * #SoupSocket:non-blocking.)
*
* Return value: a #SoupSocketIOStatus, as described above (or
* %SOUP_SOCKET_EOF or %SOUP_SOCKET_ERROR. @error will be set if the
GCancellable *cancellable, GError **error)
{
SoupSocketPrivate *priv;
- GIOStatus status;
- GIOCondition cond = G_IO_OUT;
GError *my_err = NULL;
+ gssize my_nwrote;
g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR);
g_return_val_if_fail (nwrote != NULL, SOUP_SOCKET_ERROR);
priv = SOUP_SOCKET_GET_PRIVATE (sock);
- g_mutex_lock (priv->iolock);
+ g_mutex_lock (&priv->iolock);
- if (!priv->iochannel) {
- g_mutex_unlock (priv->iolock);
+ if (!priv->conn) {
+ g_mutex_unlock (&priv->iolock);
return SOUP_SOCKET_EOF;
}
if (priv->write_src) {
- g_mutex_unlock (priv->iolock);
+ g_mutex_unlock (&priv->iolock);
return SOUP_SOCKET_WOULD_BLOCK;
}
- status = g_io_channel_write_chars (priv->iochannel,
- buffer, len, nwrote, &my_err);
- if (my_err) {
- if (my_err->domain == SOUP_SSL_ERROR &&
- my_err->code == SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ)
- cond = G_IO_IN;
- g_propagate_error (error, my_err);
- }
-
- /* If the socket is sync and we get EAGAIN, then it is a
- * socket timeout and should be treated as an error condition.
- */
- if (!priv->non_blocking && status == G_IO_STATUS_AGAIN) {
- g_mutex_unlock (priv->iolock);
- return SOUP_SOCKET_ERROR;
+ if (!priv->non_blocking) {
+ my_nwrote = g_output_stream_write (priv->ostream,
+ buffer, len,
+ cancellable, &my_err);
+ } else {
+ my_nwrote = g_pollable_output_stream_write_nonblocking (
+ G_POLLABLE_OUTPUT_STREAM (priv->ostream),
+ buffer, len, cancellable, &my_err);
}
- if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN) {
- g_mutex_unlock (priv->iolock);
- return SOUP_SOCKET_ERROR;
+ if (my_nwrote > 0) {
+ g_mutex_unlock (&priv->iolock);
+ g_clear_error (&my_err);
+ *nwrote = my_nwrote;
+ return SOUP_SOCKET_OK;
}
- g_clear_error (error);
+ if (g_error_matches (my_err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+ g_mutex_unlock (&priv->iolock);
+ g_clear_error (&my_err);
- if (*nwrote) {
- g_mutex_unlock (priv->iolock);
- return SOUP_SOCKET_OK;
+ priv->write_src =
+ soup_socket_create_watch (priv,
+ G_IO_OUT,
+ socket_write_watch, sock, cancellable);
+ return SOUP_SOCKET_WOULD_BLOCK;
}
- priv->write_src =
- soup_add_io_watch (priv->async_context,
- priv->iochannel,
- cond | G_IO_HUP | G_IO_ERR,
- socket_write_watch, sock);
- g_mutex_unlock (priv->iolock);
- return SOUP_SOCKET_WOULD_BLOCK;
+ g_mutex_unlock (&priv->iolock);
+ g_propagate_error (error, my_err);
+ return SOUP_SOCKET_ERROR;
}