soup-message-io: use gio streams rather than SoupSocket
[platform/upstream/libsoup.git] / libsoup / soup-socket.c
index 0b51ce3..2d72b38 100644 (file)
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
- * soup-socket.c: Platform neutral socket networking code.
+ * soup-socket.c: Socket networking code.
  *
- * Authors:
- *      David Helder  (dhelder@umich.edu)
- *      Alex Graveley (alex@ximian.com)
- *
- * Original code compliments of David Helder's GNET Networking Library, and is
- * Copyright (C) 2000  David Helder & Andrew Lanoix.
- *
- * All else Copyright (C) 2000, Ximian, Inc.
+ * Copyright (C) 2000-2003, Ximian, Inc.
  */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
+
+#include <stdio.h>
+#include <errno.h>
 #include <fcntl.h>
-#include <glib.h>
+#include <signal.h>
 #include <string.h>
+#include <unistd.h>
 
-#include "soup-private.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-misc-private.h"
 
-#include <unistd.h>
-#ifndef socklen_t
-#  define socklen_t size_t
-#endif
-
-#ifndef INET_ADDRSTRLEN
-#  define INET_ADDRSTRLEN 16
-#  define INET6_ADDRSTRLEN 46
-#endif
+/**
+ * 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.
+ **/
 
-#define SOUP_SOCKADDR_IN(s) (*((struct sockaddr_in*) &s))
+G_DEFINE_TYPE (SoupSocket, soup_socket, G_TYPE_OBJECT)
+
+enum {
+       READABLE,
+       WRITABLE,
+       DISCONNECTED,
+       NEW_CONNECTION,
+       EVENT,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum {
+       PROP_0,
+
+       PROP_LOCAL_ADDRESS,
+       PROP_REMOTE_ADDRESS,
+       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 {
-       SoupSocketConnectFn  func;
-       gpointer             data;
+       SoupAddress *local_addr, *remote_addr;
+       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;
+
+       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))
+
+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);
+
+static void soup_socket_peer_certificate_changed (GObject *conn,
+                                                 GParamSpec *pspec,
+                                                 gpointer user_data);
 
-       gpointer             inetaddr_id;
-       gpointer             tcp_id;
-} SoupSocketConnectState;
+static void
+soup_socket_init (SoupSocket *sock)
+{
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+       priv->non_blocking = TRUE;
+       g_mutex_init (&priv->addrlock);
+       g_mutex_init (&priv->iolock);
+}
 
 static void
-soup_address_new_sync_cb (SoupAddress *addr,
-                         SoupAddressStatus  status,
-                         gpointer           user_data)
+disconnect_internal (SoupSocket *sock, gboolean close)
 {
-       SoupAddress **ret = user_data;
-       *ret = addr;
+       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);
+               priv->read_src = NULL;
+       }
+       if (priv->write_src) {
+               g_source_destroy (priv->write_src);
+               priv->write_src = NULL;
+       }
 }
 
-SoupAddress *
-soup_address_new_sync (const gchar *name, const gint port)
+static void
+finalize (GObject *object)
 {
-       SoupAddress *ret = (SoupAddress *) 0xdeadbeef;
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object);
+
+       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);
+       }
 
-       soup_address_new (name, port, soup_address_new_sync_cb, &ret);
+       g_clear_object (&priv->istream);
+       g_clear_object (&priv->ostream);
 
-       while (1) {
-               g_main_iteration (TRUE);
-               if (ret != (SoupAddress *) 0xdeadbeef) return ret;
+       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->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);
+
+       g_mutex_clear (&priv->addrlock);
+       g_mutex_clear (&priv->iolock);
 
-       return ret;
+       G_OBJECT_CLASS (soup_socket_parent_class)->finalize (object);
 }
 
-/**
- * soup_address_ref
- * @ia: SoupAddress to reference
- *
- * Increment the reference counter of the SoupAddress.
- **/
-void
-soup_address_ref (SoupAddress* ia)
+static void
+soup_socket_class_init (SoupSocketClass *socket_class)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (socket_class);
+
+       g_type_class_add_private (socket_class, sizeof (SoupSocketPrivate));
+
+       /* virtual method override */
+       object_class->finalize = finalize;
+       object_class->set_property = set_property;
+       object_class->get_property = get_property;
+
+       /* signals */
+
+       /**
+        * SoupSocket::readable:
+        * @sock: the socket
+        *
+        * Emitted when an async socket is readable. See
+        * soup_socket_read(), soup_socket_read_until() and
+        * #SoupSocket:non-blocking.
+        **/
+       signals[READABLE] =
+               g_signal_new ("readable",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (SoupSocketClass, readable),
+                             NULL, NULL,
+                             _soup_marshal_NONE__NONE,
+                             G_TYPE_NONE, 0);
+
+       /**
+        * SoupSocket::writable:
+        * @sock: the socket
+        *
+        * Emitted when an async socket is writable. See
+        * soup_socket_write() and #SoupSocket:non-blocking.
+        **/
+       signals[WRITABLE] =
+               g_signal_new ("writable",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (SoupSocketClass, writable),
+                             NULL, NULL,
+                             _soup_marshal_NONE__NONE,
+                             G_TYPE_NONE, 0);
+
+       /**
+        * SoupSocket::disconnected:
+        * @sock: the socket
+        *
+        * Emitted when the socket is disconnected, for whatever
+        * reason.
+        **/
+       signals[DISCONNECTED] =
+               g_signal_new ("disconnected",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (SoupSocketClass, disconnected),
+                             NULL, NULL,
+                             _soup_marshal_NONE__NONE,
+                             G_TYPE_NONE, 0);
+
+       /**
+        * SoupSocket::new-connection:
+        * @sock: the socket
+        * @new: the new socket
+        *
+        * Emitted when a listening socket (set up with
+        * soup_socket_listen()) receives a new connection.
+        *
+        * You must ref the @new if you want to keep it; otherwise it
+        * will be destroyed after the signal is emitted.
+        **/
+       signals[NEW_CONNECTION] =
+               g_signal_new ("new_connection",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_FIRST,
+                             G_STRUCT_OFFSET (SoupSocketClass, new_connection),
+                             NULL, NULL,
+                             _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,
+                                    "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,
+                                    "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,
+                                     "Non-blocking",
+                                     "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,
+                                     "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,
+                                     "Async GMainContext",
+                                     "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,
+                                  "Timeout value",
+                                  "Value in seconds to timeout a blocking I/O",
+                                  0, G_MAXUINT, 0,
+                                  G_PARAM_READWRITE));
+
+       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
+finish_socket_setup (SoupSocketPrivate *priv)
+{
+       if (!priv->gsock)
+               return;
+
+       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));
+
+       g_socket_set_timeout (priv->gsock, priv->timeout);
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+             const GValue *value, GParamSpec *pspec)
 {
-       g_return_if_fail (ia != NULL);
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object);
+
+       switch (prop_id) {
+       case PROP_LOCAL_ADDRESS:
+               priv->local_addr = (SoupAddress *)g_value_dup_object (value);
+               break;
+       case PROP_REMOTE_ADDRESS:
+               priv->remote_addr = (SoupAddress *)g_value_dup_object (value);
+               break;
+       case PROP_NON_BLOCKING:
+               priv->non_blocking = g_value_get_boolean (value);
+               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;
+       }
+}
 
-       ++ia->ref_count;
+static void
+get_property (GObject *object, guint prop_id,
+             GValue *value, GParamSpec *pspec)
+{
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object);
+
+       switch (prop_id) {
+       case PROP_LOCAL_ADDRESS:
+               g_value_set_object (value, soup_socket_get_local_address (SOUP_SOCKET (object)));
+               break;
+       case PROP_REMOTE_ADDRESS:
+               g_value_set_object (value, soup_socket_get_remote_address (SOUP_SOCKET (object)));
+               break;
+       case PROP_NON_BLOCKING:
+               g_value_set_boolean (value, priv->non_blocking);
+               break;
+       case PROP_IS_SERVER:
+               g_value_set_boolean (value, priv->is_server);
+               break;
+       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;
+       }
 }
 
+
 /**
- * soup_address_copy
- * @ia: SoupAddress to copy
+ * soup_socket_new:
+ * @optname1: name of first property to set (or %NULL)
+ * @...: value of @optname1, followed by additional property/value pairs
+ *
+ * Creates a new (disconnected) socket
  *
- * Creates a copy of the given SoupAddress
+ * Return value: the new socket
  **/
-SoupAddress *
-soup_address_copy (SoupAddress* ia)
+SoupSocket *
+soup_socket_new (const char *optname1, ...)
 {
-       SoupAddress* new_ia;
-       g_return_val_if_fail (ia != NULL, NULL);
-
-       new_ia = g_new0(SoupAddress, 1);
-       new_ia->ref_count = 1;
+       SoupSocket *sock;
+       va_list ap;
 
-       new_ia->name = g_strdup (ia->name);
-       memcpy (&new_ia->sa, &ia->sa, sizeof(struct sockaddr));
+       va_start (ap, optname1);
+       sock = (SoupSocket *)g_object_new_valist (SOUP_TYPE_SOCKET,
+                                                 optname1, ap);
+       va_end (ap);
 
-       return new_ia;
+       return sock;
 }
 
 static void
-soup_address_get_name_sync_cb (SoupAddress       *addr,
-                              SoupAddressStatus  status,
-                              const char        *name,
-                              gpointer           user_data)
+proxy_socket_client_event (GSocketClient       *client,
+                          GSocketClientEvent   event,
+                          GSocketConnectable  *connectable,
+                          GIOStream           *connection,
+                          gpointer             user_data)
 {
-       const char **ret = user_data;
-       *ret = name;
+       SoupSocket *sock = user_data;
+
+       g_signal_emit (sock, signals[EVENT], 0,
+                      event, connection);
 }
 
-const gchar *
-soup_address_get_name_sync (SoupAddress *addr)
+static guint
+socket_connected (SoupSocket *sock, GSocketConnection *conn, GError *error)
 {
-       const char *ret = (const char *) 0xdeadbeef;
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+       if (priv->connect_cancel) {
+               GCancellable *cancellable = priv->connect_cancel;
 
-       soup_address_get_name (addr, 
-                              soup_address_get_name_sync_cb, 
-                              (gpointer) &ret);
+               g_object_unref (priv->connect_cancel);
+               priv->connect_cancel = NULL;
+               if (g_cancellable_is_cancelled (cancellable))
+                       return SOUP_STATUS_CANCELLED;
+       }
 
-       while (1) {
-               g_main_iteration (TRUE);
-               if (ret != (const char *) 0xdeadbeef) return ret;
+       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;
+               }
        }
 
-       return ret;
+       priv->conn = (GIOStream *)conn;
+       priv->gsock = g_object_ref (g_socket_connection_get_socket (conn));
+       finish_socket_setup (priv);
+
+       return SOUP_STATUS_OK;
 }
 
 /**
- * soup_address_get_canonical_name:
- * @ia: Address to get the canonical name of.
+ * SoupSocketCallback:
+ * @sock: the #SoupSocket
+ * @status: an HTTP status code indicating success or failure
+ * @user_data: the data passed to soup_socket_connect_async()
  *
- * Get the "canonical" name of an address (eg, for IP4 the dotted
- * decimal name 141.213.8.59).
- *
- * Returns: NULL if there was an error.  The caller is responsible
- * for deleting the returned string.
+ * The callback function passed to soup_socket_connect_async().
  **/
-gchar*
-soup_address_get_canonical_name (SoupAddress* ia)
-{
-       gchar buffer [INET_ADDRSTRLEN]; /* defined in netinet/in.h */
-       guchar* p = (guchar*) &(SOUP_SOCKADDR_IN(ia->sa).sin_addr);
-
-       g_return_val_if_fail (ia != NULL, NULL);
 
-       g_snprintf(buffer,
-                  sizeof (buffer),
-                  "%d.%d.%d.%d",
-                  p [0],
-                  p [1],
-                  p [2],
-                  p [3]);
+typedef struct {
+       SoupSocket *sock;
+       SoupSocketCallback callback;
+       gpointer user_data;
+} SoupSocketAsyncConnectData;
 
-       return g_strdup (buffer);
+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_address_get_port:
- * @ia: Address to get the port number of.
+ * soup_socket_connect_async:
+ * @sock: a client #SoupSocket (which must not already be connected)
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: (scope async): callback to call after connecting
+ * @user_data: data to pass to @callback
  *
- * Get the port number.
- * Returns: the port number.
- */
-gint
-soup_address_get_port (const SoupAddress* ia)
+ * Begins asynchronously connecting to @sock's remote address. The
+ * socket will call @callback when it succeeds or fails (but not
+ * before returning from this function).
+ *
+ * If @cancellable is non-%NULL, it can be used to cancel the
+ * connection. @callback will still be invoked in this case, with a
+ * status of %SOUP_STATUS_CANCELLED.
+ **/
+void
+soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable,
+                          SoupSocketCallback callback, gpointer user_data)
 {
-       g_return_val_if_fail(ia != NULL, -1);
-
-       return (gint) g_ntohs (((struct sockaddr_in*) &ia->sa)->sin_port);
+       SoupSocketPrivate *priv;
+       SoupSocketAsyncConnectData *sacd;
+       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 = g_object_ref (sock);
+       sacd->callback = callback;
+       sacd->user_data = user_data;
+
+       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_address_get_sockaddr:
- * @ia: The %SoupAddress.
- * @addrlen: Pointer to socklen_t the returned sockaddr's length is to be 
- * placed in.
+ * soup_socket_connect_sync:
+ * @sock: a client #SoupSocket (which must not already be connected)
+ * @cancellable: a #GCancellable, or %NULL
+ *
+ * Attempt to synchronously connect @sock to its remote address.
  *
- * Return value: const pointer to @ia's sockaddr buffer.
+ * If @cancellable is non-%NULL, it can be used to cancel the
+ * connection, in which case soup_socket_connect_sync() will return
+ * %SOUP_STATUS_CANCELLED.
+ *
+ * Return value: a success or failure code.
  **/
-const struct sockaddr *
-soup_address_get_sockaddr (SoupAddress *ia, guint *addrlen)
+guint
+soup_socket_connect_sync (SoupSocket *sock, GCancellable *cancellable)
+{
+       SoupSocketPrivate *priv;
+       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->gsock == NULL, SOUP_STATUS_MALFORMED);
+       g_return_val_if_fail (priv->remote_addr != NULL, SOUP_STATUS_MALFORMED);
+
+       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);
+}
+
+int
+soup_socket_get_fd (SoupSocket *sock)
+{
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), -1);
+
+       return g_socket_get_fd (SOUP_SOCKET_GET_PRIVATE (sock)->gsock);
+}
+
+GSocket *
+soup_socket_get_gsocket (SoupSocket *sock)
+{
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
+
+       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 (GObject *pollable, gpointer data)
 {
-       g_return_val_if_fail (ia != NULL, NULL);
+       SoupSocket *sock = data, *new;
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock), *new_priv;
+       GSocket *new_gsock;
+
+       new_gsock = g_socket_accept (priv->gsock, NULL, NULL);
+       if (!new_gsock)
+               return FALSE;
+
+       new = g_object_new (SOUP_TYPE_SOCKET, NULL);
+       new_priv = SOUP_SOCKET_GET_PRIVATE (new);
+       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 = 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_proxy_ssl (new, NULL, NULL)) {
+                       g_object_unref (new);
+                       return TRUE;
+               }
+       }
 
-       if (addrlen)
-               *addrlen = sizeof (struct sockaddr_in);
+       g_signal_emit (sock, signals[NEW_CONNECTION], 0, new);
+       g_object_unref (new);
 
-       return &ia->sa;
+       return TRUE;
 }
 
 /**
- * soup_address_hash:
- * @p: Pointer to an #SoupAddress.
+ * soup_socket_listen:
+ * @sock: a server #SoupSocket (which must not already be connected or
+ * listening)
  *
- * Hash the address.  This is useful for glib containers.
+ * Makes @sock start listening on its local address. When connections
+ * come in, @sock will emit #SoupSocket::new_connection.
  *
- * Returns: hash value.
+ * Return value: whether or not @sock is now listening.
  **/
-guint
-soup_address_hash (const gpointer p)
+gboolean
+soup_socket_listen (SoupSocket *sock)
+
 {
-       const SoupAddress* ia;
-       guint32 port;
-       guint32 addr;
+       SoupSocketPrivate *priv;
+       GSocketAddress *addr;
 
-       g_assert(p != NULL);
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE);
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
+       g_return_val_if_fail (priv->gsock == NULL, FALSE);
+       g_return_val_if_fail (priv->local_addr != NULL, FALSE);
 
-       ia = (const SoupAddress*) p;
+       priv->is_server = TRUE;
 
-       /* 
-        * We do pay attention to network byte order just in case the hash
-        * result is saved or sent to a different host.  
+       /* @local_addr may have its port set to 0. So we intentionally
+        * don't store it in priv->local_addr, so that if the
+        * caller calls soup_socket_get_local_address() later, we'll
+        * have to make a new addr by calling getsockname(), which
+        * will have the right port number.
         */
-       port = (guint32) g_ntohs (((struct sockaddr_in*) &ia->sa)->sin_port);
-       addr = g_ntohl (((struct sockaddr_in*) &ia->sa)->sin_addr.s_addr);
+       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;
+       finish_socket_setup (priv);
+
+       /* Bind */
+       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;
 
-       return (port ^ addr);
+       /* Listen */
+       if (!g_socket_listen (priv->gsock, NULL))
+               goto cant_listen;
+
+       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->conn)
+               disconnect_internal (sock, TRUE);
+       g_object_unref (addr);
+
+       return FALSE;
 }
 
-/**
- * soup_address_equal:
- * @p1: Pointer to first #SoupAddress.
- * @p2: Pointer to second #SoupAddress.
- *
- * Compare two #SoupAddress's.
- *
- * Returns: 1 if they are the same; 0 otherwise.
- **/
-gint
-soup_address_equal (const gpointer p1, const gpointer p2)
+static void
+soup_socket_peer_certificate_changed (GObject *conn, GParamSpec *pspec,
+                                     gpointer sock)
 {
-       const SoupAddress* ia1 = (const SoupAddress*) p1;
-       const SoupAddress* ia2 = (const SoupAddress*) p2;
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       g_assert (p1 != NULL && p2 != NULL);
+       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");
+}
 
-       /* Note network byte order doesn't matter */
-       return ((SOUP_SOCKADDR_IN(ia1->sa).sin_addr.s_addr ==
-                SOUP_SOCKADDR_IN(ia2->sa).sin_addr.s_addr) &&
-               (SOUP_SOCKADDR_IN(ia1->sa).sin_port ==
-                SOUP_SOCKADDR_IN(ia2->sa).sin_port));
+static gboolean
+soup_socket_accept_certificate (GTlsConnection *conn, GTlsCertificate *cert,
+                               GTlsCertificateFlags errors, gpointer sock)
+{
+       return TRUE;
 }
 
 /**
- * soup_address_noport_equal:
- * @p1: Pointer to first SoupAddress.
- * @p2: Pointer to second SoupAddress.
+ * soup_socket_start_ssl:
+ * @sock: the socket
+ * @cancellable: a #GCancellable
  *
- * Compare two #SoupAddress's, but does not compare the port numbers.
+ * Starts using SSL on @socket.
  *
- * Returns: 1 if they are the same; 0 otherwise.
+ * Return value: success or failure
  **/
-gint
-soup_address_noport_equal (const gpointer p1, const gpointer p2)
+gboolean
+soup_socket_start_ssl (SoupSocket *sock, GCancellable *cancellable)
 {
-       const SoupAddress* ia1 = (const SoupAddress*) p1;
-       const SoupAddress* ia2 = (const SoupAddress*) p2;
-
-       g_assert (p1 != NULL && p2 != NULL);
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       /* Note network byte order doesn't matter */
-       return (SOUP_SOCKADDR_IN(ia1->sa).sin_addr.s_addr ==
-               SOUP_SOCKADDR_IN(ia2->sa).sin_addr.s_addr);
+       return soup_socket_start_proxy_ssl (sock, soup_address_get_name (priv->remote_addr), cancellable);
 }
-
+       
 /**
- * soup_address_gethostaddr:
+ * soup_socket_start_proxy_ssl:
+ * @sock: the socket
+ * @ssl_host: hostname of the SSL server
+ * @cancellable: a #GCancellable
  *
- * Get the primary host's #SoupAddress.
+ * Starts using SSL on @socket, expecting to find a host named
+ * @ssl_host.
  *
- * Returns: the #SoupAddress of the host; NULL if there was an error.
- * The caller is responsible for deleting the returned #SoupAddress.
+ * Return value: success or failure
  **/
-SoupAddress *
-soup_address_gethostaddr (void)
+gboolean
+soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host,
+                            GCancellable *cancellable)
 {
-       gchar* name;
-       struct sockaddr_in* sa_in, sa;
-       SoupAddress* ia = NULL;
-
-       name = soup_address_gethostname ();
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+       GTlsBackend *backend = g_tls_backend_get_default ();
+
+       if (G_IS_TLS_CONNECTION (priv->conn))
+               return TRUE;
+
+       if (g_cancellable_is_cancelled (cancellable))
+               return FALSE;
+
+       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);
+       }
 
-       if (name && soup_gethostbyname (name, &sa, NULL)) {
-               ia = g_new0 (SoupAddress, 1);
-               ia->name = g_strdup (name);
-               ia->ref_count = 1;
+       g_signal_connect (priv->conn, "notify::peer-certificate",
+                         G_CALLBACK (soup_socket_peer_certificate_changed), sock);
 
-               sa_in = (struct sockaddr_in*) &ia->sa;
-               sa_in->sin_family = AF_INET;
-               sa_in->sin_port = 0;
-               memcpy (&sa_in->sin_addr, &sa.sin_addr, 4);
-        }
+       if (priv->istream)
+               g_object_unref (priv->istream);
+       if (priv->ostream)
+               g_object_unref (priv->ostream);
 
-       return ia;
+       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
-soup_socket_connect_tcp_cb (SoupSocket* socket,
-                           SoupSocketConnectStatus status,
-                           gpointer data)
-{
-       SoupSocketConnectState* state = (SoupSocketConnectState*) data;
-       SoupSocketConnectFn func = state->func;
-       gpointer user_data = state->data;
-
-       if (status == SOUP_SOCKET_NEW_STATUS_OK)
-               (*func) (socket,
-                        SOUP_SOCKET_CONNECT_ERROR_NONE,
-                        user_data);
+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
-               (*func) (NULL,
-                        SOUP_SOCKET_CONNECT_ERROR_NETWORK,
-                        user_data);
+               status = SOUP_STATUS_SSL_FAILED;
+       g_clear_error (&error);
 
-       if (state->tcp_id)
-               g_free (state);
+       data->callback (data->sock, status, data->user_data);
+       g_object_unref (data->sock);
+       g_slice_free (SoupSocketAsyncConnectData, data);
 }
 
-static void
-soup_socket_connect_inetaddr_cb (SoupAddress* inetaddr,
-                                SoupAddressStatus status,
-                                gpointer data)
+void
+soup_socket_handshake_async (SoupSocket         *sock,
+                            GCancellable       *cancellable,
+                            SoupSocketCallback  callback,
+                            gpointer            user_data)
 {
-       SoupSocketConnectState* state = (SoupSocketConnectState*) data;
-
-       if (status == SOUP_ADDRESS_STATUS_OK) {
-               state->tcp_id = soup_socket_new (inetaddr,
-                                                soup_socket_connect_tcp_cb,
-                                                state);
-               soup_address_unref (inetaddr);
-       } else {
-               SoupSocketConnectFn func = state->func;
-               gpointer user_data = state->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);
+}
 
-               (*func) (NULL, 
-                        SOUP_SOCKET_CONNECT_ERROR_ADDR_RESOLVE, 
-                        user_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);
 
-       if (state->inetaddr_id && !state->tcp_id)
-               g_free (state);
-       else
-               state->inetaddr_id = NULL;
+       return priv->ssl;
 }
 
 /**
- * soup_socket_connect:
- * @hostname: Name of host to connect to
- * @port: Port to connect to
- * @func: Callback function
- * @data: User data passed when callback function is called.
- *
- * A quick and easy non-blocking #SoupSocket constructor.  This
- * connects to the specified address and port and then calls the
- * callback with the data.  Use this function when you're a client
- * connecting to a server and you don't want to block or mess with
- * #SoupAddress's.  It may call the callback before the function
- * returns.  It will call the callback if there is a failure.
- *
- * Returns: ID of the connection which can be used with
- * soup_socket_connect_cancel() to cancel it; NULL if it succeeds
- * or fails immediately.
+ * soup_socket_disconnect:
+ * @sock: a #SoupSocket
+ *
+ * Disconnects @sock. Any further read or write attempts on it will
+ * fail.
  **/
-SoupSocketConnectId
-soup_socket_connect (const gchar*        hostname,
-                    const gint          port,
-                    SoupSocketConnectFn func,
-                    gpointer            data)
-{
-       SoupSocketConnectState* state;
-       SoupAddress *cached_addr;
-
-       g_return_val_if_fail (hostname != NULL, NULL);
-       g_return_val_if_fail (func != NULL, NULL);
-
-       state = g_new0 (SoupSocketConnectState, 1);
-       state->func = func;
-       state->data = data;
-
-       /* Check if a cached version of the address already exists */
-       cached_addr = soup_address_lookup_in_cache (hostname, port);
-       if (cached_addr) {
-               state->tcp_id = soup_socket_new (cached_addr,
-                                                soup_socket_connect_tcp_cb,
-                                                state);
-               soup_address_unref (cached_addr);
+void
+soup_socket_disconnect (SoupSocket *sock)
+{
+       SoupSocketPrivate *priv;
+       gboolean already_disconnected = FALSE;
+
+       g_return_if_fail (SOUP_IS_SOCKET (sock));
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+       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);
        } else {
-               state->inetaddr_id = soup_address_new (hostname,
-                                                      port,
-                                                      soup_socket_connect_inetaddr_cb,
-                                                      state);
-               /* NOTE: soup_address_new could succeed immediately
-                * and call our callback, in which case state->inetaddr_id
-                * will be NULL but state->tcp_id may be set.
+               /* Another thread is currently doing IO, so
+                * 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.)
                 */
+               g_socket_shutdown (priv->gsock, TRUE, TRUE, NULL);
        }
 
-       if (state->tcp_id || state->inetaddr_id)
-               return state;
-       else {
-               g_free (state);
-               return 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);
+
+       /* FIXME: can't disconnect until all data is read */
+
+       /* Then let everyone know we're disconnected */
+       g_signal_emit (sock, signals[DISCONNECTED], 0);
+
+       g_object_unref (sock);
 }
 
 /**
- * soup_socket_connect_cancel:
- * @id: Id of the connection.
+ * soup_socket_is_connected:
+ * @sock: a #SoupSocket
  *
- * Cancel an asynchronous connection that was started with
- * soup_socket_connect().
- */
-void
-soup_socket_connect_cancel (SoupSocketConnectId id)
+ * Tests if @sock is connected to another host
+ *
+ * Return value: %TRUE or %FALSE.
+ **/
+gboolean
+soup_socket_is_connected (SoupSocket *sock)
 {
-       SoupSocketConnectState* state = (SoupSocketConnectState*) id;
+       SoupSocketPrivate *priv;
 
-       g_return_if_fail (state != NULL);
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE);
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       if (state->inetaddr_id)
-               soup_address_new_cancel (state->inetaddr_id);
-       else if (state->tcp_id)
-               soup_socket_new_cancel (state->tcp_id);
-
-       g_free (state);
+       return priv->conn != NULL;
 }
 
-static void
-soup_socket_connect_sync_cb (SoupSocket              *socket,
-                            SoupSocketConnectStatus  status,
-                            gpointer                 data)
+/**
+ * soup_socket_get_local_address:
+ * @sock: a #SoupSocket
+ *
+ * Returns the #SoupAddress corresponding to the local end of @sock.
+ *
+ * Return value: (transfer none): the #SoupAddress
+ **/
+SoupAddress *
+soup_socket_get_local_address (SoupSocket *sock)
 {
-       SoupSocket **ret = data;
-       *ret = socket;
+       SoupSocketPrivate *priv;
+
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+       g_mutex_lock (&priv->addrlock);
+       if (!priv->local_addr) {
+               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);
+
+       return priv->local_addr;
 }
 
-SoupSocket *
-soup_socket_connect_sync (const gchar *name,
-                         const gint   port)
+/**
+ * soup_socket_get_remote_address:
+ * @sock: a #SoupSocket
+ *
+ * Returns the #SoupAddress corresponding to the remote end of @sock.
+ *
+ * Return value: (transfer none): the #SoupAddress
+ **/
+SoupAddress *
+soup_socket_get_remote_address (SoupSocket *sock)
 {
-       SoupSocket *ret = (SoupSocket *) 0xdeadbeef;
+       SoupSocketPrivate *priv;
+
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+       g_mutex_lock (&priv->addrlock);
+       if (!priv->remote_addr) {
+               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);
 
-       soup_socket_connect (name, port, soup_socket_connect_sync_cb, &ret);
+       return priv->remote_addr;
+}
 
-       while (1) {
-               g_main_iteration (TRUE);
-               if (ret != (SoupSocket *) 0xdeadbeef) return ret;
-       }
+GInputStream *
+soup_socket_get_input_stream (SoupSocket *sock)
+{
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
 
-       return ret;
+       return SOUP_SOCKET_GET_PRIVATE (sock)->istream;
 }
 
-static void
-soup_socket_new_sync_cb (SoupSocket*         socket,
-                        SoupSocketNewStatus status,
-                        gpointer            data)
+GOutputStream *
+soup_socket_get_output_stream (SoupSocket *sock)
 {
-       SoupSocket **ret = data;
-       *ret = socket;
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL);
+
+       return SOUP_SOCKET_GET_PRIVATE (sock)->ostream;
 }
 
-SoupSocket *
-soup_socket_new_sync (SoupAddress *addr)
+
+static gboolean
+socket_read_watch (GObject *pollable, gpointer user_data)
 {
-       SoupSocket *ret = (SoupSocket *) 0xdeadbeef;
+       SoupSocket *sock = user_data;
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       soup_socket_new (addr, soup_socket_new_sync_cb, &ret);
+       priv->read_src = NULL;
+       g_signal_emit (sock, signals[READABLE], 0);
+       return FALSE;
+}
 
-       while (1) {
-               g_main_iteration (TRUE);
-               if (ret != (SoupSocket *) 0xdeadbeef) return ret;
+static SoupSocketIOStatus
+translate_read_status (SoupSocket *sock, GCancellable *cancellable,
+                      gssize my_nread, gsize *nread,
+                      GError *my_err, GError **error)
+{
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+       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;
+       } 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_socket_create_watch (priv, G_IO_IN,
+                                                         socket_read_watch, sock,
+                                                         cancellable);
+               }
+               return SOUP_SOCKET_WOULD_BLOCK;
        }
 
-       return ret;
+       g_propagate_error (error, my_err);
+       return SOUP_SOCKET_ERROR;
 }
 
 /**
- * soup_socket_ref
- * @s: SoupSocket to reference
+ * SoupSocketIOStatus:
+ * @SOUP_SOCKET_OK: Success
+ * @SOUP_SOCKET_WOULD_BLOCK: Cannot read/write any more at this time
+ * @SOUP_SOCKET_EOF: End of file
+ * @SOUP_SOCKET_ERROR: Other error
  *
- * Increment the reference counter of the SoupSocket.
+ * Return value from the #SoupSocket IO methods.
  **/
-void
-soup_socket_ref (SoupSocket* s)
-{
-       g_return_if_fail (s != NULL);
-
-       ++s->ref_count;
-}
 
 /**
- * soup_socket_unref
- * @s: #SoupSocket to unreference
+ * soup_socket_read:
+ * @sock: the socket
+ * @buffer: buffer to read into
+ * @len: size of @buffer in bytes
+ * @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 (which may be less than @len).
  *
- * Remove a reference from the #SoupSocket.  When reference count
- * reaches 0, the socket is deleted.
+ * 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 #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
+ * %SOUP_SOCKET_ERROR on any other error, in which case @error will
+ * also be set).
  **/
-void
-soup_socket_unref (SoupSocket* s)
+SoupSocketIOStatus
+soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len,
+                 gsize *nread, GCancellable *cancellable, GError **error)
 {
-       g_return_if_fail(s != NULL);
+       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);
 
-       --s->ref_count;
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       if (s->ref_count == 0) {
-               close (s->sockfd);
-               if (s->addr) soup_address_unref (s->addr);
-               if (s->iochannel) g_io_channel_unref (s->iochannel);
+       g_mutex_lock (&priv->iolock);
 
-               g_free(s);
+       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;
 }
 
 /**
- * soup_socket_get_iochannel:
- * @socket: SoupSocket to get GIOChannel from.
- *
- * Get the #GIOChannel for the #SoupSocket.
- *
- * For a client socket, the #GIOChannel represents the data stream.
- * Use it like you would any other #GIOChannel.
+ * soup_socket_read_until:
+ * @sock: the socket
+ * @buffer: buffer to read into
+ * @len: size of @buffer in bytes
+ * @boundary: boundary to read until
+ * @boundary_len: length of @boundary in bytes
+ * @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
+ * @error: error pointer
  *
- * For a server socket however, the #GIOChannel represents incoming
- * connections.  If you can read from it, there's a connection
- * waiting.
+ * Like soup_socket_read(), but reads no further than the first
+ * occurrence of @boundary. (If the boundary is found, it will be
+ * included in the returned data, and *@got_boundary will be set to
+ * %TRUE.) Any data after the boundary will returned in future reads.
  *
- * There is one channel for every socket.  This function refs the
- * channel before returning it.  You should unref the channel when
- * you are done with it.  However, you should not close the channel -
- * this is done when you delete the socket.
- *
- * Returns: A #GIOChannel; NULL on failure.
+ * 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()
  **/
-GIOChannel*
-soup_socket_get_iochannel (SoupSocket* socket)
+SoupSocketIOStatus
+soup_socket_read_until (SoupSocket *sock, gpointer buffer, gsize len,
+                       gconstpointer boundary, gsize boundary_len,
+                       gsize *nread, gboolean *got_boundary,
+                       GCancellable *cancellable, GError **error)
 {
-       g_return_val_if_fail (socket != NULL, NULL);
+       SoupSocketPrivate *priv;
+       SoupSocketIOStatus status;
+       gssize my_nread;
+       GError *my_err = NULL;
 
-       if (socket->iochannel == NULL)
-               socket->iochannel = g_io_channel_unix_new (socket->sockfd);
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR);
+       g_return_val_if_fail (nread != NULL, SOUP_SOCKET_ERROR);
+       g_return_val_if_fail (len >= boundary_len, SOUP_SOCKET_ERROR);
 
-       g_io_channel_ref (socket->iochannel);
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       return socket->iochannel;
-}
+       g_mutex_lock (&priv->iolock);
 
-/**
- * soup_socket_get_address:
- * @socket: #SoupSocket to get address of.
- *
- * Get the address of the socket.  If the socket is client socket,
- * the address is the address of the remote host it is connected to.
- * If the socket is a server socket, the address is the address of
- * the local host.  (Though you should use
- * soup_address_gethostaddr() to get the #SoupAddress of the local
- * host.)
- *
- * Returns: #SoupAddress of socket; NULL on failure.
- **/
-SoupAddress *
-soup_socket_get_address (const SoupSocket* socket)
-{
-       g_return_val_if_fail (socket != NULL, NULL);
-       g_return_val_if_fail (socket->addr != NULL, NULL);
+       *got_boundary = FALSE;
 
-       soup_address_ref (socket->addr);
+       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 socket->addr;
+       g_mutex_unlock (&priv->iolock);
+       return status;
 }
 
-/**
- * soup_socket_get_port:
- * @socket: SoupSocket to get the port number of.
- *
- * Get the port number the socket is bound to.
- *
- * Returns: Port number of the socket.
- **/
-gint
-soup_socket_get_port(const SoupSocket* socket)
+static gboolean
+socket_write_watch (GObject *pollable, gpointer user_data)
 {
-       g_return_val_if_fail (socket != NULL, 0);
+       SoupSocket *sock = user_data;
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       return g_ntohs (SOUP_SOCKADDR_IN (socket->addr->sa).sin_port);
+       priv->write_src = NULL;
+       g_signal_emit (sock, signals[WRITABLE], 0);
+       return FALSE;
 }
 
 /**
- * soup_socket_server_new:
- * @port: Port number for the socket (SOUP_SERVER_ANY_PORT if you don't care).
+ * soup_socket_write:
+ * @sock: the socket
+ * @buffer: data to write
+ * @len: size of @buffer, in bytes
+ * @nwrote: (out): on return, number of bytes written
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: error pointer
  *
- * Create and open a new #SoupSocket with the specified port number.
- * Use this sort of socket when your are a server and you know what
- * the port number should be (or pass 0 if you don't care what the
- * port is).
+ * Attempts to write @len bytes from @buffer to @sock. If some data is
+ * 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).
  *
- * Returns: a new #SoupSocket, or NULL if there was a failure.
+ * 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 #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
+ * return value is %SOUP_SOCKET_ERROR.)
  **/
-SoupSocket *
-soup_socket_server_new (const gint port)
+SoupSocketIOStatus
+soup_socket_write (SoupSocket *sock, gconstpointer buffer,
+                  gsize len, gsize *nwrote,
+                  GCancellable *cancellable, GError **error)
 {
-       SoupSocket* s;
-       struct sockaddr_in* sa_in;
-       socklen_t socklen;
-       const int on = 1;
-       gint flags;
+       SoupSocketPrivate *priv;
+       GError *my_err = NULL;
+       gssize my_nwrote;
 
-       /* Create socket */
-       s = g_new0 (SoupSocket, 1);
-       s->ref_count = 1;
-
-       if ((s->sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
-               g_free (s);
-               return NULL;
-       }
+       g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR);
+       g_return_val_if_fail (nwrote != NULL, SOUP_SOCKET_ERROR);
 
-       s->addr = g_new0 (SoupAddress, 1);
-       s->addr->ref_count = 1;
+       priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
-       /* Set up address and port for connection */
-       sa_in = (struct sockaddr_in*) &s->addr->sa;
-       sa_in->sin_family = AF_INET;
-       sa_in->sin_addr.s_addr = g_htonl (INADDR_ANY);
-       sa_in->sin_port = g_htons (port);
+       g_mutex_lock (&priv->iolock);
 
-       /* Set REUSEADDR so we can reuse the port */
-       if (setsockopt (s->sockfd,
-                       SOL_SOCKET,
-                       SO_REUSEADDR,
-                       &on,
-                       sizeof (on)) != 0)
-               g_warning("Can't set reuse on tcp socket\n");
-
-       /* Get the flags (should all be 0?) */
-       flags = fcntl (s->sockfd, F_GETFL, 0);
-       if (flags == -1) goto SETUP_ERROR;
-
-       /* Make the socket non-blocking */
-       if (fcntl (s->sockfd, F_SETFL, flags | O_NONBLOCK) == -1)
-               goto SETUP_ERROR;
+       if (!priv->conn) {
+               g_mutex_unlock (&priv->iolock);
+               return SOUP_SOCKET_EOF;
+       }
+       if (priv->write_src) {
+               g_mutex_unlock (&priv->iolock);
+               return SOUP_SOCKET_WOULD_BLOCK;
+       }
 
-       /* Bind */
-       if (bind (s->sockfd, &s->addr->sa, sizeof (s->addr->sa)) != 0)
-               goto SETUP_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);
+       }
 
-       /* Get the socket name - don't care if it fails */
-       socklen = sizeof (s->addr->sa);
-       getsockname (s->sockfd, &s->addr->sa, &socklen);
+       if (my_nwrote > 0) {
+               g_mutex_unlock (&priv->iolock);
+               g_clear_error (&my_err);
+               *nwrote = my_nwrote;
+               return SOUP_SOCKET_OK;
+       }
 
-       /* Listen */
-       if (listen (s->sockfd, 10) != 0) goto SETUP_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);
 
-       return s;
+               priv->write_src =
+                       soup_socket_create_watch (priv,
+                                                 G_IO_OUT,
+                                                 socket_write_watch, sock, cancellable);
+               return SOUP_SOCKET_WOULD_BLOCK;
+       }
 
- SETUP_ERROR:
-       close (s->sockfd);
-       g_free (s->addr);
-       g_free (s);
-       return NULL;
+       g_mutex_unlock (&priv->iolock);
+       g_propagate_error (error, my_err);
+       return SOUP_SOCKET_ERROR;
 }