glib/gio/gsimpleproxyresolver.c
Philip Withnall b654eb1846 guri: Make G_URI_FLAGS_PARSE_STRICT the default
Make `G_URI_FLAGS_PARSE_RELAXED` available instead, for the
implementations which need to handle user-provided or incorrect URIs.
The default should nudge people towards being compliant with RFC 3986.

This required also adding a new `G_URI_PARAMS_PARSE_RELAXED` flag, as
previously parsing param strings *always* used relaxed mode and there
was no way to control it. Now it defaults to using strict mode, and the
new flag allows for relaxed mode to be enabled if needed.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Fixes: #2149
2020-08-07 14:02:18 +01:00

593 lines
18 KiB
C

/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2010, 2013 Red Hat, Inc.
*
* 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/>.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include "gsimpleproxyresolver.h"
#include "ginetaddress.h"
#include "ginetaddressmask.h"
#include "gnetworkingprivate.h"
#include "gtask.h"
#include "glibintl.h"
/**
* SECTION:gsimpleproxyresolver
* @short_description: Simple proxy resolver implementation
* @include: gio/gio.h
* @see_also: g_socket_client_set_proxy_resolver()
*
* #GSimpleProxyResolver is a simple #GProxyResolver implementation
* that handles a single default proxy, multiple URI-scheme-specific
* proxies, and a list of hosts that proxies should not be used for.
*
* #GSimpleProxyResolver is never the default proxy resolver, but it
* can be used as the base class for another proxy resolver
* implementation, or it can be created and used manually, such as
* with g_socket_client_set_proxy_resolver().
*
* Since: 2.36
*/
typedef struct {
gchar *name;
gint length;
gushort port;
} GSimpleProxyResolverDomain;
struct _GSimpleProxyResolverPrivate {
gchar *default_proxy, **ignore_hosts;
GHashTable *uri_proxies;
GPtrArray *ignore_ips;
GSimpleProxyResolverDomain *ignore_domains;
};
static void g_simple_proxy_resolver_iface_init (GProxyResolverInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GSimpleProxyResolver, g_simple_proxy_resolver, G_TYPE_OBJECT,
G_ADD_PRIVATE (GSimpleProxyResolver)
G_IMPLEMENT_INTERFACE (G_TYPE_PROXY_RESOLVER,
g_simple_proxy_resolver_iface_init))
enum
{
PROP_0,
PROP_DEFAULT_PROXY,
PROP_IGNORE_HOSTS
};
static void reparse_ignore_hosts (GSimpleProxyResolver *resolver);
static void
g_simple_proxy_resolver_finalize (GObject *object)
{
GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (object);
GSimpleProxyResolverPrivate *priv = resolver->priv;
g_free (priv->default_proxy);
g_hash_table_destroy (priv->uri_proxies);
g_clear_pointer (&priv->ignore_hosts, g_strfreev);
/* This will free ignore_ips and ignore_domains */
reparse_ignore_hosts (resolver);
G_OBJECT_CLASS (g_simple_proxy_resolver_parent_class)->finalize (object);
}
static void
g_simple_proxy_resolver_init (GSimpleProxyResolver *resolver)
{
resolver->priv = g_simple_proxy_resolver_get_instance_private (resolver);
resolver->priv->uri_proxies = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
}
static void
g_simple_proxy_resolver_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (object);
switch (prop_id)
{
case PROP_DEFAULT_PROXY:
g_simple_proxy_resolver_set_default_proxy (resolver, g_value_get_string (value));
break;
case PROP_IGNORE_HOSTS:
g_simple_proxy_resolver_set_ignore_hosts (resolver, g_value_get_boxed (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_simple_proxy_resolver_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (object);
switch (prop_id)
{
case PROP_DEFAULT_PROXY:
g_value_set_string (value, resolver->priv->default_proxy);
break;
case PROP_IGNORE_HOSTS:
g_value_set_boxed (value, resolver->priv->ignore_hosts);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
reparse_ignore_hosts (GSimpleProxyResolver *resolver)
{
GSimpleProxyResolverPrivate *priv = resolver->priv;
GPtrArray *ignore_ips;
GArray *ignore_domains;
gchar *host, *tmp, *colon, *bracket;
GInetAddress *iaddr;
GInetAddressMask *mask;
GSimpleProxyResolverDomain domain;
gushort port;
int i;
if (priv->ignore_ips)
g_ptr_array_free (priv->ignore_ips, TRUE);
if (priv->ignore_domains)
{
for (i = 0; priv->ignore_domains[i].name; i++)
g_free (priv->ignore_domains[i].name);
g_free (priv->ignore_domains);
}
priv->ignore_ips = NULL;
priv->ignore_domains = NULL;
if (!priv->ignore_hosts || !priv->ignore_hosts[0])
return;
ignore_ips = g_ptr_array_new_with_free_func (g_object_unref);
ignore_domains = g_array_new (TRUE, FALSE, sizeof (GSimpleProxyResolverDomain));
for (i = 0; priv->ignore_hosts[i]; i++)
{
host = g_strchomp (priv->ignore_hosts[i]);
/* See if it's an IP address or IP/length mask */
mask = g_inet_address_mask_new_from_string (host, NULL);
if (mask)
{
g_ptr_array_add (ignore_ips, mask);
continue;
}
port = 0;
if (*host == '[')
{
/* [IPv6]:port */
host++;
bracket = strchr (host, ']');
if (!bracket || !bracket[1] || bracket[1] != ':')
goto bad;
port = strtoul (bracket + 2, &tmp, 10);
if (*tmp)
goto bad;
*bracket = '\0';
}
else
{
colon = strchr (host, ':');
if (colon && !strchr (colon + 1, ':'))
{
/* hostname:port or IPv4:port */
port = strtoul (colon + 1, &tmp, 10);
if (*tmp)
goto bad;
*colon = '\0';
}
}
iaddr = g_inet_address_new_from_string (host);
if (iaddr)
g_object_unref (iaddr);
else
{
if (g_str_has_prefix (host, "*."))
host += 2;
else if (*host == '.')
host++;
}
memset (&domain, 0, sizeof (domain));
domain.name = g_strdup (host);
domain.length = strlen (domain.name);
domain.port = port;
g_array_append_val (ignore_domains, domain);
continue;
bad:
g_warning ("Ignoring invalid ignore_hosts value '%s'", host);
}
if (ignore_ips->len)
priv->ignore_ips = ignore_ips;
else
g_ptr_array_free (ignore_ips, TRUE);
if (ignore_domains->len)
priv->ignore_domains = (GSimpleProxyResolverDomain *)ignore_domains->data;
g_array_free (ignore_domains, ignore_domains->len == 0);
}
static gboolean
ignore_host (GSimpleProxyResolver *resolver,
const gchar *host,
gushort port)
{
GSimpleProxyResolverPrivate *priv = resolver->priv;
gchar *ascii_host = NULL;
gboolean ignore = FALSE;
gint i, length, offset;
if (priv->ignore_ips)
{
GInetAddress *iaddr;
iaddr = g_inet_address_new_from_string (host);
if (iaddr)
{
for (i = 0; i < priv->ignore_ips->len; i++)
{
GInetAddressMask *mask = priv->ignore_ips->pdata[i];
if (g_inet_address_mask_matches (mask, iaddr))
{
ignore = TRUE;
break;
}
}
g_object_unref (iaddr);
if (ignore)
return TRUE;
}
}
if (priv->ignore_domains)
{
length = 0;
if (g_hostname_is_non_ascii (host))
host = ascii_host = g_hostname_to_ascii (host);
if (host)
length = strlen (host);
for (i = 0; length > 0 && priv->ignore_domains[i].length; i++)
{
GSimpleProxyResolverDomain *domain = &priv->ignore_domains[i];
offset = length - domain->length;
if ((domain->port == 0 || domain->port == port) &&
(offset == 0 || (offset > 0 && host[offset - 1] == '.')) &&
(g_ascii_strcasecmp (domain->name, host + offset) == 0))
{
ignore = TRUE;
break;
}
}
g_free (ascii_host);
}
return ignore;
}
static gchar **
g_simple_proxy_resolver_lookup (GProxyResolver *proxy_resolver,
const gchar *uri,
GCancellable *cancellable,
GError **error)
{
GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (proxy_resolver);
GSimpleProxyResolverPrivate *priv = resolver->priv;
const gchar *proxy = NULL;
gchar **proxies;
if (priv->ignore_ips || priv->ignore_domains)
{
gchar *host = NULL;
gint port;
if (g_uri_split_network (uri, G_URI_FLAGS_NONE, NULL,
&host, &port, NULL) &&
ignore_host (resolver, host, port > 0 ? port : 0))
proxy = "direct://";
g_free (host);
}
if (!proxy && g_hash_table_size (priv->uri_proxies))
{
gchar *scheme = g_ascii_strdown (uri, strcspn (uri, ":"));
proxy = g_hash_table_lookup (priv->uri_proxies, scheme);
g_free (scheme);
}
if (!proxy)
proxy = priv->default_proxy;
if (!proxy)
proxy = "direct://";
if (!strncmp (proxy, "socks://", 8))
{
proxies = g_new0 (gchar *, 4);
proxies[0] = g_strdup_printf ("socks5://%s", proxy + 8);
proxies[1] = g_strdup_printf ("socks4a://%s", proxy + 8);
proxies[2] = g_strdup_printf ("socks4://%s", proxy + 8);
proxies[3] = NULL;
}
else
{
proxies = g_new0 (gchar *, 2);
proxies[0] = g_strdup (proxy);
}
return proxies;
}
static void
g_simple_proxy_resolver_lookup_async (GProxyResolver *proxy_resolver,
const gchar *uri,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (proxy_resolver);
GTask *task;
GError *error = NULL;
char **proxies;
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, g_simple_proxy_resolver_lookup_async);
proxies = g_simple_proxy_resolver_lookup (proxy_resolver, uri,
cancellable, &error);
if (proxies)
g_task_return_pointer (task, proxies, (GDestroyNotify)g_strfreev);
else
g_task_return_error (task, error);
g_object_unref (task);
}
static gchar **
g_simple_proxy_resolver_lookup_finish (GProxyResolver *resolver,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
g_simple_proxy_resolver_class_init (GSimpleProxyResolverClass *resolver_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (resolver_class);
object_class->get_property = g_simple_proxy_resolver_get_property;
object_class->set_property = g_simple_proxy_resolver_set_property;
object_class->finalize = g_simple_proxy_resolver_finalize;
/**
* GSimpleProxyResolver:default-proxy:
*
* The default proxy URI that will be used for any URI that doesn't
* match #GSimpleProxyResolver:ignore-hosts, and doesn't match any
* of the schemes set with g_simple_proxy_resolver_set_uri_proxy().
*
* Note that as a special case, if this URI starts with
* "socks://", #GSimpleProxyResolver will treat it as referring
* to all three of the socks5, socks4a, and socks4 proxy types.
*/
g_object_class_install_property (object_class, PROP_DEFAULT_PROXY,
g_param_spec_string ("default-proxy",
P_("Default proxy"),
P_("The default proxy URI"),
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* GSimpleProxyResolver:ignore-hosts:
*
* A list of hostnames and IP addresses that the resolver should
* allow direct connections to.
*
* Entries can be in one of 4 formats:
*
* - A hostname, such as "example.com", ".example.com", or
* "*.example.com", any of which match "example.com" or
* any subdomain of it.
*
* - An IPv4 or IPv6 address, such as "192.168.1.1",
* which matches only that address.
*
* - A hostname or IP address followed by a port, such as
* "example.com:80", which matches whatever the hostname or IP
* address would match, but only for URLs with the (explicitly)
* indicated port. In the case of an IPv6 address, the address
* part must appear in brackets: "[::1]:443"
*
* - An IP address range, given by a base address and prefix length,
* such as "fe80::/10", which matches any address in that range.
*
* Note that when dealing with Unicode hostnames, the matching is
* done against the ASCII form of the name.
*
* Also note that hostname exclusions apply only to connections made
* to hosts identified by name, and IP address exclusions apply only
* to connections made to hosts identified by address. That is, if
* example.com has an address of 192.168.1.1, and the :ignore-hosts list
* contains only "192.168.1.1", then a connection to "example.com"
* (eg, via a #GNetworkAddress) will use the proxy, and a connection to
* "192.168.1.1" (eg, via a #GInetSocketAddress) will not.
*
* These rules match the "ignore-hosts"/"noproxy" rules most
* commonly used by other applications.
*/
g_object_class_install_property (object_class, PROP_IGNORE_HOSTS,
g_param_spec_boxed ("ignore-hosts",
P_("Ignore hosts"),
P_("Hosts that will not use the proxy"),
G_TYPE_STRV,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
}
static void
g_simple_proxy_resolver_iface_init (GProxyResolverInterface *iface)
{
iface->lookup = g_simple_proxy_resolver_lookup;
iface->lookup_async = g_simple_proxy_resolver_lookup_async;
iface->lookup_finish = g_simple_proxy_resolver_lookup_finish;
}
/**
* g_simple_proxy_resolver_new:
* @default_proxy: (nullable): the default proxy to use, eg
* "socks://192.168.1.1"
* @ignore_hosts: (nullable): an optional list of hosts/IP addresses
* to not use a proxy for.
*
* Creates a new #GSimpleProxyResolver. See
* #GSimpleProxyResolver:default-proxy and
* #GSimpleProxyResolver:ignore-hosts for more details on how the
* arguments are interpreted.
*
* Returns: (transfer full) a new #GSimpleProxyResolver
*
* Since: 2.36
*/
GProxyResolver *
g_simple_proxy_resolver_new (const gchar *default_proxy,
gchar **ignore_hosts)
{
return g_object_new (G_TYPE_SIMPLE_PROXY_RESOLVER,
"default-proxy", default_proxy,
"ignore-hosts", ignore_hosts,
NULL);
}
/**
* g_simple_proxy_resolver_set_default_proxy:
* @resolver: a #GSimpleProxyResolver
* @default_proxy: the default proxy to use
*
* Sets the default proxy on @resolver, to be used for any URIs that
* don't match #GSimpleProxyResolver:ignore-hosts or a proxy set
* via g_simple_proxy_resolver_set_uri_proxy().
*
* If @default_proxy starts with "socks://",
* #GSimpleProxyResolver will treat it as referring to all three of
* the socks5, socks4a, and socks4 proxy types.
*
* Since: 2.36
*/
void
g_simple_proxy_resolver_set_default_proxy (GSimpleProxyResolver *resolver,
const gchar *default_proxy)
{
g_return_if_fail (G_IS_SIMPLE_PROXY_RESOLVER (resolver));
g_free (resolver->priv->default_proxy);
resolver->priv->default_proxy = g_strdup (default_proxy);
g_object_notify (G_OBJECT (resolver), "default-proxy");
}
/**
* g_simple_proxy_resolver_set_ignore_hosts:
* @resolver: a #GSimpleProxyResolver
* @ignore_hosts: %NULL-terminated list of hosts/IP addresses
* to not use a proxy for
*
* Sets the list of ignored hosts.
*
* See #GSimpleProxyResolver:ignore-hosts for more details on how the
* @ignore_hosts argument is interpreted.
*
* Since: 2.36
*/
void
g_simple_proxy_resolver_set_ignore_hosts (GSimpleProxyResolver *resolver,
gchar **ignore_hosts)
{
g_return_if_fail (G_IS_SIMPLE_PROXY_RESOLVER (resolver));
g_strfreev (resolver->priv->ignore_hosts);
resolver->priv->ignore_hosts = g_strdupv (ignore_hosts);
reparse_ignore_hosts (resolver);
g_object_notify (G_OBJECT (resolver), "ignore-hosts");
}
/**
* g_simple_proxy_resolver_set_uri_proxy:
* @resolver: a #GSimpleProxyResolver
* @uri_scheme: the URI scheme to add a proxy for
* @proxy: the proxy to use for @uri_scheme
*
* Adds a URI-scheme-specific proxy to @resolver; URIs whose scheme
* matches @uri_scheme (and which don't match
* #GSimpleProxyResolver:ignore-hosts) will be proxied via @proxy.
*
* As with #GSimpleProxyResolver:default-proxy, if @proxy starts with
* "socks://", #GSimpleProxyResolver will treat it
* as referring to all three of the socks5, socks4a, and socks4 proxy
* types.
*
* Since: 2.36
*/
void
g_simple_proxy_resolver_set_uri_proxy (GSimpleProxyResolver *resolver,
const gchar *uri_scheme,
const gchar *proxy)
{
g_return_if_fail (G_IS_SIMPLE_PROXY_RESOLVER (resolver));
g_hash_table_replace (resolver->priv->uri_proxies,
g_ascii_strdown (uri_scheme, -1),
g_strdup (proxy));
}