diff options
| author | Dan Winship <danw@gnome.org> | 2014-03-11 09:01:57 -0400 |
|---|---|---|
| committer | Dan Winship <danw@gnome.org> | 2014-05-02 09:05:15 -0400 |
| commit | dc94ea443305ae2a326f462bf8ce9fc03a3ad4b9 (patch) | |
| tree | 3950f35597ef5774ee15b99c26dbe1809eacd912 | |
| parent | f2753f27a1cd9059e7392f60c1d31c9c4df7bf65 (diff) | |
| download | libsoup-dc94ea443305ae2a326f462bf8ce9fc03a3ad4b9.tar.gz | |
soup-socket: add properties to create a SoupSocket from an fd or GSocket
| -rw-r--r-- | libsoup/soup-socket-private.h | 2 | ||||
| -rw-r--r-- | libsoup/soup-socket.c | 142 | ||||
| -rw-r--r-- | po/POTFILES.in | 1 | ||||
| -rw-r--r-- | tests/socket-test.c | 231 |
4 files changed, 361 insertions, 15 deletions
diff --git a/libsoup/soup-socket-private.h b/libsoup/soup-socket-private.h index fc6690a1..159dd241 100644 --- a/libsoup/soup-socket-private.h +++ b/libsoup/soup-socket-private.h @@ -10,6 +10,8 @@ #define SOUP_SOCKET_SOCKET_PROPERTIES "socket-properties" #define SOUP_SOCKET_CLOSE_ON_DISPOSE "close-on-dispose" +#define SOUP_SOCKET_FD "fd" +#define SOUP_SOCKET_GSOCKET "gsocket" gboolean soup_socket_connect_sync_internal (SoupSocket *sock, GCancellable *cancellable, diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index 87671c1a..b309169f 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -11,6 +11,7 @@ #include <string.h> +#include <glib/gi18n-lib.h> #include <gio/gnetworking.h> #include "soup-socket.h" @@ -29,7 +30,11 @@ * soup_socket_get_remote_address()) may be useful to applications. **/ -G_DEFINE_TYPE (SoupSocket, soup_socket, G_TYPE_OBJECT) +static void soup_socket_initable_interface_init (GInitableIface *initable_interface); + +G_DEFINE_TYPE_WITH_CODE (SoupSocket, soup_socket, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + soup_socket_initable_interface_init)) enum { READABLE, @@ -45,6 +50,8 @@ static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, + PROP_FD, + PROP_GSOCKET, PROP_LOCAL_ADDRESS, PROP_REMOTE_ADDRESS, PROP_NON_BLOCKING, @@ -91,12 +98,15 @@ typedef struct { guint timeout; GCancellable *connect_cancel; + int fd; } SoupSocketPrivate; #define SOUP_SOCKET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SOCKET, SoupSocketPrivate)) static void soup_socket_peer_certificate_changed (GObject *conn, GParamSpec *pspec, gpointer user_data); +static void finish_socket_setup (SoupSocket *sock); +static void finish_listener_setup (SoupSocket *sock); static void soup_socket_init (SoupSocket *sock) @@ -104,10 +114,63 @@ soup_socket_init (SoupSocket *sock) SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); priv->non_blocking = TRUE; + priv->fd = -1; g_mutex_init (&priv->addrlock); g_mutex_init (&priv->iolock); } +static gboolean +soup_socket_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + SoupSocket *sock = SOUP_SOCKET (initable); + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + + if (priv->fd != -1) { + guint type, len = sizeof (type); + + g_warn_if_fail (priv->gsock == NULL); + + /* GSocket will g_error() this, so we have to check ourselves. */ + if (getsockopt (priv->fd, SOL_SOCKET, SO_TYPE, + (gpointer)&type, (gpointer)&len) == -1) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Can't import non-socket as SoupSocket")); + return FALSE; + } + + priv->gsock = g_socket_new_from_fd (priv->fd, error); + if (!priv->gsock) + return FALSE; + } + + if (priv->gsock != NULL) { + int listening; + + g_warn_if_fail (priv->local_addr == NULL); + g_warn_if_fail (priv->remote_addr == NULL); + + if (!g_socket_get_option (priv->gsock, + SOL_SOCKET, SO_ACCEPTCONN, + &listening, error)) { + g_prefix_error (error, _("Could not import existing socket: ")); + return FALSE; + } + + finish_socket_setup (sock); + if (listening) + finish_listener_setup (sock); + else if (!g_socket_is_connected (priv->gsock)) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Can't import unconnected socket")); + return FALSE; + } + } + + return TRUE; +} + static void disconnect_internal (SoupSocket *sock, gboolean close) { @@ -167,10 +230,11 @@ soup_socket_finalize (GObject *object) G_OBJECT_CLASS (soup_socket_parent_class)->finalize (object); } - static void -finish_socket_setup (SoupSocketPrivate *priv) +finish_socket_setup (SoupSocket *sock) { + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + if (!priv->gsock) return; @@ -195,11 +259,17 @@ soup_socket_set_property (GObject *object, guint prop_id, SoupSocketProperties *props; switch (prop_id) { + case PROP_FD: + priv->fd = g_value_get_int (value); + break; + case PROP_GSOCKET: + priv->gsock = g_value_dup_object (value); + break; case PROP_LOCAL_ADDRESS: - priv->local_addr = (SoupAddress *)g_value_dup_object (value); + priv->local_addr = g_value_dup_object (value); break; case PROP_REMOTE_ADDRESS: - priv->remote_addr = (SoupAddress *)g_value_dup_object (value); + priv->remote_addr = g_value_dup_object (value); break; case PROP_NON_BLOCKING: priv->non_blocking = g_value_get_boolean (value); @@ -271,6 +341,9 @@ soup_socket_get_property (GObject *object, guint prop_id, SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object); switch (prop_id) { + case PROP_FD: + g_value_set_int (value, priv->fd); + break; case PROP_LOCAL_ADDRESS: g_value_set_object (value, soup_socket_get_local_address (SOUP_SOCKET (object))); break; @@ -430,6 +503,21 @@ soup_socket_class_init (SoupSocketClass *socket_class) /* properties */ + g_object_class_install_property ( + object_class, PROP_FD, + g_param_spec_int (SOUP_SOCKET_FD, + "FD", + "The socket's file descriptor", + -1, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, PROP_GSOCKET, + g_param_spec_object (SOUP_SOCKET_GSOCKET, + "GSocket", + "The socket's underlying GSocket", + G_TYPE_SOCKET, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + /** * SOUP_SOCKET_LOCAL_ADDRESS: * @@ -496,8 +584,18 @@ soup_socket_class_init (SoupSocketClass *socket_class) /** * SOUP_SOCKET_IS_SERVER: * - * Alias for the #SoupSocket:is-server property. (Whether or - * not the socket is a server socket.) + * Alias for the #SoupSocket:is-server property, qv. + **/ + /** + * SoupSocket:is-server: + * + * Whether or not the socket is a server socket. + * + * Note that for "ordinary" #SoupSockets this will be set for + * both listening sockets and the sockets emitted by + * #SoupSocket::new-connection, but for sockets created by + * setting #SoupSocket:fd, it will only be set for listening + * sockets. **/ g_object_class_install_property ( object_class, PROP_IS_SERVER, @@ -661,6 +759,12 @@ soup_socket_class_init (SoupSocketClass *socket_class) G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } +static void +soup_socket_initable_interface_init (GInitableIface *initable_interface) +{ + initable_interface->init = soup_socket_initable_init; +} + /** * soup_socket_new: @@ -708,7 +812,7 @@ socket_connect_finish (SoupSocket *sock, GSocketConnection *conn) if (conn) { priv->conn = (GIOStream *)conn; priv->gsock = g_object_ref (g_socket_connection_get_socket (conn)); - finish_socket_setup (priv); + finish_socket_setup (sock); return TRUE; } else return FALSE; @@ -1032,11 +1136,12 @@ listen_watch (GObject *pollable, gpointer data) 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->clean_dispose = priv->clean_dispose; new_priv->is_server = TRUE; new_priv->ssl = priv->ssl; if (priv->ssl_creds) new_priv->ssl_creds = g_object_ref (priv->ssl_creds); - finish_socket_setup (new_priv); + finish_socket_setup (new); if (new_priv->ssl_creds) { if (!soup_socket_start_proxy_ssl (new, NULL, NULL)) { @@ -1051,6 +1156,17 @@ listen_watch (GObject *pollable, gpointer data) return TRUE; } +static void +finish_listener_setup (SoupSocket *sock) +{ + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + + priv->is_server = TRUE; + priv->watch_src = soup_socket_create_watch (priv, G_IO_IN, + listen_watch, sock, + NULL); +} + /** * soup_socket_listen: * @sock: a server #SoupSocket (which must not already be connected or @@ -1073,8 +1189,6 @@ soup_socket_listen (SoupSocket *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 priv->local_addr, so that if the * caller calls soup_socket_get_local_address() later, we'll @@ -1090,7 +1204,7 @@ soup_socket_listen (SoupSocket *sock) NULL); if (!priv->gsock) goto cant_listen; - finish_socket_setup (priv); + finish_socket_setup (sock); /* Bind */ if (!g_socket_bind (priv->gsock, addr, TRUE, NULL)) @@ -1102,10 +1216,8 @@ soup_socket_listen (SoupSocket *sock) /* Listen */ if (!g_socket_listen (priv->gsock, NULL)) goto cant_listen; + finish_listener_setup (sock); - priv->watch_src = soup_socket_create_watch (priv, G_IO_IN, - listen_watch, sock, - NULL); g_object_unref (addr); return TRUE; diff --git a/po/POTFILES.in b/po/POTFILES.in index 21c70d42..23a19a5e 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -6,4 +6,5 @@ libsoup/soup-message-io.c libsoup/soup-message-server-io.c libsoup/soup-request.c libsoup/soup-session.c +libsoup/soup-socket.c libsoup/soup-tld.c diff --git a/tests/socket-test.c b/tests/socket-test.c index 5bcc3b0c..821b1a22 100644 --- a/tests/socket-test.c +++ b/tests/socket-test.c @@ -5,7 +5,9 @@ */ #include "test-utils.h" +#include "libsoup/soup-socket-private.h" +#include <fcntl.h> #include <gio/gnetworking.h> static void @@ -108,6 +110,232 @@ do_unconnected_socket_test (void) g_object_unref (sock); } +static void +do_socket_from_fd_client_test (void) +{ + SoupServer *server; + GSocket *gsock; + SoupSocket *sock; + SoupAddress *local, *remote; + GSocketAddress *gaddr; + gboolean is_server; + int type; + GError *error = NULL; + + server = soup_test_server_new (FALSE); + + gsock = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + gaddr = g_inet_socket_address_new_from_string ("127.0.0.1", soup_server_get_port (server)); + g_socket_connect (gsock, gaddr, NULL, &error); + g_object_unref (gaddr); + g_assert_no_error (error); + g_assert_true (g_socket_is_connected (gsock)); + + gaddr = g_socket_get_local_address (gsock, &error); + g_assert_no_error (error); + + sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error, + SOUP_SOCKET_FD, g_socket_get_fd (gsock), + SOUP_SOCKET_CLOSE_ON_DISPOSE, FALSE, + NULL); + g_assert_no_error (error); + g_assert_nonnull (sock); + + g_object_get (G_OBJECT (sock), + SOUP_SOCKET_LOCAL_ADDRESS, &local, + SOUP_SOCKET_REMOTE_ADDRESS, &remote, + SOUP_SOCKET_IS_SERVER, &is_server, + NULL); + g_assert_cmpint (soup_socket_get_fd (sock), ==, g_socket_get_fd (gsock)); + g_assert_false (is_server); + g_assert_true (soup_socket_is_connected (sock)); + + g_assert_cmpstr (soup_address_get_physical (local), ==, "127.0.0.1"); + g_assert_cmpint (soup_address_get_port (local), ==, g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (gaddr))); + g_assert_cmpstr (soup_address_get_physical (remote), ==, "127.0.0.1"); + g_assert_cmpint (soup_address_get_port (remote), ==, soup_server_get_port (server)); + + g_object_unref (local); + g_object_unref (remote); + g_object_unref (gaddr); + + g_object_unref (sock); + /* We specified close-on-dispose=FALSE */ + g_socket_get_option (gsock, SOL_SOCKET, SO_TYPE, &type, &error); + g_assert_no_error (error); + + g_object_unref (gsock); + + g_object_unref (server); +} + +static void +do_socket_from_fd_server_test (void) +{ + GSocket *gsock; + SoupSocket *sock; + SoupAddress *local; + GSocketAddress *gaddr; + gboolean is_server; + GError *error = NULL; + + gsock = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + gaddr = g_inet_socket_address_new_from_string ("127.0.0.1", 0); + g_socket_bind (gsock, gaddr, TRUE, &error); + g_object_unref (gaddr); + g_assert_no_error (error); + g_socket_listen (gsock, &error); + g_assert_no_error (error); + g_assert_false (g_socket_is_connected (gsock)); + + gaddr = g_socket_get_local_address (gsock, &error); + g_assert_no_error (error); + + sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error, + SOUP_SOCKET_GSOCKET, gsock, + NULL); + g_assert_no_error (error); + g_assert_nonnull (sock); + + g_object_get (G_OBJECT (sock), + SOUP_SOCKET_LOCAL_ADDRESS, &local, + SOUP_SOCKET_IS_SERVER, &is_server, + NULL); + g_assert_cmpint (soup_socket_get_fd (sock), ==, g_socket_get_fd (gsock)); + g_assert_true (is_server); + g_assert_true (soup_socket_is_connected (sock)); + + g_assert_cmpstr (soup_address_get_physical (local), ==, "127.0.0.1"); + g_assert_cmpint (soup_address_get_port (local), ==, g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (gaddr))); + g_object_unref (local); + + g_object_unref (sock); + + /* Closing the SoupSocket should have closed the GSocket */ + g_assert_true (g_socket_is_closed (gsock)); + + g_object_unref (gsock); +} + +static void +do_socket_from_fd_bad_test (void) +{ + GSocket *gsock, *gsock2, *gsockcli; + SoupSocket *sock, *sock2; + SoupAddress *local, *remote; + GSocketAddress *gaddr; + gboolean is_server; + int fd; + GError *error = NULL; + + /* Importing a non-socket fd gives an error */ + fd = open (g_test_get_filename (G_TEST_DIST, "test-cert.pem", NULL), O_RDONLY); + g_assert_cmpint (fd, !=, -1); + + sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error, + SOUP_SOCKET_FD, fd, + NULL); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + g_clear_error (&error); + g_assert_null (sock); + close (fd); + + /* Importing an unconnected socket gives an error */ + gsock = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + g_assert_false (g_socket_is_connected (gsock)); + + sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error, + SOUP_SOCKET_FD, g_socket_get_fd (gsock), + NULL); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + g_clear_error (&error); + g_assert_null (sock); + g_object_unref (gsock); + + /* Importing a non-listening server-side socket works, but + * gives the wrong answer for soup_socket_is_server(). + */ + gsock = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + gaddr = g_inet_socket_address_new_from_string ("127.0.0.1", 0); + g_socket_bind (gsock, gaddr, TRUE, &error); + g_object_unref (gaddr); + g_assert_no_error (error); + g_socket_listen (gsock, &error); + g_assert_no_error (error); + g_assert_false (g_socket_is_connected (gsock)); + + gaddr = g_socket_get_local_address (gsock, &error); + g_assert_no_error (error); + + gsockcli = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + g_socket_connect (gsockcli, gaddr, NULL, &error); + g_assert_no_error (error); + g_assert_true (g_socket_is_connected (gsockcli)); + + gsock2 = g_socket_accept (gsock, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (gsock2); + + sock2 = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error, + SOUP_SOCKET_GSOCKET, gsock2, + NULL); + g_assert_no_error (error); + g_assert_nonnull (sock2); + + g_object_get (G_OBJECT (sock2), + SOUP_SOCKET_LOCAL_ADDRESS, &local, + SOUP_SOCKET_REMOTE_ADDRESS, &remote, + SOUP_SOCKET_IS_SERVER, &is_server, + NULL); + g_assert_cmpint (soup_socket_get_fd (sock2), ==, g_socket_get_fd (gsock2)); + g_assert_true (soup_socket_is_connected (sock2)); + /* This is wrong, but can't be helped. */ + g_assert_false (is_server); + + g_assert_cmpstr (soup_address_get_physical (local), ==, "127.0.0.1"); + g_assert_cmpint (soup_address_get_port (local), ==, g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (gaddr))); + g_object_unref (gaddr); + + gaddr = g_socket_get_local_address (gsockcli, &error); + g_assert_no_error (error); + g_assert_cmpstr (soup_address_get_physical (remote), ==, "127.0.0.1"); + g_assert_cmpint (soup_address_get_port (remote), ==, g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (gaddr))); + g_object_unref (gaddr); + + g_object_unref (local); + g_object_unref (remote); + + g_object_unref (sock2); + + g_object_unref (gsock); + g_object_unref (gsock2); + g_object_unref (gsockcli); +} + int main (int argc, char **argv) { @@ -116,6 +344,9 @@ main (int argc, char **argv) test_init (argc, argv, NULL); g_test_add_func ("/sockets/unconnected", do_unconnected_socket_test); + g_test_add_func ("/sockets/from-fd/client", do_socket_from_fd_client_test); + g_test_add_func ("/sockets/from-fd/server", do_socket_from_fd_server_test); + g_test_add_func ("/sockets/from-fd/bad", do_socket_from_fd_bad_test); ret = g_test_run (); |
