Blob Blame History Raw
diff -up evolution-data-server-3.12.11/libebackend/e-collection-backend.c.google-calendar-discover evolution-data-server-3.12.11/libebackend/e-collection-backend.c
--- evolution-data-server-3.12.11/libebackend/e-collection-backend.c.google-calendar-discover	2014-11-11 18:40:25.000000000 +0100
+++ evolution-data-server-3.12.11/libebackend/e-collection-backend.c	2015-07-07 16:23:23.649982529 +0200
@@ -72,6 +72,9 @@ struct _ECollectionBackendPrivate {
 	gulong source_added_handler_id;
 	gulong source_removed_handler_id;
 	gulong notify_enabled_handler_id;
+	gulong notify_collection_handler_id;
+
+	guint scheduled_populate_idle_id;
 };
 
 enum {
@@ -509,6 +512,8 @@ collection_backend_populate_idle_cb (gpo
 
 	backend = E_COLLECTION_BACKEND (user_data);
 
+	backend->priv->scheduled_populate_idle_id = 0;
+
 	class = E_COLLECTION_BACKEND_GET_CLASS (backend);
 	g_return_val_if_fail (class->populate != NULL, FALSE);
 
@@ -518,6 +523,40 @@ collection_backend_populate_idle_cb (gpo
 }
 
 static void
+collection_backend_schedule_populate_idle (ECollectionBackend *backend)
+{
+	g_return_if_fail (E_IS_COLLECTION_BACKEND (backend));
+
+	if (!backend->priv->scheduled_populate_idle_id)
+		backend->priv->scheduled_populate_idle_id = g_idle_add_full (
+			G_PRIORITY_LOW,
+			collection_backend_populate_idle_cb,
+			g_object_ref (backend),
+			(GDestroyNotify) g_object_unref);
+}
+
+static void
+collection_backend_notify_collection_cb (ESourceCollection *collection_extension,
+					 GParamSpec *param,
+					 ECollectionBackend *collection_backend)
+{
+	ESource *source;
+
+	g_return_if_fail (E_IS_SOURCE_COLLECTION (collection_extension));
+	g_return_if_fail (param != NULL);
+	g_return_if_fail (E_IS_COLLECTION_BACKEND (collection_backend));
+
+	source = e_backend_get_source (E_BACKEND (collection_backend));
+	if (!e_source_get_enabled (source) || (
+	    g_strcmp0 (g_param_spec_get_name (param), "calendar-enabled") != 0 &&
+	    g_strcmp0 (g_param_spec_get_name (param), "contacts-enabled") != 0 &&
+	    g_strcmp0 (g_param_spec_get_name (param), "mail-enabled") != 0))
+		return;
+
+	collection_backend_schedule_populate_idle (collection_backend);
+}
+
+static void
 collection_backend_update_proxy_resolver (ECollectionBackend *backend)
 {
 	GProxyResolver *proxy_resolver = NULL;
@@ -662,6 +701,20 @@ collection_backend_dispose (GObject *obj
 		priv->notify_enabled_handler_id = 0;
 	}
 
+	if (priv->notify_collection_handler_id) {
+		ESource *source = e_backend_get_source (E_BACKEND (object));
+
+		if (source) {
+			ESourceCollection *collection_extension;
+
+			collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+
+			g_signal_handler_disconnect (collection_extension, priv->notify_collection_handler_id);
+		}
+
+		priv->notify_collection_handler_id = 0;
+	}
+
 	g_mutex_lock (&priv->children_lock);
 	g_hash_table_remove_all (priv->children);
 	g_mutex_unlock (&priv->children_lock);
@@ -777,14 +830,17 @@ collection_backend_constructed (GObject
 	backend->priv->notify_enabled_handler_id = g_signal_connect (source, "notify::enabled",
 		G_CALLBACK (collection_backend_source_enabled_cb), backend);
 
+	if (e_source_has_extension (source, E_SOURCE_EXTENSION_COLLECTION)) {
+		ESourceCollection *collection_extension;
+
+		collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+		backend->priv->notify_collection_handler_id = g_signal_connect (collection_extension, "notify",
+			G_CALLBACK (collection_backend_notify_collection_cb), backend);
+	}
+
 	/* Populate the newly-added collection from an idle callback
 	 * so persistent child sources have a chance to be added first. */
-
-	g_idle_add_full (
-		G_PRIORITY_LOW,
-		collection_backend_populate_idle_cb,
-		g_object_ref (backend),
-		(GDestroyNotify) g_object_unref);
+	collection_backend_schedule_populate_idle (backend);
 }
 
 static gboolean
diff -up evolution-data-server-3.12.11/libedataserver/e-source.c.google-calendar-discover evolution-data-server-3.12.11/libedataserver/e-source.c
--- evolution-data-server-3.12.11/libedataserver/e-source.c.google-calendar-discover	2014-10-13 07:52:42.000000000 +0200
+++ evolution-data-server-3.12.11/libedataserver/e-source.c	2015-07-07 16:23:23.649982529 +0200
@@ -738,6 +738,9 @@ source_parse_dbus_data (ESource *source,
 	gchar *data;
 	gboolean success;
 
+	if (!source->priv->dbus_object)
+		return FALSE;
+
 	dbus_object = E_DBUS_OBJECT (source->priv->dbus_object);
 
 	dbus_source = e_dbus_object_get_source (dbus_object);
diff -up evolution-data-server-3.12.11/libedataserver/e-source-webdav.c.google-calendar-discover evolution-data-server-3.12.11/libedataserver/e-source-webdav.c
--- evolution-data-server-3.12.11/libedataserver/e-source-webdav.c.google-calendar-discover	2014-08-27 14:08:27.000000000 +0200
+++ evolution-data-server-3.12.11/libedataserver/e-source-webdav.c	2015-07-07 16:23:23.649982529 +0200
@@ -172,9 +172,14 @@ source_webdav_update_properties_from_sou
 		extension,
 		"host", soup_uri->host,
 		"port", soup_uri->port,
-		"user", soup_uri->user,
 		NULL);
 
+	if (soup_uri->user && *soup_uri->user)
+		g_object_set (
+			extension,
+			"user", soup_uri->user,
+			NULL);
+
 	extension_name = E_SOURCE_EXTENSION_SECURITY;
 	extension = e_source_get_extension (source, extension_name);
 
diff -up evolution-data-server-3.12.11/modules/gnome-online-accounts/module-gnome-online-accounts.c.google-calendar-discover evolution-data-server-3.12.11/modules/gnome-online-accounts/module-gnome-online-accounts.c
--- evolution-data-server-3.12.11/modules/gnome-online-accounts/module-gnome-online-accounts.c.google-calendar-discover	2014-07-18 14:54:31.000000000 +0200
+++ evolution-data-server-3.12.11/modules/gnome-online-accounts/module-gnome-online-accounts.c	2015-07-07 16:23:23.650982526 +0200
@@ -169,11 +169,41 @@ gnome_online_accounts_object_is_non_null
                                           GValue *target_value,
                                           gpointer unused)
 {
+	GoaObject *goa_object = GOA_OBJECT (g_binding_get_source (binding));
+	ESourceExtension *source_extension = E_SOURCE_EXTENSION (g_binding_get_target (binding));
+	ESource *source;
+	ESourceGoa *goa_extension;
 	gpointer v_object;
 
 	v_object = g_value_get_object (source_value);
 	g_value_set_boolean (target_value, v_object != NULL);
 
+	g_return_val_if_fail (goa_object != NULL, TRUE);
+	g_return_val_if_fail (source_extension != NULL, TRUE);
+
+	source = e_source_extension_get_source (source_extension);
+	goa_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_GOA);
+
+	if (g_strcmp0 (g_binding_get_source_property (binding), "calendar") == 0) {
+		gchar *uri = NULL;
+
+		if (v_object && GOA_IS_CALENDAR (v_object))
+			uri = goa_calendar_dup_uri (v_object);
+
+		e_source_goa_set_calendar_url (goa_extension, uri);
+
+		g_free (uri);
+	} else if (g_strcmp0 (g_binding_get_source_property (binding), "contacts") == 0) {
+		gchar *uri = NULL;
+
+		if (v_object && GOA_IS_CONTACTS (v_object))
+			uri = goa_contacts_dup_uri (v_object);
+
+		e_source_goa_set_contacts_url (goa_extension, uri);
+
+		g_free (uri);
+	}
+
 	return TRUE;
 }
 
diff -up evolution-data-server-3.12.11/modules/google-backend/e-webdav-discover.c.google-calendar-discover evolution-data-server-3.12.11/modules/google-backend/e-webdav-discover.c
--- evolution-data-server-3.12.11/modules/google-backend/e-webdav-discover.c.google-calendar-discover	2015-07-07 16:23:23.650982526 +0200
+++ evolution-data-server-3.12.11/modules/google-backend/e-webdav-discover.c	2015-07-07 16:23:23.650982526 +0200
@@ -0,0 +1,1887 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <libsoup/soup.h>
+
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include <libebackend/libebackend.h>
+
+#include "e-webdav-discover.h"
+
+typedef struct _EWebdavAuthenticator EWebdavAuthenticator;
+typedef struct _EWebdavAuthenticatorClass EWebdavAuthenticatorClass;
+
+struct _EWebdavAuthenticator {
+	GObject parent;
+
+	ECollectionBackend *collection;
+	gchar *username;
+	GString *password;
+};
+
+struct _EWebdavAuthenticatorClass {
+	GObjectClass parent_class;
+};
+
+static ESourceAuthenticationResult
+webdav_authenticator_try_password_sync (ESourceAuthenticator *auth,
+					const GString *password,
+					GCancellable *cancellable,
+					GError **error)
+{
+	EWebdavAuthenticator *authenticator = (EWebdavAuthenticator *) auth;
+
+	if (authenticator->password)
+		g_string_free (authenticator->password, TRUE);
+	authenticator->password = g_string_new (password->str);
+
+	return E_SOURCE_AUTHENTICATION_ACCEPTED;
+}
+
+#define E_TYPE_WEBDAV_AUTHENTICATOR (e_webdav_authenticator_get_type ())
+
+GType		e_webdav_authenticator_get_type
+				(void) G_GNUC_CONST;
+static void	e_webdav_authenticator_authenticator_init
+				(ESourceAuthenticatorInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (
+	EWebdavAuthenticator,
+	e_webdav_authenticator,
+	G_TYPE_OBJECT, 0,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_SOURCE_AUTHENTICATOR,
+		e_webdav_authenticator_authenticator_init))
+
+static void
+webdav_authenticator_finalize (GObject *object)
+{
+	EWebdavAuthenticator *authenticator = (EWebdavAuthenticator *) object;
+
+	g_free (authenticator->username);
+	if (authenticator->password)
+		g_string_free (authenticator->password, TRUE);
+
+	G_OBJECT_CLASS (e_webdav_authenticator_parent_class)->finalize (object);
+}
+
+static void
+e_webdav_authenticator_class_init (EWebdavAuthenticatorClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = webdav_authenticator_finalize;
+}
+
+static void
+e_webdav_authenticator_authenticator_init (ESourceAuthenticatorInterface *iface)
+{
+	iface->try_password_sync = webdav_authenticator_try_password_sync;
+}
+
+static void
+e_webdav_authenticator_init (EWebdavAuthenticator *authenticator)
+{
+}
+
+#define XC(string) ((xmlChar *) string)
+
+/* Standard Namespaces */
+#define NS_WEBDAV  "DAV:"
+#define NS_CALDAV  "urn:ietf:params:xml:ns:caldav"
+#define NS_CARDDAV "urn:ietf:params:xml:ns:carddav"
+
+/* Application-Specific Namespaces */
+#define NS_ICAL    "http://apple.com/ns/ical/"
+
+/* Mainly for readability. */
+enum {
+	DEPTH_0 = 0,
+	DEPTH_1 = 1
+};
+
+typedef struct _EWebDAVDiscoverContext {
+	ECollectionBackend *collection;
+	gchar *url_use_path;
+	GSList *out_discovered_sources;
+	GSList *out_calendar_user_addresses;
+} EWebDAVDiscoverContext;
+
+static EWebDAVDiscoverContext *
+e_webdav_discover_context_new (ECollectionBackend *collection,
+			       const gchar *url_use_path)
+{
+	EWebDAVDiscoverContext *context;
+
+	context = g_new0 (EWebDAVDiscoverContext, 1);
+	context->collection = g_object_ref (collection);
+	context->url_use_path = g_strdup (url_use_path);
+	context->out_discovered_sources = NULL;
+	context->out_calendar_user_addresses = NULL;
+
+	return context;
+}
+
+static void
+e_webdav_discover_context_free (gpointer ptr)
+{
+	EWebDAVDiscoverContext *context = ptr;
+
+	if (!context)
+		return;
+
+	g_clear_object (&context->collection);
+	g_free (context->url_use_path);
+	e_webdav_discover_free_discovered_sources (context->out_discovered_sources);
+	g_slist_free_full (context->out_calendar_user_addresses, g_free);
+	g_free (context);
+}
+
+static gchar *
+e_webdav_discover_make_href_full_uri (SoupURI *base_uri,
+				      const gchar *href)
+{
+	SoupURI *soup_uri;
+	gchar *full_uri;
+
+	if (!base_uri || !href)
+		return g_strdup (href);
+
+	if (strstr (href, "://"))
+		return g_strdup (href);
+
+	soup_uri = soup_uri_copy (base_uri);
+	soup_uri_set_path (soup_uri, href);
+	soup_uri_set_user (soup_uri, NULL);
+	soup_uri_set_password (soup_uri, NULL);
+
+	full_uri = soup_uri_to_string (soup_uri, FALSE);
+
+	soup_uri_free (soup_uri);
+
+	return full_uri;
+}
+
+static void
+e_webdav_discover_redirect (SoupMessage *message,
+			    SoupSession *session)
+{
+	SoupURI *soup_uri;
+	const gchar *location;
+
+	if (!SOUP_STATUS_IS_REDIRECTION (message->status_code))
+		return;
+
+	location = soup_message_headers_get_list (message->response_headers, "Location");
+
+	if (location == NULL)
+		return;
+
+	soup_uri = soup_uri_new_with_base (soup_message_get_uri (message), location);
+
+	if (soup_uri == NULL) {
+		soup_message_set_status_full (
+			message, SOUP_STATUS_MALFORMED,
+			"Invalid Redirect URL");
+		return;
+	}
+
+	soup_message_set_uri (message, soup_uri);
+	soup_session_requeue_message (session, message);
+
+	soup_uri_free (soup_uri);
+}
+
+static gconstpointer
+compat_libxml_output_buffer_get_content (xmlOutputBufferPtr buf,
+                                         gsize *out_len)
+{
+#ifdef LIBXML2_NEW_BUFFER
+	*out_len = xmlOutputBufferGetSize (buf);
+	return xmlOutputBufferGetContent (buf);
+#else
+	*out_len = buf->buffer->use;
+	return buf->buffer->content;
+#endif
+}
+
+static G_GNUC_NULL_TERMINATED SoupMessage *
+e_webdav_discover_new_propfind (SoupSession *session,
+				SoupURI *soup_uri,
+				gint depth,
+				...)
+{
+	GHashTable *namespaces;
+	SoupMessage *message;
+	xmlDocPtr doc;
+	xmlNodePtr root;
+	xmlNodePtr node;
+	xmlNsPtr ns;
+	xmlOutputBufferPtr output;
+	gconstpointer content;
+	gsize length;
+	gpointer key;
+	va_list va;
+
+	/* Construct the XML content. */
+
+	doc = xmlNewDoc (XC ("1.0"));
+	node = xmlNewDocNode (doc, NULL, XC ("propfind"), NULL);
+
+	/* Build a hash table of namespace URIs to xmlNs structs. */
+	namespaces = g_hash_table_new (NULL, NULL);
+
+	ns = xmlNewNs (node, XC (NS_CALDAV), XC ("C"));
+	g_hash_table_insert (namespaces, (gpointer) NS_CALDAV, ns);
+
+	ns = xmlNewNs (node, XC (NS_CARDDAV), XC ("A"));
+	g_hash_table_insert (namespaces, (gpointer) NS_CARDDAV, ns);
+
+	ns = xmlNewNs (node, XC (NS_ICAL), XC ("IC"));
+	g_hash_table_insert (namespaces, (gpointer) NS_ICAL, ns);
+
+	/* Add WebDAV last since we use it below. */
+	ns = xmlNewNs (node, XC (NS_WEBDAV), XC ("D"));
+	g_hash_table_insert (namespaces, (gpointer) NS_WEBDAV, ns);
+
+	xmlSetNs (node, ns);
+	xmlDocSetRootElement (doc, node);
+
+	node = xmlNewTextChild (node, ns, XC ("prop"), NULL);
+
+	va_start (va, depth);
+	while ((key = va_arg (va, gpointer)) != NULL) {
+		xmlChar *name;
+
+		ns = g_hash_table_lookup (namespaces, key);
+		name = va_arg (va, xmlChar *);
+
+		if (ns != NULL && name != NULL)
+			xmlNewTextChild (node, ns, name, NULL);
+		else
+			g_warn_if_reached ();
+	}
+	va_end (va);
+
+	g_hash_table_destroy (namespaces);
+
+	/* Construct the SoupMessage. */
+
+	message = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, soup_uri);
+
+	soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
+
+	soup_message_headers_append (
+		message->request_headers,
+		"User-Agent", "Evolution/" VERSION);
+
+	soup_message_headers_append (
+		message->request_headers,
+		"Connection", "close");
+
+	soup_message_headers_append (
+		message->request_headers,
+		"Depth", (depth == 0) ? "0" : "1");
+
+	output = xmlAllocOutputBuffer (NULL);
+
+	root = xmlDocGetRootElement (doc);
+	xmlNodeDumpOutput (output, doc, root, 0, 1, NULL);
+	xmlOutputBufferFlush (output);
+
+	content = compat_libxml_output_buffer_get_content (output, &length);
+
+	soup_message_set_request (
+		message, "application/xml", SOUP_MEMORY_COPY,
+		content, length);
+
+	xmlOutputBufferClose (output);
+	xmlFreeDoc (doc);
+
+	soup_message_add_header_handler (
+		message, "got-body", "Location",
+		G_CALLBACK (e_webdav_discover_redirect), session);
+
+	return message;
+}
+
+static xmlXPathObjectPtr
+e_webdav_discover_get_xpath (xmlXPathContextPtr xp_ctx,
+			     const gchar *path_format,
+			     ...)
+{
+	xmlXPathObjectPtr xp_obj;
+	va_list va;
+	gchar *path;
+
+	va_start (va, path_format);
+	path = g_strdup_vprintf (path_format, va);
+	va_end (va);
+
+	xp_obj = xmlXPathEvalExpression (XC (path), xp_ctx);
+
+	g_free (path);
+
+	if (xp_obj == NULL)
+		return NULL;
+
+	if (xp_obj->type != XPATH_NODESET) {
+		xmlXPathFreeObject (xp_obj);
+		return NULL;
+	}
+
+	if (xmlXPathNodeSetGetLength (xp_obj->nodesetval) == 0) {
+		xmlXPathFreeObject (xp_obj);
+		return NULL;
+	}
+
+	return xp_obj;
+}
+
+static gchar *
+e_webdav_discover_get_xpath_string (xmlXPathContextPtr xp_ctx,
+				    const gchar *path_format,
+				    ...)
+{
+	xmlXPathObjectPtr xp_obj;
+	va_list va;
+	gchar *path;
+	gchar *expression;
+	gchar *string = NULL;
+
+	va_start (va, path_format);
+	path = g_strdup_vprintf (path_format, va);
+	va_end (va);
+
+	expression = g_strdup_printf ("string(%s)", path);
+	xp_obj = xmlXPathEvalExpression (XC (expression), xp_ctx);
+	g_free (expression);
+
+	g_free (path);
+
+	if (xp_obj == NULL)
+		return NULL;
+
+	if (xp_obj->type == XPATH_STRING)
+		string = g_strdup ((gchar *) xp_obj->stringval);
+
+	/* If the string is empty, return NULL. */
+	if (string != NULL && *string == '\0') {
+		g_free (string);
+		string = NULL;
+	}
+
+	xmlXPathFreeObject (xp_obj);
+
+	return string;
+}
+
+static void
+e_webdav_discover_authenticate_cb (SoupSession *session,
+				   SoupMessage *msg,
+				   SoupAuth *auth,
+				   gboolean retrying,
+				   gpointer user_data)
+{
+	EWebdavAuthenticator *authenticator = user_data;
+	ESource *source;
+
+	g_return_if_fail (authenticator != NULL);
+
+	if (retrying && E_IS_SOUP_AUTH_BEARER (auth))
+		return;
+
+	source = e_backend_get_source (E_BACKEND (authenticator->collection));
+
+	if (E_IS_SOUP_AUTH_BEARER (auth)) {
+		gchar *access_token = NULL;
+		gint expires_in_seconds = -1;
+		GError *local_error = NULL;
+
+		e_source_get_oauth2_access_token_sync (
+			source, NULL, &access_token,
+			&expires_in_seconds, &local_error);
+
+		e_soup_auth_bearer_set_access_token (
+			E_SOUP_AUTH_BEARER (auth),
+			access_token, expires_in_seconds);
+
+		if (local_error != NULL) {
+			soup_message_set_status_full (msg, SOUP_STATUS_FORBIDDEN, local_error->message);
+
+			g_error_free (local_error);
+		}
+
+		g_free (access_token);
+
+	} else {
+		if (retrying || !authenticator->password) {
+			ESourceRegistryServer *server;
+			EAuthenticationSession *auth_session;
+
+			server = e_collection_backend_ref_server (
+				authenticator->collection);
+
+			auth_session = e_source_registry_server_new_auth_session (
+				server,
+				E_SOURCE_AUTHENTICATOR (authenticator),
+				e_source_get_uid (source));
+			if (!e_source_registry_server_authenticate_sync (server, auth_session, NULL, NULL)) {
+				if (authenticator->password)
+					g_string_free (authenticator->password, TRUE);
+				authenticator->password = NULL;
+			}
+
+			g_object_unref (auth_session);
+			g_object_unref (server);
+		}
+
+		if (authenticator->username && authenticator->password)
+			soup_auth_authenticate (
+				auth, authenticator->username,
+				authenticator->password->str);
+	}
+}
+
+static gboolean
+e_webdav_discover_check_successful (SoupMessage *message,
+				    GError **error)
+{
+	GIOErrorEnum error_code;
+
+	g_return_val_if_fail (message != NULL, FALSE);
+
+	/* Loosely copied from the GVFS DAV backend. */
+
+	if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code))
+		return TRUE;
+
+	switch (message->status_code) {
+		case SOUP_STATUS_CANCELLED:
+			error_code = G_IO_ERROR_CANCELLED;
+			break;
+		case SOUP_STATUS_NOT_FOUND:
+			error_code = G_IO_ERROR_NOT_FOUND;
+			break;
+		case SOUP_STATUS_UNAUTHORIZED:
+		case SOUP_STATUS_PAYMENT_REQUIRED:
+		case SOUP_STATUS_FORBIDDEN:
+			error_code = G_IO_ERROR_PERMISSION_DENIED;
+			break;
+		case SOUP_STATUS_REQUEST_TIMEOUT:
+			error_code = G_IO_ERROR_TIMED_OUT;
+			break;
+		case SOUP_STATUS_CANT_RESOLVE:
+			error_code = G_IO_ERROR_HOST_NOT_FOUND;
+			break;
+		case SOUP_STATUS_NOT_IMPLEMENTED:
+			error_code = G_IO_ERROR_NOT_SUPPORTED;
+			break;
+		case SOUP_STATUS_INSUFFICIENT_STORAGE:
+			error_code = G_IO_ERROR_NO_SPACE;
+			break;
+		case SOUP_STATUS_SSL_FAILED:
+			g_set_error (
+				error, SOUP_HTTP_ERROR, message->status_code,
+				"HTTP Error: %s", message->reason_phrase);
+			return FALSE;
+		default:
+			error_code = G_IO_ERROR_FAILED;
+			break;
+	}
+
+	g_set_error (
+		error, G_IO_ERROR, error_code,
+		"HTTP Error: %s", message->reason_phrase);
+
+	return FALSE;
+}
+
+static xmlDocPtr
+e_webdav_discover_parse_xml (SoupMessage *message,
+			     const gchar *expected_name,
+			     GError **error)
+{
+	xmlDocPtr doc;
+	xmlNodePtr root;
+
+	if (!e_webdav_discover_check_successful (message, error))
+		return NULL;
+
+	doc = xmlReadMemory (
+		message->response_body->data,
+		message->response_body->length,
+		"response.xml", NULL,
+		XML_PARSE_NONET |
+		XML_PARSE_NOWARNING |
+		XML_PARSE_NOCDATA |
+		XML_PARSE_COMPACT);
+
+	if (doc == NULL) {
+		g_set_error_literal (
+			error, G_IO_ERROR, G_IO_ERROR_FAILED,
+			"Could not parse response");
+		return NULL;
+	}
+
+	root = xmlDocGetRootElement (doc);
+
+	if (root == NULL || root->children == NULL) {
+		g_set_error_literal (
+			error, G_IO_ERROR, G_IO_ERROR_FAILED,
+			"Empty response");
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+
+	if (g_strcmp0 ((gchar *) root->name, expected_name) != 0) {
+		g_set_error_literal (
+			error, G_IO_ERROR, G_IO_ERROR_FAILED,
+			"Unexpected reply from server");
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+
+	return doc;
+}
+
+static void
+e_webdav_discover_process_user_address_set (xmlXPathContextPtr xp_ctx,
+					    GSList **out_calendar_user_addresses)
+{
+	xmlXPathObjectPtr xp_obj;
+	gint ii, length;
+
+	if (!out_calendar_user_addresses)
+		return;
+
+	xp_obj = e_webdav_discover_get_xpath (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/C:calendar-user-address-set");
+
+	if (xp_obj == NULL)
+		return;
+
+	length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
+
+	for (ii = 0; ii < length; ii++) {
+		GSList *duplicate;
+		const gchar *address;
+		gchar *href;
+
+		href = e_webdav_discover_get_xpath_string (
+			xp_ctx,
+			"/D:multistatus"
+			"/D:response"
+			"/D:propstat"
+			"/D:prop"
+			"/C:calendar-user-address-set"
+			"/D:href[%d]", ii + 1);
+
+		if (href == NULL)
+			continue;
+
+		if (!g_str_has_prefix (href, "mailto:")) {
+			g_free (href);
+			continue;
+		}
+
+		/* strlen("mailto:") == 7 */
+		address = href + 7;
+
+		/* Avoid duplicates. */
+		duplicate = g_slist_find_custom (
+			*out_calendar_user_addresses,
+			address, (GCompareFunc) g_ascii_strcasecmp);
+
+		if (duplicate != NULL) {
+			g_free (href);
+			continue;
+		}
+
+		*out_calendar_user_addresses = g_slist_prepend (
+			*out_calendar_user_addresses, g_strdup (address));
+
+		g_free (href);
+	}
+
+	xmlXPathFreeObject (xp_obj);
+}
+
+static guint32
+e_webdav_discover_get_supported_component_set (xmlXPathContextPtr xp_ctx,
+					       gint index)
+{
+	xmlXPathObjectPtr xp_obj;
+	guint32 set = 0;
+	gint ii, length;
+
+	xp_obj = e_webdav_discover_get_xpath (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:prop"
+		"/C:supported-calendar-component-set"
+		"/C:comp", index);
+
+	/* If the property is not present, assume all component
+	 * types are supported.  (RFC 4791, Section 5.2.3) */
+	if (xp_obj == NULL)
+		return E_WEBDAV_DISCOVER_SUPPORTS_EVENTS |
+		       E_WEBDAV_DISCOVER_SUPPORTS_MEMOS |
+		       E_WEBDAV_DISCOVER_SUPPORTS_TASKS;
+
+	length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
+
+	for (ii = 0; ii < length; ii++) {
+		gchar *name;
+
+		name = e_webdav_discover_get_xpath_string (
+			xp_ctx,
+			"/D:multistatus"
+			"/D:response[%d]"
+			"/D:propstat"
+			"/D:prop"
+			"/C:supported-calendar-component-set"
+			"/C:comp[%d]"
+			"/@name", index, ii + 1);
+
+		if (name == NULL)
+			continue;
+
+		if (g_ascii_strcasecmp (name, "VEVENT") == 0)
+			set |= E_WEBDAV_DISCOVER_SUPPORTS_EVENTS;
+		else if (g_ascii_strcasecmp (name, "VJOURNAL") == 0)
+			set |= E_WEBDAV_DISCOVER_SUPPORTS_MEMOS;
+		else if (g_ascii_strcasecmp (name, "VTODO") == 0)
+			set |= E_WEBDAV_DISCOVER_SUPPORTS_TASKS;
+
+		g_free (name);
+	}
+
+	xmlXPathFreeObject (xp_obj);
+
+	return set;
+}
+
+static void
+e_webdav_discover_process_calendar_response (SoupMessage *message,
+					     xmlXPathContextPtr xp_ctx,
+					     gint index,
+					     GSList **out_discovered_sources)
+{
+	xmlXPathObjectPtr xp_obj;
+	guint32 comp_set;
+	gchar *color_spec;
+	gchar *display_name;
+	gchar *description;
+	gchar *href_encoded;
+	gchar *status_line;
+	guint status;
+	gboolean success;
+	EWebDAVDiscoveredSource *discovered_source;
+
+	if (!out_discovered_sources)
+		return;
+
+	status_line = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:status",
+		index);
+
+	if (status_line == NULL)
+		return;
+
+	success = soup_headers_parse_status_line (
+		status_line, NULL, &status, NULL);
+
+	g_free (status_line);
+
+	if (!success || status != SOUP_STATUS_OK)
+		return;
+
+	comp_set = e_webdav_discover_get_supported_component_set (xp_ctx, index);
+	if (comp_set == E_WEBDAV_DISCOVER_SUPPORTS_NONE)
+		return;
+
+	href_encoded = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:href",
+		index);
+
+	if (href_encoded == NULL)
+		return;
+
+	/* Make sure the resource is a calendar. */
+
+	xp_obj = e_webdav_discover_get_xpath (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:prop"
+		"/D:resourcetype"
+		"/C:calendar",
+		index);
+
+	if (xp_obj == NULL) {
+		g_free (href_encoded);
+		return;
+	}
+
+	xmlXPathFreeObject (xp_obj);
+
+	/* Get the display name or fall back to the href. */
+
+	display_name = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:prop"
+		"/D:displayname",
+		index);
+
+	if (display_name == NULL) {
+		gchar *href_decoded = soup_uri_decode (href_encoded);
+
+		if (href_decoded) {
+			gchar *cp;
+
+			/* Use the last non-empty path segment. */
+			while ((cp = strrchr (href_decoded, '/')) != NULL) {
+				if (*(cp + 1) == '\0')
+					*cp = '\0';
+				else {
+					display_name = g_strdup (cp + 1);
+					break;
+				}
+			}
+		}
+
+		g_free (href_decoded);
+	}
+
+	description = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:prop"
+		"/C:calendar-description",
+		index);
+
+	/* Get the color specification string. */
+
+	color_spec = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:prop"
+		"/IC:calendar-color",
+		index);
+
+	discovered_source = g_new0 (EWebDAVDiscoveredSource, 1);
+	discovered_source->href = e_webdav_discover_make_href_full_uri (soup_message_get_uri (message), href_encoded);
+	discovered_source->supports = comp_set;
+	discovered_source->display_name = g_strdup (display_name);
+	discovered_source->description = g_strdup (description);
+	discovered_source->color = g_strdup (color_spec);
+
+	*out_discovered_sources = g_slist_prepend (*out_discovered_sources, discovered_source);
+
+	g_free (href_encoded);
+	g_free (display_name);
+	g_free (description);
+	g_free (color_spec);
+}
+
+static gboolean
+e_webdav_discover_get_calendar_collection_details (SoupSession *session,
+						   SoupMessage *message,
+						   const gchar *path_or_uri,
+						   ESource *source,
+						   GSList **out_discovered_sources,
+						   GCancellable *cancellable,
+						   GError **error)
+{
+	xmlDocPtr doc;
+	xmlXPathContextPtr xp_ctx;
+	xmlXPathObjectPtr xp_obj;
+	SoupURI *soup_uri;
+
+	if (g_cancellable_is_cancelled (cancellable))
+		return FALSE;
+
+	soup_uri = soup_uri_new (path_or_uri);
+	if (!soup_uri ||
+	    !soup_uri_get_scheme (soup_uri) ||
+	    !soup_uri_get_host (soup_uri) ||
+	    !soup_uri_get_path (soup_uri) ||
+	    !*soup_uri_get_scheme (soup_uri) ||
+	    !*soup_uri_get_host (soup_uri) ||
+	    !*soup_uri_get_path (soup_uri)) {
+		/* it's a path only, not full uri */
+		if (soup_uri)
+			soup_uri_free (soup_uri);
+		soup_uri = soup_uri_copy (soup_message_get_uri (message));
+		soup_uri_set_path (soup_uri, path_or_uri);
+	}
+
+	message = e_webdav_discover_new_propfind (
+		session, soup_uri, DEPTH_1,
+		NS_WEBDAV, XC ("displayname"),
+		NS_WEBDAV, XC ("resourcetype"),
+		NS_CALDAV, XC ("calendar-description"),
+		NS_CALDAV, XC ("supported-calendar-component-set"),
+		NS_CALDAV, XC ("calendar-user-address-set"),
+		NS_ICAL,   XC ("calendar-color"),
+		NULL);
+
+	/* This takes ownership of the message. */
+	soup_session_send_message (session, message);
+
+	soup_uri_free (soup_uri);
+
+	doc = e_webdav_discover_parse_xml (message, "multistatus", error);
+	if (!doc)
+		return FALSE;
+
+	xp_ctx = xmlXPathNewContext (doc);
+	xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("A"), XC (NS_CARDDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("IC"), XC (NS_ICAL));
+
+	xp_obj = e_webdav_discover_get_xpath (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response");
+
+	if (xp_obj != NULL) {
+		gint length, ii;
+
+		length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
+
+		for (ii = 0; ii < length; ii++)
+			e_webdav_discover_process_calendar_response (
+				message, xp_ctx, ii + 1, out_discovered_sources);
+
+		xmlXPathFreeObject (xp_obj);
+	}
+
+	xmlXPathFreeContext (xp_ctx);
+	xmlFreeDoc (doc);
+
+	return TRUE;
+}
+
+static gboolean
+e_webdav_discover_process_calendar_home_set (SoupSession *session,
+					     SoupMessage *message,
+					     ESource *source,
+					     GSList **out_discovered_sources,
+					     GSList **out_calendar_user_addresses,
+					     GCancellable *cancellable,
+					     GError **error)
+{
+	SoupURI *soup_uri;
+	xmlDocPtr doc;
+	xmlXPathContextPtr xp_ctx;
+	xmlXPathObjectPtr xp_obj;
+	gchar *calendar_home_set;
+	gboolean success;
+
+	g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE);
+	g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
+	g_return_val_if_fail (out_discovered_sources != NULL, FALSE);
+	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+	if (g_cancellable_is_cancelled (cancellable))
+		return FALSE;
+
+	doc = e_webdav_discover_parse_xml (message, "multistatus", error);
+
+	if (!doc)
+		return FALSE;
+
+	xp_ctx = xmlXPathNewContext (doc);
+	xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("A"), XC (NS_CARDDAV));
+
+	/* Record any "C:calendar-user-address-set" properties. */
+	e_webdav_discover_process_user_address_set (xp_ctx, out_calendar_user_addresses);
+
+	/* Try to find the calendar home URL using the
+	 * following properties in order of preference:
+	 *
+	 *   "C:calendar-home-set"
+	 *   "D:current-user-principal"
+	 *   "D:principal-URL"
+	 *
+	 * If the second or third URL preference is used, rerun
+	 * the PROPFIND method on that URL at Depth=1 in hopes
+	 * of getting a proper "C:calendar-home-set" property.
+	 */
+
+	/* FIXME There can be multiple "D:href" elements for a
+	 *       "C:calendar-home-set".  We're only processing
+	 *       the first one.  Need to iterate over them. */
+
+	calendar_home_set = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/C:calendar-home-set"
+		"/D:href");
+
+	if (calendar_home_set != NULL)
+		goto get_collection_details;
+
+	g_free (calendar_home_set);
+
+	calendar_home_set = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/D:current-user-principal"
+		"/D:href");
+
+	if (calendar_home_set != NULL)
+		goto retry_propfind;
+
+	g_free (calendar_home_set);
+
+	calendar_home_set = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/D:principal-URL"
+		"/D:href");
+
+	if (calendar_home_set != NULL)
+		goto retry_propfind;
+
+	g_free (calendar_home_set);
+	calendar_home_set = NULL;
+
+	/* None of the aforementioned properties are present.  If the
+	 * user-supplied CalDAV URL is a calendar resource, use that. */
+
+	xp_obj = e_webdav_discover_get_xpath (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/D:resourcetype"
+		"/C:calendar");
+
+	if (xp_obj != NULL) {
+		soup_uri = soup_message_get_uri (message);
+
+		if (soup_uri->path != NULL && *soup_uri->path != '\0') {
+			gchar *slash;
+
+			soup_uri = soup_uri_copy (soup_uri);
+
+			slash = strrchr (soup_uri->path, '/');
+			while (slash != NULL && slash != soup_uri->path) {
+
+				if (slash[1] != '\0') {
+					slash[1] = '\0';
+					calendar_home_set =
+						g_strdup (soup_uri->path);
+					break;
+				}
+
+				slash[0] = '\0';
+				slash = strrchr (soup_uri->path, '/');
+			}
+
+			soup_uri_free (soup_uri);
+		}
+
+		xmlXPathFreeObject (xp_obj);
+	}
+
+	if (calendar_home_set == NULL || *calendar_home_set == '\0') {
+		g_free (calendar_home_set);
+		xmlXPathFreeContext (xp_ctx);
+		xmlFreeDoc (doc);
+		return TRUE;
+	}
+
+ get_collection_details:
+
+	xmlXPathFreeContext (xp_ctx);
+	xmlFreeDoc (doc);
+
+	if (!e_webdav_discover_get_calendar_collection_details (
+		session, message, calendar_home_set, source,
+		out_discovered_sources, cancellable, error)) {
+		g_free (calendar_home_set);
+		return FALSE;
+	}
+
+	g_free (calendar_home_set);
+
+	return TRUE;
+
+ retry_propfind:
+
+	xmlXPathFreeContext (xp_ctx);
+	xmlFreeDoc (doc);
+
+	soup_uri = soup_uri_copy (soup_message_get_uri (message));
+	soup_uri_set_path (soup_uri, calendar_home_set);
+
+	/* Note that we omit "D:resourcetype", "D:current-user-principal"
+	 * and "D:principal-URL" in order to short-circuit the recursion. */
+	message = e_webdav_discover_new_propfind (
+		session, soup_uri, DEPTH_1,
+		NS_CALDAV, XC ("calendar-home-set"),
+		NS_CALDAV, XC ("calendar-user-address-set"),
+		NULL);
+
+	/* This takes ownership of the message. */
+	soup_session_send_message (session, message);
+
+	soup_uri_free (soup_uri);
+
+	g_free (calendar_home_set);
+
+	success = e_webdav_discover_process_calendar_home_set (session, message, source,
+		out_discovered_sources, out_calendar_user_addresses,
+		cancellable, error);
+
+	g_object_unref (message);
+
+	return success;
+}
+
+static void
+e_webdav_discover_process_addressbook_response (SoupMessage *message,
+						xmlXPathContextPtr xp_ctx,
+						gint index,
+						GSList **out_discovered_sources)
+{
+	xmlXPathObjectPtr xp_obj;
+	gchar *display_name;
+	gchar *description;
+	gchar *href_encoded;
+	gchar *status_line;
+	guint status;
+	gboolean success;
+	EWebDAVDiscoveredSource *discovered_source;
+
+	if (!out_discovered_sources)
+		return;
+
+	status_line = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:status",
+		index);
+
+	if (status_line == NULL)
+		return;
+
+	success = soup_headers_parse_status_line (
+		status_line, NULL, &status, NULL);
+
+	g_free (status_line);
+
+	if (!success || status != SOUP_STATUS_OK)
+		return;
+
+	href_encoded = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:href",
+		index);
+
+	if (href_encoded == NULL)
+		return;
+
+	/* Make sure the resource is an addressbook. */
+
+	xp_obj = e_webdav_discover_get_xpath (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:prop"
+		"/D:resourcetype"
+		"/A:addressbook",
+		index);
+
+	if (xp_obj == NULL) {
+		g_free (href_encoded);
+		return;
+	}
+
+	xmlXPathFreeObject (xp_obj);
+
+	/* Get the display name or fall back to the href. */
+
+	display_name = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:prop"
+		"/D:displayname",
+		index);
+
+	if (display_name == NULL) {
+		gchar *href_decoded = soup_uri_decode (href_encoded);
+
+		if (href_decoded) {
+			gchar *cp;
+
+			/* Use the last non-empty path segment. */
+			while ((cp = strrchr (href_decoded, '/')) != NULL) {
+				if (*(cp + 1) == '\0')
+					*cp = '\0';
+				else {
+					display_name = g_strdup (cp + 1);
+					break;
+				}
+			}
+		}
+
+		g_free (href_decoded);
+	}
+
+	description = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response[%d]"
+		"/D:propstat"
+		"/D:prop"
+		"/A:addressbook-description",
+		index);
+
+	discovered_source = g_new0 (EWebDAVDiscoveredSource, 1);
+	discovered_source->href = e_webdav_discover_make_href_full_uri (soup_message_get_uri (message), href_encoded);
+	discovered_source->supports = E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS;
+	discovered_source->display_name = g_strdup (display_name);
+	discovered_source->description = g_strdup (description);
+	discovered_source->color = NULL;
+
+	*out_discovered_sources = g_slist_prepend (*out_discovered_sources, discovered_source);
+
+	g_free (href_encoded);
+	g_free (display_name);
+	g_free (description);
+}
+
+static gboolean
+e_webdav_discover_get_addressbook_collection_details (SoupSession *session,
+						      SoupMessage *message,
+						      const gchar *path_or_uri,
+						      ESource *source,
+						      GSList **out_discovered_sources,
+						      GCancellable *cancellable,
+						      GError **error)
+{
+	xmlDocPtr doc;
+	xmlXPathContextPtr xp_ctx;
+	xmlXPathObjectPtr xp_obj;
+	SoupURI *soup_uri;
+
+	if (g_cancellable_is_cancelled (cancellable))
+		return FALSE;
+
+	soup_uri = soup_uri_new (path_or_uri);
+	if (!soup_uri ||
+	    !soup_uri_get_scheme (soup_uri) ||
+	    !soup_uri_get_host (soup_uri) ||
+	    !soup_uri_get_path (soup_uri) ||
+	    !*soup_uri_get_scheme (soup_uri) ||
+	    !*soup_uri_get_host (soup_uri) ||
+	    !*soup_uri_get_path (soup_uri)) {
+		/* it's a path only, not full uri */
+		if (soup_uri)
+			soup_uri_free (soup_uri);
+		soup_uri = soup_uri_copy (soup_message_get_uri (message));
+		soup_uri_set_path (soup_uri, path_or_uri);
+	}
+
+	message = e_webdav_discover_new_propfind (
+		session, soup_uri, DEPTH_1,
+		NS_WEBDAV, XC ("displayname"),
+		NS_WEBDAV, XC ("resourcetype"),
+		NS_CARDDAV, XC ("addressbook-description"),
+		NULL);
+
+	/* This takes ownership of the message. */
+	soup_session_send_message (session, message);
+
+	soup_uri_free (soup_uri);
+
+	doc = e_webdav_discover_parse_xml (message, "multistatus", error);
+	if (!doc)
+		return FALSE;
+
+	xp_ctx = xmlXPathNewContext (doc);
+	xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("A"), XC (NS_CARDDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("IC"), XC (NS_ICAL));
+
+	xp_obj = e_webdav_discover_get_xpath (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response");
+
+	if (xp_obj != NULL) {
+		gint length, ii;
+
+		length = xmlXPathNodeSetGetLength (xp_obj->nodesetval);
+
+		for (ii = 0; ii < length; ii++)
+			e_webdav_discover_process_addressbook_response (
+				message, xp_ctx, ii + 1, out_discovered_sources);
+
+		xmlXPathFreeObject (xp_obj);
+	}
+
+	xmlXPathFreeContext (xp_ctx);
+	xmlFreeDoc (doc);
+
+	return TRUE;
+}
+
+static gboolean
+e_webdav_discover_process_addressbook_home_set (SoupSession *session,
+						SoupMessage *message,
+						ESource *source,
+						GSList **out_discovered_sources,
+						GCancellable *cancellable,
+						GError **error)
+{
+	SoupURI *soup_uri;
+	xmlDocPtr doc;
+	xmlXPathContextPtr xp_ctx;
+	xmlXPathObjectPtr xp_obj;
+	gchar *addressbook_home_set;
+	gboolean success;
+
+	g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE);
+	g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
+	g_return_val_if_fail (out_discovered_sources != NULL, FALSE);
+	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+	if (g_cancellable_is_cancelled (cancellable))
+		return FALSE;
+
+	doc = e_webdav_discover_parse_xml (message, "multistatus", error);
+
+	if (!doc)
+		return FALSE;
+
+	xp_ctx = xmlXPathNewContext (doc);
+	xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV));
+	xmlXPathRegisterNs (xp_ctx, XC ("A"), XC (NS_CARDDAV));
+
+	/* Try to find the addressbook home URL using the
+	 * following properties in order of preference:
+	 *
+	 *   "A:addressbook-home-set"
+	 *   "D:current-user-principal"
+	 *   "D:principal-URL"
+	 *
+	 * If the second or third URL preference is used, rerun
+	 * the PROPFIND method on that URL at Depth=1 in hopes
+	 * of getting a proper "A:addressbook-home-set" property.
+	 */
+
+	/* FIXME There can be multiple "D:href" elements for a
+	 *       "A:addressbook-home-set".  We're only processing
+	 *       the first one.  Need to iterate over them. */
+
+	addressbook_home_set = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/A:addressbook-home-set"
+		"/D:href");
+
+	if (addressbook_home_set != NULL)
+		goto get_collection_details;
+
+	g_free (addressbook_home_set);
+
+	addressbook_home_set = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/D:current-user-principal"
+		"/D:href");
+
+	if (addressbook_home_set != NULL)
+		goto retry_propfind;
+
+	g_free (addressbook_home_set);
+
+	addressbook_home_set = e_webdav_discover_get_xpath_string (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/D:principal-URL"
+		"/D:href");
+
+	if (addressbook_home_set != NULL)
+		goto retry_propfind;
+
+	g_free (addressbook_home_set);
+	addressbook_home_set = NULL;
+
+	/* None of the aforementioned properties are present.  If the
+	 * user-supplied CardDAV URL is an addressbook resource, use that. */
+
+	xp_obj = e_webdav_discover_get_xpath (
+		xp_ctx,
+		"/D:multistatus"
+		"/D:response"
+		"/D:propstat"
+		"/D:prop"
+		"/D:resourcetype"
+		"/A:addressbook");
+
+	if (xp_obj != NULL) {
+		soup_uri = soup_message_get_uri (message);
+
+		if (soup_uri->path != NULL && *soup_uri->path != '\0') {
+			gchar *slash;
+
+			soup_uri = soup_uri_copy (soup_uri);
+
+			slash = strrchr (soup_uri->path, '/');
+			while (slash != NULL && slash != soup_uri->path) {
+
+				if (slash[1] != '\0') {
+					slash[1] = '\0';
+					addressbook_home_set =
+						g_strdup (soup_uri->path);
+					break;
+				}
+
+				slash[0] = '\0';
+				slash = strrchr (soup_uri->path, '/');
+			}
+
+			soup_uri_free (soup_uri);
+		}
+
+		xmlXPathFreeObject (xp_obj);
+	}
+
+	if (addressbook_home_set == NULL || *addressbook_home_set == '\0') {
+		g_free (addressbook_home_set);
+		xmlXPathFreeContext (xp_ctx);
+		xmlFreeDoc (doc);
+		return TRUE;
+	}
+
+ get_collection_details:
+
+	xmlXPathFreeContext (xp_ctx);
+	xmlFreeDoc (doc);
+
+	if (!e_webdav_discover_get_addressbook_collection_details (
+		session, message, addressbook_home_set, source,
+		out_discovered_sources, cancellable, error)) {
+		g_free (addressbook_home_set);
+		return FALSE;
+	}
+
+	g_free (addressbook_home_set);
+
+	return TRUE;
+
+ retry_propfind:
+
+	xmlXPathFreeContext (xp_ctx);
+	xmlFreeDoc (doc);
+
+	soup_uri = soup_uri_copy (soup_message_get_uri (message));
+	soup_uri_set_path (soup_uri, addressbook_home_set);
+
+	/* Note that we omit "D:resourcetype", "D:current-user-principal"
+	 * and "D:principal-URL" in order to short-circuit the recursion. */
+	message = e_webdav_discover_new_propfind (
+		session, soup_uri, DEPTH_1,
+		NS_CARDDAV, XC ("addressbook-home-set"),
+		NULL);
+
+	/* This takes ownership of the message. */
+	soup_session_send_message (session, message);
+
+	soup_uri_free (soup_uri);
+
+	g_free (addressbook_home_set);
+
+	success = e_webdav_discover_process_addressbook_home_set (session, message, source,
+		out_discovered_sources, cancellable, error);
+
+	g_object_unref (message);
+
+	return success;
+}
+
+static ETrustPromptResponse
+trust_prompt_sync (const ENamedParameters *parameters,
+                   GCancellable *cancellable,
+                   GError **error)
+{
+	EUserPrompter *prompter;
+	gint response;
+
+	g_return_val_if_fail (
+		parameters != NULL, E_TRUST_PROMPT_RESPONSE_UNKNOWN);
+
+	prompter = e_user_prompter_new ();
+	g_return_val_if_fail (
+		prompter != NULL, E_TRUST_PROMPT_RESPONSE_UNKNOWN);
+
+	response = e_user_prompter_extension_prompt_sync (
+		prompter, "ETrustPrompt::trust-prompt",
+		parameters, NULL, cancellable, error);
+
+	g_object_unref (prompter);
+
+	if (response == 0)
+		return E_TRUST_PROMPT_RESPONSE_REJECT;
+	if (response == 1)
+		return E_TRUST_PROMPT_RESPONSE_ACCEPT;
+	if (response == 2)
+		return E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY;
+	if (response == -1)
+		return E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY;
+
+	return E_TRUST_PROMPT_RESPONSE_UNKNOWN;
+}
+
+static void
+e_webdav_discover_source_free (gpointer ptr)
+{
+	EWebDAVDiscoveredSource *discovered_source = ptr;
+
+	if (discovered_source) {
+		g_free (discovered_source->href);
+		g_free (discovered_source->display_name);
+		g_free (discovered_source->description);
+		g_free (discovered_source->color);
+		g_free (discovered_source);
+	}
+}
+
+void
+e_webdav_discover_free_discovered_sources (GSList *discovered_sources)
+{
+	g_slist_free_full (discovered_sources, e_webdav_discover_source_free);
+}
+
+static void
+e_webdav_discover_sources_thread (GTask *task,
+				  gpointer source_object,
+				  gpointer task_data,
+				  GCancellable *cancellable)
+{
+	EWebDAVDiscoverContext *context = task_data;
+	gboolean success;
+	GError *local_error = NULL;
+
+	g_return_if_fail (context != NULL);
+	g_return_if_fail (E_IS_COLLECTION_BACKEND (source_object));
+
+	success = e_webdav_discover_sources_sync (E_COLLECTION_BACKEND (source_object),
+		context->url_use_path, &context->out_discovered_sources,
+		&context->out_calendar_user_addresses, cancellable, &local_error);
+
+	if (local_error != NULL) {
+		g_task_return_error (task, local_error);
+	} else {
+		g_task_return_boolean (task, success);
+	}
+}
+
+void
+e_webdav_discover_sources (ECollectionBackend *collection,
+			   const gchar *url_use_path,
+			   GCancellable *cancellable,
+			   GAsyncReadyCallback callback,
+			   gpointer user_data)
+{
+	EWebDAVDiscoverContext *context;
+	GTask *task;
+
+	g_return_if_fail (E_IS_COLLECTION_BACKEND (collection));
+
+	context = e_webdav_discover_context_new (collection, url_use_path);
+
+	task = g_task_new (collection, cancellable, callback, user_data);
+	g_task_set_source_tag (task, e_webdav_discover_sources);
+	g_task_set_task_data (task, context, e_webdav_discover_context_free);
+
+	g_task_run_in_thread (task, e_webdav_discover_sources_thread);
+
+	g_object_unref (task);
+}
+
+gboolean
+e_webdav_discover_sources_finish (ECollectionBackend *collection,
+				  GAsyncResult *result,
+				  GSList **out_discovered_sources,
+				  GSList **out_calendar_user_addresses,
+				  GError **error)
+{
+	EWebDAVDiscoverContext *context;
+
+	g_return_val_if_fail (E_IS_COLLECTION_BACKEND (collection), FALSE);
+	g_return_val_if_fail (g_task_is_valid (result, collection), FALSE);
+
+	g_return_val_if_fail (
+		g_async_result_is_tagged (
+		result, e_webdav_discover_sources), FALSE);
+
+	if (!g_task_propagate_boolean (G_TASK (result), error))
+		return FALSE;
+
+	context = g_task_get_task_data (G_TASK (result));
+	g_return_val_if_fail (context != NULL, FALSE);
+
+	if (out_discovered_sources) {
+		*out_discovered_sources = context->out_discovered_sources;
+		context->out_discovered_sources = NULL;
+	}
+
+	if (out_calendar_user_addresses) {
+		*out_calendar_user_addresses = context->out_calendar_user_addresses;
+		context->out_calendar_user_addresses = NULL;
+	}
+
+	return TRUE;
+}
+
+static void
+e_webdav_discover_cancelled_cb (GCancellable *cancellable,
+				SoupSession *session)
+{
+	soup_session_abort (session);
+}
+
+gboolean
+e_webdav_discover_sources_sync (ECollectionBackend *collection,
+				const gchar *url_use_path,
+				GSList **out_discovered_sources,
+				GSList **out_calendar_user_addresses,
+				GCancellable *cancellable,
+				GError **error)
+{
+	ESourceWebdav *webdav_extension;
+	ESourceCollection *collection_extension;
+	ESource *source;
+	EWebdavAuthenticator *authenticator;
+	SoupSession *session;
+	SoupMessage *message;
+	SoupURI *soup_uri;
+	gulong cancelled_handler_id = 0, authenticate_handler_id;
+	gboolean success;
+
+	g_return_val_if_fail (E_IS_COLLECTION_BACKEND (collection), FALSE);
+
+	source = e_backend_get_source (E_BACKEND (collection));
+	collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+
+	if (url_use_path && (g_ascii_strncasecmp (url_use_path, "http://", 7) == 0 ||
+	    g_ascii_strncasecmp (url_use_path, "https://", 8) == 0)) {
+		soup_uri = soup_uri_new (url_use_path);
+		url_use_path = NULL;
+	} else {
+		g_return_val_if_fail (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND), FALSE);
+
+		webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+		soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
+	}
+
+	g_return_val_if_fail (soup_uri != NULL, FALSE);
+
+	if (url_use_path) {
+		GString *new_path;
+
+		/* Absolute path overrides whole path, while relative path is only appended. */
+		if (*url_use_path == '/') {
+			new_path = g_string_new (url_use_path);
+		} else {
+			const gchar *current_path;
+
+			current_path = soup_uri_get_path (soup_uri);
+			new_path = g_string_new (current_path ? current_path : "");
+			if (!new_path->len || new_path->str[new_path->len - 1] != '/')
+				g_string_append_c (new_path, '/');
+			g_string_append (new_path, url_use_path);
+		}
+
+		if (!new_path->len || new_path->str[new_path->len - 1] != '/')
+			g_string_append_c (new_path, '/');
+
+		soup_uri_set_path (soup_uri, new_path->str);
+
+		g_string_free (new_path, TRUE);
+	}
+
+	session = soup_session_new ();
+	message = e_webdav_discover_new_propfind (
+		session, soup_uri, DEPTH_0,
+		NS_WEBDAV, XC ("resourcetype"),
+		NS_WEBDAV, XC ("current-user-principal"),
+		NS_WEBDAV, XC ("principal-URL"),
+		NS_CALDAV, XC ("calendar-home-set"),
+		NS_CALDAV, XC ("calendar-user-address-set"),
+		NS_CARDDAV, XC ("addressbook-home-set"),
+		NS_CARDDAV, XC ("principal-address"),
+		NULL);
+
+	if (!message) {
+		soup_uri_free (soup_uri);
+		g_object_unref (session);
+		return FALSE;
+	}
+
+	if (g_getenv ("WEBDAV_DEBUG") != NULL) {
+		SoupLogger *logger;
+
+		logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, 100 * 1024 * 1024);
+		soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
+		g_object_unref (logger);
+	}
+
+	if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION) || e_source_has_extension (source, E_SOURCE_EXTENSION_GOA)) {
+		SoupSessionFeature *feature;
+		ESourceAuthentication *auth_extension;
+		gchar *auth_method;
+
+		feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER);
+		soup_session_feature_add_feature (feature, E_TYPE_SOUP_AUTH_BEARER);
+
+		success = TRUE;
+
+		auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
+		auth_method = e_source_authentication_dup_method (auth_extension);
+
+		if (g_strcmp0 (auth_method, "OAuth2") == 0 || e_source_has_extension (source, E_SOURCE_EXTENSION_GOA)) {
+			SoupAuth *soup_auth;
+			gchar *access_token = NULL;
+			gint expires_in_seconds = -1;
+
+			soup_auth = g_object_new (E_TYPE_SOUP_AUTH_BEARER, SOUP_AUTH_HOST, soup_uri->host, NULL);
+
+			success = e_source_get_oauth2_access_token_sync (
+				source, cancellable, &access_token,
+				&expires_in_seconds, error);
+
+			if (success) {
+				e_soup_auth_bearer_set_access_token (
+					E_SOUP_AUTH_BEARER (soup_auth),
+					access_token, expires_in_seconds);
+
+				soup_auth_manager_use_auth (
+					SOUP_AUTH_MANAGER (feature),
+					soup_uri, soup_auth);
+			}
+
+			g_free (access_token);
+			g_object_unref (soup_auth);
+		}
+
+		g_free (auth_method);
+
+		if (!success) {
+			soup_uri_free (soup_uri);
+			g_object_unref (message);
+			g_object_unref (session);
+			return FALSE;
+		}
+	}
+
+	authenticator = g_object_new (E_TYPE_WEBDAV_AUTHENTICATOR, NULL);
+	authenticator->collection = collection;
+	authenticator->username = e_source_collection_dup_identity (collection_extension);
+
+	authenticate_handler_id = g_signal_connect (session, "authenticate",
+		G_CALLBACK (e_webdav_discover_authenticate_cb), authenticator);
+
+	if (cancellable)
+		cancelled_handler_id = g_cancellable_connect (cancellable, G_CALLBACK (e_webdav_discover_cancelled_cb), session, NULL);
+
+	if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
+		GSList *calendars = NULL, *addressbooks = NULL;
+
+		if (soup_session_send_message (session, message) == SOUP_STATUS_SSL_FAILED) {
+			ETrustPromptResponse response;
+			ENamedParameters *parameters;
+			ESourceWebdav *extension;
+			ESource *source;
+
+			source = e_backend_get_source (E_BACKEND (collection));
+			extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+			parameters = e_named_parameters_new ();
+
+			/* this is the master source, thus there is no parent_source */
+			response = e_source_webdav_prepare_ssl_trust_prompt_with_parent (extension, message, NULL, parameters);
+			if (response == E_TRUST_PROMPT_RESPONSE_UNKNOWN) {
+				response = trust_prompt_sync (parameters, NULL, NULL);
+				if (response != E_TRUST_PROMPT_RESPONSE_UNKNOWN)
+					e_source_webdav_store_ssl_trust_prompt (extension, message, response);
+			}
+
+			e_named_parameters_free (parameters);
+
+			if (response == E_TRUST_PROMPT_RESPONSE_ACCEPT ||
+			    response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY) {
+				g_object_set (session, SOUP_SESSION_SSL_STRICT, FALSE, NULL);
+
+				soup_session_send_message (session, message);
+			}
+		}
+
+		success = e_webdav_discover_process_calendar_home_set (session, message, source,
+			&calendars, out_calendar_user_addresses, cancellable, error);
+		if (success)
+			success = e_webdav_discover_process_addressbook_home_set (session, message, source,
+				&addressbooks, cancellable, error);
+
+		if (success && !calendars && !g_cancellable_is_cancelled (cancellable)) {
+			g_clear_object (&message);
+
+			soup_uri_set_path (soup_uri, "/.well-known/caldav");
+
+			message = e_webdav_discover_new_propfind (
+				session, soup_uri, DEPTH_0,
+				NS_WEBDAV, XC ("resourcetype"),
+				NS_WEBDAV, XC ("current-user-principal"),
+				NS_WEBDAV, XC ("principal-URL"),
+				NS_CALDAV, XC ("calendar-home-set"),
+				NS_CALDAV, XC ("calendar-user-address-set"),
+				NULL);
+
+			if (message) {
+				soup_session_send_message (session, message);
+
+				/* Ignore errors here */
+				e_webdav_discover_process_calendar_home_set (session, message, source,
+					&calendars, out_calendar_user_addresses, cancellable, NULL);
+			}
+		}
+
+		if (success && !addressbooks && !g_cancellable_is_cancelled (cancellable)) {
+			g_clear_object (&message);
+
+			soup_uri_set_path (soup_uri, "/.well-known/carddav");
+
+			message = e_webdav_discover_new_propfind (
+				session, soup_uri, DEPTH_0,
+				NS_WEBDAV, XC ("resourcetype"),
+				NS_WEBDAV, XC ("current-user-principal"),
+				NS_WEBDAV, XC ("principal-URL"),
+				NS_CARDDAV, XC ("addressbook-home-set"),
+				NS_CARDDAV, XC ("principal-address"),
+				NULL);
+
+			if (message) {
+				soup_session_send_message (session, message);
+
+				/* Ignore errors here */
+				e_webdav_discover_process_addressbook_home_set (session, message, source,
+					&addressbooks, cancellable, NULL);
+			}
+		}
+
+		if (out_discovered_sources) {
+			if (calendars)
+				*out_discovered_sources = g_slist_concat (*out_discovered_sources, calendars);
+			if (addressbooks)
+				*out_discovered_sources = g_slist_concat (*out_discovered_sources, addressbooks);
+		} else {
+			e_webdav_discover_free_discovered_sources (calendars);
+			e_webdav_discover_free_discovered_sources (addressbooks);
+		}
+
+		if (out_calendar_user_addresses && *out_calendar_user_addresses)
+			*out_calendar_user_addresses = g_slist_reverse (*out_calendar_user_addresses);
+
+		if (out_discovered_sources && *out_discovered_sources)
+			*out_discovered_sources = g_slist_reverse (*out_discovered_sources);
+	} else {
+		success = FALSE;
+	}
+
+	if (cancellable && cancelled_handler_id)
+		g_cancellable_disconnect (cancellable, cancelled_handler_id);
+
+	if (authenticate_handler_id)
+		g_signal_handler_disconnect (session, authenticate_handler_id);
+
+	g_clear_object (&authenticator);
+	soup_uri_free (soup_uri);
+	g_clear_object (&message);
+	g_object_unref (session);
+
+	return success;
+}
diff -up evolution-data-server-3.12.11/modules/google-backend/e-webdav-discover.h.google-calendar-discover evolution-data-server-3.12.11/modules/google-backend/e-webdav-discover.h
--- evolution-data-server-3.12.11/modules/google-backend/e-webdav-discover.h.google-calendar-discover	2015-07-07 16:23:23.651982522 +0200
+++ evolution-data-server-3.12.11/modules/google-backend/e-webdav-discover.h	2015-07-07 16:23:23.650982526 +0200
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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/>.
+ *
+ */
+
+#ifndef E_WEBDAV_DISCOVER_H
+#define E_WEBDAV_DISCOVER_H
+
+#include <glib.h>
+
+#include <libedataserver/libedataserver.h>
+#include <libebackend/libebackend.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E_WEBDAV_DISCOVER_SUPPORTS_NONE		= 0,
+	E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS	= 1 << 0,
+	E_WEBDAV_DISCOVER_SUPPORTS_EVENTS	= 1 << 1,
+	E_WEBDAV_DISCOVER_SUPPORTS_MEMOS	= 1 << 2,
+	E_WEBDAV_DISCOVER_SUPPORTS_TASKS	= 1 << 3
+} EWebDAVDiscoverSupports;
+
+typedef struct _EWebDAVDiscoveredSource {
+	gchar *href;
+	guint32 supports;
+	gchar *display_name;
+	gchar *description;
+	gchar *color;
+} EWebDAVDiscoveredSource;
+
+void		e_webdav_discover_free_discovered_sources
+							(GSList *discovered_sources);
+
+void		e_webdav_discover_sources		(ECollectionBackend *collection,
+							 const gchar *url_use_path,
+							 GCancellable *cancellable,
+							 GAsyncReadyCallback callback,
+							 gpointer user_data);
+
+gboolean	e_webdav_discover_sources_finish	(ECollectionBackend *collection,
+							 GAsyncResult *result,
+							 GSList **out_discovered_sources,
+							 GSList **out_calendar_user_addresses,
+							 GError **error);
+
+gboolean	e_webdav_discover_sources_sync		(ECollectionBackend *collection,
+							 const gchar *url_use_path,
+							 GSList **out_discovered_sources,
+							 GSList **out_calendar_user_addresses,
+							 GCancellable *cancellable,
+							 GError **error);
+
+G_END_DECLS
+
+#endif /* E_WEBDAV_DISCOVER_H */
diff -up evolution-data-server-3.12.11/modules/google-backend/Makefile.am.google-calendar-discover evolution-data-server-3.12.11/modules/google-backend/Makefile.am
--- evolution-data-server-3.12.11/modules/google-backend/Makefile.am.google-calendar-discover	2014-09-01 11:17:11.000000000 +0200
+++ evolution-data-server-3.12.11/modules/google-backend/Makefile.am	2015-07-07 16:23:23.651982522 +0200
@@ -12,6 +12,8 @@ module_google_backend_la_CPPFLAGS = \
 	$(NULL)
 
 module_google_backend_la_SOURCES = \
+	e-webdav-discover.h \
+	e-webdav-discover.c \
 	module-google-backend.c \
 	$(NULL)
 
diff -up evolution-data-server-3.12.11/modules/google-backend/module-google-backend.c.google-calendar-discover evolution-data-server-3.12.11/modules/google-backend/module-google-backend.c
--- evolution-data-server-3.12.11/modules/google-backend/module-google-backend.c.google-calendar-discover	2014-09-01 11:17:12.000000000 +0200
+++ evolution-data-server-3.12.11/modules/google-backend/module-google-backend.c	2015-07-07 16:23:46.916848261 +0200
@@ -24,6 +24,8 @@
 #include <gdata/gdata.h>
 #endif
 
+#include "e-webdav-discover.h"
+
 /* This macro was introduced in libgdata 0.11,
  * but we currently only require libgdata 0.10. */
 #ifndef GDATA_CHECK_VERSION
@@ -52,18 +54,6 @@
 #define GOOGLE_SMTP_PORT		587
 #define GOOGLE_SMTP_SECURITY_METHOD	METHOD (STARTTLS_ON_STANDARD_PORT)
 
-/* Calendar Configuration Details */
-#define GOOGLE_CALENDAR_BACKEND_NAME	"caldav"
-#define GOOGLE_CALENDAR_RESOURCE_ID	"Calendar"
-
-/* CalDAV v1 Configuration Details */
-#define GOOGLE_CALDAV_V1_HOST		"www.google.com"
-#define GOOGLE_CALDAV_V1_PATH		"/calendar/dav/%s/events"
-
-/* CalDAV v2 Configuration Details */
-#define GOOGLE_CALDAV_V2_HOST		"apidata.googleusercontent.com"
-#define GOOGLE_CALDAV_V2_PATH		"/caldav/v2/%s/events"
-
 /* Contacts Configuration Details */
 #define GOOGLE_CONTACTS_BACKEND_NAME	"google"
 #define GOOGLE_CONTACTS_HOST		"www.google.com"
@@ -118,44 +108,22 @@ google_backend_calendar_update_auth_meth
 {
 	EOAuth2Support *oauth2_support;
 	ESourceAuthentication *auth_extension;
-	ESourceWebdav *webdav_extension;
-	const gchar *extension_name;
-	const gchar *host;
 	const gchar *method;
-	const gchar *path_format;
-	gchar *path;
-	gchar *user;
 
-	oauth2_support = e_server_side_source_ref_oauth2_support (
-		E_SERVER_SIDE_SOURCE (source));
+	oauth2_support = e_server_side_source_ref_oauth2_support (E_SERVER_SIDE_SOURCE (source));
 
 	/* The host name and WebDAV resource path depend on the
 	 * authentication method used, so update those here too. */
 
 	if (oauth2_support != NULL) {
 		method = "OAuth2";
-		host = GOOGLE_CALDAV_V2_HOST;
-		path_format = GOOGLE_CALDAV_V2_PATH;
 	} else {
 		method = "plain/password";
-		host = GOOGLE_CALDAV_V1_HOST;
-		path_format = GOOGLE_CALDAV_V1_PATH;
 	}
 
-	extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
-	auth_extension = e_source_get_extension (source, extension_name);
-	e_source_authentication_set_host (auth_extension, host);
+	auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
 	e_source_authentication_set_method (auth_extension, method);
 
-	extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
-	webdav_extension = e_source_get_extension (source, extension_name);
-
-	user = e_source_authentication_dup_user (auth_extension);
-	path = g_strdup_printf (path_format, (user != NULL) ? user : "");
-	e_source_webdav_set_resource_path (webdav_extension, path);
-	g_free (path);
-	g_free (user);
-
 	g_clear_object (&oauth2_support);
 }
 
@@ -179,80 +147,174 @@ google_backend_contacts_update_auth_meth
 }
 
 static void
-google_backend_add_calendar (ECollectionBackend *backend)
+google_add_uid_to_hashtable (gpointer source,
+			     gpointer known_sources)
 {
+	ESourceResource *resource;
+	gchar *uid, *rid;
+
+	if (!e_source_has_extension (source, E_SOURCE_EXTENSION_RESOURCE))
+		return;
+
+	resource = e_source_get_extension (source, E_SOURCE_EXTENSION_RESOURCE);
+
+	uid = e_source_dup_uid (source);
+	if (!uid || !*uid) {
+		g_free (uid);
+		return;
+	}
+
+	rid = e_source_resource_dup_identity (resource);
+	if (!rid || !*rid) {
+		g_free (rid);
+		g_free (uid);
+		return;
+	}
+
+	g_hash_table_insert (known_sources, rid, uid);
+}
+
+static void
+google_remove_unknown_sources_cb (gpointer resource_id,
+				  gpointer uid,
+				  gpointer user_data)
+{
+	ESourceRegistryServer *server = user_data;
 	ESource *source;
-	ESource *collection_source;
-	ESourceRegistryServer *server;
-	ESourceExtension *extension;
-	ESourceCollection *collection_extension;
-	const gchar *backend_name;
-	const gchar *extension_name;
-	const gchar *resource_id;
 
-	/* FIXME As a future enhancement, we should query Google
-	 *       for a list of user calendars and add them to the
-	 *       collection with matching display names and colors. */
+	source = e_source_registry_server_ref_source (server, uid);
 
-	/* NOTE: Host name and WebDAV resource path are set in
-	 *       google_backend_calendar_update_auth_method(),
-	 *       since they depend on the auth method used. */
+	if (source) {
+		e_source_remove_sync (source, NULL, NULL);
+		g_object_unref (source);
+	}
+}
 
-	collection_source = e_backend_get_source (E_BACKEND (backend));
+static void
+google_add_found_source (ECollectionBackend *collection,
+			   EWebDAVDiscoverSupports source_type,
+			   SoupURI *uri,
+			   const gchar *display_name,
+			   const gchar *color,
+			   GHashTable *known_sources)
+{
+	ESourceRegistryServer *server;
+	ESourceBackend *backend;
+	ESource *source = NULL;
+	const gchar *backend_name = NULL;
+	const gchar *provider = NULL;
+	const gchar *identity_prefix = NULL;
+	const gchar *source_uid;
+	gboolean is_new;
+	gchar *url;
+	gchar *identity;
+
+	g_return_if_fail (collection != NULL);
+	g_return_if_fail (uri != NULL);
+	g_return_if_fail (display_name != NULL);
+	g_return_if_fail (known_sources != NULL);
+
+	switch (source_type) {
+	case E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS:
+		backend_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+		provider = "webdav";
+		identity_prefix = "contacts";
+		break;
+	case E_WEBDAV_DISCOVER_SUPPORTS_EVENTS:
+		backend_name = E_SOURCE_EXTENSION_CALENDAR;
+		provider = "caldav";
+		identity_prefix = "events";
+		break;
+	case E_WEBDAV_DISCOVER_SUPPORTS_MEMOS:
+		backend_name = E_SOURCE_EXTENSION_MEMO_LIST;
+		provider = "caldav";
+		identity_prefix = "memos";
+		break;
+	case E_WEBDAV_DISCOVER_SUPPORTS_TASKS:
+		backend_name = E_SOURCE_EXTENSION_TASK_LIST;
+		provider = "caldav";
+		identity_prefix = "tasks";
+		break;
+	default:
+		g_warn_if_reached ();
+		return;
+	}
 
-	resource_id = GOOGLE_CALENDAR_RESOURCE_ID;
-	source = e_collection_backend_new_child (backend, resource_id);
-	e_source_set_display_name (source, _("Calendar"));
+	g_return_if_fail (backend_name != NULL);
 
-	collection_extension = e_source_get_extension (
-		collection_source, E_SOURCE_EXTENSION_COLLECTION);
+	server = e_collection_backend_ref_server (collection);
 
-	/* Configure the calendar source. */
+	url = soup_uri_to_string (uri, FALSE);
+	identity = g_strconcat (identity_prefix, "::", url, NULL);
+	source_uid = g_hash_table_lookup (known_sources, identity);
+	is_new = !source_uid;
+	if (is_new) {
+		ESource *master_source;
+
+		source = e_collection_backend_new_child (collection, identity);
+		g_warn_if_fail (source != NULL);
+
+		if (source) {
+			ESourceCollection *collection_extension;
+			ESourceAuthentication *child_auth;
+			ESourceResource *resource;
+			ESourceWebdav *master_webdav, *child_webdav;
+
+			master_source = e_backend_get_source (E_BACKEND (collection));
+			master_webdav = e_source_get_extension (master_source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+			collection_extension = e_source_get_extension (master_source, E_SOURCE_EXTENSION_COLLECTION);
+			child_auth = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
+			child_webdav = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+			resource = e_source_get_extension (source, E_SOURCE_EXTENSION_RESOURCE);
+
+			e_source_authentication_set_user (child_auth, e_source_collection_get_identity (collection_extension));
+			e_source_webdav_set_soup_uri (child_webdav, uri);
+			e_source_resource_set_identity (resource, identity);
+
+			/* inherit ssl trust options */
+			e_source_webdav_set_ssl_trust (child_webdav, e_source_webdav_get_ssl_trust (master_webdav));
+		}
+	} else {
+		source = e_source_registry_server_ref_source (server, source_uid);
+		g_warn_if_fail (source != NULL);
 
-	backend_name = GOOGLE_CALENDAR_BACKEND_NAME;
+		g_hash_table_remove (known_sources, identity);
+	}
 
-	extension_name = E_SOURCE_EXTENSION_CALENDAR;
-	extension = e_source_get_extension (source, extension_name);
+	g_free (identity);
+	g_free (url);
 
-	e_source_backend_set_backend_name (
-		E_SOURCE_BACKEND (extension), backend_name);
+	/* these properties are synchronized always */
+	if (source) {
+		gint rr, gg, bb;
 
-	extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
-	extension = e_source_get_extension (source, extension_name);
+		backend = e_source_get_extension (source, backend_name);
+		e_source_backend_set_backend_name (backend, provider);
 
-	g_object_bind_property (
-		collection_extension, "identity",
-		extension, "user",
-		G_BINDING_SYNC_CREATE);
+		e_source_set_display_name (source, display_name);
+		e_source_set_enabled (source, TRUE);
 
-	/* Make sure the WebDAV resource path is up-to-date, since
-	 * it's built from the "user" property that we just set. */
-	google_backend_calendar_update_auth_method (source);
+		/* Also check whether the color format is as expected; it cannot
+		   be used gdk_rgba_parse here, because it required gdk/gtk. */
+		if (is_new && source_type != E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS && color &&
+		    sscanf (color, "#%02x%02x%02x", &rr, &gg, &bb) == 3) {
+			gchar *safe_color;
 
-	extension_name = E_SOURCE_EXTENSION_SECURITY;
-	extension = e_source_get_extension (source, extension_name);
+			/* In case an #RRGGBBAA is returned */
+			safe_color = g_strdup_printf ("#%02x%02x%02x", rr, gg, bb);
 
-	e_source_security_set_secure (
-		E_SOURCE_SECURITY (extension), TRUE);
+			e_source_selectable_set_color (E_SOURCE_SELECTABLE (backend), safe_color);
 
-	extension_name = E_SOURCE_EXTENSION_ALARMS;
-	extension = e_source_get_extension (source, extension_name);
-	if (!e_source_alarms_get_last_notified (E_SOURCE_ALARMS (extension))) {
-		GTimeVal today_tv;
-		gchar *today;
-
-		g_get_current_time (&today_tv);
-		today = g_time_val_to_iso8601 (&today_tv);
-		e_source_alarms_set_last_notified (
-			E_SOURCE_ALARMS (extension), today);
-		g_free (today);
+			g_free (safe_color);
+		}
+
+		if (is_new)
+			e_source_registry_server_add_source (server, source);
+
+		g_object_unref (source);
 	}
 
-	server = e_collection_backend_ref_server (backend);
-	e_source_registry_server_add_source (server, source);
 	g_object_unref (server);
-
-	g_object_unref (source);
 }
 
 #if GDATA_CHECK_VERSION(0,15,1)
@@ -365,26 +427,140 @@ google_backend_add_contacts (ECollection
 	g_object_unref (source);
 }
 
+static gpointer
+google_populate_thread (gpointer data)
+{
+	ECollectionBackend *collection = data;
+	ESourceCollection *collection_extension;
+	ESourceGoa *goa_extension = NULL;
+	ESource *source;
+	GHashTable *known_sources;
+	GList *sources;
+	GSList *discovered_sources = NULL;
+	const gchar *calendar_url;
+	gboolean any_success = FALSE;
+	GError *local_error = NULL;
+
+	g_return_val_if_fail (collection != NULL, E_SOURCE_AUTHENTICATION_ERROR);
+
+	source = e_backend_get_source (E_BACKEND (collection));
+	collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+	if (e_source_has_extension (source, E_SOURCE_EXTENSION_GOA))
+		goa_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_GOA);
+
+	g_return_val_if_fail (e_source_collection_get_calendar_enabled (collection_extension) ||
+		e_source_collection_get_contacts_enabled (collection_extension), E_SOURCE_AUTHENTICATION_ERROR);
+
+	/* resource-id => source's UID */
+	known_sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+	sources = e_collection_backend_list_calendar_sources (collection);
+	g_list_foreach (sources, google_add_uid_to_hashtable, known_sources);
+	g_list_free_full (sources, g_object_unref);
+
+	if (goa_extension)
+		calendar_url = e_source_goa_get_calendar_url (goa_extension);
+	else
+		calendar_url = "https://www.google.com/calendar/dav/";
+
+	if (e_source_collection_get_calendar_enabled (collection_extension) && calendar_url &&
+	    e_webdav_discover_sources_sync (collection, calendar_url,
+		&discovered_sources, NULL, NULL, &local_error)) {
+		EWebDAVDiscoverSupports source_types[] = {
+			E_WEBDAV_DISCOVER_SUPPORTS_EVENTS,
+			E_WEBDAV_DISCOVER_SUPPORTS_MEMOS,
+			E_WEBDAV_DISCOVER_SUPPORTS_TASKS
+		};
+		GSList *link;
+		gint ii;
+
+		for (link = discovered_sources; link; link = g_slist_next (link)) {
+			EWebDAVDiscoveredSource *discovered_source = link->data;
+			SoupURI *soup_uri;
+
+			if (!discovered_source || !discovered_source->href || !discovered_source->display_name)
+				continue;
+
+			soup_uri = soup_uri_new (discovered_source->href);
+			if (!soup_uri)
+				continue;
+
+			for (ii = 0; ii < G_N_ELEMENTS (source_types); ii++) {
+				if ((discovered_source->supports & source_types[ii]) == source_types[ii])
+					google_add_found_source (collection, source_types[ii], soup_uri,
+						discovered_source->display_name, discovered_source->color, known_sources);
+			}
+
+			soup_uri_free (soup_uri);
+		}
+
+		any_success = TRUE;
+	}
+
+	if (any_success) {
+		ESourceRegistryServer *server;
+
+		server = e_collection_backend_ref_server (collection);
+
+		g_hash_table_foreach (known_sources, google_remove_unknown_sources_cb, server);
+
+		g_object_unref (server);
+	}
+
+	g_hash_table_destroy (known_sources);
+
+	if (local_error) {
+		g_print ("%s: Failed with: %s\n", G_STRFUNC, local_error->message);
+		g_error_free (local_error);
+	}
+
+	return NULL;
+}
+
 static void
 google_backend_populate (ECollectionBackend *backend)
 {
 	GList *list, *link;
-	gboolean have_calendar = FALSE, have_tasks = FALSE;
+	gboolean have_tasks = FALSE;
+	ESourceRegistryServer *server;
+	ESourceCollection *collection_extension;
+	ESource *source;
 
-	list = e_collection_backend_list_calendar_sources (backend);
+	server = e_collection_backend_ref_server (backend);
+	list = e_collection_backend_claim_all_resources (backend);
 	for (link = list; link; link = g_list_next (link)) {
 		ESource *source = link->data;
+		ESource *child = NULL;
 
-		have_calendar = have_calendar || e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR);
-		have_tasks = have_tasks || e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
+		if (e_source_has_extension (source, E_SOURCE_EXTENSION_RESOURCE)) {
+			ESourceResource *resource;
 
-		if (have_calendar && have_tasks)
-			break;
+			resource = e_source_get_extension (source, E_SOURCE_EXTENSION_RESOURCE);
+			child = e_collection_backend_new_child (backend, e_source_resource_get_identity (resource));
+#if GDATA_CHECK_VERSION(0,15,1)
+		} else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) {
+			child = e_collection_backend_new_child (backend, GOOGLE_TASKS_RESOURCE_ID);
+#endif
+		} else if (e_source_has_extension (source, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
+			child = e_collection_backend_new_child (backend, GOOGLE_CONTACTS_RESOURCE_ID);
+		}
+
+		if (child) {
+			e_source_registry_server_add_source (server, source);
+			g_object_unref (child);
+		}
 	}
-	g_list_free_full (list, (GDestroyNotify) g_object_unref);
 
-	if (!have_calendar)
-		google_backend_add_calendar (backend);
+	g_list_free_full (list, g_object_unref);
+	g_object_unref (server);
+
+	list = e_collection_backend_list_calendar_sources (backend);
+	for (link = list; link && !have_tasks; link = g_list_next (link)) {
+		ESource *source = link->data;
+
+		have_tasks = have_tasks || e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
+	}
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
 
 #if GDATA_CHECK_VERSION(0,15,1)
 	if (!have_tasks)
@@ -397,8 +573,17 @@ google_backend_populate (ECollectionBack
 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
 
 	/* Chain up to parent's populate() method. */
-	E_COLLECTION_BACKEND_CLASS (e_google_backend_parent_class)->
-		populate (backend);
+	E_COLLECTION_BACKEND_CLASS (e_google_backend_parent_class)->populate (backend);
+
+	source = e_backend_get_source (E_BACKEND (backend));
+	collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+
+	if (e_source_collection_get_calendar_enabled (collection_extension)) {
+		GThread *thread;
+
+		thread = g_thread_new (NULL, google_populate_thread, g_object_ref (backend));
+		g_thread_unref (thread);
+	}
 }
 
 static gchar *
@@ -412,7 +597,7 @@ google_backend_dup_resource_id (ECollect
 
 	extension_name = E_SOURCE_EXTENSION_CALENDAR;
 	if (e_source_has_extension (child_source, extension_name))
-		return g_strdup (GOOGLE_CALENDAR_RESOURCE_ID);
+		return E_COLLECTION_BACKEND_CLASS (e_google_backend_parent_class)->dup_resource_id (backend, child_source);
 
 	extension_name = E_SOURCE_EXTENSION_TASK_LIST;
 	if (e_source_has_extension (child_source, extension_name))
diff -up evolution-data-server-3.12.11/modules/owncloud-backend/module-owncloud-backend.c.google-calendar-discover evolution-data-server-3.12.11/modules/owncloud-backend/module-owncloud-backend.c
--- evolution-data-server-3.12.11/modules/owncloud-backend/module-owncloud-backend.c.google-calendar-discover	2014-05-22 08:45:46.000000000 +0200
+++ evolution-data-server-3.12.11/modules/owncloud-backend/module-owncloud-backend.c	2015-07-07 16:23:23.652982519 +0200
@@ -81,7 +81,7 @@ owncloud_remove_unknown_sources_cb (gpoi
 	source = e_source_registry_server_ref_source (server, uid);
 
 	if (source) {
-		e_source_registry_server_remove_source (server, source);
+		e_source_remove_sync (source, NULL, NULL);
 		g_object_unref (source);
 	}
 }
@@ -181,9 +181,11 @@ owncloud_source_found_cb (ECollectionBac
 		e_source_backend_set_backend_name (backend, provider);
 
 		e_source_set_display_name (source, display_name);
+		e_source_set_enabled (source, TRUE);
+
 		/* Also check whether the color format is as expected; it cannot
 		   be used gdk_rgba_parse here, because it required gdk/gtk. */
-		if (source_type != OwnCloud_Source_Contacts && color &&
+		if (is_new && source_type != OwnCloud_Source_Contacts && color &&
 		    sscanf (color, "#%02x%02x%02x", &rr, &gg, &bb) == 3)
 			e_source_selectable_set_color (E_SOURCE_SELECTABLE (backend), color);
 
diff -up evolution-data-server-3.12.11/modules/owncloud-backend/owncloud-utils.c.google-calendar-discover evolution-data-server-3.12.11/modules/owncloud-backend/owncloud-utils.c
--- evolution-data-server-3.12.11/modules/owncloud-backend/owncloud-utils.c.google-calendar-discover	2014-03-24 10:07:52.000000000 +0100
+++ evolution-data-server-3.12.11/modules/owncloud-backend/owncloud-utils.c	2015-07-07 16:23:23.653982516 +0200
@@ -632,6 +632,11 @@ owncloud_utils_search_server (ECollectio
 
 	source = e_backend_get_source (E_BACKEND (collection));
 	collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+
+	/* Ignore the request for non-GOA ownCloud sources */
+	if (!e_source_has_extension (source, E_SOURCE_EXTENSION_GOA))
+		return FALSE;
+
 	goa_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_GOA);
 
 	authenticator = g_object_new (E_TYPE_OWNCLOUD_AUTHENTICATOR, NULL);