blob: 0d1539a9db7b34fbab80618a50c714f8a19cd7d1 [file] [log] [blame]
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2010 Collabora, Ltd.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
*/
#include "config.h"
#include "gproxyaddressenumerator.h"
#include <string.h>
#include "gasyncresult.h"
#include "ginetaddress.h"
#include "gioerror.h"
#include "glibintl.h"
#include "glib-private.h"
#include "gnetworkaddress.h"
#include "gnetworkingprivate.h"
#include "gproxy.h"
#include "gproxyaddress.h"
#include "gproxyresolver.h"
#include "gtask.h"
#include "gresolver.h"
#include "gsocketaddress.h"
#include "gsocketaddressenumerator.h"
#include "gsocketconnectable.h"
/**
* GProxyAddressEnumerator:
*
* `GProxyAddressEnumerator` is a wrapper around
* [class@Gio.SocketAddressEnumerator] which takes the [class@Gio.SocketAddress]
* instances returned by the [class@Gio.SocketAddressEnumerator]
* and wraps them in [class@Gio.ProxyAddress] instances, using the given
* [property@Gio.ProxyAddressEnumerator:proxy-resolver].
*
* This enumerator will be returned (for example, by
* [method@Gio.SocketConnectable.enumerate]) as appropriate when a proxy is
* configured; there should be no need to manually wrap a
* [class@Gio.SocketAddressEnumerator] instance with one.
*/
#define GET_PRIVATE(o) (G_PROXY_ADDRESS_ENUMERATOR (o)->priv)
enum
{
PROP_0,
PROP_URI,
PROP_DEFAULT_PORT,
PROP_CONNECTABLE,
PROP_PROXY_RESOLVER
};
struct _GProxyAddressEnumeratorPrivate
{
/* Destination address */
GSocketConnectable *connectable;
gchar *dest_uri;
guint16 default_port;
gchar *dest_hostname;
guint16 dest_port;
GList *dest_ips;
/* Proxy enumeration */
GProxyResolver *proxy_resolver;
gchar **proxies;
gchar **next_proxy;
GSocketAddressEnumerator *addr_enum;
GSocketAddress *proxy_address;
const gchar *proxy_uri;
gchar *proxy_type;
gchar *proxy_username;
gchar *proxy_password;
gboolean supports_hostname;
GList *next_dest_ip;
GError *last_error;
/* ever_enumerated is TRUE after we've returned a result for the first time
* via g_proxy_address_enumerator_next() or _next_async(). If FALSE, we have
* never returned yet, and should return an error if returning NULL because
* it does not make sense for a proxy resolver to return NULL except on error.
* (Whereas a DNS resolver would return NULL with no error to indicate "no
* results", a proxy resolver would want to return "direct://" instead, so
* NULL without error does not make sense for us.)
*
* But if ever_enumerated is TRUE, then we must not report any further errors
* (except for G_IO_ERROR_CANCELLED), because this is an API contract of
* GSocketAddressEnumerator.
*/
gboolean ever_enumerated;
};
G_DEFINE_TYPE_WITH_PRIVATE (GProxyAddressEnumerator, g_proxy_address_enumerator, G_TYPE_SOCKET_ADDRESS_ENUMERATOR)
static void
save_userinfo (GProxyAddressEnumeratorPrivate *priv,
const gchar *proxy)
{
g_clear_pointer (&priv->proxy_username, g_free);
g_clear_pointer (&priv->proxy_password, g_free);
g_uri_split_with_user (proxy, G_URI_FLAGS_HAS_PASSWORD, NULL,
&priv->proxy_username, &priv->proxy_password,
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
}
static void
next_enumerator (GProxyAddressEnumeratorPrivate *priv)
{
if (priv->proxy_address)
return;
while (priv->addr_enum == NULL && *priv->next_proxy)
{
GSocketConnectable *connectable = NULL;
GProxy *proxy;
priv->proxy_uri = *priv->next_proxy++;
g_free (priv->proxy_type);
priv->proxy_type = g_uri_parse_scheme (priv->proxy_uri);
if (priv->proxy_type == NULL)
continue;
/* Assumes hostnames are supported for unknown protocols */
priv->supports_hostname = TRUE;
proxy = g_proxy_get_default_for_protocol (priv->proxy_type);
if (proxy)
{
priv->supports_hostname = g_proxy_supports_hostname (proxy);
g_object_unref (proxy);
}
if (strcmp ("direct", priv->proxy_type) == 0)
{
if (priv->connectable)
connectable = g_object_ref (priv->connectable);
else
connectable = g_network_address_new (priv->dest_hostname,
priv->dest_port);
}
else
{
GError *error = NULL;
int default_port;
default_port = GLIB_PRIVATE_CALL (g_uri_get_default_scheme_port) (priv->proxy_type);
if (default_port == -1)
default_port = 0;
connectable = g_network_address_parse_uri (priv->proxy_uri, default_port, &error);
if (error)
{
g_warning ("Invalid proxy URI '%s': %s",
priv->proxy_uri, error->message);
g_error_free (error);
}
save_userinfo (priv, priv->proxy_uri);
}
if (connectable)
{
priv->addr_enum = g_socket_connectable_enumerate (connectable);
g_object_unref (connectable);
}
}
}
static GSocketAddress *
g_proxy_address_enumerator_next (GSocketAddressEnumerator *enumerator,
GCancellable *cancellable,
GError **error)
{
GProxyAddressEnumeratorPrivate *priv = GET_PRIVATE (enumerator);
GSocketAddress *result = NULL;
GError *first_error = NULL;
if (!priv->ever_enumerated)
{
g_assert (priv->proxies == NULL);
priv->proxies = g_proxy_resolver_lookup (priv->proxy_resolver,
priv->dest_uri,
cancellable,
error);
priv->next_proxy = priv->proxies;
if (priv->proxies == NULL)
{
priv->ever_enumerated = TRUE;
return NULL;
}
}
while (result == NULL && (*priv->next_proxy || priv->addr_enum))
{
gchar *dest_hostname;
gchar *dest_protocol;
GInetSocketAddress *inetsaddr;
GInetAddress *inetaddr;
guint16 port;
next_enumerator (priv);
if (!priv->addr_enum)
continue;
if (priv->proxy_address == NULL)
{
priv->proxy_address = g_socket_address_enumerator_next (
priv->addr_enum,
cancellable,
first_error ? NULL : &first_error);
}
if (priv->proxy_address == NULL)
{
g_object_unref (priv->addr_enum);
priv->addr_enum = NULL;
if (priv->dest_ips)
{
g_resolver_free_addresses (priv->dest_ips);
priv->dest_ips = NULL;
}
continue;
}
if (strcmp ("direct", priv->proxy_type) == 0)
{
result = priv->proxy_address;
priv->proxy_address = NULL;
continue;
}
if (!priv->supports_hostname)
{
GInetAddress *dest_ip;
if (!priv->dest_ips)
{
GResolver *resolver;
resolver = g_resolver_get_default();
priv->dest_ips = g_resolver_lookup_by_name (resolver,
priv->dest_hostname,
cancellable,
first_error ? NULL : &first_error);
g_object_unref (resolver);
if (!priv->dest_ips)
{
g_object_unref (priv->proxy_address);
priv->proxy_address = NULL;
continue;
}
}
if (!priv->next_dest_ip)
priv->next_dest_ip = priv->dest_ips;
dest_ip = G_INET_ADDRESS (priv->next_dest_ip->data);
dest_hostname = g_inet_address_to_string (dest_ip);
priv->next_dest_ip = g_list_next (priv->next_dest_ip);
}
else
{
dest_hostname = g_strdup (priv->dest_hostname);
}
dest_protocol = g_uri_parse_scheme (priv->dest_uri);
if (!G_IS_INET_SOCKET_ADDRESS (priv->proxy_address))
{
g_free (dest_hostname);
g_free (dest_protocol);
}
g_return_val_if_fail (G_IS_INET_SOCKET_ADDRESS (priv->proxy_address), NULL);
inetsaddr = G_INET_SOCKET_ADDRESS (priv->proxy_address);
inetaddr = g_inet_socket_address_get_address (inetsaddr);
port = g_inet_socket_address_get_port (inetsaddr);
result = g_object_new (G_TYPE_PROXY_ADDRESS,
"address", inetaddr,
"port", port,
"protocol", priv->proxy_type,
"destination-protocol", dest_protocol,
"destination-hostname", dest_hostname,
"destination-port", priv->dest_port,
"username", priv->proxy_username,
"password", priv->proxy_password,
"uri", priv->proxy_uri,
NULL);
g_free (dest_hostname);
g_free (dest_protocol);
if (priv->supports_hostname || priv->next_dest_ip == NULL)
{
g_object_unref (priv->proxy_address);
priv->proxy_address = NULL;
}
}
if (result == NULL && first_error && (!priv->ever_enumerated || g_error_matches (first_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)))
g_propagate_error (error, first_error);
else if (first_error)
g_error_free (first_error);
if (result == NULL && error != NULL && *error == NULL && !priv->ever_enumerated)
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unspecified proxy lookup failure"));
priv->ever_enumerated = TRUE;
return result;
}
static void
complete_async (GTask *task)
{
GProxyAddressEnumeratorPrivate *priv = g_task_get_task_data (task);
if (priv->last_error && (!priv->ever_enumerated || g_error_matches (priv->last_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)))
{
g_task_return_error (task, priv->last_error);
priv->last_error = NULL;
}
else if (!priv->ever_enumerated)
{
g_task_return_new_error_literal (task, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Unspecified proxy lookup failure"));
}
else
{
g_task_return_pointer (task, NULL, NULL);
}
priv->ever_enumerated = TRUE;
g_clear_error (&priv->last_error);
g_object_unref (task);
}
static void
return_result (GTask *task)
{
GProxyAddressEnumeratorPrivate *priv = g_task_get_task_data (task);
GSocketAddress *result;
gboolean is_inet_socket_address;
if (strcmp ("direct", priv->proxy_type) == 0)
{
result = priv->proxy_address;
priv->proxy_address = NULL;
}
else
{
gchar *dest_hostname, *dest_protocol;
GInetSocketAddress *inetsaddr;
GInetAddress *inetaddr;
guint16 port;
if (!priv->supports_hostname)
{
GInetAddress *dest_ip;
if (!priv->next_dest_ip)
priv->next_dest_ip = priv->dest_ips;
dest_ip = G_INET_ADDRESS (priv->next_dest_ip->data);
dest_hostname = g_inet_address_to_string (dest_ip);
priv->next_dest_ip = g_list_next (priv->next_dest_ip);
}
else
{
dest_hostname = g_strdup (priv->dest_hostname);
}
dest_protocol = g_uri_parse_scheme (priv->dest_uri);
is_inet_socket_address = G_IS_INET_SOCKET_ADDRESS (priv->proxy_address);
if (!is_inet_socket_address)
{
g_free (dest_hostname);
g_free (dest_protocol);
}
g_return_if_fail (is_inet_socket_address);
inetsaddr = G_INET_SOCKET_ADDRESS (priv->proxy_address);
inetaddr = g_inet_socket_address_get_address (inetsaddr);
port = g_inet_socket_address_get_port (inetsaddr);
result = g_object_new (G_TYPE_PROXY_ADDRESS,
"address", inetaddr,
"port", port,
"protocol", priv->proxy_type,
"destination-protocol", dest_protocol,
"destination-hostname", dest_hostname,
"destination-port", priv->dest_port,
"username", priv->proxy_username,
"password", priv->proxy_password,
"uri", priv->proxy_uri,
NULL);
g_free (dest_hostname);
g_free (dest_protocol);
if (priv->supports_hostname || priv->next_dest_ip == NULL)
{
g_object_unref (priv->proxy_address);
priv->proxy_address = NULL;
}
}
priv->ever_enumerated = TRUE;
g_task_return_pointer (task, result, g_object_unref);
g_object_unref (task);
}
static void address_enumerate_cb (GObject *object,
GAsyncResult *result,
gpointer user_data);
static void
next_proxy (GTask *task)
{
GProxyAddressEnumeratorPrivate *priv = g_task_get_task_data (task);
if (*priv->next_proxy)
{
g_object_unref (priv->addr_enum);
priv->addr_enum = NULL;
if (priv->dest_ips)
{
g_resolver_free_addresses (priv->dest_ips);
priv->dest_ips = NULL;
}
next_enumerator (priv);
if (priv->addr_enum)
{
g_socket_address_enumerator_next_async (priv->addr_enum,
g_task_get_cancellable (task),
address_enumerate_cb,
task);
return;
}
}
complete_async (task);
}
static void
dest_hostname_lookup_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = user_data;
GProxyAddressEnumeratorPrivate *priv = g_task_get_task_data (task);
g_clear_error (&priv->last_error);
priv->dest_ips = g_resolver_lookup_by_name_finish (G_RESOLVER (object),
result,
&priv->last_error);
if (priv->dest_ips)
return_result (task);
else
{
g_clear_object (&priv->proxy_address);
next_proxy (task);
}
}
static void
address_enumerate_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = user_data;
GProxyAddressEnumeratorPrivate *priv = g_task_get_task_data (task);
g_clear_error (&priv->last_error);
priv->proxy_address =
g_socket_address_enumerator_next_finish (priv->addr_enum,
result,
&priv->last_error);
if (priv->proxy_address)
{
if (!priv->supports_hostname && !priv->dest_ips)
{
GResolver *resolver;
resolver = g_resolver_get_default();
g_resolver_lookup_by_name_async (resolver,
priv->dest_hostname,
g_task_get_cancellable (task),
dest_hostname_lookup_cb,
task);
g_object_unref (resolver);
return;
}
return_result (task);
}
else
next_proxy (task);
}
static void
proxy_lookup_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = user_data;
GProxyAddressEnumeratorPrivate *priv = g_task_get_task_data (task);
g_clear_error (&priv->last_error);
priv->proxies = g_proxy_resolver_lookup_finish (G_PROXY_RESOLVER (object),
result,
&priv->last_error);
priv->next_proxy = priv->proxies;
if (priv->last_error)
{
complete_async (task);
return;
}
else
{
next_enumerator (priv);
if (priv->addr_enum)
{
g_socket_address_enumerator_next_async (priv->addr_enum,
g_task_get_cancellable (task),
address_enumerate_cb,
task);
return;
}
}
complete_async (task);
}
static void
g_proxy_address_enumerator_next_async (GSocketAddressEnumerator *enumerator,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GProxyAddressEnumeratorPrivate *priv = GET_PRIVATE (enumerator);
GTask *task;
task = g_task_new (enumerator, cancellable, callback, user_data);
g_task_set_source_tag (task, g_proxy_address_enumerator_next_async);
g_task_set_task_data (task, priv, NULL);
if (priv->proxies == NULL)
{
g_proxy_resolver_lookup_async (priv->proxy_resolver,
priv->dest_uri,
cancellable,
proxy_lookup_cb,
task);
return;
}
if (priv->addr_enum)
{
if (priv->proxy_address)
{
return_result (task);
return;
}
else
{
g_socket_address_enumerator_next_async (priv->addr_enum,
cancellable,
address_enumerate_cb,
task);
return;
}
}
complete_async (task);
}
static GSocketAddress *
g_proxy_address_enumerator_next_finish (GSocketAddressEnumerator *enumerator,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, enumerator), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
g_proxy_address_enumerator_constructed (GObject *object)
{
GProxyAddressEnumeratorPrivate *priv = GET_PRIVATE (object);
GSocketConnectable *conn;
guint port;
if (priv->dest_uri)
{
conn = g_network_address_parse_uri (priv->dest_uri, priv->default_port, NULL);
if (conn)
{
g_object_get (conn,
"hostname", &priv->dest_hostname,
"port", &port,
NULL);
priv->dest_port = port;
g_object_unref (conn);
}
else
g_warning ("Invalid URI '%s'", priv->dest_uri);
}
G_OBJECT_CLASS (g_proxy_address_enumerator_parent_class)->constructed (object);
}
static void
g_proxy_address_enumerator_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GProxyAddressEnumeratorPrivate *priv = GET_PRIVATE (object);
switch (property_id)
{
case PROP_URI:
g_value_set_string (value, priv->dest_uri);
break;
case PROP_DEFAULT_PORT:
g_value_set_uint (value, priv->default_port);
break;
case PROP_CONNECTABLE:
g_value_set_object (value, priv->connectable);
break;
case PROP_PROXY_RESOLVER:
g_value_set_object (value, priv->proxy_resolver);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
g_proxy_address_enumerator_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GProxyAddressEnumeratorPrivate *priv = GET_PRIVATE (object);
switch (property_id)
{
case PROP_URI:
priv->dest_uri = g_value_dup_string (value);
break;
case PROP_DEFAULT_PORT:
priv->default_port = g_value_get_uint (value);
break;
case PROP_CONNECTABLE:
priv->connectable = g_value_dup_object (value);
break;
case PROP_PROXY_RESOLVER:
if (priv->proxy_resolver)
g_object_unref (priv->proxy_resolver);
priv->proxy_resolver = g_value_get_object (value);
if (!priv->proxy_resolver)
priv->proxy_resolver = g_proxy_resolver_get_default ();
g_object_ref (priv->proxy_resolver);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
g_proxy_address_enumerator_finalize (GObject *object)
{
GProxyAddressEnumeratorPrivate *priv = GET_PRIVATE (object);
if (priv->connectable)
g_object_unref (priv->connectable);
if (priv->proxy_resolver)
g_object_unref (priv->proxy_resolver);
g_free (priv->dest_uri);
g_free (priv->dest_hostname);
if (priv->dest_ips)
g_resolver_free_addresses (priv->dest_ips);
g_strfreev (priv->proxies);
if (priv->addr_enum)
g_object_unref (priv->addr_enum);
g_free (priv->proxy_type);
g_free (priv->proxy_username);
g_free (priv->proxy_password);
g_clear_error (&priv->last_error);
G_OBJECT_CLASS (g_proxy_address_enumerator_parent_class)->finalize (object);
}
static void
g_proxy_address_enumerator_init (GProxyAddressEnumerator *self)
{
self->priv = g_proxy_address_enumerator_get_instance_private (self);
}
static void
g_proxy_address_enumerator_class_init (GProxyAddressEnumeratorClass *proxy_enumerator_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (proxy_enumerator_class);
GSocketAddressEnumeratorClass *enumerator_class = G_SOCKET_ADDRESS_ENUMERATOR_CLASS (proxy_enumerator_class);
object_class->constructed = g_proxy_address_enumerator_constructed;
object_class->set_property = g_proxy_address_enumerator_set_property;
object_class->get_property = g_proxy_address_enumerator_get_property;
object_class->finalize = g_proxy_address_enumerator_finalize;
enumerator_class->next = g_proxy_address_enumerator_next;
enumerator_class->next_async = g_proxy_address_enumerator_next_async;
enumerator_class->next_finish = g_proxy_address_enumerator_next_finish;
/**
* GProxyAddressEnumerator:uri:
*
* The destination URI. Use `none://` for a generic socket.
*/
g_object_class_install_property (object_class,
PROP_URI,
g_param_spec_string ("uri", NULL, NULL,
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* GProxyAddressEnumerator:default-port:
*
* The default port to use if #GProxyAddressEnumerator:uri does not
* specify one.
*
* Since: 2.38
*/
g_object_class_install_property (object_class,
PROP_DEFAULT_PORT,
g_param_spec_uint ("default-port", NULL, NULL,
0, 65535, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* GProxyAddressEnumerator:connectable:
*
* The connectable being enumerated.
*/
g_object_class_install_property (object_class,
PROP_CONNECTABLE,
g_param_spec_object ("connectable", NULL, NULL,
G_TYPE_SOCKET_CONNECTABLE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* GProxyAddressEnumerator:proxy-resolver:
*
* The proxy resolver to use.
*
* Since: 2.36
*/
g_object_class_install_property (object_class,
PROP_PROXY_RESOLVER,
g_param_spec_object ("proxy-resolver", NULL, NULL,
G_TYPE_PROXY_RESOLVER,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
}