| /* GIO - GLib Input, Output and Streaming Library | |
| * | |
| * Copyright 2010, 2013 Red Hat, Inc. | |
| * | |
| * 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/>. | |
| */ | |
| #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" | |
| /** | |
| * GSimpleProxyResolver: | |
| * | |
| * `GSimpleProxyResolver` is a simple [iface@Gio.ProxyResolver] 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 [method@Gio.SocketClient.set_proxy_resolver]. | |
| * | |
| * Since: 2.36 | |
| */ | |
| typedef struct { | |
| gchar *name; | |
| gsize 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; | |
| gsize offset, length; | |
| guint i; | |
| 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]; | |
| if (domain->length > length) | |
| continue; | |
| 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", NULL, NULL, | |
| 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", NULL, NULL, | |
| 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: (array zero-terminated=1) (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: (nullable): 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_return_if_fail (default_proxy == NULL || g_uri_is_valid (default_proxy, G_URI_FLAGS_NONE, NULL)); | |
| 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: (array zero-terminated=1): %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)); | |
| } |