glib/gio/gproxyaddressenumerator.c
Michael Catanzaro cc5bb80b9a Fix failure to respect default port of proxy URLs
Currently we require explicitly specifying the port when configuring a
proxy server, which is seriously weird. I take the fact that nobody
reported a bug until 2022 to indicate that almost nobody is using
proxies. Whatever. Let's assume that if no port is provided, the default
port for the protocol should be used instead.

For example, you can now specify in GNOME settings that your proxy server
is https://example.com and it will work. Previously, you had to write
https://example.com:443. Yuck!

This was originally reported as GProxyResolver bug, but nothing is
actually wrong there. It's actually GProxyAddressEnumerator that gets
tripped up by URLs returned by GProxyResolver without a default port.
This breaks GSocketClient.

Fixing this requires exposing GUri's _default_scheme_port() function to
GIO. I considered copy/pasting it since it's not very much code, but I
figure the private call mechanism is probably not too expensive, and I
don't like code duplication.

Fixes #2832
2023-05-30 09:49:43 -05:00

809 lines
22 KiB
C

/* 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"
/**
* SECTION:gproxyaddressenumerator
* @short_description: Proxy wrapper enumerator for socket addresses
* @include: gio/gio.h
*
* #GProxyAddressEnumerator is a wrapper around #GSocketAddressEnumerator which
* takes the #GSocketAddress instances returned by the #GSocketAddressEnumerator
* and wraps them in #GProxyAddress instances, using the given
* #GProxyAddressEnumerator:proxy-resolver.
*
* This enumerator will be returned (for example, by
* g_socket_connectable_enumerate()) as appropriate when a proxy is configured;
* there should be no need to manually wrap a #GSocketAddressEnumerator 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 (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;
g_object_class_install_property (object_class,
PROP_URI,
g_param_spec_string ("uri",
P_("URI"),
P_("The destination URI, use none:// for generic socket"),
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",
P_("Default port"),
P_("The default port to use if uri does not specify one"),
0, 65535, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_CONNECTABLE,
g_param_spec_object ("connectable",
P_("Connectable"),
P_("The connectable being enumerated."),
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",
P_("Proxy resolver"),
P_("The proxy resolver to use."),
G_TYPE_PROXY_RESOLVER,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
}