/* 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 . * * Author: Nicolas Dufresne */ #include "config.h" #include "gproxyaddressenumerator.h" #include #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); } g_assert (G_IS_INET_SOCKET_ADDRESS (priv->proxy_address)); dest_protocol = g_uri_parse_scheme (priv->dest_uri); 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; 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); g_assert (G_IS_INET_SOCKET_ADDRESS (priv->proxy_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)); }