Esempio n. 1
0
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_PRIVATE (conn);

#ifdef G_OS_UNIX
	if (priv->state == SOUP_CONNECTION_IDLE) {
		GPollFD pfd;

		pfd.fd = soup_socket_get_fd (priv->socket);
		pfd.events = G_IO_IN;
		pfd.revents = 0;
		if (g_poll (&pfd, 1, 0) == 1)
			soup_connection_set_state (conn, SOUP_CONNECTION_REMOTE_DISCONNECTED);
	}
#endif
	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;
}
Esempio n. 2
0
static void
tunnel_message_completed (SoupMessage *msg, gpointer user_data)
{
	SoupMessageQueueItem *item = user_data;
	SoupSession *session = item->session;

	if (item->state == SOUP_MESSAGE_RESTARTING) {
		soup_message_restarted (msg);
		if (item->conn) {
			soup_session_send_queue_item (session, item, tunnel_message_completed);
			return;
		}

		soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN);
	}

	item->state = SOUP_MESSAGE_FINISHED;

	if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
		if (item->conn)
			soup_connection_disconnect (item->conn);
		if (msg->status_code == SOUP_STATUS_TRY_AGAIN) {
			item->related->state = SOUP_MESSAGE_AWAITING_CONNECTION;
			g_object_unref (item->related->conn);
			item->related->conn = NULL;
		} else
			soup_message_set_status (item->related->msg, msg->status_code);
		goto done;
	}

	if (!soup_connection_start_ssl (item->conn)) {
		if (item->conn)
			soup_connection_disconnect (item->conn);
		soup_message_set_status (item->related->msg, SOUP_STATUS_SSL_FAILED);
		goto done;
	}

	g_signal_connect (item->conn, "disconnected",
			  G_CALLBACK (connection_closed), item->session);
	soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
	soup_connection_set_state (item->conn, SOUP_CONNECTION_IN_USE);

	item->related->state = SOUP_MESSAGE_READY;

done:
	soup_message_finished (msg);
	if (item->related->msg->status_code)
		item->related->state = SOUP_MESSAGE_FINISHING;

	do_idle_run_queue (item->session);
	soup_message_queue_item_unref (item->related);
	soup_session_unqueue_item (session, item);
	soup_message_queue_item_unref (item);
	g_object_unref (session);
}
Esempio n. 3
0
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_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_SSL_FALLBACK, priv->ssl_fallback,
				 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;
}
Esempio n. 4
0
/**
 * soup_connection_connect_sync:
 * @conn: the connection
 *
 * Synchronously connects @conn.
 *
 * Return value: the soup status
 **/
guint
soup_connection_connect_sync (SoupConnection *conn)
{
	SoupConnectionPrivate *priv;
	guint status;

	g_return_val_if_fail (SOUP_IS_CONNECTION (conn), SOUP_STATUS_MALFORMED);
	priv = SOUP_CONNECTION_GET_PRIVATE (conn);
	g_return_val_if_fail (priv->socket == NULL, SOUP_STATUS_MALFORMED);

	soup_connection_set_state (conn, SOUP_CONNECTION_CONNECTING);

	priv->socket =
		soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->remote_addr,
				 SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds,
				 SOUP_SOCKET_FLAG_NONBLOCKING, FALSE,
				 SOUP_SOCKET_TIMEOUT, priv->io_timeout,
				 NULL);

	status = soup_socket_connect_sync (priv->socket, NULL);

	if (!SOUP_STATUS_IS_SUCCESSFUL (status))
		goto fail;
		
	g_signal_connect (priv->socket, "disconnected",
			  G_CALLBACK (socket_disconnected), conn);

	if (priv->ssl_creds && !priv->tunnel_addr) {
		if (!soup_socket_start_ssl (priv->socket, NULL)) {
			status = SOUP_STATUS_SSL_FAILED;
			goto fail;
		}
	}

	if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
		soup_connection_set_state (conn, SOUP_CONNECTION_IDLE);
		priv->unused_timeout = time (NULL) + SOUP_CONNECTION_UNUSED_TIMEOUT;
		start_idle_timer (conn);
	} else {
	fail:
		if (priv->socket) {
			g_object_unref (priv->socket);
			priv->socket = NULL;
		}
	}

	if (priv->proxy_uri != NULL)
		status = soup_status_proxify (status);
	return status;
}
Esempio n. 5
0
static void
queue_message_restarted (SoupMessage *msg, gpointer user_data)
{
	SoupMessageQueueItem *item = user_data;

	if (item->proxy_addr) {
		g_object_unref (item->proxy_addr);
		item->proxy_addr = NULL;
	}
	if (item->proxy_uri) {
		soup_uri_free (item->proxy_uri);
		item->proxy_uri = NULL;
	}

	if (item->conn &&
	    (!soup_message_is_keepalive (msg) ||
	     SOUP_STATUS_IS_REDIRECTION (msg->status_code))) {
		if (soup_connection_get_state (item->conn) == SOUP_CONNECTION_IN_USE)
			soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
		g_object_unref (item->conn);
		item->conn = NULL;
	}

	g_cancellable_reset (item->cancellable);

	item->state = SOUP_MESSAGE_STARTING;
}
Esempio n. 6
0
static void
soup_connection_set_property (GObject *object, guint prop_id,
			      const GValue *value, GParamSpec *pspec)
{
	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_REMOTE_URI:
		priv->remote_uri = g_value_dup_boxed (value);
		if (priv->remote_uri)
			priv->ssl = (priv->remote_uri->scheme == SOUP_URI_SCHEME_HTTPS);
		else
			priv->ssl = FALSE;
		break;
		break;
	case PROP_SSL_FALLBACK:
		priv->ssl_fallback = g_value_get_boolean (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;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
Esempio n. 7
0
/**
 * 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;

	g_return_if_fail (SOUP_IS_CONNECTION (conn));
	priv = SOUP_CONNECTION_GET_PRIVATE (conn);

	if (!priv->socket)
		return;

	g_signal_handlers_disconnect_by_func (priv->socket,
					      socket_disconnected, conn);
	soup_socket_disconnect (priv->socket);
	g_object_unref (priv->socket);
	priv->socket = NULL;

	/* Don't emit "disconnected" if we aren't yet connected */
	if (priv->state < SOUP_CONNECTION_IDLE)
		return;

	soup_connection_set_state (conn, SOUP_CONNECTION_DISCONNECTED);
	/* NB: this might cause conn to be destroyed. */
	g_signal_emit (conn, signals[DISCONNECTED], 0);
}
Esempio n. 8
0
static void
tunnel_connected (SoupMessage *msg, gpointer user_data)
{
	SoupSessionAsyncTunnelData *data = user_data;

	if (SOUP_MESSAGE_IS_STARTING (msg)) {
		soup_session_send_queue_item (data->session, data->item, data->conn);
		return;
	}

	if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
		soup_session_connection_failed (data->session, data->conn,
						msg->status_code);
		goto done;
	}

	if (!soup_connection_start_ssl (data->conn)) {
		soup_session_connection_failed (data->session, data->conn,
						SOUP_STATUS_SSL_FAILED);
		goto done;
	}

	g_signal_connect (data->conn, "disconnected",
			  G_CALLBACK (connection_closed), data->session);
	soup_connection_set_state (data->conn, SOUP_CONNECTION_IDLE);

	do_idle_run_queue (data->session);

done:
	soup_message_queue_item_unref (data->item);
	g_slice_free (SoupSessionAsyncTunnelData, data);
}
Esempio n. 9
0
/**
 * 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_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);
}
Esempio n. 10
0
/**
 * soup_connection_connect_async:
 * @conn: the connection
 * @callback: callback to call when the connection succeeds or fails
 * @user_data: data for @callback
 *
 * Asynchronously connects @conn.
 **/
void
soup_connection_connect_async (SoupConnection *conn,
			       SoupConnectionCallback callback,
			       gpointer user_data)
{
	SoupConnectionAsyncConnectData *data;
	SoupConnectionPrivate *priv;

	g_return_if_fail (SOUP_IS_CONNECTION (conn));
	priv = SOUP_CONNECTION_GET_PRIVATE (conn);
	g_return_if_fail (priv->socket == NULL);

	soup_connection_set_state (conn, SOUP_CONNECTION_CONNECTING);

	data = g_slice_new (SoupConnectionAsyncConnectData);
	data->conn = conn;
	data->callback = callback;
	data->callback_data = user_data;

	priv->socket =
		soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->remote_addr,
				 SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds,
				 SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context,
				 SOUP_SOCKET_TIMEOUT, priv->io_timeout,
				 NULL);
	soup_socket_connect_async (priv->socket, NULL,
				   socket_connect_result, data);
}
Esempio n. 11
0
static void
socket_connect_result (SoupSocket *sock, guint status, gpointer user_data)
{
	SoupConnectionAsyncConnectData *data = user_data;
	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (data->conn);

	if (!SOUP_STATUS_IS_SUCCESSFUL (status))
		goto done;

	if (priv->ssl_creds && !priv->tunnel_addr) {
		if (!soup_socket_start_ssl (sock, NULL)) {
			status = SOUP_STATUS_SSL_FAILED;
			goto done;
		}
	}

	g_signal_connect (priv->socket, "disconnected",
			  G_CALLBACK (socket_disconnected), data->conn);

	soup_connection_set_state (data->conn, SOUP_CONNECTION_IDLE);
	priv->unused_timeout = time (NULL) + SOUP_CONNECTION_UNUSED_TIMEOUT;
	start_idle_timer (data->conn);

 done:
	if (data->callback) {
		if (priv->proxy_uri != NULL)
			status = soup_status_proxify (status);
		data->callback (data->conn, status, data->callback_data);
	}
	g_slice_free (SoupConnectionAsyncConnectData, data);
}
Esempio n. 12
0
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_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;
}
Esempio n. 13
0
static void
got_connection (SoupConnection *conn, guint status, gpointer session)
{
	SoupAddress *tunnel_addr;

	if (status != SOUP_STATUS_OK) {
		/* There may have been messages waiting for the
		 * connection count to go down, so queue a run_queue.
		 */
		do_idle_run_queue (session);

		soup_session_connection_failed (session, conn, status);
		/* session may be destroyed at this point */

		return;
	}

	tunnel_addr = soup_connection_get_tunnel_addr (conn);
	if (tunnel_addr) {
		SoupSessionAsyncTunnelData *data;

		data = g_slice_new (SoupSessionAsyncTunnelData);
		data->session = session;
		data->conn = conn;
		data->item = soup_session_make_connect_message (session, tunnel_addr);
		g_signal_emit_by_name (session, "tunneling", conn);
		g_signal_connect (data->item->msg, "finished",
				  G_CALLBACK (tunnel_connected), data);
		g_signal_connect (data->item->msg, "restarted",
				  G_CALLBACK (tunnel_connected), data);
		soup_session_send_queue_item (session, data->item, conn);
		return;
	}

	g_signal_connect (conn, "disconnected",
			  G_CALLBACK (connection_closed), session);

	/* @conn has been marked reserved by SoupSession, but
	 * we don't actually have any specific message in mind
	 * for it. (In particular, the message we were
	 * originally planning to queue on it may have already
	 * been queued on some other connection that became
	 * available while we were waiting for this one to
	 * connect.) So we release the connection into the
	 * idle pool and then just run the queue and see what
	 * happens.
	 */
	soup_connection_set_state (conn, SOUP_CONNECTION_IDLE);
	do_idle_run_queue (session);
}
Esempio n. 14
0
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_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_SSL_FALLBACK, priv->ssl_fallback,
				 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);
}
Esempio n. 15
0
static void
set_property (GObject *object, guint prop_id,
	      const GValue *value, GParamSpec *pspec)
{
	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_REMOTE_ADDRESS:
		priv->remote_addr = g_value_dup_object (value);
		break;
	case PROP_TUNNEL_ADDRESS:
		priv->tunnel_addr = g_value_dup_object (value);
		break;
	case PROP_PROXY_URI:
		if (priv->proxy_uri)
			soup_uri_free (priv->proxy_uri);
		priv->proxy_uri = g_value_dup_boxed (value);
		break;
	case PROP_SSL_CREDS:
		priv->ssl_creds = g_value_get_pointer (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_TIMEOUT:
		priv->io_timeout = g_value_get_uint (value);
		break;
	case PROP_IDLE_TIMEOUT:
		priv->idle_timeout = g_value_get_uint (value);
		break;
	case PROP_STATE:
		soup_connection_set_state (SOUP_CONNECTION (object), g_value_get_uint (value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
Esempio n. 16
0
static void
socket_connect_finished (GTask *task, SoupSocket *sock, GError *error)
{
	SoupConnection *conn = g_task_get_source_object (task);
	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_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);
}
Esempio n. 17
0
/**
 * soup_message_io_stop:
 * @msg: a #SoupMessage
 *
 * Immediately stops I/O on msg; if the connection would be left in an
 * inconsistent state, it will be closed.
 *
 * Note: this is a low-level function that does not cause any signals
 * to be emitted on @msg; it is up to the caller to make sure that
 * @msg doesn't get "stranded".
 **/
void
soup_message_io_stop (SoupMessage *msg)
{
	SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
	SoupMessageIOData *io = priv->io_data;

	if (!io)
		return;

	if (io->read_tag) {
		g_signal_handler_disconnect (io->sock, io->read_tag);
		io->read_tag = 0;
	}
	if (io->write_tag) {
		g_signal_handler_disconnect (io->sock, io->write_tag);
		io->write_tag = 0;
	}
	if (io->err_tag) {
		g_signal_handler_disconnect (io->sock, io->err_tag);
		io->err_tag = 0;
	}

	if (io->unpause_source) {
		g_source_destroy (io->unpause_source);
		io->unpause_source = NULL;
	}

	if (io->read_state < SOUP_MESSAGE_IO_STATE_FINISHING)
		soup_socket_disconnect (io->sock);
	else if (io->conn) {
		SoupConnection *conn = io->conn;
		io->conn = NULL;
		soup_connection_set_state (conn, SOUP_CONNECTION_IDLE);
		g_object_unref (conn);
	}
}
Esempio n. 18
0
static void
set_current_request (SoupConnection *conn, SoupMessage *req)
{
	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn);

	g_return_if_fail (priv->cur_req == NULL);

	g_object_freeze_notify (G_OBJECT (conn));

	stop_idle_timer (priv);
	priv->unused_timeout = 0;

	soup_message_set_io_status (req, SOUP_MESSAGE_IO_STATUS_RUNNING);
	priv->cur_req = req;
	g_object_notify (G_OBJECT (conn), "message");

	if (priv->state == SOUP_CONNECTION_IDLE ||
	    req->method != SOUP_METHOD_CONNECT)
		soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE);

	g_object_add_weak_pointer (G_OBJECT (req), (gpointer)&priv->cur_req);

	g_object_thaw_notify (G_OBJECT (conn));
}
Esempio n. 19
0
/**
 * soup_session_get_connection:
 * @session: a #SoupSession
 * @item: a #SoupMessageQueueItem
 * @try_pruning: on return, whether or not to try pruning a connection
 * 
 * Tries to find or create a connection for @item; this is an internal
 * method for #SoupSession subclasses.
 *
 * If there is an idle connection to the relevant host available, then
 * that connection will be returned. The connection will be set to
 * %SOUP_CONNECTION_IN_USE, so the caller must call
 * soup_connection_set_state() to set it to %SOUP_CONNECTION_IDLE if
 * it ends up not using the connection right away.
 *
 * If there is no idle connection available, but it is possible to
 * create a new connection, then one will be created and returned
 * (with state %SOUP_CONNECTION_NEW). The caller MUST then call
 * soup_connection_connect_sync() or soup_connection_connect_async()
 * to connect it. If the connection attempt fails, the caller must
 * call soup_session_connection_failed() to tell the session to free
 * the connection.
 *
 * If no connection is available and a new connection cannot be made,
 * soup_session_get_connection() will return %NULL. If @session has
 * the maximum number of open connections open, but does not have the
 * maximum number of per-host connections open to the relevant host,
 * then *@try_pruning will be set to %TRUE. In this case, the caller
 * can call soup_session_try_prune_connection() to close an idle
 * connection, and then try soup_session_get_connection() again. (If
 * calling soup_session_try_prune_connection() wouldn't help, then
 * *@try_pruning is left untouched; it is NOT set to %FALSE.)
 *
 * Return value: a #SoupConnection, or %NULL
 **/
SoupConnection *
soup_session_get_connection (SoupSession *session,
			     SoupMessageQueueItem *item,
			     gboolean *try_pruning)
{
	SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
	SoupConnection *conn;
	SoupSessionHost *host;
	SoupAddress *remote_addr, *tunnel_addr;
	SoupSSLCredentials *ssl_creds;
	GSList *conns;
	gboolean has_pending = FALSE;
	SoupURI *uri;

	g_mutex_lock (priv->host_lock);

	host = get_host_for_message (session, item->msg);
	for (conns = host->connections; conns; conns = conns->next) {
		if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_IDLE) {
			soup_connection_set_state (conns->data, SOUP_CONNECTION_IN_USE);
			g_mutex_unlock (priv->host_lock);
			return conns->data;
		} else if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_CONNECTING)
			has_pending = TRUE;
	}

	if (has_pending) {
		/* We've already started one connection to this
		 * address, so don't start another one until it's
		 * done.
		 */
		g_mutex_unlock (priv->host_lock);
		return NULL;
	}

	if (host->num_conns >= priv->max_conns_per_host) {
		g_mutex_unlock (priv->host_lock);
		return NULL;
	}

	if (priv->num_conns >= priv->max_conns) {
		*try_pruning = TRUE;
		g_mutex_unlock (priv->host_lock);
		return NULL;
	}

	if (item->proxy_addr) {
		remote_addr = item->proxy_addr;
		tunnel_addr = NULL;
	} else {
		remote_addr = host->addr;
		tunnel_addr = NULL;
	}

	uri = soup_message_get_uri (item->msg);
	if (uri->scheme == SOUP_URI_SCHEME_HTTPS) {
		if (!priv->ssl_creds)
			priv->ssl_creds = soup_ssl_get_client_credentials (priv->ssl_ca_file);
		ssl_creds = priv->ssl_creds;

		if (item->proxy_addr)
			tunnel_addr = host->addr;
	} else
		ssl_creds = NULL;

	conn = soup_connection_new (
		SOUP_CONNECTION_REMOTE_ADDRESS, remote_addr,
		SOUP_CONNECTION_TUNNEL_ADDRESS, tunnel_addr,
		SOUP_CONNECTION_PROXY_URI, item->proxy_uri,
		SOUP_CONNECTION_SSL_CREDENTIALS, ssl_creds,
		SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
		SOUP_CONNECTION_TIMEOUT, priv->io_timeout,
		SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout,
		NULL);
	g_signal_connect (conn, "disconnected",
			  G_CALLBACK (connection_disconnected),
			  session);

	g_hash_table_insert (priv->conns, conn, host);

	priv->num_conns++;
	host->num_conns++;
	host->connections = g_slist_prepend (host->connections, conn);

	g_mutex_unlock (priv->host_lock);
	return conn;
}