/*
 * This program 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 of the License, or (at your option) version 3.
 *
 * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *		Jeffrey Stedfast <fejj@ximian.com>
 *		Veerapuram Varadhan <vvaradhan@novell.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <stdlib.h>

#ifdef _WIN32
#include <winsock2.h>
#ifndef IN6_ARE_ADDR_EQUAL
#define IN6_ARE_ADDR_EQUAL(a, b)	\
    (memcmp ((gpointer)(a), (gpointer)(b), sizeof (struct in6_addr)) == 0)
#endif
#else
#include <netinet/in.h>
#include <sys/socket.h>
#endif

#include <gconf/gconf.h>
#include <gconf/gconf-client.h>

#include <libsoup/soup-address.h>
#include <libsoup/soup-uri.h>
#include "e-proxy.h"

G_DEFINE_TYPE (EProxy, e_proxy, G_TYPE_OBJECT)

/* Debug */
#define d(x)

enum ProxyType {
	PROXY_TYPE_SYSTEM = 0,
	PROXY_TYPE_NO_PROXY,
	PROXY_TYPE_MANUAL,
	PROXY_TYPE_AUTO_URL /* no auto-proxy at the moment */
};

/* GConf paths and keys */
#define PATH_GCONF_EVO_NETWORK_CONFIG "/apps/evolution/shell/network_config"
#define KEY_GCONF_EVO_PROXY_TYPE       PATH_GCONF_EVO_NETWORK_CONFIG "/proxy_type"

#define KEY_GCONF_EVO_USE_HTTP_PROXY    PATH_GCONF_EVO_NETWORK_CONFIG "/use_http_proxy"
#define KEY_GCONF_EVO_HTTP_HOST         PATH_GCONF_EVO_NETWORK_CONFIG "/http_host"
#define KEY_GCONF_EVO_HTTP_PORT         PATH_GCONF_EVO_NETWORK_CONFIG "/http_port"
#define KEY_GCONF_EVO_HTTP_USE_AUTH     PATH_GCONF_EVO_NETWORK_CONFIG "/use_authentication"
#define KEY_GCONF_EVO_HTTP_AUTH_USER    PATH_GCONF_EVO_NETWORK_CONFIG "/authentication_user"
#define KEY_GCONF_EVO_HTTP_AUTH_PWD     PATH_GCONF_EVO_NETWORK_CONFIG "/authentication_password"
#define KEY_GCONF_EVO_HTTP_IGNORE_HOSTS PATH_GCONF_EVO_NETWORK_CONFIG "/ignore_hosts"
#define KEY_GCONF_EVO_HTTPS_HOST        PATH_GCONF_EVO_NETWORK_CONFIG "/secure_host"
#define KEY_GCONF_EVO_HTTPS_PORT        PATH_GCONF_EVO_NETWORK_CONFIG "/secure_port"
#define KEY_GCONF_EVO_SOCKS_HOST        PATH_GCONF_EVO_NETWORK_CONFIG "/socks_host"
#define KEY_GCONF_EVO_SOCKS_PORT        PATH_GCONF_EVO_NETWORK_CONFIG "/socks_port"
#define KEY_GCONF_EVO_AUTOCONFIG_URL    PATH_GCONF_EVO_NETWORK_CONFIG "/autoconfig_url"

typedef enum {
	E_PROXY_KEY_MODE,
	E_PROXY_KEY_USE_HTTP_PROXY,
	E_PROXY_KEY_HTTP_HOST,
	E_PROXY_KEY_HTTP_PORT,
	E_PROXY_KEY_HTTP_USE_AUTH,
	E_PROXY_KEY_HTTP_AUTH_USER,
	E_PROXY_KEY_HTTP_AUTH_PWD,
	E_PROXY_KEY_HTTP_IGNORE_HOSTS,
	E_PROXY_KEY_HTTPS_HOST,
	E_PROXY_KEY_HTTPS_PORT,
	E_PROXY_KEY_SOCKS_HOST,
	E_PROXY_KEY_SOCKS_PORT,
	E_PROXY_KEY_AUTOCONFIG_URL
} EProxyKey;

struct _EProxyPrivate {
	SoupURI *uri_http, *uri_https, *uri_socks;
	guint notify_id_evo; /* conxn id of gconf_client_notify_add  */
	GSList * ign_hosts;	/* List of hostnames. (Strings)		*/
	GSList * ign_addrs;	/* List of hostaddrs. (ProxyHostAddrs)	*/
	gboolean use_proxy;	/* Is our-proxy enabled? */
	enum ProxyType type;
	GSettings *proxy_settings;
	GSettings *proxy_http_settings;
	GSettings *proxy_https_settings;
	GSettings *proxy_socks_settings;
};

/* Enum definition is copied from gnome-vfs/modules/http-proxy.c */
typedef enum {
	PROXY_IPV4 = 4,
	PROXY_IPV6 = 6
} ProxyAddrType;

typedef struct {
	ProxyAddrType type;	/* Specifies whether IPV4 or IPV6 */
	gpointer  addr;		/* Either in_addr * or in6_addr * */
	gpointer  mask;		/* Either in_addr * or in6_addr * */
} ProxyHostAddr;

/* Signals.  */
enum {
	CHANGED,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

/* Forward declarations.  */

static void	ep_setting_changed	(GConfClient *client, guint32 cnxn_id,
					 GConfEntry *entry, gpointer user_data);

static void	ipv6_network_addr	(const struct in6_addr *addr,
					 const struct in6_addr *mask,
					 struct in6_addr *res);

static void
ep_free_proxy_host_addr (ProxyHostAddr *host)
{
	if (host) {
		if (host->addr) {
			g_free (host->addr);
			host->addr = NULL;
		}
		if (host->mask) {
			g_free (host->mask);
			host->mask = NULL;
		}
		g_free (host);
	}
}

static gboolean
ep_read_key_boolean (EProxy *proxy,
                     EProxyKey key,
                     GConfClient *client)
{
	gboolean res = FALSE;

	g_return_val_if_fail (proxy != NULL, FALSE);
	g_return_val_if_fail (E_IS_PROXY (proxy), FALSE);
	g_return_val_if_fail (proxy->priv != NULL, FALSE);

	switch (key) {
	case E_PROXY_KEY_USE_HTTP_PROXY:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			/* it's not used in the UI, thus behave like always set to TRUE */
			res = TRUE; /* g_settings_get_boolean (proxy->priv->proxy_http_settings, "enabled"); */
		else
			res = gconf_client_get_bool (client, KEY_GCONF_EVO_USE_HTTP_PROXY, NULL);
		break;
	case E_PROXY_KEY_HTTP_USE_AUTH:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_boolean (proxy->priv->proxy_http_settings, "use-authentication");
		else
			res = gconf_client_get_bool (client, KEY_GCONF_EVO_HTTP_USE_AUTH, NULL);
		break;
	default:
		g_warn_if_reached ();
		break;
	}

	return res;
}

static gint
ep_read_key_int (EProxy *proxy,
                 EProxyKey key,
                 GConfClient *client)
{
	gint res = 0;

	g_return_val_if_fail (proxy != NULL, 0);
	g_return_val_if_fail (E_IS_PROXY (proxy), 0);
	g_return_val_if_fail (proxy->priv != NULL, 0);

	switch (key) {
	case E_PROXY_KEY_HTTP_PORT:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_int (proxy->priv->proxy_http_settings, "port");
		else
			res = gconf_client_get_int (client, KEY_GCONF_EVO_HTTP_PORT, NULL);
		break;
	case E_PROXY_KEY_HTTPS_PORT:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_int (proxy->priv->proxy_https_settings, "port");
		else
			res = gconf_client_get_int (client, KEY_GCONF_EVO_HTTPS_PORT, NULL);
		break;
	case E_PROXY_KEY_SOCKS_PORT:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_int (proxy->priv->proxy_socks_settings, "port");
		else
			res = gconf_client_get_int (client, KEY_GCONF_EVO_SOCKS_PORT, NULL);
		break;
	default:
		g_warn_if_reached ();
		break;
	}

	return res;
}

/* free returned pointer with g_free() */
static gchar *
ep_read_key_string (EProxy *proxy,
                    EProxyKey key,
                    GConfClient *client)
{
	gchar *res = NULL;

	g_return_val_if_fail (proxy != NULL, NULL);
	g_return_val_if_fail (E_IS_PROXY (proxy), NULL);
	g_return_val_if_fail (proxy->priv != NULL, NULL);

	switch (key) {
	case E_PROXY_KEY_MODE:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_string (proxy->priv->proxy_settings, "mode");
		else
			g_warn_if_reached ();
		break;
	case E_PROXY_KEY_HTTP_HOST:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_string (proxy->priv->proxy_http_settings, "host");
		else
			res = gconf_client_get_string (client, KEY_GCONF_EVO_HTTP_HOST, NULL);
		break;
	case E_PROXY_KEY_HTTPS_HOST:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_string (proxy->priv->proxy_https_settings, "host");
		else
			res = gconf_client_get_string (client, KEY_GCONF_EVO_HTTPS_HOST, NULL);
		break;
	case E_PROXY_KEY_SOCKS_HOST:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_string (proxy->priv->proxy_socks_settings, "host");
		else
			res = gconf_client_get_string (client, KEY_GCONF_EVO_SOCKS_HOST, NULL);
		break;
	case E_PROXY_KEY_HTTP_AUTH_USER:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_string (proxy->priv->proxy_http_settings, "authentication-user");
		else
			res = gconf_client_get_string (client, KEY_GCONF_EVO_HTTP_AUTH_USER, NULL);
		break;
	case E_PROXY_KEY_HTTP_AUTH_PWD:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_string (proxy->priv->proxy_http_settings, "authentication-password");
		else
			res = gconf_client_get_string (client, KEY_GCONF_EVO_HTTP_AUTH_PWD, NULL);
		break;
	case E_PROXY_KEY_AUTOCONFIG_URL:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM)
			res = g_settings_get_string (proxy->priv->proxy_settings, "autoconfig-url");
		else
			res = gconf_client_get_string (client, KEY_GCONF_EVO_AUTOCONFIG_URL, NULL);
		break;
	default:
		g_warn_if_reached ();
		break;
	}

	return res;
}

/* list of newly allocated strings, use g_free() for each member and free list itself too */
static GSList *
ep_read_key_list (EProxy *proxy,
                  EProxyKey key,
                  GConfClient *client)
{
	GSList *res = NULL;

	g_return_val_if_fail (proxy != NULL, NULL);
	g_return_val_if_fail (E_IS_PROXY (proxy), NULL);
	g_return_val_if_fail (proxy->priv != NULL, NULL);

	switch (key) {
	case E_PROXY_KEY_HTTP_IGNORE_HOSTS:
		if (proxy->priv->type == PROXY_TYPE_SYSTEM) {
			gchar **strv;
			gint ii;

			strv = g_settings_get_strv (proxy->priv->proxy_settings, "ignore-hosts");
			for (ii = 0; strv && strv[ii]; ii++) {
				res = g_slist_prepend (res, g_strdup (strv[ii]));
			}

			g_strfreev (strv);

			res = g_slist_reverse (res);
		} else
			res = gconf_client_get_list (client, KEY_GCONF_EVO_HTTP_IGNORE_HOSTS, GCONF_VALUE_STRING, NULL);
		break;
	default:
		g_warn_if_reached ();
		break;
	}

	return res;
}

static gboolean
ep_is_in_ignored (EProxy *proxy,
                  const gchar *host)
{
	EProxyPrivate *priv;
	GSList * l;
	gchar *hn;

	g_return_val_if_fail (proxy != NULL, FALSE);
	g_return_val_if_fail (host != NULL, FALSE);

	priv = proxy->priv;
	if (!priv->ign_hosts)
		return FALSE;

	hn = g_ascii_strdown (host, -1);

	for (l = priv->ign_hosts; l; l = l->next) {
		if (*((gchar *) l->data) == '*') {
			if (g_str_has_suffix (hn, ((gchar *) l->data) + 1)) {
				g_free (hn);
				return TRUE;
			}
		} else if (strcmp (hn, l->data) == 0) {
				g_free (hn);
				return TRUE;
		}
	}
	g_free (hn);

	return FALSE;
}

static gboolean
ep_need_proxy_http (EProxy *proxy,
                    const gchar *host)
{
	SoupAddress *addr = NULL;
	EProxyPrivate *priv = proxy->priv;
	ProxyHostAddr *p_addr = NULL;
	GSList *l;
	guint status;

	/* check for ignored first */
	if (ep_is_in_ignored (proxy, host))
		return FALSE;

	addr = soup_address_new (host, 0);
	status = soup_address_resolve_sync (addr, NULL);
	if (status == SOUP_STATUS_OK) {
		gint addr_len;
		struct sockaddr * so_addr = NULL;

		so_addr = soup_address_get_sockaddr (addr, &addr_len);

		/* This will never happen, since we have already called
		 * soup_address_resolve_sync ().
		*/
		if (!so_addr)
			return TRUE;

		if (so_addr->sa_family == AF_INET) {
			struct in_addr in, *mask, *addr_in;

			in = ((struct sockaddr_in *) so_addr)->sin_addr;
			for (l = priv->ign_addrs; l; l = l->next) {
				p_addr = (ProxyHostAddr *) l->data;
				if (p_addr->type == PROXY_IPV4) {
					addr_in =  ((struct in_addr *) p_addr->addr);
					mask = ((struct in_addr *) p_addr->mask);
					d(g_print ("ep_need_proxy:ipv4: in: %ul\t mask: %ul\t addr: %ul\n",
						   in.s_addr, mask->s_addr, addr_in->s_addr));
					if ((in.s_addr & mask->s_addr) == addr_in->s_addr) {
						d(g_print ("Host [%s] doesn't require proxy\n", host));
						return FALSE;
					}
				}
			}
		} else {
			struct in6_addr in6, net6;
			struct in_addr *addr_in, *mask;

			in6 = ((struct sockaddr_in6 *) so_addr)->sin6_addr;
			for (l = priv->ign_addrs; l; l = l->next) {
				p_addr = (ProxyHostAddr *) l->data;
				ipv6_network_addr (&in6, (struct in6_addr *) p_addr->mask, &net6);
				if (p_addr->type == PROXY_IPV6) {
					if (IN6_ARE_ADDR_EQUAL (&net6, (struct in6_addr *) p_addr->addr)) {
						d(g_print ("Host [%s] doesn't require proxy\n", host));
						return FALSE;
					}
				} else if (p_addr->type == PROXY_IPV6 &&
					   IN6_IS_ADDR_V4MAPPED (&net6)) {
					guint32 v4addr;

					addr_in =  ((struct in_addr *) p_addr->addr);
					mask = ((struct in_addr *) p_addr->mask);

					v4addr = net6.s6_addr[12] << 24
						| net6.s6_addr[13] << 16
						| net6.s6_addr[14] << 8
						| net6.s6_addr[15];
					if ((v4addr & mask->s_addr) != addr_in->s_addr) {
						d(g_print ("Host [%s] doesn't require proxy\n", host));
						return FALSE;
					}
				}
			}
		}
	}

	d(g_print ("%s needs a proxy to connect to internet\n", host));
	return TRUE;
}

static gboolean
ep_need_proxy_https (EProxy *proxy,
                     const gchar *host)
{
	/* Can we share ignore list from HTTP at all? */
	return !ep_is_in_ignored (proxy, host);
}

static gboolean
ep_need_proxy_socks (EProxy *proxy,
                     const gchar *host)
{
	/* Can we share ignore list from HTTP at all? */
	return !ep_is_in_ignored (proxy, host);
}

static gboolean
ep_manipulate_ipv4 (ProxyHostAddr *host_addr,
                    struct in_addr *addr_in,
                    gchar *netmask)
{
	gboolean has_error = FALSE;
	struct in_addr *addr, *mask;

	if (!addr_in)
		return has_error;

	host_addr->type = PROXY_IPV4;
	addr = g_new0 (struct in_addr, 1);
	memcpy (addr, addr_in, sizeof (struct in_addr));
	mask = g_new0 (struct in_addr, 1);

	if (netmask) {
		gchar *endptr;
		gint width = strtol (netmask, &endptr, 10);

		if (*endptr != '\0' || width < 0 || width > 32) {
			has_error = TRUE;
		}
		mask->s_addr = htonl (~0 << width);
		addr->s_addr &= mask->s_addr;
	} else {
		mask->s_addr = 0xFFFFFFFF;
	}

	d(g_print ("ep_manipulate_ipv4: addr_in: %ul, addr: %ul, mask: %ul\n",
		   addr_in->s_addr, addr->s_addr, mask->s_addr));

	host_addr->addr = addr;
	host_addr->mask = mask;

	return has_error;
}

static void
ipv6_network_addr (const struct in6_addr *addr,
                   const struct in6_addr *mask,
                   struct in6_addr *res)
{
	gint i;

	for (i = 0; i < 16; ++i) {
		res->s6_addr[i] = addr->s6_addr[i] & mask->s6_addr[i];
	}
}

static gboolean
ep_manipulate_ipv6 (ProxyHostAddr *host_addr,
                    struct in6_addr *addr_in6,
                    gchar *netmask)
{
	gboolean has_error = FALSE;
	struct in6_addr *addr, *mask;
	gint i;

	if (!addr_in6)
		return has_error;

	host_addr->type = PROXY_IPV6;

	addr = g_new0 (struct in6_addr, 1);
	mask = g_new0 (struct in6_addr, 1);

	for (i = 0; i < 16; ++i) {
		addr->s6_addr[i] = addr_in6->s6_addr[i];
	}
	if (netmask) {
		gchar *endptr;
		gint width = strtol (netmask, &endptr, 10);

		if (*endptr != '\0' || width < 0 || width > 128) {
			has_error = TRUE;
		}
		for (i = 0; i < 16; ++i) {
			mask->s6_addr[i] = 0;
		}
		for (i = 0; i < width / 8; i++) {
			mask->s6_addr[i] = 0xff;
		}
		mask->s6_addr[i] = (0xff << (8 - width % 8)) & 0xff;
		ipv6_network_addr (addr, mask, addr);
	} else {
		for (i = 0; i < 16; ++i) {
			mask->s6_addr[i] = 0xff;
		}
	}

	host_addr->addr = addr;
	host_addr->mask = mask;

	return has_error;
}

static void
ep_parse_ignore_host (gpointer data,
                      gpointer user_data)
{
	EProxy * proxy = (EProxy *) user_data;
	EProxyPrivate * priv = NULL;
	SoupAddress *addr;
	guint status;
	gchar *input, *netmask, *hostname;
	ProxyHostAddr *host_addr;
	gboolean has_error = FALSE;

	if (!proxy || !proxy->priv)
		return;

	priv = proxy->priv;
	input = (gchar *) data;

	if ((netmask = strrchr (input, '/')) != NULL) {
		hostname = g_strndup (input, netmask - input);
		++netmask;
	} else {
		hostname = g_ascii_strdown (input, -1);
	}

	addr = soup_address_new (hostname, 0);
	status = soup_address_resolve_sync (addr, NULL);
	if (status == SOUP_STATUS_OK) {
		gint addr_len;
		struct sockaddr * so_addr = NULL;

		host_addr = g_new0 (ProxyHostAddr, 1);

		so_addr = soup_address_get_sockaddr (addr, &addr_len);

		/* This will never happen, since we have already called
		 * soup_address_resolve_sync ().
		*/
		if (!so_addr)
			goto error;

		if (so_addr->sa_family == AF_INET)
			has_error = ep_manipulate_ipv4 (host_addr,
							&((struct sockaddr_in *) so_addr)->sin_addr,
							netmask);
		else
			has_error = ep_manipulate_ipv6 (host_addr,
							&((struct sockaddr_in6 *) so_addr)->sin6_addr,
							netmask);

		if (!has_error) {
			priv->ign_addrs = g_slist_append (priv->ign_addrs, host_addr);
			priv->ign_hosts = g_slist_append (priv->ign_hosts, hostname);
		} else {
			g_free (hostname);
		}
	} else {
		d(g_print ("Unable to resolve %s\n", hostname));
		priv->ign_hosts = g_slist_append (priv->ign_hosts, hostname);
	}
 error:
	g_object_unref (addr);
}

static gboolean
ep_change_uri (SoupURI **soup_uri,
               const gchar *uri)
{
	gboolean changed = FALSE;

	g_return_val_if_fail (soup_uri != NULL, FALSE);

	if (!uri || !*uri) {
		if (*soup_uri) {
			soup_uri_free (*soup_uri);
			*soup_uri = NULL;
			changed = TRUE;
		}
	} else if (*soup_uri) {
		gchar *old = soup_uri_to_string (*soup_uri, FALSE);

		if (old && *old) {
			gint len = strlen (old);

			/* remove ending slash, if there */
			if (old[len - 1] == '/')
				old[len - 1] = 0;
		}

		changed = old && uri && g_ascii_strcasecmp (old, uri) != 0;
		if (changed) {
			soup_uri_free (*soup_uri);
			*soup_uri = soup_uri_new (uri);
		}

		g_free (old);
	} else {
		*soup_uri = soup_uri_new (uri);
		changed = TRUE;
	}

	return changed;
}

static gchar *
update_proxy_uri (const gchar *uri,
                  const gchar *proxy_user,
                  const gchar *proxy_pw)
{
	gchar *res, *user = NULL, *pw = NULL;
	gboolean is_https;

	g_return_val_if_fail (uri != NULL, NULL);

	if (proxy_user && *proxy_user) {
		user = soup_uri_encode (proxy_user, ":/;#@?\\");
		if (proxy_pw)
			pw = soup_uri_encode (proxy_pw, ":/;#@?\\");
	}

	if (!user)
		return g_strdup (uri);

	/*  here can be only http or https and nothing else */
	is_https = g_str_has_prefix (uri, "https://");

	res = g_strdup_printf ("%s://%s%s%s%s%s",
		is_https ? "https" : "http",
		user ? user : "",
		pw ? ":" : "",
		pw ? pw : "",
		(user || pw) ? "@" : "",
		uri + strlen ("http://") + (is_https ? 1 : 0));

	g_free (user);
	g_free (pw);

	return res;
}

static void
ep_set_proxy (EProxy *proxy,
              gboolean regen_ign_host_list)
{
	gchar *proxy_server, *uri_http = NULL, *uri_https = NULL, *uri_socks = NULL;
	gint proxy_port, old_type;
	EProxyPrivate * priv = proxy->priv;
	GSList *ignore;
	GConfClient *client = gconf_client_get_default ();
	gboolean changed = FALSE, sys_manual = TRUE;

	old_type = priv->type;
	priv->type = gconf_client_get_int (client, KEY_GCONF_EVO_PROXY_TYPE, NULL);
	if (priv->type > PROXY_TYPE_AUTO_URL)
		priv->type = PROXY_TYPE_SYSTEM;
	changed = priv->type != old_type;

	if (priv->type == PROXY_TYPE_SYSTEM) {
		gchar *mode = ep_read_key_string (proxy, E_PROXY_KEY_MODE, client);

		/* supporting only manual system proxy setting */
		sys_manual = mode && g_str_equal (mode, "manual");

		g_free (mode);
	}

	priv->use_proxy = ep_read_key_boolean (proxy, E_PROXY_KEY_USE_HTTP_PROXY, client);
	if (!priv->use_proxy || priv->type == PROXY_TYPE_NO_PROXY || !sys_manual) {
		changed = ep_change_uri (&priv->uri_http, NULL) || changed;
		changed = ep_change_uri (&priv->uri_https, NULL) || changed;
		changed = ep_change_uri (&priv->uri_socks, NULL) || changed;
		goto emit_signal;
	}

	proxy_server = ep_read_key_string (proxy, E_PROXY_KEY_HTTP_HOST, client);
	proxy_port = ep_read_key_int (proxy, E_PROXY_KEY_HTTP_PORT, client);
	if (proxy_server != NULL && *proxy_server && !g_ascii_isspace (*proxy_server)) {
		if (proxy_port > 0)
			uri_http = g_strdup_printf ("http://%s:%d", proxy_server, proxy_port);
		else
			uri_http = g_strdup_printf ("http://%s", proxy_server);
	} else
		uri_http = NULL;
	g_free (proxy_server);
	d(g_print ("ep_set_proxy: uri_http: %s\n", uri_http));

	proxy_server = ep_read_key_string (proxy, E_PROXY_KEY_HTTPS_HOST, client);
	proxy_port = ep_read_key_int (proxy, E_PROXY_KEY_HTTPS_PORT, client);
	if (proxy_server != NULL && *proxy_server && !g_ascii_isspace (*proxy_server)) {
		if (proxy_port > 0)
			uri_https = g_strdup_printf ("https://%s:%d", proxy_server, proxy_port);
		else
			uri_https = g_strdup_printf ("https://%s", proxy_server);
	} else
		uri_https = NULL;
	g_free (proxy_server);
	d(g_print ("ep_set_proxy: uri_https: %s\n", uri_https));

	proxy_server = ep_read_key_string (proxy, E_PROXY_KEY_SOCKS_HOST, client);
	proxy_port = ep_read_key_int (proxy, E_PROXY_KEY_SOCKS_PORT, client);
	if (proxy_server != NULL && *proxy_server && !g_ascii_isspace (*proxy_server)) {
		if (proxy_port > 0)
			uri_socks = g_strdup_printf ("socks://%s:%d", proxy_server, proxy_port);
		else
			uri_socks = g_strdup_printf ("socks://%s", proxy_server);
	} else
		uri_socks = NULL;
	g_free (proxy_server);
	d(g_print ("ep_set_proxy: uri_socks: %s\n", uri_socks));

	if (regen_ign_host_list) {
		if (priv->ign_hosts) {
			g_slist_foreach (priv->ign_hosts, (GFunc) g_free, NULL);
			g_slist_free (priv->ign_hosts);
			priv->ign_hosts = NULL;
		}

		if (priv->ign_addrs) {
			g_slist_foreach (priv->ign_addrs, (GFunc) ep_free_proxy_host_addr, NULL);
			g_slist_free (priv->ign_addrs);
			priv->ign_addrs = NULL;
		}

		ignore = ep_read_key_list (proxy, E_PROXY_KEY_HTTP_IGNORE_HOSTS, client);
		if (ignore) {
			g_slist_foreach (ignore, (GFunc) ep_parse_ignore_host, proxy);
			g_slist_foreach (ignore, (GFunc) g_free, NULL);
			g_slist_free (ignore);
		}
	}

	if (ep_read_key_boolean (proxy, E_PROXY_KEY_HTTP_USE_AUTH, client)) {
		gchar *proxy_user, *proxy_pw, *tmp = NULL, *tmps = NULL;

		proxy_user = ep_read_key_string (proxy, E_PROXY_KEY_HTTP_AUTH_USER, client);
		proxy_pw = ep_read_key_string (proxy, E_PROXY_KEY_HTTP_AUTH_PWD, client);

		if (uri_http && proxy_user && *proxy_user) {
			tmp = uri_http;
			uri_http = update_proxy_uri (uri_http, proxy_user, proxy_pw);
		}

		if (uri_https && proxy_user && *proxy_user) {
			tmps = uri_https;
			uri_https = update_proxy_uri (uri_https, proxy_user, proxy_pw);
		}

		g_free (proxy_user);
		g_free (proxy_pw);
		g_free (tmp);
		g_free (tmps);
	}

	changed = ep_change_uri (&priv->uri_http, uri_http) || changed;
	changed = ep_change_uri (&priv->uri_https, uri_https) || changed;
	changed = ep_change_uri (&priv->uri_socks, uri_socks) || changed;

 emit_signal:
	d(g_print ("%s: changed:%d uri_http: %s; uri_https: %s; uri_socks: %s\n", G_STRFUNC, changed ? 1 : 0, uri_http ? uri_http : "[null]", uri_https ? uri_https : "[null]", uri_socks ? uri_socks : "[null]"));
	if (changed)
		g_signal_emit (proxy, signals[CHANGED], 0);

	g_free (uri_http);
	g_free (uri_https);
	g_free (uri_socks);
	g_object_unref (client);
}

static void
ep_setting_changed (GConfClient *client,
                    guint32 cnxn_id,
                    GConfEntry *entry,
                    gpointer user_data)
{
	const gchar *key;
	EProxy * proxy = (EProxy *) user_data;
	EProxyPrivate *priv;

	if (!proxy || !proxy->priv)
		return;

	priv = proxy->priv;
	key = gconf_entry_get_key (entry);

	if (g_str_equal (key, KEY_GCONF_EVO_PROXY_TYPE)) {
		ep_set_proxy (user_data, TRUE);
		d(g_print ("e-proxy.c:ep_settings_changed: proxy type changed\n"));
	} else if (priv->type == PROXY_TYPE_SYSTEM) {
		return;
	}

	if (g_str_equal (key, KEY_GCONF_EVO_USE_HTTP_PROXY) ||
		   g_str_equal (key, KEY_GCONF_EVO_HTTP_IGNORE_HOSTS) ||
		   g_str_equal (key, KEY_GCONF_EVO_HTTP_HOST) ||
		   g_str_equal (key, KEY_GCONF_EVO_HTTP_PORT)) {
		gboolean regen_ign_host_list = FALSE;

		if (g_str_equal (key, KEY_GCONF_EVO_HTTP_IGNORE_HOSTS))
			regen_ign_host_list = TRUE;

		ep_set_proxy (proxy, regen_ign_host_list);
		d(g_print ("e-proxy.c:ep_settings_changed: proxy settings changed\n"));
	} else if (g_str_equal (key, KEY_GCONF_EVO_HTTP_AUTH_USER) ||
		   g_str_equal (key, KEY_GCONF_EVO_HTTP_AUTH_PWD) ||
		   g_str_equal (key, KEY_GCONF_EVO_HTTP_USE_AUTH)) {

		ep_set_proxy (proxy, FALSE);
		d(g_print ("e-proxy.c:ep_settings_changed: auth settings changed\n"));
	} else if (g_str_equal (key, KEY_GCONF_EVO_HTTPS_HOST) ||
		   g_str_equal (key, KEY_GCONF_EVO_HTTPS_PORT)) {

		ep_set_proxy (proxy, FALSE);
		d(g_print ("e-proxy.c:ep_settings_changed: https\n"));
	} else if (g_str_equal (key, KEY_GCONF_EVO_SOCKS_HOST) ||
		   g_str_equal (key, KEY_GCONF_EVO_SOCKS_PORT)) {

		ep_set_proxy (proxy, FALSE);
		d(g_print ("e-proxy.c:ep_settings_changed: socks\n"));
	} else if (g_str_equal (key, KEY_GCONF_EVO_AUTOCONFIG_URL)) {

		/* ep_set_proxy (proxy, FALSE); */
		d(g_print ("e-proxy.c:ep_settings_changed: socks/autoconf-url changed\n"));
	}
}

static void
ep_sys_proxy_changed_cb (GSettings *settings,
                         const gchar *key,
                         EProxy *proxy)
{
	g_return_if_fail (proxy != NULL);

	if (proxy->priv->type != PROXY_TYPE_SYSTEM)
		return;

	ep_set_proxy (proxy, g_strcmp0 (key, "ignore-hosts") == 0);
}

static void
ep_sys_proxy_http_changed_cb (GSettings *settings,
                              const gchar *key,
                              EProxy *proxy)
{
	g_return_if_fail (proxy != NULL);

	if (proxy->priv->type != PROXY_TYPE_SYSTEM)
		return;

	ep_set_proxy (proxy, FALSE);
}

static void
ep_sys_proxy_https_changed_cb (GSettings *settings,
                               const gchar *key,
                               EProxy *proxy)
{
	g_return_if_fail (proxy != NULL);

	if (proxy->priv->type != PROXY_TYPE_SYSTEM)
		return;

	ep_set_proxy (proxy, FALSE);
}

static void
ep_sys_proxy_socks_changed_cb (GSettings *settings,
                               const gchar *key,
                               EProxy *proxy)
{
	g_return_if_fail (proxy != NULL);

	if (proxy->priv->type != PROXY_TYPE_SYSTEM)
		return;

	ep_set_proxy (proxy, FALSE);
}

static void
e_proxy_dispose (GObject *object)
{
	EProxyPrivate *priv;
	GConfClient * client;

	priv = E_PROXY (object)->priv;

	client = gconf_client_get_default ();

	if (priv->notify_id_evo > 0) {
		gconf_client_notify_remove (client, priv->notify_id_evo);
		priv->notify_id_evo = 0;
	}

	g_object_unref (client);

	if (priv->proxy_settings) {
		g_object_unref (priv->proxy_settings);
		priv->proxy_settings = NULL;
	}

	if (priv->proxy_http_settings) {
		g_object_unref (priv->proxy_http_settings);
		priv->proxy_http_settings = NULL;
	}

	if (priv->proxy_https_settings) {
		g_object_unref (priv->proxy_https_settings);
		priv->proxy_https_settings = NULL;
	}

	if (priv->proxy_socks_settings) {
		g_object_unref (priv->proxy_socks_settings);
		priv->proxy_socks_settings = NULL;
	}

	if (priv->uri_http)
		soup_uri_free (priv->uri_http);

	if (priv->uri_https)
		soup_uri_free (priv->uri_https);

	if (priv->uri_socks)
		soup_uri_free (priv->uri_socks);

	g_slist_foreach (priv->ign_hosts, (GFunc) g_free, NULL);
	g_slist_free (priv->ign_hosts);

	g_slist_foreach (priv->ign_addrs, (GFunc) ep_free_proxy_host_addr, NULL);
	g_slist_free (priv->ign_addrs);

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (e_proxy_parent_class)->dispose (object);
}

static void
e_proxy_class_init (EProxyClass *class)
{
	GObjectClass *object_class;

	g_type_class_add_private (class, sizeof (EProxyPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->dispose = e_proxy_dispose;

	/**
	 * EProxy::changed:
	 * @proxy: the #EProxy which emitted the signal
	 *
	 * Emitted when proxy settings in gconf changes.
	 **/
	signals[CHANGED] = g_signal_new (
		"changed",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_FIRST,
		G_STRUCT_OFFSET (EProxyClass, changed),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

}

static void
e_proxy_init (EProxy *proxy)
{
	proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (
		proxy, E_TYPE_PROXY, EProxyPrivate);

	proxy->priv->type = PROXY_TYPE_SYSTEM;

	proxy->priv->proxy_settings = g_settings_new ("org.gnome.system.proxy");
	proxy->priv->proxy_http_settings = g_settings_get_child (proxy->priv->proxy_settings, "http");
	proxy->priv->proxy_https_settings = g_settings_get_child (proxy->priv->proxy_settings, "https");
	proxy->priv->proxy_socks_settings = g_settings_get_child (proxy->priv->proxy_settings, "socks");

	g_signal_connect (proxy->priv->proxy_settings, "changed", G_CALLBACK (ep_sys_proxy_changed_cb), proxy);
	g_signal_connect (proxy->priv->proxy_http_settings, "changed", G_CALLBACK (ep_sys_proxy_http_changed_cb), proxy);
	g_signal_connect (proxy->priv->proxy_https_settings, "changed", G_CALLBACK (ep_sys_proxy_https_changed_cb), proxy);
	g_signal_connect (proxy->priv->proxy_socks_settings, "changed", G_CALLBACK (ep_sys_proxy_socks_changed_cb), proxy);
}

/**
 * e_proxy_new:
 *
 * Since: 2.24
 **/
EProxy *
e_proxy_new (void)
{
	return g_object_new (E_TYPE_PROXY, NULL);
}

/**
 * e_proxxy_setup_proxy:
 *
 * Since: 2.24
 **/
void
e_proxy_setup_proxy (EProxy *proxy)
{
	GConfClient *client;

	/* We get the evolution-shell proxy keys here
	 * set soup up to use the proxy,
	 * and listen to any changes */

	/* XXX Why can't we do this automatically in constructed() ? */

	g_return_if_fail (E_IS_PROXY (proxy));

	client = gconf_client_get_default ();

	if (proxy->priv->notify_id_evo == 0) {
		/* Listen to the changes in the evolution-shell path */
		gconf_client_add_dir (
			client, PATH_GCONF_EVO_NETWORK_CONFIG,
			GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);

		proxy->priv->notify_id_evo = gconf_client_notify_add (
			client, PATH_GCONF_EVO_NETWORK_CONFIG,
			ep_setting_changed, (gpointer) proxy, NULL, NULL);
	}

	ep_set_proxy (proxy, TRUE);

	g_object_unref (client);
}

/**
 * e_proxy_peek_uri_for:
 *
 * Since: 2.26
 **/
SoupURI *
e_proxy_peek_uri_for (EProxy *proxy,
                      const gchar *uri)
{
	SoupURI *soup_uri;

	g_return_val_if_fail (E_IS_PROXY (proxy), NULL);
	g_return_val_if_fail (uri != NULL, NULL);

	soup_uri = soup_uri_new (uri);
	if (soup_uri == NULL)
		return NULL;

	if (soup_uri->scheme == SOUP_URI_SCHEME_HTTP)
		return proxy->priv->uri_http;

	if (soup_uri->scheme == SOUP_URI_SCHEME_HTTPS)
		return proxy->priv->uri_https;

	if (soup_uri->scheme && g_ascii_strcasecmp (soup_uri->scheme, "socks") == 0)
		return proxy->priv->uri_socks;

	return NULL;
}

/**
 * e_proxy_require_proxy_for_uri:
 *
 * Since: 2.24
 **/
gboolean
e_proxy_require_proxy_for_uri (EProxy *proxy,
                               const gchar *uri)
{
	SoupURI *soup_uri = NULL;
	gboolean need_proxy = FALSE;

	g_return_val_if_fail (E_IS_PROXY (proxy), FALSE);
	g_return_val_if_fail (uri != NULL, FALSE);

	if (!proxy->priv->use_proxy || proxy->priv->type == PROXY_TYPE_NO_PROXY) {
		d(g_print ("[%s] don't need a proxy to connect to internet\n", uri));
		return FALSE;
	}

	soup_uri = soup_uri_new (uri);
	if (soup_uri == NULL)
		return FALSE;

	if (soup_uri->scheme == SOUP_URI_SCHEME_HTTP)
		need_proxy = ep_need_proxy_http (proxy, soup_uri->host);
	else if (soup_uri->scheme == SOUP_URI_SCHEME_HTTPS)
		need_proxy = ep_need_proxy_https (proxy, soup_uri->host);
	else if (soup_uri->scheme && g_ascii_strcasecmp (soup_uri->scheme, "socks") == 0)
		need_proxy = ep_need_proxy_socks (proxy, soup_uri->host);

	soup_uri_free (soup_uri);

	return need_proxy;
}
