/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2010 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 "gproxyresolvergnome.h"
#include <glib/gi18n-lib.h>
#include <gdesktop-enums.h>
#define GNOME_PROXY_SETTINGS_SCHEMA "org.gnome.system.proxy"
#define GNOME_PROXY_MODE_KEY "mode"
#define GNOME_PROXY_AUTOCONFIG_URL_KEY "autoconfig-url"
#define GNOME_PROXY_IGNORE_HOSTS_KEY "ignore-hosts"
#define GNOME_PROXY_USE_SAME_PROXY_KEY "use-same-proxy"
#define GNOME_PROXY_HTTP_CHILD_SCHEMA "http"
#define GNOME_PROXY_HTTP_HOST_KEY "host"
#define GNOME_PROXY_HTTP_PORT_KEY "port"
#define GNOME_PROXY_HTTP_USE_AUTH_KEY "use-authentication"
#define GNOME_PROXY_HTTP_USER_KEY "authentication-user"
#define GNOME_PROXY_HTTP_PASSWORD_KEY "authentication-password"
#define GNOME_PROXY_HTTPS_CHILD_SCHEMA "https"
#define GNOME_PROXY_HTTPS_HOST_KEY "host"
#define GNOME_PROXY_HTTPS_PORT_KEY "port"
#define GNOME_PROXY_FTP_CHILD_SCHEMA "ftp"
#define GNOME_PROXY_FTP_HOST_KEY "host"
#define GNOME_PROXY_FTP_PORT_KEY "port"
#define GNOME_PROXY_SOCKS_CHILD_SCHEMA "socks"
#define GNOME_PROXY_SOCKS_HOST_KEY "host"
#define GNOME_PROXY_SOCKS_PORT_KEY "port"
/* We have to has-a GSimpleProxyResolver rather than is-a one,
* because a dynamic type cannot reimplement an interface that
* its parent also implements... for some reason.
*/
struct _GProxyResolverGnome {
GObject parent_instance;
GProxyResolver *base_resolver;
GSettings *proxy_settings;
GSettings *http_settings;
GSettings *https_settings;
GSettings *ftp_settings;
GSettings *socks_settings;
gboolean need_update;
GDesktopProxyMode mode;
gchar *autoconfig_url;
gboolean use_same_proxy;
GDBusProxy *pacrunner;
GMutex lock;
};
static GProxyResolverInterface *g_proxy_resolver_gnome_parent_iface;
static void g_proxy_resolver_gnome_iface_init (GProxyResolverInterface *iface);
G_DEFINE_DYNAMIC_TYPE_EXTENDED (GProxyResolverGnome,
g_proxy_resolver_gnome,
G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE_DYNAMIC (G_TYPE_PROXY_RESOLVER,
g_proxy_resolver_gnome_iface_init))
static void
g_proxy_resolver_gnome_class_finalize (GProxyResolverGnomeClass *klass)
{
}
static void
gsettings_changed (GSettings *settings,
const gchar *key,
gpointer user_data)
{
GProxyResolverGnome *resolver = user_data;
g_mutex_lock (&resolver->lock);
resolver->need_update = TRUE;
g_mutex_unlock (&resolver->lock);
}
static void
g_proxy_resolver_gnome_finalize (GObject *object)
{
GProxyResolverGnome *resolver = G_PROXY_RESOLVER_GNOME (object);
if (resolver->proxy_settings)
{
g_signal_handlers_disconnect_by_func (resolver->proxy_settings,
(gpointer)gsettings_changed,
resolver);
g_object_unref (resolver->proxy_settings);
g_signal_handlers_disconnect_by_func (resolver->http_settings,
(gpointer)gsettings_changed,
resolver);
g_object_unref (resolver->http_settings);
g_signal_handlers_disconnect_by_func (resolver->https_settings,
(gpointer)gsettings_changed,
resolver);
g_object_unref (resolver->https_settings);
g_signal_handlers_disconnect_by_func (resolver->ftp_settings,
(gpointer)gsettings_changed,
resolver);
g_object_unref (resolver->ftp_settings);
g_signal_handlers_disconnect_by_func (resolver->socks_settings,
(gpointer)gsettings_changed,
resolver);
g_object_unref (resolver->socks_settings);
}
g_clear_object (&resolver->base_resolver);
g_clear_object (&resolver->pacrunner);
g_free (resolver->autoconfig_url);
g_mutex_clear (&resolver->lock);
G_OBJECT_CLASS (g_proxy_resolver_gnome_parent_class)->finalize (object);
}
static void
g_proxy_resolver_gnome_init (GProxyResolverGnome *resolver)
{
g_mutex_init (&resolver->lock);
resolver->base_resolver = g_simple_proxy_resolver_new (NULL, NULL);
resolver->proxy_settings = g_settings_new (GNOME_PROXY_SETTINGS_SCHEMA);
g_signal_connect (resolver->proxy_settings, "changed",
G_CALLBACK (gsettings_changed), resolver);
resolver->http_settings = g_settings_get_child (resolver->proxy_settings,
GNOME_PROXY_HTTP_CHILD_SCHEMA);
g_signal_connect (resolver->http_settings, "changed",
G_CALLBACK (gsettings_changed), resolver);
resolver->https_settings = g_settings_get_child (resolver->proxy_settings,
GNOME_PROXY_HTTPS_CHILD_SCHEMA);
g_signal_connect (resolver->https_settings, "changed",
G_CALLBACK (gsettings_changed), resolver);
resolver->ftp_settings = g_settings_get_child (resolver->proxy_settings,
GNOME_PROXY_FTP_CHILD_SCHEMA);
g_signal_connect (resolver->ftp_settings, "changed",
G_CALLBACK (gsettings_changed), resolver);
resolver->socks_settings = g_settings_get_child (resolver->proxy_settings,
GNOME_PROXY_SOCKS_CHILD_SCHEMA);
g_signal_connect (resolver->socks_settings, "changed",
G_CALLBACK (gsettings_changed), resolver);
resolver->need_update = TRUE;
}
/* called with lock held */
static void
update_settings (GProxyResolverGnome *resolver)
{
GSimpleProxyResolver *simple = G_SIMPLE_PROXY_RESOLVER (resolver->base_resolver);
gchar **ignore_hosts;
gchar *host, *http_proxy, *proxy;
guint port;
resolver->need_update = FALSE;
g_free (resolver->autoconfig_url);
g_simple_proxy_resolver_set_default_proxy (simple, NULL);
g_simple_proxy_resolver_set_ignore_hosts (simple, NULL);
g_simple_proxy_resolver_set_uri_proxy (simple, "http", NULL);
g_simple_proxy_resolver_set_uri_proxy (simple, "https", NULL);
g_simple_proxy_resolver_set_uri_proxy (simple, "ftp", NULL);
resolver->mode =
g_settings_get_enum (resolver->proxy_settings, GNOME_PROXY_MODE_KEY);
resolver->autoconfig_url =
g_settings_get_string (resolver->proxy_settings, GNOME_PROXY_AUTOCONFIG_URL_KEY);
if (resolver->mode == G_DESKTOP_PROXY_MODE_AUTO && !resolver->pacrunner)
{
GError *error = NULL;
resolver->pacrunner =
g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
"org.gtk.GLib.PACRunner",
"/org/gtk/GLib/PACRunner",
"org.gtk.GLib.PACRunner",
NULL, &error);
if (error)
{
g_warning ("Could not start proxy autoconfiguration helper:"
"\n %s\nProxy autoconfiguration will not work",
error->message);
}
}
else if (resolver->mode != G_DESKTOP_PROXY_MODE_AUTO && resolver->pacrunner)
{
g_object_unref (resolver->pacrunner);
resolver->pacrunner = NULL;
}
ignore_hosts =
g_settings_get_strv (resolver->proxy_settings, GNOME_PROXY_IGNORE_HOSTS_KEY);
g_simple_proxy_resolver_set_ignore_hosts (simple, ignore_hosts);
g_strfreev (ignore_hosts);
if (resolver->mode == G_DESKTOP_PROXY_MODE_AUTO)
{
/* We use the base_resolver to handle ignore_hosts in the AUTO case,
* so we have to set a non-"direct://" default proxy so we can distinguish
* the two cases.
*/
g_simple_proxy_resolver_set_default_proxy (simple, "use-proxy:");
}
if (resolver->mode != G_DESKTOP_PROXY_MODE_MANUAL)
return;
host = g_settings_get_string (resolver->http_settings, GNOME_PROXY_HTTP_HOST_KEY);
port = g_settings_get_int (resolver->http_settings, GNOME_PROXY_HTTP_PORT_KEY);
if (host && *host)
{
if (g_settings_get_boolean (resolver->http_settings, GNOME_PROXY_HTTP_USE_AUTH_KEY))
{
gchar *user, *password;
gchar *enc_user, *enc_password;
user = g_settings_get_string (resolver->http_settings, GNOME_PROXY_HTTP_USER_KEY);
enc_user = g_uri_escape_string (user, NULL, TRUE);
g_free (user);
password = g_settings_get_string (resolver->http_settings, GNOME_PROXY_HTTP_PASSWORD_KEY);
enc_password = g_uri_escape_string (password, NULL, TRUE);
g_free (password);
http_proxy = g_strdup_printf ("http://%s:%s@%s:%u",
enc_user, enc_password,
host, port);
g_free (enc_user);
g_free (enc_password);
}
else
http_proxy = g_strdup_printf ("http://%s:%u", host, port);
g_simple_proxy_resolver_set_uri_proxy (simple, "http", http_proxy);
if (g_settings_get_boolean (resolver->proxy_settings, GNOME_PROXY_USE_SAME_PROXY_KEY))
g_simple_proxy_resolver_set_default_proxy (simple, http_proxy);
}
else
http_proxy = NULL;
g_free (host);
host = g_settings_get_string (resolver->https_settings, GNOME_PROXY_HTTPS_HOST_KEY);
port = g_settings_get_int (resolver->https_settings, GNOME_PROXY_HTTPS_PORT_KEY);
if (host && *host)
{
proxy = g_strdup_printf ("http://%s:%u", host, port);
g_simple_proxy_resolver_set_uri_proxy (simple, "https", proxy);
g_free (proxy);
}
else if (http_proxy)
g_simple_proxy_resolver_set_uri_proxy (simple, "https", http_proxy);
g_free (host);
host = g_settings_get_string (resolver->socks_settings, GNOME_PROXY_SOCKS_HOST_KEY);
port = g_settings_get_int (resolver->socks_settings, GNOME_PROXY_SOCKS_PORT_KEY);
if (host && *host)
{
proxy = g_strdup_printf ("socks://%s:%u", host, port);
g_simple_proxy_resolver_set_default_proxy (simple, proxy);
g_free (proxy);
}
g_free (host);
g_free (http_proxy);
host = g_settings_get_string (resolver->ftp_settings, GNOME_PROXY_FTP_HOST_KEY);
port = g_settings_get_int (resolver->ftp_settings, GNOME_PROXY_FTP_PORT_KEY);
if (host && *host)
{
proxy = g_strdup_printf ("ftp://%s:%u", host, port);
g_simple_proxy_resolver_set_uri_proxy (simple, "ftp", proxy);
g_free (proxy);
}
g_free (host);
}
static gboolean
g_proxy_resolver_gnome_is_supported (GProxyResolver *object)
{
const char *desktop;
desktop = g_getenv ("XDG_CURRENT_DESKTOP");
if (desktop == NULL)
return FALSE;
/* Remember that XDG_CURRENT_DESKTOP is a list of strings. Desktops that
* pretend to be GNOME and want to use our proxy settings will list
* themselves alongside GNOME. That's fine; they'll get our proxy settings.
*/
return strstr (desktop, "GNOME") != NULL;
}
static inline gchar **
make_proxies (const gchar *proxy)
{
gchar **proxies;
proxies = g_new (gchar *, 2);
proxies[0] = g_strdup (proxy);
proxies[1] = NULL;
return proxies;
}
/* Threadsafely determines what to do with @uri; returns %FALSE if an
* error occurs, %TRUE and an array of proxies if the mode is NONE or
* MANUAL, or if @uri is covered by ignore-hosts, or %TRUE and a
* (transfer-full) pacrunner and autoconfig url if the mode is AUTOMATIC.
*/
static gboolean
g_proxy_resolver_gnome_lookup_internal (GProxyResolverGnome *resolver,
const gchar *uri,
gchar ***out_proxies,
GDBusProxy **out_pacrunner,
gchar **out_autoconfig_url,
GCancellable *cancellable,
GError **error)
{
gchar **proxies = NULL;
*out_proxies = NULL;
*out_pacrunner = NULL;
*out_autoconfig_url = NULL;
g_mutex_lock (&resolver->lock);
if (resolver->need_update)
update_settings (resolver);
proxies = g_proxy_resolver_lookup (resolver->base_resolver,
uri, cancellable, error);
if (!proxies)
goto done;
/* Parent class does ignore-host handling */
if (!strcmp (proxies[0], "direct://") && !proxies[1])
goto done;
if (resolver->pacrunner)
{
g_clear_pointer (&proxies, g_strfreev);
*out_pacrunner = g_object_ref (resolver->pacrunner);
*out_autoconfig_url = g_strdup (resolver->autoconfig_url);
goto done;
}
done:
g_mutex_unlock (&resolver->lock);
if (proxies)
{
*out_proxies = proxies;
return TRUE;
}
else if (*out_pacrunner)
return TRUE;
else
return FALSE;
}
static gchar **
g_proxy_resolver_gnome_lookup (GProxyResolver *proxy_resolver,
const gchar *uri,
GCancellable *cancellable,
GError **error)
{
GProxyResolverGnome *resolver = G_PROXY_RESOLVER_GNOME (proxy_resolver);
GDBusProxy *pacrunner;
gchar **proxies, *autoconfig_url;
if (!g_proxy_resolver_gnome_lookup_internal (resolver, uri,
&proxies, &pacrunner, &autoconfig_url,
cancellable, error))
return NULL;
if (pacrunner)
{
GVariant *vproxies;
vproxies = g_dbus_proxy_call_sync (pacrunner,
"Lookup",
g_variant_new ("(ss)",
autoconfig_url,
uri),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable, error);
if (vproxies)
{
g_variant_get (vproxies, "(^as)", &proxies);
g_variant_unref (vproxies);
}
else
proxies = NULL;
g_object_unref (pacrunner);
g_free (autoconfig_url);
}
return proxies;
}
static void
got_autoconfig_proxies (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = user_data;
GVariant *vproxies;
char **proxies;
GError *error = NULL;
vproxies = g_dbus_proxy_call_finish (G_DBUS_PROXY (source),
result, &error);
if (vproxies)
{
g_variant_get (vproxies, "(^as)", &proxies);
g_task_return_pointer (task, proxies, (GDestroyNotify)g_strfreev);
g_variant_unref (vproxies);
}
else
g_task_return_error (task, error);
g_object_unref (task);
}
static void
g_proxy_resolver_gnome_lookup_async (GProxyResolver *proxy_resolver,
const gchar *uri,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GProxyResolverGnome *resolver = G_PROXY_RESOLVER_GNOME (proxy_resolver);
GTask *task;
char **proxies, *autoconfig_url;
GDBusProxy *pacrunner;
GError *error = NULL;
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, g_proxy_resolver_gnome_lookup_async);
if (!g_proxy_resolver_gnome_lookup_internal (resolver, uri,
&proxies, &pacrunner, &autoconfig_url,
cancellable, &error))
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
else if (proxies)
{
g_task_return_pointer (task, proxies, (GDestroyNotify)g_strfreev);
g_object_unref (task);
return;
}
g_dbus_proxy_call (pacrunner,
"Lookup",
g_variant_new ("(ss)",
autoconfig_url,
uri),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
got_autoconfig_proxies,
task);
g_object_unref (pacrunner);
g_free (autoconfig_url);
}
static gchar **
g_proxy_resolver_gnome_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_proxy_resolver_gnome_class_init (GProxyResolverGnomeClass *resolver_class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (resolver_class);
object_class->finalize = g_proxy_resolver_gnome_finalize;
}
static void
g_proxy_resolver_gnome_iface_init (GProxyResolverInterface *iface)
{
g_proxy_resolver_gnome_parent_iface = g_type_interface_peek_parent (iface);
iface->is_supported = g_proxy_resolver_gnome_is_supported;
iface->lookup = g_proxy_resolver_gnome_lookup;
iface->lookup_async = g_proxy_resolver_gnome_lookup_async;
iface->lookup_finish = g_proxy_resolver_gnome_lookup_finish;
}
void
g_proxy_resolver_gnome_register (GIOModule *module)
{
g_proxy_resolver_gnome_register_type (G_TYPE_MODULE (module));
if (module == NULL)
g_io_extension_point_register (G_PROXY_RESOLVER_EXTENSION_POINT_NAME);
g_io_extension_point_implement (G_PROXY_RESOLVER_EXTENSION_POINT_NAME,
g_proxy_resolver_gnome_get_type(),
"gnome",
80);
}