/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * soup-connection.c: A single HTTP/HTTPS connection * * Copyright (C) 2000-2003, Ximian, Inc. */ #ifdef HAVE_CONFIG_H #include #endif #include "soup-connection.h" #include "soup.h" #include "soup-message-queue.h" #include "soup-socket-private.h" #include "soup-misc-private.h" typedef struct { SoupSocket *socket; SoupSocketProperties *socket_props; SoupURI *remote_uri, *proxy_uri; gboolean ssl; SoupMessage *current_msg; SoupConnectionState state; time_t unused_timeout; GSource *idle_timeout_src; gboolean reusable; } SoupConnectionPrivate; G_DEFINE_TYPE_WITH_PRIVATE (SoupConnection, soup_connection, G_TYPE_OBJECT) enum { EVENT, DISCONNECTED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, PROP_REMOTE_URI, PROP_SOCKET_PROPERTIES, PROP_STATE, PROP_SSL, LAST_PROP }; static void stop_idle_timer (SoupConnectionPrivate *priv); /* Number of seconds after which we close a connection that hasn't yet * been used. */ #define SOUP_CONNECTION_UNUSED_TIMEOUT 3 static void soup_connection_init (SoupConnection *conn) { } static void soup_connection_finalize (GObject *object) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (SOUP_CONNECTION (object)); g_clear_pointer (&priv->remote_uri, soup_uri_free); g_clear_pointer (&priv->proxy_uri, soup_uri_free); g_clear_pointer (&priv->socket_props, soup_socket_properties_unref); g_clear_object (&priv->current_msg); if (priv->socket) { g_signal_handlers_disconnect_by_data (priv->socket, object); g_object_unref (priv->socket); } G_OBJECT_CLASS (soup_connection_parent_class)->finalize (object); } static void soup_connection_dispose (GObject *object) { SoupConnection *conn = SOUP_CONNECTION (object); SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); stop_idle_timer (priv); G_OBJECT_CLASS (soup_connection_parent_class)->dispose (object); } static void soup_connection_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (SOUP_CONNECTION (object)); switch (prop_id) { case PROP_REMOTE_URI: priv->remote_uri = g_value_dup_boxed (value); break; case PROP_SOCKET_PROPERTIES: priv->socket_props = g_value_dup_boxed (value); break; case PROP_STATE: soup_connection_set_state (SOUP_CONNECTION (object), g_value_get_uint (value)); break; case PROP_SSL: priv->ssl = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void soup_connection_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (SOUP_CONNECTION (object)); switch (prop_id) { case PROP_REMOTE_URI: g_value_set_boxed (value, priv->remote_uri); break; case PROP_SOCKET_PROPERTIES: g_value_set_boxed (value, priv->socket_props); break; case PROP_STATE: g_value_set_enum (value, priv->state); break; case PROP_SSL: g_value_set_boolean (value, priv->ssl); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void soup_connection_class_init (SoupConnectionClass *connection_class) { GObjectClass *object_class = G_OBJECT_CLASS (connection_class); /* virtual method override */ object_class->dispose = soup_connection_dispose; object_class->finalize = soup_connection_finalize; object_class->set_property = soup_connection_set_property; object_class->get_property = soup_connection_get_property; /* signals */ signals[EVENT] = g_signal_new ("event", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_SOCKET_CLIENT_EVENT, G_TYPE_IO_STREAM); signals[DISCONNECTED] = g_signal_new ("disconnected", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupConnectionClass, disconnected), NULL, NULL, NULL, G_TYPE_NONE, 0); /* properties */ g_object_class_install_property ( object_class, PROP_REMOTE_URI, g_param_spec_boxed (SOUP_CONNECTION_REMOTE_URI, "Remote URI", "The URI of the HTTP server", SOUP_TYPE_URI, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_SOCKET_PROPERTIES, g_param_spec_boxed (SOUP_CONNECTION_SOCKET_PROPERTIES, "Socket properties", "Socket properties", SOUP_TYPE_SOCKET_PROPERTIES, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_STATE, g_param_spec_enum (SOUP_CONNECTION_STATE, "Connection state", "Current state of connection", SOUP_TYPE_CONNECTION_STATE, SOUP_CONNECTION_NEW, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SSL, g_param_spec_boolean (SOUP_CONNECTION_SSL, "Connection uses TLS", "Whether the connection should use TLS", FALSE, G_PARAM_READWRITE)); } static void soup_connection_event (SoupConnection *conn, GSocketClientEvent event, GIOStream *connection) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); if (!connection && priv->socket) connection = soup_socket_get_connection (priv->socket); g_signal_emit (conn, signals[EVENT], 0, event, connection); } static gboolean idle_timeout (gpointer conn) { soup_connection_disconnect (conn); return FALSE; } static void start_idle_timer (SoupConnection *conn) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); if (priv->socket_props->idle_timeout > 0 && !priv->idle_timeout_src) { priv->idle_timeout_src = soup_add_timeout (priv->socket_props->async_context, priv->socket_props->idle_timeout * 1000, idle_timeout, conn); } } static void stop_idle_timer (SoupConnectionPrivate *priv) { if (priv->idle_timeout_src) { g_source_destroy (priv->idle_timeout_src); priv->idle_timeout_src = NULL; } } static void current_msg_got_body (SoupMessage *msg, gpointer user_data) { SoupConnection *conn = user_data; SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); priv->unused_timeout = 0; if (priv->proxy_uri && msg->method == SOUP_METHOD_CONNECT && SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { soup_connection_event (conn, G_SOCKET_CLIENT_PROXY_NEGOTIATED, NULL); /* We're now effectively no longer proxying */ g_clear_pointer (&priv->proxy_uri, soup_uri_free); } priv->reusable = soup_message_is_keepalive (msg); } static void clear_current_msg (SoupConnection *conn) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); SoupMessage *msg; msg = priv->current_msg; priv->current_msg = NULL; g_signal_handlers_disconnect_by_func (msg, G_CALLBACK (current_msg_got_body), conn); g_object_unref (msg); } static void set_current_msg (SoupConnection *conn, SoupMessage *msg) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); g_return_if_fail (priv->state == SOUP_CONNECTION_IN_USE); g_object_freeze_notify (G_OBJECT (conn)); if (priv->current_msg) { g_return_if_fail (priv->current_msg->method == SOUP_METHOD_CONNECT); clear_current_msg (conn); } stop_idle_timer (priv); priv->current_msg = g_object_ref (msg); priv->reusable = FALSE; g_signal_connect (msg, "got-body", G_CALLBACK (current_msg_got_body), conn); if (priv->proxy_uri && msg->method == SOUP_METHOD_CONNECT) soup_connection_event (conn, G_SOCKET_CLIENT_PROXY_NEGOTIATING, NULL); g_object_thaw_notify (G_OBJECT (conn)); } static void re_emit_socket_event (SoupSocket *socket, GSocketClientEvent event, GIOStream *connection, gpointer user_data) { SoupConnection *conn = user_data; /* We handle COMPLETE ourselves */ if (event != G_SOCKET_CLIENT_COMPLETE) soup_connection_event (conn, event, connection); } static void socket_connect_finished (GTask *task, SoupSocket *sock, GError *error) { SoupConnection *conn = g_task_get_source_object (task); SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); if (!error) { if (!priv->ssl || !priv->proxy_uri) { soup_connection_event (conn, G_SOCKET_CLIENT_COMPLETE, NULL); } soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE); priv->unused_timeout = time (NULL) + SOUP_CONNECTION_UNUSED_TIMEOUT; start_idle_timer (conn); g_task_return_boolean (task, TRUE); } else g_task_return_error (task, error); g_object_unref (task); } static void socket_handshake_complete (GObject *object, GAsyncResult *result, gpointer user_data) { SoupSocket *sock = SOUP_SOCKET (object); GTask *task = user_data; GError *error = NULL; soup_socket_handshake_finish (sock, result, &error); socket_connect_finished (task, sock, error); } static void socket_connect_complete (GObject *object, GAsyncResult *result, gpointer user_data) { SoupSocket *sock = SOUP_SOCKET (object); GTask *task = user_data; SoupConnection *conn = g_task_get_source_object (task); SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); GError *error = NULL; if (!soup_socket_connect_finish_internal (sock, result, &error)) { socket_connect_finished (task, sock, error); return; } priv->proxy_uri = soup_socket_get_http_proxy_uri (sock); if (priv->ssl && !priv->proxy_uri) { soup_socket_handshake_async (sock, priv->remote_uri->host, g_task_get_cancellable (task), socket_handshake_complete, task); return; } socket_connect_finished (task, sock, NULL); } void soup_connection_connect_async (SoupConnection *conn, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { SoupConnectionPrivate *priv; SoupAddress *remote_addr; GTask *task; g_return_if_fail (SOUP_IS_CONNECTION (conn)); priv = soup_connection_get_instance_private (conn); g_return_if_fail (priv->socket == NULL); soup_connection_set_state (conn, SOUP_CONNECTION_CONNECTING); /* Set the protocol to ensure correct proxy resolution. */ remote_addr = g_object_new (SOUP_TYPE_ADDRESS, SOUP_ADDRESS_NAME, priv->remote_uri->host, SOUP_ADDRESS_PORT, priv->remote_uri->port, SOUP_ADDRESS_PROTOCOL, priv->remote_uri->scheme, NULL); priv->socket = soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, remote_addr, SOUP_SOCKET_SOCKET_PROPERTIES, priv->socket_props, NULL); g_object_unref (remote_addr); g_signal_connect (priv->socket, "event", G_CALLBACK (re_emit_socket_event), conn); soup_socket_properties_push_async_context (priv->socket_props); task = g_task_new (conn, cancellable, callback, user_data); soup_socket_connect_async_internal (priv->socket, cancellable, socket_connect_complete, task); soup_socket_properties_pop_async_context (priv->socket_props); } gboolean soup_connection_connect_finish (SoupConnection *conn, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } gboolean soup_connection_connect_sync (SoupConnection *conn, GCancellable *cancellable, GError **error) { SoupConnectionPrivate *priv; SoupAddress *remote_addr; g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); priv = soup_connection_get_instance_private (conn); g_return_val_if_fail (priv->socket == NULL, FALSE); soup_connection_set_state (conn, SOUP_CONNECTION_CONNECTING); /* Set the protocol to ensure correct proxy resolution. */ remote_addr = g_object_new (SOUP_TYPE_ADDRESS, SOUP_ADDRESS_NAME, priv->remote_uri->host, SOUP_ADDRESS_PORT, priv->remote_uri->port, SOUP_ADDRESS_PROTOCOL, priv->remote_uri->scheme, NULL); priv->socket = soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, remote_addr, SOUP_SOCKET_SOCKET_PROPERTIES, priv->socket_props, SOUP_SOCKET_FLAG_NONBLOCKING, FALSE, NULL); g_object_unref (remote_addr); g_signal_connect (priv->socket, "event", G_CALLBACK (re_emit_socket_event), conn); if (!soup_socket_connect_sync_internal (priv->socket, cancellable, error)) return FALSE; priv->proxy_uri = soup_socket_get_http_proxy_uri (priv->socket); if (priv->ssl && !priv->proxy_uri) { if (!soup_socket_handshake_sync (priv->socket, priv->remote_uri->host, cancellable, error)) return FALSE; } if (!priv->ssl || !priv->proxy_uri) { soup_connection_event (conn, G_SOCKET_CLIENT_COMPLETE, NULL); } soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE); priv->unused_timeout = time (NULL) + SOUP_CONNECTION_UNUSED_TIMEOUT; start_idle_timer (conn); return TRUE; } gboolean soup_connection_is_tunnelled (SoupConnection *conn) { SoupConnectionPrivate *priv; g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); priv = soup_connection_get_instance_private (conn); return priv->ssl && priv->proxy_uri != NULL; } gboolean soup_connection_start_ssl_sync (SoupConnection *conn, GCancellable *cancellable, GError **error) { SoupConnectionPrivate *priv; g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); priv = soup_connection_get_instance_private (conn); if (soup_socket_handshake_sync (priv->socket, priv->remote_uri->host, cancellable, error)) { soup_connection_event (conn, G_SOCKET_CLIENT_COMPLETE, NULL); return TRUE; } else return FALSE; } static void start_ssl_completed (GObject *object, GAsyncResult *result, gpointer user_data) { GTask *task = user_data; SoupConnection *conn = g_task_get_source_object (task); SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); GError *error = NULL; if (soup_socket_handshake_finish (priv->socket, result, &error)) { soup_connection_event (conn, G_SOCKET_CLIENT_COMPLETE, NULL); g_task_return_boolean (task, TRUE); } else g_task_return_error (task, error); g_object_unref (task); } void soup_connection_start_ssl_async (SoupConnection *conn, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { SoupConnectionPrivate *priv; GTask *task; g_return_if_fail (SOUP_IS_CONNECTION (conn)); priv = soup_connection_get_instance_private (conn); soup_socket_properties_push_async_context (priv->socket_props); task = g_task_new (conn, cancellable, callback, user_data); soup_socket_handshake_async (priv->socket, priv->remote_uri->host, cancellable, start_ssl_completed, task); soup_socket_properties_pop_async_context (priv->socket_props); } gboolean soup_connection_start_ssl_finish (SoupConnection *conn, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } /** * soup_connection_disconnect: * @conn: a connection * * Disconnects @conn's socket and emits a %disconnected signal. * After calling this, @conn will be essentially useless. **/ void soup_connection_disconnect (SoupConnection *conn) { SoupConnectionPrivate *priv; SoupConnectionState old_state; g_return_if_fail (SOUP_IS_CONNECTION (conn)); priv = soup_connection_get_instance_private (conn); old_state = priv->state; if (old_state != SOUP_CONNECTION_DISCONNECTED) soup_connection_set_state (conn, SOUP_CONNECTION_DISCONNECTED); if (priv->socket) { SoupSocket *socket = priv->socket; g_signal_handlers_disconnect_by_func (socket, G_CALLBACK (re_emit_socket_event), conn); priv->socket = NULL; soup_socket_disconnect (socket); g_object_unref (socket); } if (old_state != SOUP_CONNECTION_DISCONNECTED) g_signal_emit (conn, signals[DISCONNECTED], 0); } SoupSocket * soup_connection_get_socket (SoupConnection *conn) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); g_return_val_if_fail (SOUP_IS_CONNECTION (conn), NULL); return priv->socket; } SoupURI * soup_connection_get_remote_uri (SoupConnection *conn) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); g_return_val_if_fail (SOUP_IS_CONNECTION (conn), NULL); return priv->remote_uri; } SoupURI * soup_connection_get_proxy_uri (SoupConnection *conn) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); g_return_val_if_fail (SOUP_IS_CONNECTION (conn), NULL); return priv->proxy_uri; } gboolean soup_connection_is_via_proxy (SoupConnection *conn) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); return priv->proxy_uri != NULL; } SoupConnectionState soup_connection_get_state (SoupConnection *conn) { SoupConnectionPrivate *priv; g_return_val_if_fail (SOUP_IS_CONNECTION (conn), SOUP_CONNECTION_DISCONNECTED); priv = soup_connection_get_instance_private (conn); if (priv->state == SOUP_CONNECTION_IDLE && (!soup_socket_is_connected (priv->socket) || soup_socket_is_readable (priv->socket))) soup_connection_set_state (conn, SOUP_CONNECTION_REMOTE_DISCONNECTED); if (priv->state == SOUP_CONNECTION_IDLE && priv->unused_timeout && priv->unused_timeout < time (NULL)) soup_connection_set_state (conn, SOUP_CONNECTION_REMOTE_DISCONNECTED); return priv->state; } void soup_connection_set_state (SoupConnection *conn, SoupConnectionState state) { SoupConnectionPrivate *priv; g_return_if_fail (SOUP_IS_CONNECTION (conn)); g_return_if_fail (state >= SOUP_CONNECTION_NEW && state <= SOUP_CONNECTION_DISCONNECTED); g_object_freeze_notify (G_OBJECT (conn)); priv = soup_connection_get_instance_private (conn); if (priv->current_msg) { g_warn_if_fail (state == SOUP_CONNECTION_IDLE || state == SOUP_CONNECTION_DISCONNECTED); clear_current_msg (conn); } if (state == SOUP_CONNECTION_IDLE && !priv->reusable) { /* This will recursively call set_state() */ soup_connection_disconnect (conn); } else { priv->state = state; if (priv->state == SOUP_CONNECTION_IDLE) start_idle_timer (conn); g_object_notify (G_OBJECT (conn), "state"); } g_object_thaw_notify (G_OBJECT (conn)); } gboolean soup_connection_get_ever_used (SoupConnection *conn) { SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn); g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); return priv->unused_timeout == 0; } void soup_connection_send_request (SoupConnection *conn, SoupMessageQueueItem *item, SoupMessageCompletionFn completion_cb, gpointer user_data) { SoupConnectionPrivate *priv; g_return_if_fail (SOUP_IS_CONNECTION (conn)); g_return_if_fail (item != NULL); priv = soup_connection_get_instance_private (conn); g_return_if_fail (priv->state != SOUP_CONNECTION_NEW && priv->state != SOUP_CONNECTION_DISCONNECTED); if (item->msg != priv->current_msg) set_current_msg (conn, item->msg); else priv->reusable = FALSE; soup_message_send_request (item, completion_cb, user_data); }