soup-message-io: use gio streams rather than SoupSocket
[platform/upstream/libsoup.git] / libsoup / soup-socket.c
index 953c6f8..2d72b38 100644 (file)
@@ -9,25 +9,28 @@
 #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
  * @short_description: A network socket
  *
+ * #SoupSocket is libsoup's TCP socket type. While it is primarily
+ * intended for internal use, #SoupSocket<!-- -->s are exposed in the
+ * API in various places, and some of their methods (eg,
+ * soup_socket_get_remote_address()) may be useful to applications.
  **/
 
 G_DEFINE_TYPE (SoupSocket, soup_socket, G_TYPE_OBJECT)
@@ -37,6 +40,7 @@ enum {
        WRITABLE,
        DISCONNECTED,
        NEW_CONNECTION,
+       EVENT,
        LAST_SIGNAL
 };
 
@@ -50,70 +54,82 @@ enum {
        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)
-#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);
@@ -130,24 +146,35 @@ finalize (GObject *object)
 {
        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);
 }
@@ -171,7 +198,8 @@ soup_socket_class_init (SoupSocketClass *socket_class)
         * @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",
@@ -179,7 +207,7 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                              G_SIGNAL_RUN_LAST,
                              G_STRUCT_OFFSET (SoupSocketClass, readable),
                              NULL, NULL,
-                             soup_marshal_NONE__NONE,
+                             _soup_marshal_NONE__NONE,
                              G_TYPE_NONE, 0);
 
        /**
@@ -187,7 +215,7 @@ soup_socket_class_init (SoupSocketClass *socket_class)
         * @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",
@@ -195,7 +223,7 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                              G_SIGNAL_RUN_LAST,
                              G_STRUCT_OFFSET (SoupSocketClass, writable),
                              NULL, NULL,
-                             soup_marshal_NONE__NONE,
+                             _soup_marshal_NONE__NONE,
                              G_TYPE_NONE, 0);
 
        /**
@@ -211,7 +239,7 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                              G_SIGNAL_RUN_LAST,
                              G_STRUCT_OFFSET (SoupSocketClass, disconnected),
                              NULL, NULL,
-                             soup_marshal_NONE__NONE,
+                             _soup_marshal_NONE__NONE,
                              G_TYPE_NONE, 0);
 
        /**
@@ -231,11 +259,39 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                              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,
@@ -243,6 +299,12 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                     "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,
@@ -250,6 +312,36 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                     "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,
@@ -257,6 +349,12 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                      "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,
@@ -264,12 +362,65 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                      "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,
@@ -277,6 +428,35 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                      "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,
@@ -285,88 +465,64 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                   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)
+finish_socket_setup (SoupSocketPrivate *priv)
 {
-#ifndef G_OS_WIN32
-       int flags;
-#else
-       u_log val;
-#endif
-
-       if (priv->sockfd == -1)
+       if (!priv->gsock)
                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
-}
+       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));
 
-static void
-set_fdflags (SoupSocketPrivate *priv)
-{
-       int opt;
-       struct timeval timeout;
-#ifndef G_OS_WIN32
-       int flags;
-#endif
-
-       if (priv->sockfd == -1)
-               return;
-
-       set_nonblocking (priv);
-
-#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
@@ -384,20 +540,34 @@ set_property (GObject *object, guint prop_id,
                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);
                break;
        }
 }
@@ -424,13 +594,35 @@ get_property (GObject *object, guint prop_id,
        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;
        }
 }
@@ -459,114 +651,48 @@ soup_socket_new (const char *optname1, ...)
        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)
-{
-       SoupSocketAsyncConnectData *sacd = user_data;
-
-       if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
-               sacd->callback (sacd->sock, status, sacd->user_data);
-               g_slice_free (SoupSocketAsyncConnectData, sacd);
-               return;
-       }
-
-       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)
+proxy_socket_client_event (GSocketClient       *client,
+                          GSocketClientEvent   event,
+                          GSocketConnectable  *connectable,
+                          GIOStream           *connection,
+                          gpointer             user_data)
 {
-       SoupSocketAsyncConnectData *sacd = user_data;
-       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
+       SoupSocket *sock = user_data;
 
-       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;
 }
 
 /**
@@ -578,11 +704,38 @@ socket_connect_internal (SoupSocket *sock)
  * 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
@@ -599,54 +752,32 @@ soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable,
 {
        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);
 }
 
 /**
@@ -666,73 +797,110 @@ guint
 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;
                }
@@ -750,7 +918,7 @@ listen_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data)
  * 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.
  **/
@@ -759,12 +927,11 @@ soup_socket_listen (SoupSocket *sock)
 
 {
        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;
@@ -775,38 +942,61 @@ soup_socket_listen (SoupSocket *sock)
         * 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
@@ -840,30 +1030,157 @@ soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host,
                             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 doing (or has attempted to do) SSL.
+ *
+ * Return value: %TRUE if @sock has SSL credentials set
+ **/
 gboolean
 soup_socket_is_ssl (SoupSocket *sock)
 {
        SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       return priv->ssl_creds != NULL;
+       return priv->ssl;
 }
 
 /**
@@ -882,33 +1199,34 @@ soup_socket_disconnect (SoupSocket *sock)
        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);
 
@@ -916,6 +1234,8 @@ soup_socket_disconnect (SoupSocket *sock)
 
        /* Then let everyone know we're disconnected */
        g_signal_emit (sock, signals[DISCONNECTED], 0);
+
+       g_object_unref (sock);
 }
 
 /**
@@ -934,7 +1254,7 @@ soup_socket_is_connected (SoupSocket *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;
 }
 
 /**
@@ -943,7 +1263,7 @@ soup_socket_is_connected (SoupSocket *sock)
  *
  * 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)
@@ -953,16 +1273,19 @@ 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;
 }
@@ -973,7 +1296,7 @@ soup_socket_get_local_address (SoupSocket *sock)
  *
  * 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)
@@ -983,113 +1306,79 @@ 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;
 
-       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;
 }
 
 /**
@@ -1107,21 +1396,22 @@ read_from_buf (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread)
  * @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
@@ -1134,16 +1424,34 @@ soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len,
 {
        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;
 }
@@ -1155,7 +1463,7 @@ soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len,
  * @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
@@ -1166,6 +1474,14 @@ soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len,
  * 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
@@ -1176,70 +1492,43 @@ soup_socket_read_until (SoupSocket *sock, gpointer buffer, gsize len,
 {
        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);
-       priv = SOUP_SOCKET_GET_PRIVATE (sock);
+       g_return_val_if_fail (nread != NULL, SOUP_SOCKET_ERROR);
        g_return_val_if_fail (len >= boundary_len, SOUP_SOCKET_ERROR);
 
-       g_mutex_lock (priv->iolock);
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       *got_boundary = FALSE;
+       g_mutex_lock (&priv->iolock);
 
-       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;
-               }
-       }
+       *got_boundary = FALSE;
 
-       /* 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;
-               }
+       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);
        }
 
-       /* 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;
 }
 
@@ -1248,20 +1537,22 @@ socket_write_watch (GIOChannel *chan, GIOCondition cond, gpointer user_data)
  * @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_SUCCESS, 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
@@ -1273,67 +1564,54 @@ soup_socket_write (SoupSocket *sock, gconstpointer buffer,
                   GCancellable *cancellable, GError **error)
 {
        SoupSocketPrivate *priv;
-       GIOStatus status;
-#ifdef SIGPIPE
-       gpointer pipe_handler;
-#endif
-       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;
        }
 
-#ifdef SIGPIPE
-       pipe_handler = signal (SIGPIPE, SIG_IGN);
-#endif
-       status = g_io_channel_write_chars (priv->iochannel,
-                                          buffer, len, nwrote, &my_err);
-#ifdef SIGPIPE
-       signal (SIGPIPE, pipe_handler);
-#endif
-       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;
 }