X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;ds=sidebyside;f=libsoup%2Fsoup-socket.c;h=2d72b385a68502bdfed4c349bd097f682fa0238b;hb=c0414594616131e082e87b78b41542be6785158a;hp=ece538fc99f500f705007ec6a357b458a6d96d80;hpb=c2720d6770639fed2bf57a972fa7c9b47919d0ac;p=platform%2Fupstream%2Flibsoup.git diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index ece538f..2d72b38 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -9,32 +9,38 @@ #include #endif +#include #include #include #include #include #include -#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 "soup-misc-private.h" -#include -#include -#include -#include +/** + * SECTION:soup-socket + * @short_description: A network socket + * + * #SoupSocket is libsoup's TCP socket type. While it is primarily + * intended for internal use, #SoupSockets 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 PARENT_TYPE G_TYPE_OBJECT -static GObjectClass *parent_class; +G_DEFINE_TYPE (SoupSocket, soup_socket, G_TYPE_OBJECT) enum { - CONNECT_RESULT, READABLE, WRITABLE, DISCONNECTED, NEW_CONNECTION, + EVENT, LAST_SIGNAL }; @@ -43,113 +49,142 @@ static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, + PROP_LOCAL_ADDRESS, + PROP_REMOTE_ADDRESS, PROP_NON_BLOCKING, - PROP_NODELAY, - PROP_REUSEADDR, 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 }; -struct SoupSocketPrivate { - int sockfd; +typedef struct { SoupAddress *local_addr, *remote_addr; - GIOChannel *iochannel; + GIOStream *conn; + GSocket *gsock; + GInputStream *istream; + GOutputStream *ostream; + GTlsCertificateFlags tls_errors; guint non_blocking:1; - guint nodelay:1; - guint reuseaddr: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; - guint watch; - guint read_tag, write_tag, error_tag; - GByteArray *read_buf; -}; + GMainContext *async_context; + GSource *watch_src; + GSource *read_src, *write_src; -#ifdef HAVE_IPV6 -#define soup_sockaddr_max sockaddr_in6 -#else -#define soup_sockaddr_max sockaddr_in -#endif + 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); + static void -init (GObject *object) +soup_socket_init (SoupSocket *sock) { - SoupSocket *sock = SOUP_SOCKET (object); + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - sock->priv = g_new0 (SoupSocketPrivate, 1); - sock->priv->sockfd = -1; - sock->priv->non_blocking = sock->priv->nodelay = TRUE; - sock->priv->reuseaddr = TRUE; + priv->non_blocking = TRUE; + g_mutex_init (&priv->addrlock); + g_mutex_init (&priv->iolock); } -static gboolean -disconnect_internal (SoupSocket *sock) +static void +disconnect_internal (SoupSocket *sock, gboolean close) { - GIOChannel *iochannel; - - /* If we close the socket from one thread while - * reading/writing from another, it's possible that the other - * thread will get an I/O error and try to close the socket - * while we're still in this function. So we clear - * sock->priv->iochannel early to make sure that the other - * thread's attempt to close the socket becomes a no-op. - */ - iochannel = sock->priv->iochannel; - sock->priv->iochannel = NULL; - if (iochannel == NULL) - return FALSE; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - g_io_channel_unref (iochannel); - - if (sock->priv->read_tag) { - g_source_remove (sock->priv->read_tag); - sock->priv->read_tag = 0; - } - if (sock->priv->write_tag) { - g_source_remove (sock->priv->write_tag); - sock->priv->write_tag = 0; + if (priv->gsock) { + if (close) + g_socket_close (priv->gsock, NULL); + g_object_unref (priv->gsock); + priv->gsock = NULL; } - if (sock->priv->error_tag) { - g_source_remove (sock->priv->error_tag); - sock->priv->error_tag = 0; + 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); } - return TRUE; + 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; + } } static void finalize (GObject *object) { - SoupSocket *sock = SOUP_SOCKET (object); + 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); + } - if (sock->priv->iochannel) - disconnect_internal (sock); + g_clear_object (&priv->istream); + g_clear_object (&priv->ostream); - if (sock->priv->local_addr) - g_object_unref (sock->priv->local_addr); - if (sock->priv->remote_addr) - g_object_unref (sock->priv->remote_addr); + if (priv->local_addr) + g_object_unref (priv->local_addr); + if (priv->remote_addr) + g_object_unref (priv->remote_addr); - if (sock->priv->watch) - g_source_remove (sock->priv->watch); + 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_free (sock->priv); + g_mutex_clear (&priv->addrlock); + g_mutex_clear (&priv->iolock); - G_OBJECT_CLASS (parent_class)->finalize (object); + G_OBJECT_CLASS (soup_socket_parent_class)->finalize (object); } static void -class_init (GObjectClass *object_class) +soup_socket_class_init (SoupSocketClass *socket_class) { - parent_class = g_type_class_ref (PARENT_TYPE); + GObjectClass *object_class = G_OBJECT_CLASS (socket_class); + + g_type_class_add_private (socket_class, sizeof (SoupSocketPrivate)); /* virtual method override */ object_class->finalize = finalize; @@ -157,50 +192,156 @@ class_init (GObjectClass *object_class) object_class->get_property = get_property; /* signals */ - signals[CONNECT_RESULT] = - g_signal_new ("connect_result", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupSocketClass, connect_result), - NULL, NULL, - soup_marshal_NONE__INT, - G_TYPE_NONE, 1, - G_TYPE_INT); + + /** + * 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, + _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, + _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, + _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, + _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, @@ -208,20 +349,12 @@ class_init (GObjectClass *object_class) "Whether or not the socket uses non-blocking I/O", TRUE, G_PARAM_READWRITE)); - g_object_class_install_property ( - object_class, PROP_NODELAY, - g_param_spec_boolean (SOUP_SOCKET_FLAG_NODELAY, - "NODELAY", - "Whether or not the socket uses TCP NODELAY", - TRUE, - G_PARAM_READWRITE)); - g_object_class_install_property ( - object_class, PROP_REUSEADDR, - g_param_spec_boolean (SOUP_SOCKET_FLAG_REUSEADDR, - "REUSEADDR", - "Whether or not the socket uses the TCP REUSEADDR flag", - 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, @@ -229,66 +362,212 @@ class_init (GObjectClass *object_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, + "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)); -SOUP_MAKE_TYPE (soup_socket, SoupSocket, class_init, init, PARENT_TYPE) + 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 -update_fdflags (SoupSocket *sock) +finish_socket_setup (SoupSocketPrivate *priv) { - int flags, opt; - - if (!sock->priv->sockfd) + if (!priv->gsock) return; - flags = fcntl (sock->priv->sockfd, F_GETFL, 0); - if (flags != -1) { - if (sock->priv->non_blocking) - flags |= O_NONBLOCK; - else - flags &= ~O_NONBLOCK; - fcntl (sock->priv->sockfd, F_SETFL, flags); - } - - opt = (sock->priv->nodelay != 0); - setsockopt (sock->priv->sockfd, IPPROTO_TCP, - TCP_NODELAY, &opt, sizeof (opt)); + 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)); - opt = (sock->priv->reuseaddr != 0); - setsockopt (sock->priv->sockfd, SOL_SOCKET, - SO_REUSEADDR, &opt, sizeof (opt)); + g_socket_set_timeout (priv->gsock, priv->timeout); } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { - SoupSocket *sock = SOUP_SOCKET (object); + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object); switch (prop_id) { - case PROP_NON_BLOCKING: - sock->priv->non_blocking = g_value_get_boolean (value); - update_fdflags (sock); + case PROP_LOCAL_ADDRESS: + priv->local_addr = (SoupAddress *)g_value_dup_object (value); break; - case PROP_NODELAY: - sock->priv->nodelay = g_value_get_boolean (value); - update_fdflags (sock); + case PROP_REMOTE_ADDRESS: + priv->remote_addr = (SoupAddress *)g_value_dup_object (value); break; - case PROP_REUSEADDR: - sock->priv->reuseaddr = g_value_get_boolean (value); - update_fdflags (sock); + case PROP_NON_BLOCKING: + priv->non_blocking = g_value_get_boolean (value); break; case PROP_SSL_CREDENTIALS: - sock->priv->ssl_creds = g_value_get_pointer (value); + 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; } } @@ -297,25 +576,53 @@ static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { - SoupSocket *sock = SOUP_SOCKET (object); + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object); switch (prop_id) { - case PROP_NON_BLOCKING: - g_value_set_boolean (value, sock->priv->non_blocking); + case PROP_LOCAL_ADDRESS: + g_value_set_object (value, soup_socket_get_local_address (SOUP_SOCKET (object))); break; - case PROP_NODELAY: - g_value_set_boolean (value, sock->priv->nodelay); + case PROP_REMOTE_ADDRESS: + g_value_set_object (value, soup_socket_get_remote_address (SOUP_SOCKET (object))); break; - case PROP_REUSEADDR: - g_value_set_boolean (value, sock->priv->reuseaddr); + case PROP_NON_BLOCKING: + g_value_set_boolean (value, priv->non_blocking); break; case PROP_IS_SERVER: - g_value_set_boolean (value, sock->priv->is_server); + g_value_set_boolean (value, priv->is_server); break; case PROP_SSL_CREDENTIALS: - g_value_set_pointer (value, sock->priv->ssl_creds); + 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; } } @@ -326,7 +633,9 @@ get_property (GObject *object, guint prop_id, * @optname1: name of first property to set (or %NULL) * @...: value of @optname1, followed by additional property/value pairs * - * Return value: a new (disconnected) socket + * Creates a new (disconnected) socket + * + * Return value: the new socket **/ SoupSocket * soup_socket_new (const char *optname1, ...) @@ -342,193 +651,260 @@ soup_socket_new (const char *optname1, ...) return sock; } -static GIOChannel * -get_iochannel (SoupSocket *sock) -{ - if (!sock->priv->iochannel) { - sock->priv->iochannel = - g_io_channel_unix_new (sock->priv->sockfd); - g_io_channel_set_close_on_unref (sock->priv->iochannel, TRUE); - g_io_channel_set_encoding (sock->priv->iochannel, NULL, NULL); - g_io_channel_set_buffered (sock->priv->iochannel, FALSE); - } - return sock->priv->iochannel; -} - -static gboolean -idle_connect_result (gpointer user_data) +static void +proxy_socket_client_event (GSocketClient *client, + GSocketClientEvent event, + GSocketConnectable *connectable, + GIOStream *connection, + gpointer user_data) { SoupSocket *sock = user_data; - sock->priv->watch = 0; - - g_signal_emit (sock, signals[CONNECT_RESULT], 0, - sock->priv->sockfd != -1 ? SOUP_STATUS_OK : SOUP_STATUS_CANT_CONNECT); - return FALSE; + g_signal_emit (sock, signals[EVENT], 0, + event, connection); } -static gboolean -connect_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) +static guint +socket_connected (SoupSocket *sock, GSocketConnection *conn, GError *error) { - SoupSocket *sock = data; - int error = 0; - int len = sizeof (error); + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - /* Remove the watch now in case we don't return immediately */ - g_source_remove (sock->priv->watch); - sock->priv->watch = 0; + if (priv->connect_cancel) { + GCancellable *cancellable = priv->connect_cancel; - if (condition & ~(G_IO_IN | G_IO_OUT)) - goto cant_connect; + g_object_unref (priv->connect_cancel); + priv->connect_cancel = NULL; + if (g_cancellable_is_cancelled (cancellable)) + return SOUP_STATUS_CANCELLED; + } - if (getsockopt (sock->priv->sockfd, SOL_SOCKET, SO_ERROR, - &error, &len) != 0) - goto cant_connect; - if (error) - goto cant_connect; + 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 idle_connect_result (sock); + priv->conn = (GIOStream *)conn; + priv->gsock = g_object_ref (g_socket_connection_get_socket (conn)); + finish_socket_setup (priv); - cant_connect: - g_signal_emit (sock, signals[CONNECT_RESULT], 0, SOUP_STATUS_CANT_CONNECT); - return FALSE; + return SOUP_STATUS_OK; } +/** + * SoupSocketCallback: + * @sock: the #SoupSocket + * @status: an HTTP status code indicating success or failure + * @user_data: the data passed to soup_socket_connect_async() + * + * The callback function passed to soup_socket_connect_async(). + **/ + +typedef struct { + SoupSocket *sock; + SoupSocketCallback callback; + gpointer user_data; +} SoupSocketAsyncConnectData; + static void -got_address (SoupAddress *addr, guint status, gpointer user_data) +async_connected (GObject *client, GAsyncResult *result, gpointer data) { - SoupSocket *sock = user_data; + SoupSocketAsyncConnectData *sacd = data; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock); + GError *error = NULL; + GSocketConnection *conn; + guint status; - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_signal_emit (sock, signals[CONNECT_RESULT], 0, status); - g_object_unref (sock); - return; - } + if (priv->async_context && !priv->use_thread_context) + g_main_context_pop_thread_default (priv->async_context); - soup_socket_connect (sock, sock->priv->remote_addr); - /* soup_socket_connect re-reffed addr */ - g_object_unref (addr); + conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client), + result, &error); + status = socket_connected (sacd->sock, conn, error); - g_object_unref (sock); + sacd->callback (sacd->sock, status, sacd->user_data); + g_object_unref (sacd->sock); + g_slice_free (SoupSocketAsyncConnectData, sacd); } /** - * soup_socket_connect: + * soup_socket_connect_async: * @sock: a client #SoupSocket (which must not already be connected) - * @remote_addr: address to connect to + * @cancellable: a #GCancellable, or %NULL + * @callback: (scope async): callback to call after connecting + * @user_data: data to pass to @callback * - * If %SOUP_SOCKET_FLAG_NONBLOCKING has been set on the socket, this - * begins asynchronously connecting to the given address. The socket - * will emit %connect_result when it succeeds or fails (but not before - * returning from this function). + * 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 %SOUP_SOCKET_FLAG_NONBLOCKING has not been set, this will - * attempt to synchronously connect. + * 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) +{ + 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_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. + * + * 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: %SOUP_STATUS_CONTINUE if connecting asynchronously, - * otherwise a success or failure code. + * Return value: a success or failure code. **/ guint -soup_socket_connect (SoupSocket *sock, SoupAddress *remote_addr) +soup_socket_connect_sync (SoupSocket *sock, GCancellable *cancellable) { - struct sockaddr *sa; - int len, status; + SoupSocketPrivate *priv; + GSocketClient *client; + GSocketConnection *conn; + GError *error = NULL; g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_STATUS_MALFORMED); - g_return_val_if_fail (!sock->priv->is_server, SOUP_STATUS_MALFORMED); - g_return_val_if_fail (sock->priv->sockfd == -1, SOUP_STATUS_MALFORMED); - g_return_val_if_fail (SOUP_IS_ADDRESS (remote_addr), SOUP_STATUS_MALFORMED); - - sock->priv->remote_addr = g_object_ref (remote_addr); - if (!sock->priv->non_blocking) { - status = soup_address_resolve_sync (remote_addr); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) - return status; - } + 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); - sa = soup_address_get_sockaddr (sock->priv->remote_addr, &len); - if (!sa) { - if (!sock->priv->non_blocking) - return SOUP_STATUS_CANT_RESOLVE; + 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); +} - g_object_ref (sock); - soup_address_resolve_async (remote_addr, got_address, sock); - return SOUP_STATUS_CONTINUE; - } +int +soup_socket_get_fd (SoupSocket *sock) +{ + g_return_val_if_fail (SOUP_IS_SOCKET (sock), -1); - sock->priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0); - if (sock->priv->sockfd == -1) { - g_free (sa); - goto done; - } - update_fdflags (sock); - - status = connect (sock->priv->sockfd, sa, len); - g_free (sa); - - if (status == -1) { - if (errno == EINPROGRESS) { - /* Wait for connect to succeed or fail */ - sock->priv->watch = - g_io_add_watch (get_iochannel (sock), - G_IO_IN | G_IO_OUT | - G_IO_PRI | G_IO_ERR | - G_IO_HUP | G_IO_NVAL, - connect_watch, sock); - return SOUP_STATUS_CONTINUE; - } else { - close (sock->priv->sockfd); - sock->priv->sockfd = -1; - } - } + return g_socket_get_fd (SOUP_SOCKET_GET_PRIVATE (sock)->gsock); +} - done: - if (sock->priv->non_blocking) { - sock->priv->watch = g_idle_add (idle_connect_result, sock); - return SOUP_STATUS_CONTINUE; - } else if (sock->priv->sockfd == -1) - return SOUP_STATUS_CANT_CONNECT; - else { - get_iochannel (sock); - return SOUP_STATUS_OK; - } +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 (GIOChannel* iochannel, GIOCondition condition, gpointer data) +listen_watch (GObject *pollable, gpointer data) { SoupSocket *sock = data, *new; - struct soup_sockaddr_max sa; - int sa_len, sockfd; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock), *new_priv; + GSocket *new_gsock; - if (condition & (G_IO_HUP | G_IO_ERR)) { - g_source_remove (sock->priv->watch); - sock->priv->watch = 0; + new_gsock = g_socket_accept (priv->gsock, NULL, NULL); + if (!new_gsock) return FALSE; - } - - sa_len = sizeof (sa); - sockfd = accept (sock->priv->sockfd, (struct sockaddr *)&sa, &sa_len); - if (sockfd == -1) - return TRUE; new = g_object_new (SOUP_TYPE_SOCKET, NULL); - new->priv->sockfd = sockfd; - new->priv->non_blocking = sock->priv->non_blocking; - new->priv->nodelay = sock->priv->nodelay; - new->priv->is_server = TRUE; - new->priv->ssl_creds = sock->priv->ssl_creds; - update_fdflags (new); - - new->priv->remote_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&sa, sa_len); - - if (new->priv->ssl_creds) { - if (!soup_socket_start_ssl (new)) { + 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; } - } else - get_iochannel (new); + } g_signal_emit (sock, signals[NEW_CONNECTION], 0, new); g_object_unref (new); @@ -540,208 +916,316 @@ listen_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) * soup_socket_listen: * @sock: a server #SoupSocket (which must not already be connected or * listening) - * @local_addr: Local address to bind to. * - * Makes @sock start listening on the given interface and port. When - * connections come in, @sock will emit %new_connection. + * Makes @sock start listening on its local address. When connections + * come in, @sock will emit #SoupSocket::new_connection. * * Return value: whether or not @sock is now listening. **/ gboolean -soup_socket_listen (SoupSocket *sock, SoupAddress *local_addr) +soup_socket_listen (SoupSocket *sock) + { - struct sockaddr *sa; - int sa_len; + SoupSocketPrivate *priv; + GSocketAddress *addr; g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE); - g_return_val_if_fail (sock->priv->is_server, FALSE); - g_return_val_if_fail (sock->priv->sockfd == -1, FALSE); - g_return_val_if_fail (SOUP_IS_ADDRESS (local_addr), 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); + + priv->is_server = TRUE; /* @local_addr may have its port set to 0. So we intentionally - * don't store it in sock->priv->local_addr, so that if the + * 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. */ - sa = soup_address_get_sockaddr (local_addr, &sa_len); - g_return_val_if_fail (sa != NULL, FALSE); - - sock->priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0); - if (sock->priv->sockfd < 0) + 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; - update_fdflags (sock); + finish_socket_setup (priv); /* Bind */ - if (bind (sock->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 (sock->priv->sockfd, 10) != 0) + if (!g_socket_listen (priv->gsock, NULL)) goto cant_listen; - sock->priv->watch = g_io_add_watch (get_iochannel (sock), - 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 (sock->priv->sockfd != -1) { - close (sock->priv->sockfd); - sock->priv->sockfd = -1; - } - if (sa) - g_free (sa); + 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 + * @cancellable: a #GCancellable * * Starts using SSL on @socket. * * Return value: success or failure **/ gboolean -soup_socket_start_ssl (SoupSocket *sock) +soup_socket_start_ssl (SoupSocket *sock, GCancellable *cancellable) { - GIOChannel *ssl_chan; - - get_iochannel (sock); - ssl_chan = soup_ssl_wrap_iochannel ( - sock->priv->iochannel, sock->priv->is_server ? - SOUP_SSL_TYPE_SERVER : SOUP_SSL_TYPE_CLIENT, - soup_address_get_name (sock->priv->remote_addr), - sock->priv->ssl_creds); + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - if (!ssl_chan) - return FALSE; - - sock->priv->iochannel = ssl_chan; - return TRUE; + return soup_socket_start_proxy_ssl (sock, soup_address_get_name (priv->remote_addr), cancellable); } - /** - * soup_socket_client_new_async: - * @hostname: remote machine to connect to - * @port: remote port to connect to - * @ssl_creds: SSL credentials structure, or %NULL if not SSL - * @callback: callback to call when the socket is connected - * @user_data: data for @callback + * soup_socket_start_proxy_ssl: + * @sock: the socket + * @ssl_host: hostname of the SSL server + * @cancellable: a #GCancellable * - * Creates a connection to @hostname and @port. @callback will be - * called when the connection completes (or fails). + * Starts using SSL on @socket, expecting to find a host named + * @ssl_host. * - * Return value: the new socket (not yet ready for use). + * Return value: success or failure **/ -SoupSocket * -soup_socket_client_new_async (const char *hostname, guint port, - gpointer ssl_creds, - SoupSocketCallback callback, gpointer user_data) +gboolean +soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host, + GCancellable *cancellable) { - SoupSocket *sock; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + GTlsBackend *backend = g_tls_backend_get_default (); - g_return_val_if_fail (hostname != NULL, NULL); + if (G_IS_TLS_CONNECTION (priv->conn)) + return TRUE; - sock = g_object_new (SOUP_TYPE_SOCKET, - SOUP_SOCKET_SSL_CREDENTIALS, ssl_creds, - NULL); - soup_socket_connect (sock, soup_address_new (hostname, port)); + if (g_cancellable_is_cancelled (cancellable)) + return FALSE; - if (callback) { - soup_signal_connect_once (sock, "connect_result", - G_CALLBACK (callback), user_data); + 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); } - return sock; + + 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; -/** - * soup_socket_client_new_sync: - * @hostname: remote machine to connect to - * @port: remote port to connect to - * @ssl_creds: SSL credentials structure, or %NULL if not SSL - * @status_ret: pointer to return the soup status in - * - * Creates a connection to @hostname and @port. If @status_ret is not - * %NULL, it will contain a status code on return. - * - * Return value: the new socket, or %NULL if it could not connect. - **/ -SoupSocket * -soup_socket_client_new_sync (const char *hostname, guint port, - gpointer ssl_creds, guint *status_ret) + 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) { - SoupSocket *sock; + SoupSocketAsyncConnectData *data = user_data; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (data->sock); + GError *error = NULL; guint status; - g_return_val_if_fail (hostname != NULL, NULL); + if (priv->async_context && !priv->use_thread_context) + g_main_context_pop_thread_default (priv->async_context); - sock = g_object_new (SOUP_TYPE_SOCKET, - SOUP_SOCKET_SSL_CREDENTIALS, ssl_creds, - NULL); - sock->priv->non_blocking = FALSE; - status = soup_socket_connect (sock, soup_address_new (hostname, port)); + 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); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_object_unref (sock); - sock = NULL; - } + data->callback (data->sock, status, data->user_data); + g_object_unref (data->sock); + g_slice_free (SoupSocketAsyncConnectData, data); +} - if (status_ret) - *status_ret = status; - return sock; +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_server_new: - * @local_addr: Local address to bind to. (Use soup_address_any_new() to - * accept connections on any local address) - * @ssl_creds: SSL credentials, or %NULL if this is not an SSL server - * @callback: Callback to call when a client connects - * @user_data: data to pass to @callback. + * soup_socket_is_ssl: + * @sock: a #SoupSocket * - * Create and open a new #SoupSocket listening on the specified - * address. @callback will be called each time a client connects, - * with a new #SoupSocket. + * Tests if @sock is doing (or has attempted to do) SSL. * - * Returns: a new #SoupSocket, or NULL if there was a failure. + * Return value: %TRUE if @sock has SSL credentials set **/ -SoupSocket * -soup_socket_server_new (SoupAddress *local_addr, gpointer ssl_creds, - SoupSocketListenerCallback callback, - gpointer user_data) +gboolean +soup_socket_is_ssl (SoupSocket *sock) { - SoupSocket *sock; - - g_return_val_if_fail (SOUP_IS_ADDRESS (local_addr), NULL); - - sock = g_object_new (SOUP_TYPE_SOCKET, - SOUP_SOCKET_SSL_CREDENTIALS, ssl_creds, - NULL); - sock->priv->is_server = TRUE; - if (!soup_socket_listen (sock, local_addr)) { - g_object_unref (sock); - return NULL; - } + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - if (callback) { - g_signal_connect (sock, "new_connection", - G_CALLBACK (callback), user_data); - } - - return sock; + return priv->ssl; } - +/** + * soup_socket_disconnect: + * @sock: a #SoupSocket + * + * Disconnects @sock. Any further read or write attempts on it will + * fail. + **/ 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 (!disconnect_internal (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 { + /* 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 (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); @@ -750,148 +1234,226 @@ soup_socket_disconnect (SoupSocket *sock) /* Then let everyone know we're disconnected */ g_signal_emit (sock, signals[DISCONNECTED], 0); + + g_object_unref (sock); } +/** + * soup_socket_is_connected: + * @sock: a #SoupSocket + * + * Tests if @sock is connected to another host + * + * Return value: %TRUE or %FALSE. + **/ gboolean soup_socket_is_connected (SoupSocket *sock) { + SoupSocketPrivate *priv; + g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE); + priv = SOUP_SOCKET_GET_PRIVATE (sock); - return sock->priv->iochannel != NULL; + return priv->conn != NULL; } - +/** + * 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) { - g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL); - - if (!sock->priv->local_addr) { - struct soup_sockaddr_max bound_sa; - int sa_len; + SoupSocketPrivate *priv; - sa_len = sizeof (bound_sa); - getsockname (sock->priv->sockfd, (struct sockaddr *)&bound_sa, &sa_len); - sock->priv->local_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&bound_sa, sa_len); + 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 sock->priv->local_addr; + return priv->local_addr; } +/** + * 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) { + 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); - if (!sock->priv->local_addr) { - struct soup_sockaddr_max bound_sa; - int sa_len; + return priv->remote_addr; +} - sa_len = sizeof (bound_sa); - getpeername (sock->priv->sockfd, (struct sockaddr *)&bound_sa, &sa_len); - sock->priv->remote_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&bound_sa, sa_len); - } +GInputStream * +soup_socket_get_input_stream (SoupSocket *sock) +{ + g_return_val_if_fail (SOUP_IS_SOCKET (sock), NULL); - return sock->priv->remote_addr; + 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); - sock->priv->read_tag = 0; + priv->read_src = NULL; g_signal_emit (sock, signals[READABLE], 0); - return FALSE; } static SoupSocketIOStatus -read_from_network (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) +translate_read_status (SoupSocket *sock, GCancellable *cancellable, + gssize my_nread, gsize *nread, + GError *my_err, GError **error) { - GIOStatus status; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - if (!sock->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 (sock->priv->iochannel, - buffer, len, nread, NULL); - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - if (*nread > 0) - return SOUP_SOCKET_OK; - - if (!sock->priv->read_tag) { - sock->priv->read_tag = - g_io_add_watch (sock->priv->iochannel, G_IO_IN, - socket_read_watch, sock); + } 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; - - case G_IO_STATUS_EOF: - return SOUP_SOCKET_EOF; - - default: - return SOUP_SOCKET_ERROR; } -} - -static SoupSocketIOStatus -read_from_buf (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) -{ - GByteArray *read_buf = sock->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); - sock->priv->read_buf = NULL; - } else { - memcpy (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; } /** + * 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 + * + * Return value from the #SoupSocket IO methods. + **/ + +/** * soup_socket_read: * @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 - * %SOUP_SOCKET_ERROR on any other error). + * %SOUP_SOCKET_ERROR on any other error, in which case @error will + * also be set). **/ SoupSocketIOStatus -soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) +soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, + gsize *nread, GCancellable *cancellable, GError **error) { + 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); - if (sock->priv->read_buf) - return read_from_buf (sock, buffer, len, nread); - else - return read_from_network (sock, buffer, len, nread); + priv = SOUP_SOCKET_GET_PRIVATE (sock); + + 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; } /** @@ -901,74 +1463,72 @@ soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) * @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 + * @error: error pointer * * 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. * + * 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 (boundary_len - 1) + * 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 soup_socket_read_until (SoupSocket *sock, gpointer buffer, gsize len, gconstpointer boundary, gsize boundary_len, - gsize *nread, gboolean *got_boundary) + gsize *nread, gboolean *got_boundary, + GCancellable *cancellable, GError **error) { + SoupSocketPrivate *priv; SoupSocketIOStatus status; - GByteArray *read_buf; - guint match_len, prev_len; - guint8 *p, *end; + gssize my_nread; + GError *my_err = NULL; g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR); + g_return_val_if_fail (nread != NULL, SOUP_SOCKET_ERROR); g_return_val_if_fail (len >= boundary_len, SOUP_SOCKET_ERROR); - *got_boundary = FALSE; - - if (!sock->priv->read_buf) - sock->priv->read_buf = g_byte_array_new (); - read_buf = sock->priv->read_buf; + priv = SOUP_SOCKET_GET_PRIVATE (sock); - 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); - read_buf->len = prev_len + *nread; + g_mutex_lock (&priv->iolock); - if (status != SOUP_SOCKET_OK) - 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; - return read_from_buf (sock, buffer, MIN (len, match_len), nread); + g_mutex_unlock (&priv->iolock); + return status; } static gboolean -socket_write_watch (GIOChannel *chan, GIOCondition condition, gpointer user_data) +socket_write_watch (GObject *pollable, gpointer user_data) { SoupSocket *sock = user_data; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - sock->priv->write_tag = 0; + priv->write_src = NULL; g_signal_emit (sock, signals[WRITABLE], 0); - return FALSE; } @@ -977,48 +1537,81 @@ socket_write_watch (GIOChannel *chan, GIOCondition condition, 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). + * %SOUP_SOCKET_EOF or %SOUP_SOCKET_ERROR. @error will be set if the + * return value is %SOUP_SOCKET_ERROR.) **/ SoupSocketIOStatus soup_socket_write (SoupSocket *sock, gconstpointer buffer, - gsize len, gsize *nwrote) + gsize len, gsize *nwrote, + GCancellable *cancellable, GError **error) { - GIOStatus status; - gpointer pipe_handler; + SoupSocketPrivate *priv; + 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); - if (!sock->priv->iochannel) + if (!priv->conn) { + g_mutex_unlock (&priv->iolock); return SOUP_SOCKET_EOF; - if (sock->priv->write_tag) + } + if (priv->write_src) { + g_mutex_unlock (&priv->iolock); return SOUP_SOCKET_WOULD_BLOCK; + } - pipe_handler = signal (SIGPIPE, SIG_IGN); - status = g_io_channel_write_chars (sock->priv->iochannel, - buffer, len, nwrote, NULL); - signal (SIGPIPE, pipe_handler); - if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN) - 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 (*nwrote) + if (my_nwrote > 0) { + g_mutex_unlock (&priv->iolock); + g_clear_error (&my_err); + *nwrote = my_nwrote; return SOUP_SOCKET_OK; + } + + if (g_error_matches (my_err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + g_mutex_unlock (&priv->iolock); + g_clear_error (&my_err); + + priv->write_src = + soup_socket_create_watch (priv, + G_IO_OUT, + socket_write_watch, sock, cancellable); + return SOUP_SOCKET_WOULD_BLOCK; + } - sock->priv->write_tag = - g_io_add_watch (sock->priv->iochannel, G_IO_OUT, - socket_write_watch, sock); - return SOUP_SOCKET_WOULD_BLOCK; + g_mutex_unlock (&priv->iolock); + g_propagate_error (error, my_err); + return SOUP_SOCKET_ERROR; }