/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * SPDX-FileCopyrightText: (C) 1999-2008 Novell, Inc. (www.novell.com)
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "evolution-ews-config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <glib/gstdio.h>
#include <libedataserver/libedataserver.h>
#include <libecal/libecal.h>

#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/tree.h>

#include "e-ews-connection.h"
#include "e-ews-connection-utils.h"
#include "e-ews-message.h"
#include "e-ews-item-change.h"
#include "e-ews-debug.h"
#include "e-ews-notification.h"

#define d(x) x

#define EWS_RETRY_IO_ERROR_SECONDS 3
#define EWS_RETRY_AUTH_ERROR_SECONDS 0.1

/* A chunk size limit when moving items in chunks. */
#define EWS_MOVE_ITEMS_CHUNK_SIZE 500

#define QUEUE_LOCK(x) (g_rec_mutex_lock(&(x)->priv->queue_lock))
#define QUEUE_UNLOCK(x) (g_rec_mutex_unlock(&(x)->priv->queue_lock))

#define NOTIFICATION_LOCK(x) (g_mutex_lock(&(x)->priv->notification_lock))
#define NOTIFICATION_UNLOCK(x) (g_mutex_unlock(&(x)->priv->notification_lock))

struct _EwsNode;
static GMutex connecting;
static GHashTable *loaded_connections_permissions = NULL;
static gint comp_func (gconstpointer a, gconstpointer b);

static void ews_response_cb (SoupSession *session, SoupMessage *msg, gpointer data);

static void	ews_connection_authenticate	(SoupSession *sess,
						 SoupMessage *msg,
						 SoupAuth *auth,
						 gboolean retrying,
						 gpointer data);

/* Connection APIS */

struct _EEwsConnectionPrivate {
	ESource *source;
	ESoupAuthBearer *bearer_auth;
	SoupSession *soup_session;
	GThread *soup_thread;
	GMainLoop *soup_loop;
	GMainContext *soup_context;
	GProxyResolver *proxy_resolver;
	EEwsNotification *notification;
	guint notification_delay_id;

	CamelEwsSettings *settings;
	guint concurrent_connections;
	GMutex property_lock;

	/* Hash key for the loaded_connections_permissions table. */
	gchar *hash_key;

	gchar *uri;
	gchar *password;
	gchar *email;
	gchar *impersonate_user;

	GSList *jobs;
	GSList *active_job_queue;
	GRecMutex queue_lock;
	GMutex notification_lock;

	GHashTable *subscriptions;
	GSList *subscribed_folders;
	/* The subscription ID is not tight to the actual connection, it survives
	   disconnects, thus remember it and unsubscribe from it, before adding
	   a new subscription. */
	gchar *last_subscription_id;

	EEwsServerVersion version;
	gboolean backoff_enabled;

	/* Set to TRUE when this connection had been disconnected and cannot be used anymore */
	gboolean disconnected_flag;

	/* Do that before a request is sent, rather than on request end, to avoid deadlock in libsoup */
	gboolean clear_cached_credentials;

	gboolean ssl_info_set;
	gchar *ssl_certificate_pem;
	GTlsCertificateFlags ssl_certificate_errors;
};

enum {
	PROP_0,
	PROP_PASSWORD,
	PROP_PROXY_RESOLVER,
	PROP_SETTINGS,
	PROP_SOURCE,
	PROP_CONCURRENT_CONNECTIONS
};

enum {
	SERVER_NOTIFICATION,
	PASSWORD_WILL_EXPIRE,
	SUBSCRIPTION_ID_CHANGED,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

static guint notification_key = 1;

typedef struct _EwsNode EwsNode;
typedef struct _EwsAsyncData EwsAsyncData;
typedef struct _EwsEventsAsyncData EwsEventsAsyncData;
typedef struct _EwsUrls EwsUrls;

struct _EwsAsyncData {
	GSList *items_created;
	GSList *items_updated;
	GSList *items_deleted;
	GSList *tzds; /* EEwsCalendarTimeZoneDefinition */

	gint total_items;
	const gchar *directory;
	GSList *items;
	EwsPhotoAttachmentInfo *photo;
	gchar *sync_state;
	gboolean includes_last_item;
	EwsDelegateDeliver deliver_to;
	EEwsFolderType folder_type;
	EEwsConnection *cnc;
	gchar *custom_data; /* Can be re-used by operations, will be freed with g_free() */
};

struct _EwsNode {
	ESoapMessage *msg;
	EEwsConnection *cnc;
	GSimpleAsyncResult *simple;

	gint pri;                /* the command priority */
	EEwsResponseCallback cb;

	GCancellable *cancellable;
	gulong cancel_handler_id;

	gboolean retrying_after_network_error;
};

struct _EwsUrls {
	xmlChar *as_url;
	xmlChar *oab_url;

	/* all the below variables are for future use */
	xmlChar *oof_url;
	gpointer future1;
	gpointer future2;
};

G_DEFINE_TYPE_WITH_PRIVATE (EEwsConnection, e_ews_connection, G_TYPE_OBJECT)

static guint
ews_connection_get_concurrent_connections (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), 1);

	return cnc->priv->concurrent_connections;
}

static void
ews_connection_set_concurrent_connections (EEwsConnection *cnc,
					   guint concurrent_connections)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	concurrent_connections = CLAMP (
		concurrent_connections,
		MIN_CONCURRENT_CONNECTIONS,
		MAX_CONCURRENT_CONNECTIONS);

	if (cnc->priv->concurrent_connections == concurrent_connections)
		return;

	cnc->priv->concurrent_connections = concurrent_connections;

	if (cnc->priv->soup_session) {
		g_object_set (G_OBJECT (cnc->priv->soup_session),
			SOUP_SESSION_MAX_CONNS, concurrent_connections,
			SOUP_SESSION_MAX_CONNS_PER_HOST, concurrent_connections,
			NULL);
	}

	g_object_notify (G_OBJECT (cnc), "concurrent-connections");
}

GQuark
ews_connection_error_quark (void)
{
	static GQuark quark = 0;

	if (G_UNLIKELY (quark == 0)) {
		const gchar *string = "ews-connection-error-quark";
		quark = g_quark_from_static_string (string);
	}

	return quark;
}

static void
async_data_free (EwsAsyncData *async_data)
{
	g_free (async_data->custom_data);
	g_slice_free (EwsAsyncData, async_data);
}

EEwsNotificationEvent *
e_ews_notification_event_new (void)
{
	return g_new0 (EEwsNotificationEvent, 1);
}

void
e_ews_notification_event_free (EEwsNotificationEvent *event)
{
	if (event != NULL) {
		g_free (event->folder_id);
		g_free (event->old_folder_id);
		g_free (event);
	}
}

EEwsCalendarTo *
e_ews_calendar_to_new (void)
{
	return g_new0 (EEwsCalendarTo, 1);
}

void
e_ews_calendar_to_free (EEwsCalendarTo *to) {
	if (to != NULL) {
		g_free (to->kind);
		g_free (to->value);
		g_free (to);
	}
}

EEwsCalendarAbsoluteDateTransition *
e_ews_calendar_absolute_date_transition_new (void)
{
	return g_new0 (EEwsCalendarAbsoluteDateTransition, 1);
}

void
e_ews_calendar_absolute_date_transition_free (EEwsCalendarAbsoluteDateTransition *adt)
{
	if (adt != NULL) {
		e_ews_calendar_to_free (adt->to);
		g_free (adt->date_time);
		g_free (adt);
	}
}

EEwsCalendarRecurringDayTransition *
e_ews_calendar_recurring_day_transition_new (void)
{
	return g_new0 (EEwsCalendarRecurringDayTransition, 1);
}

void
e_ews_calendar_recurring_day_transition_free (EEwsCalendarRecurringDayTransition *rdayt)
{
	if (rdayt != NULL) {
		e_ews_calendar_to_free (rdayt->to);
		g_free (rdayt->time_offset);
		g_free (rdayt->month);
		g_free (rdayt->day_of_week);
		g_free (rdayt->occurrence);
		g_free (rdayt);
	}
}

EEwsCalendarRecurringDateTransition *
e_ews_calendar_recurring_date_transition_new (void)
{
	return g_new0 (EEwsCalendarRecurringDateTransition, 1);
}

void
e_ews_calendar_recurring_date_transition_free (EEwsCalendarRecurringDateTransition *rdatet)
{
	if (rdatet != NULL) {
		e_ews_calendar_to_free (rdatet->to);
		g_free (rdatet->time_offset);
		g_free (rdatet->month);
		g_free (rdatet->day);
		g_free (rdatet);
	}
}

EEwsCalendarPeriod *
e_ews_calendar_period_new (void)
{
	return g_new0 (EEwsCalendarPeriod, 1);
}

void
e_ews_calendar_period_free (EEwsCalendarPeriod *period)
{
	if (period != NULL) {
		g_free (period->bias);
		g_free (period->name);
		g_free (period->id);
		g_free (period);
	}
}

EEwsCalendarTransitionsGroup *
e_ews_calendar_transitions_group_new (void)
{
	return g_new0 (EEwsCalendarTransitionsGroup, 1);
}

void
e_ews_calendar_transitions_group_free (EEwsCalendarTransitionsGroup *tg)
{
	if (tg != NULL) {
		g_free (tg->id);
		e_ews_calendar_to_free (tg->transition);
		g_slist_free_full (
			tg->absolute_date_transitions,
			(GDestroyNotify) e_ews_calendar_absolute_date_transition_free);
		g_slist_free_full (
			tg->recurring_day_transitions,
			(GDestroyNotify) e_ews_calendar_recurring_day_transition_free);
		g_slist_free_full (
			tg->recurring_date_transitions,
			(GDestroyNotify) e_ews_calendar_recurring_date_transition_free);
		g_free (tg);
	}
}

EEwsCalendarTransitions *
e_ews_calendar_transitions_new (void)
{
	return g_new0 (EEwsCalendarTransitions, 1);
}

void
e_ews_calendar_transitions_free (EEwsCalendarTransitions *transitions)
{
	if (transitions != NULL) {
		e_ews_calendar_to_free (transitions->transition);
		g_slist_free_full (
			transitions->absolute_date_transitions,
			(GDestroyNotify) e_ews_calendar_absolute_date_transition_free);
		g_slist_free_full (
			transitions->recurring_day_transitions,
			(GDestroyNotify) e_ews_calendar_recurring_day_transition_free);
		g_slist_free_full (
			transitions->recurring_date_transitions,
			(GDestroyNotify) e_ews_calendar_recurring_date_transition_free);
		g_free (transitions);
	}
}

EEwsCalendarTimeZoneDefinition *
e_ews_calendar_time_zone_definition_new (void)
{
	return g_new0 (EEwsCalendarTimeZoneDefinition, 1);
}

void
e_ews_calendar_time_zone_definition_free (EEwsCalendarTimeZoneDefinition *tzd)
{
	if (tzd != NULL) {
		g_free (tzd->name);
		g_free (tzd->id);
		g_slist_free_full (tzd->periods, (GDestroyNotify) e_ews_calendar_period_free);
		g_slist_free_full (tzd->transitions_groups, (GDestroyNotify) e_ews_calendar_transitions_group_free);
		e_ews_calendar_transitions_free (tzd->transitions);
		g_free (tzd);
	}
}

EEwsExtendedFieldURI *
e_ews_extended_field_uri_new (void)
{
	return g_new0 (EEwsExtendedFieldURI, 1);
}

void
e_ews_extended_field_uri_free (EEwsExtendedFieldURI *ex_field_uri)
{
	if (ex_field_uri != NULL) {
		g_free (ex_field_uri->distinguished_prop_set_id);
		g_free (ex_field_uri->prop_set_id);
		g_free (ex_field_uri->prop_tag);
		g_free (ex_field_uri->prop_name);
		g_free (ex_field_uri->prop_id);
		g_free (ex_field_uri->prop_type);
		g_free (ex_field_uri);
	}
}

EEwsIndexedFieldURI *
e_ews_indexed_field_uri_new (const gchar *uri,
			     const gchar *index)
{
	EEwsIndexedFieldURI *furi;

	furi = g_new0 (EEwsIndexedFieldURI, 1);
	furi->field_uri = g_strdup (uri);
	furi->field_index = g_strdup (index);

	return furi;
}

void
e_ews_indexed_field_uri_free (EEwsIndexedFieldURI *id_field_uri)
{
	if (id_field_uri != NULL) {
		g_free (id_field_uri->field_uri);
		g_free (id_field_uri->field_index);
		g_free (id_field_uri);
	}
}

EEwsAdditionalProps *
e_ews_additional_props_new (void)
{
	return g_new0 (EEwsAdditionalProps, 1);
}

void
e_ews_additional_props_free (EEwsAdditionalProps *add_props)
{
	if (add_props != NULL) {
		g_free (add_props->field_uri);
		g_slist_free_full (add_props->extended_furis, (GDestroyNotify) e_ews_extended_field_uri_free);
		g_slist_free_full (add_props->indexed_furis, (GDestroyNotify) e_ews_indexed_field_uri_free);
		g_free (add_props);
	}
}

static EwsNode *
ews_node_new (void)
{
	return g_slice_new0 (EwsNode);
}

static void
autodiscover_parse_protocol (xmlNode *node,
                             EwsUrls *urls)
{
	for (node = node->children; node; node = node->next) {
		if (node->type == XML_ELEMENT_NODE &&
		    !strcmp ((gchar *) node->name, "ASUrl")) {
			if (urls->as_url != NULL)
				xmlFree (urls->as_url);

			urls->as_url = xmlNodeGetContent (node);
		} else if (node->type == XML_ELEMENT_NODE &&
		    !strcmp ((gchar *) node->name, "OABUrl")) {
			if (urls->oab_url != NULL)
				xmlFree (urls->oab_url);

			urls->oab_url = xmlNodeGetContent (node);
		}

		/* Once we find both, we can stop looking for the URLs */
		if (urls->as_url && urls->oab_url)
			return;
	}
}

static xmlChar *
autodiscover_get_protocol_type (xmlNode *node)
{
	for (node = node->children; node; node = node->next) {
		if (node->type == XML_ELEMENT_NODE &&
		    !strcmp ((gchar *) node->name, "Type")) {
			return xmlNodeGetContent (node);
		}
	}

	return NULL;
}

static gchar *
autodiscover_dup_element_value (xmlNode *node,
				const gchar *element_name)
{
	for (node = node->children; node; node = node->next) {
		if (node->type == XML_ELEMENT_NODE &&
		    !g_strcmp0 ((gchar *) node->name, element_name)) {
			xmlChar *str = xmlNodeGetContent (node);
			gchar *res;

			res = g_strdup ((const gchar *) str);
			xmlFree (str);
			return res;
		}
	}

	return NULL;
}

static gint
comp_func (gconstpointer a,
           gconstpointer b)
{
	EwsNode *node1 = (EwsNode *) a;
	EwsNode *node2 = (EwsNode *) b;
	if (node1->pri > node2->pri)
		return -1;
	else if (node1->pri < node2->pri)
		return 1;
	else
		return 0;
}

typedef enum _EwsScheduleOp {
	EWS_SCHEDULE_OP_QUEUE_MESSAGE,
	EWS_SCHEDULE_OP_CANCEL,
	EWS_SCHEDULE_OP_ABORT
} EwsScheduleOp;

typedef struct _EwsScheduleData
{
	EEwsConnection *cnc;
	SoupMessage *message;

	EwsScheduleOp op;

	SoupSessionCallback queue_callback;
	gpointer queue_user_data;
} EwsScheduleData;

/* this is run in priv->soup_thread */
static gboolean
ews_connection_scheduled_cb (gpointer user_data)
{
	EwsScheduleData *sd = user_data;

	g_return_val_if_fail (sd != NULL, FALSE);

	switch (sd->op) {
	case EWS_SCHEDULE_OP_QUEUE_MESSAGE:
		if (!e_ews_connection_utils_prepare_message (sd->cnc, NULL, sd->message, NULL)) {
			e_ews_debug_dump_raw_soup_request (sd->message);

			if (sd->queue_callback) {
				sd->queue_callback (sd->cnc->priv->soup_session, sd->message, sd->queue_user_data);
			} else {
				/* This should not happen */
				g_warn_if_reached ();

				soup_session_queue_message (
					sd->cnc->priv->soup_session, sd->message,
					sd->queue_callback, sd->queue_user_data);
				soup_session_cancel_message (sd->cnc->priv->soup_session, sd->message, sd->message->status_code);
			}
		} else {
			e_ews_debug_dump_raw_soup_request (sd->message);

			soup_session_queue_message (
				sd->cnc->priv->soup_session, sd->message,
				sd->queue_callback, sd->queue_user_data);
		}
		break;
	case EWS_SCHEDULE_OP_CANCEL:
		soup_session_cancel_message (sd->cnc->priv->soup_session, sd->message, SOUP_STATUS_CANCELLED);
		break;
	case EWS_SCHEDULE_OP_ABORT:
		soup_session_abort (sd->cnc->priv->soup_session);
		break;
	}

	if (sd->message)
		g_object_unref (sd->message);
	/* in case this is the last reference */
	e_ews_connection_utils_unref_in_thread (sd->cnc);
	g_slice_free (EwsScheduleData, sd);

	return FALSE;
}

static void
ews_connection_schedule_queue_message (EEwsConnection *cnc,
                                       SoupMessage *message,
                                       SoupSessionCallback callback,
                                       gpointer user_data)
{
	EwsScheduleData *sd;
	GSource *source;

	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));
	g_return_if_fail (SOUP_IS_MESSAGE (message));

	sd = g_slice_new0 (EwsScheduleData);
	sd->cnc = g_object_ref (cnc);
	sd->message = g_object_ref (message);
	sd->op = EWS_SCHEDULE_OP_QUEUE_MESSAGE;
	sd->queue_callback = callback;
	sd->queue_user_data = user_data;

	source = g_idle_source_new ();
	g_source_set_priority (source, G_PRIORITY_DEFAULT);
	g_source_set_callback (source, ews_connection_scheduled_cb, sd, NULL);
	g_source_attach (source, cnc->priv->soup_context);
	g_source_unref (source);
}

static void
ews_connection_schedule_cancel_message (EEwsConnection *cnc,
                                        SoupMessage *message)
{
	EwsScheduleData *sd;
	GSource *source;

	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));
	g_return_if_fail (SOUP_IS_MESSAGE (message));

	sd = g_slice_new0 (EwsScheduleData);
	sd->cnc = g_object_ref (cnc);
	sd->message = g_object_ref (message);
	sd->op = EWS_SCHEDULE_OP_CANCEL;

	source = g_idle_source_new ();
	g_source_set_priority (source, G_PRIORITY_DEFAULT);
	g_source_set_callback (source, ews_connection_scheduled_cb, sd, NULL);
	g_source_attach (source, cnc->priv->soup_context);
	g_source_unref (source);
}

static void
ews_connection_schedule_abort (EEwsConnection *cnc)
{
	EwsScheduleData *sd;
	GSource *source;

	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	sd = g_slice_new0 (EwsScheduleData);
	sd->cnc = g_object_ref (cnc);
	sd->op = EWS_SCHEDULE_OP_ABORT;

	source = g_idle_source_new ();
	g_source_set_priority (source, G_PRIORITY_DEFAULT);
	g_source_set_callback (source, ews_connection_scheduled_cb, sd, NULL);
	g_source_attach (source, cnc->priv->soup_context);
	g_source_unref (source);
}

static void ews_cancel_request (GCancellable *cancellable, gpointer user_data);

static void
ews_discover_server_version (EEwsConnection *cnc,
			     ESoapResponse *response)
{
	ESoapParameter *param;
	gchar *version;

	g_return_if_fail (cnc != NULL);

	if (cnc->priv->version != E_EWS_EXCHANGE_UNKNOWN)
		return;

	param = e_soap_response_get_first_parameter_by_name (
		response, "ServerVersionInfo", NULL);
	if (!param)
		return;

	version = e_soap_parameter_get_property (param, "Version");

	e_ews_connection_set_server_version_from_string (cnc, version);

	g_free (version);
}

/* this is run in priv->soup_thread */
static gboolean
ews_next_request (gpointer _cnc)
{
	EEwsConnection *cnc = _cnc;
	GSList *l;
	EwsNode *node;

	QUEUE_LOCK (cnc);

	l = cnc->priv->jobs;

	if (!l || g_slist_length (cnc->priv->active_job_queue) >= ews_connection_get_concurrent_connections (cnc)) {
		QUEUE_UNLOCK (cnc);
		return FALSE;
	}

	node = (EwsNode *) l->data;

	/* Remove the node from the priority queue */
	cnc->priv->jobs = g_slist_remove (cnc->priv->jobs, (gconstpointer *) node);

	/* Add to active job queue */
	cnc->priv->active_job_queue = g_slist_append (cnc->priv->active_job_queue, node);

	QUEUE_UNLOCK (cnc);

	if (cnc->priv->soup_session) {
		SoupMessage *msg = SOUP_MESSAGE (node->msg);

		if (!e_ews_connection_utils_prepare_message (cnc, NULL, msg, node->cancellable)) {
			e_ews_debug_dump_raw_soup_request (msg);

			ews_response_cb (cnc->priv->soup_session, msg, node);
		} else {
			e_ews_debug_dump_raw_soup_request (msg);

			if (cnc->priv->clear_cached_credentials) {
				SoupSessionFeature *feature;

				cnc->priv->clear_cached_credentials = FALSE;

				feature = soup_session_get_feature (cnc->priv->soup_session, SOUP_TYPE_AUTH_MANAGER);
				if (feature)
					soup_auth_manager_clear_cached_credentials (SOUP_AUTH_MANAGER (feature));
			}

			soup_session_queue_message (cnc->priv->soup_session, msg, ews_response_cb, node);
		}
	} else {
		ews_cancel_request (NULL, node);
	}

	return FALSE;
}

static void
ews_trigger_next_request (EEwsConnection *cnc)
{
	GSource *source;

	g_return_if_fail (cnc != NULL);

	if (cnc->priv->soup_session) {
		source = g_idle_source_new ();
		g_source_set_priority (source, G_PRIORITY_DEFAULT);
		g_source_set_callback (source, ews_next_request, cnc, NULL);
		g_source_attach (source, cnc->priv->soup_context);
		g_source_unref (source);
	} else {
		ews_next_request (cnc);
	}
}

/**
 * ews_active_job_done
 * @cnc:
 * @msg:
 * Removes the node from active Queue and free's the node
 *
 * Returns:
 **/
static void
ews_active_job_done (EEwsConnection *cnc,
                     EwsNode *ews_node)
{
	g_return_if_fail (cnc != NULL);
	g_return_if_fail (ews_node != NULL);

	QUEUE_LOCK (cnc);

	cnc->priv->active_job_queue = g_slist_remove (cnc->priv->active_job_queue, ews_node);
	if (ews_node->cancellable && ews_node->cancel_handler_id)
		g_signal_handler_disconnect (ews_node->cancellable, ews_node->cancel_handler_id);

	QUEUE_UNLOCK (cnc);

	ews_trigger_next_request (cnc);

	if (ews_node->cancellable)
		g_object_unref (ews_node->cancellable);

	if (ews_node->simple) {
		/* the 'simple' holds reference on 'cnc' and this function
		 * is called in a dedicated thread, which 'cnc' joins on dispose,
		 * thus to avoid race condition, unref the object in its own thread */
		e_ews_connection_utils_unref_in_thread (ews_node->simple);
	}

	g_slice_free (EwsNode, ews_node);
}

static void
ews_cancel_request (GCancellable *cancellable,
                    gpointer user_data)
{
	EwsNode *node = user_data;
	EEwsConnection *cnc = node->cnc;
	GSimpleAsyncResult *simple = node->simple;
	ESoapMessage *msg = node->msg;
	GSList *found;

	QUEUE_LOCK (cnc);
	found = g_slist_find (cnc->priv->active_job_queue, node);
	cnc->priv->jobs = g_slist_remove (cnc->priv->jobs, node);
	QUEUE_UNLOCK (cnc);

	g_simple_async_result_set_error (
		simple,
		G_IO_ERROR,
		G_IO_ERROR_CANCELLED,
		_("Operation Cancelled"));
	if (found) {
		ews_connection_schedule_cancel_message (cnc, SOUP_MESSAGE (msg));
	} else {
		ews_response_cb (cnc->priv->soup_session, SOUP_MESSAGE (msg), node);
	}
}

void
e_ews_connection_queue_request (EEwsConnection *cnc,
                                ESoapMessage *msg,
                                EEwsResponseCallback cb,
                                gint pri,
                                GCancellable *cancellable,
                                GSimpleAsyncResult *simple)
{
	EwsNode *node;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (cb != NULL);
	g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple));

	node = ews_node_new ();
	node->msg = msg;
	node->pri = pri;
	node->cb = cb;
	node->cnc = cnc;
	node->simple = g_object_ref (simple);

	QUEUE_LOCK (cnc);
	cnc->priv->jobs = g_slist_insert_sorted (cnc->priv->jobs, (gpointer *) node, (GCompareFunc) comp_func);
	QUEUE_UNLOCK (cnc);

	if (cancellable) {
		node->cancellable = g_object_ref (cancellable);
		if (g_cancellable_is_cancelled (cancellable))
			ews_cancel_request (cancellable, node);
		else
			node->cancel_handler_id = g_cancellable_connect (
				cancellable,
				G_CALLBACK (ews_cancel_request),
				(gpointer) node, NULL);
	}

	ews_trigger_next_request (cnc);
}

static gboolean
ews_connection_credentials_failed (EEwsConnection *connection,
				   SoupMessage *message,
				   GSimpleAsyncResult *simple)
{
	gint expire_in_days = 0;
	gboolean expired = FALSE;
	gchar *service_url = NULL;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (connection), FALSE);
	g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), FALSE);

	if (!e_ews_connection_utils_check_x_ms_credential_headers (message, &expire_in_days, &expired, &service_url))
		return FALSE;

	if (expired) {
		GError *error = NULL;

		e_ews_connection_utils_expired_password_to_error (service_url, &error);
		g_simple_async_result_take_error (simple, error);
	} else if (expire_in_days > 0) {
		g_signal_emit (connection, signals[PASSWORD_WILL_EXPIRE], 0, expire_in_days, service_url);
	}

	g_free (service_url);

	return expired;
}

static void
ews_connection_check_ssl_error (EEwsConnection *connection,
				SoupMessage *message)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (connection));
	g_return_if_fail (SOUP_IS_MESSAGE (message));

	if (message->status_code == SOUP_STATUS_SSL_FAILED) {
		GTlsCertificate *certificate = NULL;

		g_mutex_lock (&connection->priv->property_lock);

		g_clear_pointer (&connection->priv->ssl_certificate_pem, g_free);
		connection->priv->ssl_info_set = FALSE;

		g_object_get (G_OBJECT (message),
			"tls-certificate", &certificate,
			"tls-errors", &connection->priv->ssl_certificate_errors,
			NULL);

		if (certificate) {
			g_object_get (certificate, "certificate-pem", &connection->priv->ssl_certificate_pem, NULL);
			connection->priv->ssl_info_set = TRUE;

			g_object_unref (certificate);
		}

		g_mutex_unlock (&connection->priv->property_lock);
	}
}

/* Response callbacks */

static void
ews_response_cb (SoupSession *session,
                 SoupMessage *msg,
                 gpointer data)
{
	EwsNode *enode = (EwsNode *) data;
	ESoapResponse *response = NULL;
	ESoapParameter *param;
	const gchar *persistent_auth;
	gint log_level;
	gint wait_ms = 0;

	persistent_auth = soup_message_headers_get_one (msg->response_headers, "Persistent-Auth");
	if (persistent_auth && g_ascii_strcasecmp (persistent_auth, "false") == 0) {
		enode->cnc->priv->clear_cached_credentials = TRUE;
	}

	if (g_cancellable_is_cancelled (enode->cancellable))
		goto exit;

	ews_connection_check_ssl_error (enode->cnc, msg);

	if (ews_connection_credentials_failed (enode->cnc, msg, enode->simple)) {
		goto exit;
	} else if (msg->status_code == SOUP_STATUS_SSL_FAILED) {
		g_simple_async_result_set_error (
			enode->simple, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED,
			"%s", msg->reason_phrase);
		goto exit;
	} else if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
		/* This can happen when the server terminated the connection before the request started */
		if (!enode->retrying_after_network_error &&
		    !soup_message_headers_get_one (msg->request_headers, "Authorization")) {
			wait_ms = EWS_RETRY_AUTH_ERROR_SECONDS * 1000;
			enode->retrying_after_network_error = TRUE;
			goto retrylbl;
		}

		if (msg->response_headers) {
			const gchar *diagnostics;

			diagnostics = soup_message_headers_get_list (msg->response_headers, "X-MS-DIAGNOSTICS");
			if (diagnostics && strstr (diagnostics, "invalid_grant")) {
				g_simple_async_result_set_error (
					enode->simple,
					EWS_CONNECTION_ERROR,
					EWS_CONNECTION_ERROR_ACCESSDENIED,
					"%s", diagnostics);
				goto exit;
			} else if (diagnostics && *diagnostics) {
				g_simple_async_result_set_error (
					enode->simple,
					EWS_CONNECTION_ERROR,
					EWS_CONNECTION_ERROR_AUTHENTICATION_FAILED,
					"%s", diagnostics);
				goto exit;
			}
		}

		g_simple_async_result_set_error (
			enode->simple,
			EWS_CONNECTION_ERROR,
			EWS_CONNECTION_ERROR_AUTHENTICATION_FAILED,
			_("Authentication failed"));
		goto exit;
	} else if (!enode->retrying_after_network_error &&
		   msg->status_code == SOUP_STATUS_IO_ERROR) {
		wait_ms = EWS_RETRY_IO_ERROR_SECONDS * 1000;
		enode->retrying_after_network_error = TRUE;
		goto retrylbl;
	} else if (msg->status_code == SOUP_STATUS_CANT_RESOLVE ||
		   msg->status_code == SOUP_STATUS_CANT_RESOLVE_PROXY ||
		   msg->status_code == SOUP_STATUS_CANT_CONNECT ||
		   msg->status_code == SOUP_STATUS_CANT_CONNECT_PROXY ||
		   msg->status_code == SOUP_STATUS_IO_ERROR) {
		g_simple_async_result_set_error (
			enode->simple,
			EWS_CONNECTION_ERROR,
			EWS_CONNECTION_ERROR_UNAVAILABLE,
			"%s", msg->reason_phrase);
		goto exit;
	}

	response = e_soap_message_parse_response ((ESoapMessage *) msg);

	if (response == NULL) {
		g_simple_async_result_set_error (
			enode->simple,
			EWS_CONNECTION_ERROR,
			EWS_CONNECTION_ERROR_NORESPONSE,
			_("No response: %s"), msg->reason_phrase);
		goto exit;
	}

	/* TODO: The stdout can be replaced with Evolution's
	 * Logging framework also */

	log_level = e_ews_debug_get_log_level ();
	if (log_level >= 1 && log_level < 4) {
		/* This will dump only the headers, since we stole the body.
		 * And only if EWS_DEBUG=1, since higher levels will have dumped
		 * it directly from libsoup anyway. */
		if (log_level == 1)
			e_ews_debug_dump_raw_soup_response (msg);
		/* And this will dump the body... */
		e_soap_response_dump_response (response, stdout);
	}

	if (!wait_ms && e_ews_connection_get_backoff_enabled (enode->cnc))
		param = e_soap_response_get_first_parameter_by_name (response, "detail", NULL);
	else
		param = NULL;
	if (param)
		param = e_soap_parameter_get_first_child_by_name (param, "ResponseCode");
	if (param) {
		gchar *value;

		value = e_soap_parameter_get_string_value (param);
		if (value && ews_get_error_code (value) == EWS_CONNECTION_ERROR_SERVERBUSY) {
			param = e_soap_response_get_first_parameter_by_name (response, "detail", NULL);
			if (param)
				param = e_soap_parameter_get_first_child_by_name (param, "MessageXml");
			if (param) {
				param = e_soap_parameter_get_first_child_by_name (param, "Value");
				if (param) {
					g_free (value);

					value = e_soap_parameter_get_property (param, "Name");
					if (g_strcmp0 (value, "BackOffMilliseconds") == 0) {
						wait_ms = e_soap_parameter_get_int_value (param);
					}
				}
			}
		}

		g_free (value);
	}

 retrylbl:
	if (wait_ms > 0) {
		GCancellable *cancellable = enode->cancellable;
		EFlag *flag;

		if (cancellable)
			g_object_ref (cancellable);
		g_object_ref (msg);

		flag = e_flag_new ();
		while (wait_ms > 0 && !g_cancellable_is_cancelled (cancellable) && msg->status_code != SOUP_STATUS_CANCELLED) {
			gint64 now = g_get_monotonic_time ();
			gint left_minutes, left_seconds;

			left_minutes = wait_ms / 60000;
			left_seconds = (wait_ms / 1000) % 60;

			if (left_minutes > 0) {
				camel_operation_push_message (cancellable,
					g_dngettext (GETTEXT_PACKAGE,
						"Exchange server is busy, waiting to retry (%d:%02d minute)",
						"Exchange server is busy, waiting to retry (%d:%02d minutes)", left_minutes),
					left_minutes, left_seconds);
			} else {
				camel_operation_push_message (cancellable,
					g_dngettext (GETTEXT_PACKAGE,
						"Exchange server is busy, waiting to retry (%d second)",
						"Exchange server is busy, waiting to retry (%d seconds)", left_seconds),
					left_seconds);
			}

			e_flag_wait_until (flag, now + (G_TIME_SPAN_MILLISECOND * (wait_ms > 1000 ? 1000 : wait_ms)));

			now = g_get_monotonic_time () - now;
			now = now / G_TIME_SPAN_MILLISECOND;

			if (now >= wait_ms)
				wait_ms = 0;
			wait_ms -= now;

			camel_operation_pop_message (cancellable);
		}

		e_flag_free (flag);

		g_clear_object (&response);

		if (g_cancellable_is_cancelled (cancellable) ||
		    msg->status_code == SOUP_STATUS_CANCELLED) {
			g_clear_object (&cancellable);
			g_object_unref (msg);
		} else {
			EwsNode *new_node;

			new_node = ews_node_new ();
			new_node->msg = E_SOAP_MESSAGE (msg); /* takes ownership */
			new_node->pri = enode->pri;
			new_node->cb = enode->cb;
			new_node->cnc = enode->cnc;
			new_node->simple = enode->simple;
			new_node->retrying_after_network_error = enode->retrying_after_network_error;

			enode->simple = NULL;

			QUEUE_LOCK (enode->cnc);
			enode->cnc->priv->jobs = g_slist_prepend (enode->cnc->priv->jobs, new_node);
			QUEUE_UNLOCK (enode->cnc);

			if (cancellable) {
				new_node->cancellable = g_object_ref (cancellable);
				new_node->cancel_handler_id = g_cancellable_connect (
					cancellable, G_CALLBACK (ews_cancel_request), new_node, NULL);
			}

			g_clear_object (&cancellable);
		}

		goto exit;
	}

	if (enode->cb != NULL)
		enode->cb (response, enode->simple);

	g_clear_object (&response);

exit:
	if (enode->simple)
		g_simple_async_result_complete_in_idle (enode->simple);

	ews_active_job_done (enode->cnc, enode);
}

typedef gpointer (*ItemParser) (ESoapParameter *param);

static void
sync_xxx_response_cb (ESoapParameter *subparam,
                      EwsAsyncData *async_data,
                      ItemParser parser,
                      const gchar *last_tag,
                      const gchar *delete_id_tag)
{
	ESoapParameter *node;
	gchar *new_sync_state = NULL, *value, *last;
	GSList *items_created = NULL, *items_updated = NULL, *items_deleted = NULL;
	gboolean includes_last_item;

	node = e_soap_parameter_get_first_child_by_name (subparam, "SyncState");
	new_sync_state = e_soap_parameter_get_string_value (node);

	node = e_soap_parameter_get_first_child_by_name (subparam, last_tag);
	last = e_soap_parameter_get_string_value (node);
	/*
	 * Set the includes_last_item to TRUE as default.
	 * It can avoid an infinite loop in caller, when, for some reason,
	 * we don't receive the last_tag property from the server.
	 */
	includes_last_item = g_strcmp0 (last, "false") != 0;
	g_free (last);

	node = e_soap_parameter_get_first_child_by_name (subparam, "Changes");

	if (node) {
		ESoapParameter *subparam1;
		for (subparam1 = e_soap_parameter_get_first_child_by_name (node, "Create");
		     subparam1 != NULL;
		     subparam1 = e_soap_parameter_get_next_child_by_name (subparam1, "Create")) {
			GObject *object;

			object = parser (subparam1);
			if (object && (!E_IS_EWS_FOLDER (object) || !e_ews_folder_get_is_hidden (E_EWS_FOLDER (object))))
				items_created = g_slist_append (items_created, object);
			else
				g_clear_object (&object);
		}

		for (subparam1 = e_soap_parameter_get_first_child_by_name (node, "Update");
		     subparam1 != NULL;
		     subparam1 = e_soap_parameter_get_next_child_by_name (subparam1, "Update")) {
			GObject *object;

			object = parser (subparam1);
			if (object && (!E_IS_EWS_FOLDER (object) || !e_ews_folder_get_is_hidden (E_EWS_FOLDER (object))))
				items_updated = g_slist_append (items_updated, object);
			else
				g_clear_object (&object);
		}
		  /* Exchange 2007SP1 introduced <ReadFlagChange> which is basically identical
		   * to <Update>; no idea why they thought it was a good idea. */
		for (subparam1 = e_soap_parameter_get_first_child_by_name (node, "ReadFlagChange");
		     subparam1 != NULL;
		     subparam1 = e_soap_parameter_get_next_child_by_name (subparam1, "ReadFlagChange")) {
			GObject *object;

			object = parser (subparam1);
			if (object && (!E_IS_EWS_FOLDER (object) || !e_ews_folder_get_is_hidden (E_EWS_FOLDER (object))))
				items_updated = g_slist_append (items_updated, object);
			else
				g_clear_object (&object);
		}

		for (subparam1 = e_soap_parameter_get_first_child_by_name (node, "Delete");
		     subparam1 != NULL;
		     subparam1 = e_soap_parameter_get_next_child_by_name (subparam1, "Delete")) {
			ESoapParameter *folder_param;

			folder_param = e_soap_parameter_get_first_child_by_name (subparam1, delete_id_tag);
			value = e_soap_parameter_get_property (folder_param, "Id");
			items_deleted = g_slist_append (items_deleted, value);
		}
	}

	async_data->items_created = items_created;
	async_data->items_updated = items_updated;
	async_data->items_deleted = items_deleted;
	async_data->sync_state = new_sync_state;
	async_data->includes_last_item = includes_last_item;
}

static void
sync_hierarchy_response_cb (ESoapResponse *response,
                            GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	/*
	 * During the first connection, we are able to get the current version of the Exchange server.
	 * For Addressbook/Calendar backends, we are ensuring it happens during the
	 * e_ews_connection_try_credentials_sync(), that calls e_e_ews_connection_get_folder_sync() and then
	 * we are able to get the current version of the server from this first response.
	 *
	 * For Camel, the first connection is done calling e_ews_connection_sync_folder_hierarchy_sync().
	 */
	ews_discover_server_version (async_data->cnc, response);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "SyncFolderHierarchyResponseMessage"))
			sync_xxx_response_cb (
				subparam, async_data,
				(ItemParser) e_ews_folder_new_from_soap_parameter,
				"IncludesLastFolderInRange", "FolderId");

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

static void
sync_folder_items_response_cb (ESoapResponse *response,
                               GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "SyncFolderItemsResponseMessage"))
			sync_xxx_response_cb (
				subparam, async_data,
				(ItemParser) e_ews_item_new_from_soap_parameter,
				"IncludesLastItemInRange", "ItemId");

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

static void
ews_handle_folders_param (ESoapParameter *subparam,
                          EwsAsyncData *async_data)
{
	ESoapParameter *node;
	EEwsFolder *folder;

	for (node = e_soap_parameter_get_first_child_by_name (subparam, "Folders");
	     node; node = e_soap_parameter_get_next_child_by_name (subparam, "Folders")) {
		folder = e_ews_folder_new_from_soap_parameter (node);
		if (!folder) continue;
		async_data->items = g_slist_append (async_data->items, folder);
	}
}

static void
get_folder_response_cb (ESoapResponse *response,
                        GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	/*
	 * During the first connection, we are able to get the current version of the Exchange server.
	 * For Addressbook/Calendar backends, we are ensuring it happens during the
	 * e_ews_connection_try_credentials_sync(), that calls e_e_ews_connection_get_folder_sync() and then
	 * we are able to get the current version of the server from this first response.
	 *
	 * For Camel, the first connection is done calling e_ews_connection_sync_folder_hierarchy_sync().
	 */
	ews_discover_server_version (async_data->cnc, response);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			if (g_strcmp0 (name, "GetFolderResponseMessage") == 0) {
				async_data->items = g_slist_append (async_data->items, e_ews_folder_new_from_error (error));
				g_clear_error (&error);
			} else {
				g_simple_async_result_take_error (simple, error);
				return;
			}
		} else if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "GetFolderResponseMessage"))
			ews_handle_folders_param (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

static void
ews_handle_root_folder_param_items (ESoapParameter *subparam,
				    EwsAsyncData *async_data)
{
	ESoapParameter *node, *subparam1;
	gchar *last, *total;
	gint total_items;
	EEwsItem *item;
	gboolean includes_last_item;

	node = e_soap_parameter_get_first_child_by_name (subparam, "RootFolder");
	total = e_soap_parameter_get_property (node, "TotalItemsInView");
	total_items = atoi (total);
	g_free (total);
	last = e_soap_parameter_get_property (node, "IncludesLastItemInRange");
	/*
	 * Set the includes_last_item to TRUE as default.
	 * It can avoid an infinite loop in caller, when, for some reason,
	 * we don't receive the last_tag property from the server.
	 */
	includes_last_item = g_strcmp0 (last, "false") != 0;
	g_free (last);

	node = e_soap_parameter_get_first_child_by_name (node, "Items");
	for (subparam1 = e_soap_parameter_get_first_child (node);
	     subparam1; subparam1 = e_soap_parameter_get_next_child (subparam1)) {
		item = e_ews_item_new_from_soap_parameter (subparam1);
		if (!item) continue;
		async_data->items = g_slist_append (async_data->items, item);
	}
	async_data->total_items = total_items;
	async_data->includes_last_item = includes_last_item;
}

static void
find_folder_items_response_cb (ESoapResponse *response,
                               GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "FindItemResponseMessage"))
			ews_handle_root_folder_param_items (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

/* Used for CreateItems and GetItems */
static void
ews_handle_items_param (ESoapParameter *subparam,
                        EwsAsyncData *async_data,
                        const GError *error)
{
	ESoapParameter *node;
	EEwsItem *item;

	for (node = e_soap_parameter_get_first_child_by_name (subparam, "Items");
	     node; node = e_soap_parameter_get_next_child_by_name (subparam, "Items")) {
		if (node->children)
			item = e_ews_item_new_from_soap_parameter (node);
		else
			item = NULL;
		if (!item && error != NULL)
			item = e_ews_item_new_from_error (error);
		if (!item) continue;
		async_data->items = g_slist_append (async_data->items, item);
	}
}

static void
handle_get_items_response_cb (EwsAsyncData *async_data, ESoapParameter *param)
{
	ESoapParameter *subparam;
	GError *error = NULL;

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (g_str_has_suffix (name, "ResponseMessage")) {
			if (ews_get_response_status (subparam, &error))
				error = NULL;

			ews_handle_items_param (subparam, async_data, error);
		} else {
			g_warning (
				"%s: Unexpected element <%s>",
				G_STRFUNC, name);
		}

		/* Do not stop on errors. */
		if (error != NULL)
			g_clear_error (&error);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

static void
get_items_response_cb (ESoapResponse *response,
                       GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	handle_get_items_response_cb (async_data, param);
}

static void
ews_handle_resolution_set_param (ESoapParameter *subparam,
                                 EwsAsyncData *async_data)
{
	ESoapParameter *node;
	gchar *prop;
	gboolean includes_last_item;
	GSList *mailboxes = NULL, *contact_items = NULL;

	subparam = e_soap_parameter_get_first_child_by_name (subparam, "ResolutionSet");
	prop = e_soap_parameter_get_property (subparam, "IncludesLastItemInRange");
	/*
	 * Set the includes_last_item to TRUE as default.
	 * It can avoid an infinite loop in caller, when, for some reason,
	 * we don't receive the last_tag property from the server.
	 */
	includes_last_item = g_strcmp0 (prop, "false") != 0;
	g_free (prop);

	for (subparam = e_soap_parameter_get_first_child_by_name (subparam, "Resolution");
		subparam != NULL;
		subparam = e_soap_parameter_get_next_child_by_name (subparam, "Resolution")) {
		EwsMailbox *mb;

		node = e_soap_parameter_get_first_child_by_name (subparam, "Mailbox");
		mb = e_ews_item_mailbox_from_soap_param (node);
		if (mb) {
			EEwsItem *contact_item;

			mailboxes = g_slist_prepend (mailboxes, mb);

			/* 'mailboxes' and 'contact_items' match 1:1, but if the contact information
			 * wasn't found, then NULL is stored in the corresponding position */
			node = e_soap_parameter_get_first_child_by_name (subparam, "Contact");
			if (node) {
				contact_item = e_ews_item_new_from_soap_parameter (node);
				contact_items = g_slist_prepend (contact_items, contact_item);
			} else {
				contact_items = g_slist_prepend (contact_items, NULL);
			}
		}
	}

	/* Reuse existing variables */
	async_data->items = g_slist_reverse (mailboxes);
	async_data->includes_last_item = includes_last_item;
	async_data->items_created = g_slist_reverse (contact_items);
}

static void
resolve_names_response_cb (ESoapResponse *response,
                           GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "ResolveNamesResponseMessage"))
			ews_handle_resolution_set_param (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

static void
ews_handle_dl_expansion_param (ESoapParameter *subparam,
                               EwsAsyncData *async_data)
{
	gboolean includes_last_item;
	GSList *mailboxes = NULL;
	gchar *prop;

	subparam = e_soap_parameter_get_first_child_by_name (subparam, "DLExpansion");
	prop = e_soap_parameter_get_property (subparam, "IncludesLastItemInRange");
	/*
	 * Set the includes_last_item to TRUE as default.
	 * It can avoid an infinite loop in caller, when, for some reason,
	 * we don't receive the last_tag property from the server.
	 */
	includes_last_item = g_strcmp0 (prop, "false") != 0;
	g_free (prop);

	for (subparam = e_soap_parameter_get_first_child_by_name (subparam, "Mailbox");
		subparam != NULL;
		subparam = e_soap_parameter_get_next_child_by_name (subparam, "Mailbox")) {
		EwsMailbox *mb;

		mb = e_ews_item_mailbox_from_soap_param (subparam);
		if (mb)
			mailboxes = g_slist_append (mailboxes, mb);
	}

	/* Reuse existing variables */
	async_data->items = mailboxes;
	async_data->includes_last_item = includes_last_item;
}

static void
expand_dl_response_cb (ESoapResponse *response,
                       GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "ExpandDLResponseMessage"))
			ews_handle_dl_expansion_param (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

/* TODO scan all folders if we support creating multiple folders in the request */
static void
ews_handle_create_folders_param (ESoapParameter *soapparam,
                                 EwsAsyncData *async_data)
{
	ESoapParameter *param, *node;
	EwsFolderId *fid = NULL;
	GSList *fids = NULL;
	const gchar *folder_element;

	switch (async_data->folder_type) {
		case E_EWS_FOLDER_TYPE_MAILBOX:
		case E_EWS_FOLDER_TYPE_MEMOS:
			folder_element = "Folder";
			break;
		case E_EWS_FOLDER_TYPE_CALENDAR:
			folder_element = "CalendarFolder";
			break;
		case E_EWS_FOLDER_TYPE_CONTACTS:
			folder_element = "ContactsFolder";
			break;
		case E_EWS_FOLDER_TYPE_SEARCH:
			folder_element = "SearchFolder";
			break;
		case E_EWS_FOLDER_TYPE_TASKS:
			folder_element = "TasksFolder";
			break;
		default:
			g_warn_if_reached ();
			folder_element = "Folder";
			break;
	}

	node = e_soap_parameter_get_first_child_by_name (soapparam, "Folders");
	node = e_soap_parameter_get_first_child_by_name (node, folder_element);
	param = e_soap_parameter_get_first_child_by_name (node, "FolderId");

	fid = g_new0 (EwsFolderId, 1);
	fid->id = e_soap_parameter_get_property (param, "Id");
	fid->change_key = e_soap_parameter_get_property (param, "ChangeKey");
	fids = g_slist_append (fids, fid);

	async_data->items_created = fids;
}

static void
create_folder_response_cb (ESoapResponse *response,
                           GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "CreateFolderResponseMessage"))
			ews_handle_create_folders_param (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

static gpointer
e_ews_soup_thread (gpointer user_data)
{
	EEwsConnection *cnc = user_data;

	g_main_context_push_thread_default (cnc->priv->soup_context);
	g_main_loop_run (cnc->priv->soup_loop);
	g_main_context_pop_thread_default (cnc->priv->soup_context);

	/* abort any pending operations */
	soup_session_abort (cnc->priv->soup_session);

	g_object_unref (cnc->priv->soup_session);
	cnc->priv->soup_session = NULL;

	return NULL;
}

/*
 * e_ews_debug_handler:
 *
 * GLib debug message handler, which is passed all messages from g_debug() calls,
 * and decides whether to print them.
 */
static void
e_ews_debug_handler (const gchar *log_domain,
		     GLogLevelFlags log_level,
		     const gchar *message,
		     gpointer user_data)
{
	if (e_ews_debug_get_log_level () >= 4)
		g_log_default_handler (log_domain, log_level, message, NULL);
}

/*
 * e_ews_soup_log_print:
 *
 * Log printer for the libsoup logging functionality, which just marshal all soup log
 * output to the standard GLib logging framework (and thus to debug_handler(), above).
 */
static void
e_ews_soup_log_printer (SoupLogger *logger,
			SoupLoggerLogLevel level,
			char direction,
			const gchar *data,
			gpointer user_data)
{
	g_debug ("%c %s", direction, e_ews_debug_redact_headers (direction, data));
}

static void
ews_connection_set_settings (EEwsConnection *connection,
                             CamelEwsSettings *settings)
{
	g_return_if_fail (CAMEL_IS_EWS_SETTINGS (settings));
	g_return_if_fail (connection->priv->settings == NULL);

	connection->priv->settings = g_object_ref (settings);

	e_binding_bind_property (connection->priv->settings, "concurrent-connections",
		connection, "concurrent-connections",
		G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
}

static void
ews_connection_set_source (EEwsConnection *connection,
			   ESource *source)
{
	if (source)
		g_return_if_fail (E_IS_SOURCE (source));
	g_return_if_fail (connection->priv->source == NULL);

	connection->priv->source = source ? g_object_ref (source) : NULL;
}

static void
ews_connection_set_property (GObject *object,
                             guint property_id,
                             const GValue *value,
                             GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_PASSWORD:
			e_ews_connection_set_password (
				E_EWS_CONNECTION (object),
				g_value_get_string (value));
			return;

		case PROP_PROXY_RESOLVER:
			e_ews_connection_set_proxy_resolver (
				E_EWS_CONNECTION (object),
				g_value_get_object (value));
			return;

		case PROP_SETTINGS:
			ews_connection_set_settings (
				E_EWS_CONNECTION (object),
				g_value_get_object (value));
			return;

		case PROP_SOURCE:
			ews_connection_set_source (
				E_EWS_CONNECTION (object),
				g_value_get_object (value));
			return;

		case PROP_CONCURRENT_CONNECTIONS:
			ews_connection_set_concurrent_connections (
				E_EWS_CONNECTION (object),
				g_value_get_uint (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
ews_connection_get_property (GObject *object,
                             guint property_id,
                             GValue *value,
                             GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_PASSWORD:
			g_value_take_string (
				value,
				e_ews_connection_dup_password (
				E_EWS_CONNECTION (object)));
			return;

		case PROP_PROXY_RESOLVER:
			g_value_take_object (
				value,
				e_ews_connection_ref_proxy_resolver (
				E_EWS_CONNECTION (object)));
			return;

		case PROP_SETTINGS:
			g_value_take_object (
				value,
				e_ews_connection_ref_settings (
				E_EWS_CONNECTION (object)));
			return;

		case PROP_SOURCE:
			g_value_set_object (
				value,
				e_ews_connection_get_source (
				E_EWS_CONNECTION (object)));
			return;

		case PROP_CONCURRENT_CONNECTIONS:
			g_value_set_uint (
				value,
				ews_connection_get_concurrent_connections (
				E_EWS_CONNECTION (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
ews_connection_constructed (GObject *object)
{
	EEwsConnection *cnc = E_EWS_CONNECTION (object);
	gint log_level;

	/* Chain up to parent's method. */
	G_OBJECT_CLASS (e_ews_connection_parent_class)->constructed (object);

	cnc->priv->soup_thread = g_thread_new (NULL, e_ews_soup_thread, cnc);

	cnc->priv->soup_session = soup_session_async_new_with_options (
		SOUP_SESSION_TIMEOUT, 90,
		SOUP_SESSION_SSL_STRICT, TRUE,
		SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
		SOUP_SESSION_ASYNC_CONTEXT, cnc->priv->soup_context,
		SOUP_SESSION_MAX_CONNS, cnc->priv->concurrent_connections,
		SOUP_SESSION_MAX_CONNS_PER_HOST, cnc->priv->concurrent_connections,
		NULL);

	/* Do not use G_BINDING_SYNC_CREATE because the property_lock is
	 * not initialized and we don't have a GProxyResolver yet anyway. */
	e_binding_bind_property (
		cnc, "proxy-resolver",
		cnc->priv->soup_session, "proxy-resolver",
		G_BINDING_DEFAULT);

	cnc->priv->version = E_EWS_EXCHANGE_UNKNOWN;

	log_level = e_ews_debug_get_log_level ();

	if (log_level >= 2) {
		SoupLogger *logger;
		logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);

		if (log_level >= 4) {
			soup_logger_set_printer (logger, e_ews_soup_log_printer, NULL, NULL);
			g_log_set_handler (
				G_LOG_DOMAIN,
				G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
				G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO,
				e_ews_debug_handler, cnc);
		} else if (log_level == 2) {
			soup_logger_set_printer (logger, e_ews_debug_soup_log_printer_stdout, NULL, NULL);
		}

		soup_session_add_feature (
			cnc->priv->soup_session,
			SOUP_SESSION_FEATURE (logger));
		g_object_unref (logger);
	}

	soup_session_add_feature_by_type (cnc->priv->soup_session,
					  SOUP_TYPE_COOKIE_JAR);

	g_signal_connect (
		cnc->priv->soup_session, "authenticate",
		G_CALLBACK (ews_connection_authenticate), cnc);

	e_ews_connection_utils_prepare_auth_method (cnc->priv->soup_session,
		camel_ews_settings_get_auth_mechanism (cnc->priv->settings));
}

static void
ews_connection_dispose (GObject *object)
{
	EEwsConnection *cnc = E_EWS_CONNECTION (object);

	g_mutex_lock (&connecting);

	/* remove the connection from the hash table */
	if (loaded_connections_permissions != NULL &&
	    g_hash_table_lookup (loaded_connections_permissions, cnc->priv->hash_key) == (gpointer) object) {
		g_hash_table_remove (loaded_connections_permissions, cnc->priv->hash_key);
		if (g_hash_table_size (loaded_connections_permissions) == 0) {
			g_hash_table_destroy (loaded_connections_permissions);
			loaded_connections_permissions = NULL;
		}
	}

	g_mutex_unlock (&connecting);

	NOTIFICATION_LOCK (cnc);
	if (cnc->priv->notification_delay_id) {
		g_source_remove (cnc->priv->notification_delay_id);
		cnc->priv->notification_delay_id = 0;
	}

	if (cnc->priv->notification) {
		e_ews_notification_stop_listening_sync (cnc->priv->notification);
		g_clear_object (&cnc->priv->notification);
	}
	NOTIFICATION_UNLOCK (cnc);

	if (cnc->priv->soup_session) {
		g_signal_handlers_disconnect_by_func (
			cnc->priv->soup_session,
			ews_connection_authenticate, object);

		g_main_loop_quit (cnc->priv->soup_loop);
		g_thread_join (cnc->priv->soup_thread);
		cnc->priv->soup_thread = NULL;

		g_main_loop_unref (cnc->priv->soup_loop);
		cnc->priv->soup_loop = NULL;
		g_main_context_unref (cnc->priv->soup_context);
		cnc->priv->soup_context = NULL;
	}

	g_clear_object (&cnc->priv->proxy_resolver);
	g_clear_object (&cnc->priv->source);
	g_clear_object (&cnc->priv->settings);

	e_ews_connection_set_password (cnc, NULL);

	QUEUE_LOCK (cnc);
	g_slist_free (cnc->priv->jobs);
	cnc->priv->jobs = NULL;

	g_slist_free (cnc->priv->active_job_queue);
	cnc->priv->active_job_queue = NULL;
	QUEUE_UNLOCK (cnc);

	g_slist_free_full (cnc->priv->subscribed_folders, g_free);
	cnc->priv->subscribed_folders = NULL;

	if (cnc->priv->subscriptions != NULL) {
		g_hash_table_destroy (cnc->priv->subscriptions);
		cnc->priv->subscriptions = NULL;
	}

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

static void
ews_connection_finalize (GObject *object)
{
	EEwsConnection *cnc = E_EWS_CONNECTION (object);

	g_free (cnc->priv->uri);
	g_free (cnc->priv->password);
	g_free (cnc->priv->email);
	g_free (cnc->priv->hash_key);
	g_free (cnc->priv->impersonate_user);
	g_free (cnc->priv->ssl_certificate_pem);
	g_free (cnc->priv->last_subscription_id);

	g_clear_object (&cnc->priv->bearer_auth);

	g_mutex_clear (&cnc->priv->property_lock);
	g_rec_mutex_clear (&cnc->priv->queue_lock);
	g_mutex_clear (&cnc->priv->notification_lock);

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

static void
e_ews_connection_class_init (EEwsConnectionClass *class)
{
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = ews_connection_set_property;
	object_class->get_property = ews_connection_get_property;
	object_class->constructed = ews_connection_constructed;
	object_class->dispose = ews_connection_dispose;
	object_class->finalize = ews_connection_finalize;

	g_object_class_install_property (
		object_class,
		PROP_PASSWORD,
		g_param_spec_string (
			"password",
			"Password",
			"Authentication password",
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_PROXY_RESOLVER,
		g_param_spec_object (
			"proxy-resolver",
			"Proxy Resolver",
			"The proxy resolver for this backend",
			G_TYPE_PROXY_RESOLVER,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_SETTINGS,
		g_param_spec_object (
			"settings",
			"Settings",
			"Connection settings",
			CAMEL_TYPE_EWS_SETTINGS,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT_ONLY |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_SOURCE,
		g_param_spec_object (
			"source",
			"Source",
			"Corresponding ESource",
			E_TYPE_SOURCE,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT_ONLY |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_CONCURRENT_CONNECTIONS,
		g_param_spec_uint (
			"concurrent-connections",
			"Concurrent Connections",
			"Number of concurrent connections to use",
			MIN_CONCURRENT_CONNECTIONS,
			MAX_CONCURRENT_CONNECTIONS,
			1,
			/* Do not construct it, otherwise it overrides the value derived from CamelEwsSettings */
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS));

	signals[SERVER_NOTIFICATION] = g_signal_new (
		"server-notification",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED | G_SIGNAL_ACTION,
		0, NULL, NULL,
		g_cclosure_marshal_VOID__POINTER,
		G_TYPE_NONE, 1,
		G_TYPE_POINTER);

	signals[PASSWORD_WILL_EXPIRE] = g_signal_new (
		"password-will-expire",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED | G_SIGNAL_ACTION,
		G_STRUCT_OFFSET (EEwsConnectionClass, password_will_expire),
		NULL, NULL, NULL,
		G_TYPE_NONE, 2,
		G_TYPE_INT,
		G_TYPE_STRING);

	signals[SUBSCRIPTION_ID_CHANGED] = g_signal_new (
		"subscription-id-changed",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
		0, NULL, NULL,
		g_cclosure_marshal_VOID__STRING,
		G_TYPE_NONE, 1,
		G_TYPE_STRING);
}

static void
e_ews_connection_folders_list_free (gpointer data)
{
	g_slist_free_full ((GSList *) data, g_free);
}

static void
e_ews_connection_init (EEwsConnection *cnc)
{
	cnc->priv = e_ews_connection_get_instance_private (cnc);

	cnc->priv->soup_context = g_main_context_new ();
	cnc->priv->soup_loop = g_main_loop_new (cnc->priv->soup_context, FALSE);
	cnc->priv->backoff_enabled = TRUE;
	cnc->priv->disconnected_flag = FALSE;
	cnc->priv->concurrent_connections = 1;

	cnc->priv->subscriptions = g_hash_table_new_full (
			g_direct_hash, g_direct_equal,
			NULL, e_ews_connection_folders_list_free);

	g_mutex_init (&cnc->priv->property_lock);
	g_rec_mutex_init (&cnc->priv->queue_lock);
	g_mutex_init (&cnc->priv->notification_lock);
}

static void
ews_connection_authenticate (SoupSession *sess,
                             SoupMessage *msg,
                             SoupAuth *auth,
                             gboolean retrying,
                             gpointer data)
{
	EEwsConnection *cnc = data;

	g_return_if_fail (cnc != NULL);

	e_ews_connection_utils_authenticate (cnc, sess, msg, auth, retrying);
}

void
ews_oal_free (EwsOAL *oal)
{
	if (oal != NULL) {
		g_free (oal->id);
		g_free (oal->dn);
		g_free (oal->name);
		g_free (oal);
	}
}

void
ews_oal_details_free (EwsOALDetails *details)
{
	if (details != NULL) {
		g_free (details->type);
		g_free (details->sha);
		g_free (details->filename);
		g_free (details);
	}
}

void
ews_user_id_free (EwsUserId *id)
{
	if (id) {
		g_free (id->sid);
		g_free (id->primary_smtp);
		g_free (id->display_name);
		g_free (id->distinguished_user);
		g_free (id->external_user);
		g_free (id);
	}
}

void
ews_delegate_info_free (EwsDelegateInfo *info)
{
	if (!info)
		return;

	ews_user_id_free (info->user_id);
	g_free (info);
}

EEwsAttachmentInfo *
e_ews_attachment_info_new (EEwsAttachmentInfoType type)
{
	EEwsAttachmentInfo *info;
	info = g_new0 (EEwsAttachmentInfo, 1);

	info->type = type;
	return info;
}

void
e_ews_attachment_info_free (EEwsAttachmentInfo *info)
{
	if (!info)
		return;

	switch (info->type) {
	case E_EWS_ATTACHMENT_INFO_TYPE_INLINED:
		g_free (info->data.inlined.filename);
		g_free (info->data.inlined.mime_type);
		g_free (info->data.inlined.data);
		break;
	case E_EWS_ATTACHMENT_INFO_TYPE_URI:
		g_free (info->data.uri);
		break;
	default:
		g_warning ("Unknown EEwsAttachmentInfoType %d", info->type);
		break;
	}

	g_free (info->prefer_filename);
	g_free (info->id);
	g_free (info);
}

EEwsAttachmentInfoType
e_ews_attachment_info_get_type (EEwsAttachmentInfo *info)
{
	return info->type;
}

const gchar *
e_ews_attachment_info_get_prefer_filename (EEwsAttachmentInfo *info)
{
	g_return_val_if_fail (info != NULL, NULL);

	return info->prefer_filename;
}

void
e_ews_attachment_info_set_prefer_filename (EEwsAttachmentInfo *info,
					   const gchar *prefer_filename)
{
	g_return_if_fail (info != NULL);

	if (info->prefer_filename == prefer_filename)
		return;

	g_free (info->prefer_filename);
	info->prefer_filename = g_strdup (prefer_filename);
}

const gchar *
e_ews_attachment_info_get_inlined_data (EEwsAttachmentInfo *info,
					gsize *len)
{
	g_return_val_if_fail (info != NULL, NULL);
	g_return_val_if_fail (info->type == E_EWS_ATTACHMENT_INFO_TYPE_INLINED, NULL);

	*len = info->data.inlined.length;
	return info->data.inlined.data;
}

void
e_ews_attachment_info_set_inlined_data (EEwsAttachmentInfo *info,
					const guchar *data,
					gsize len)
{
	g_return_if_fail (info != NULL);
	g_return_if_fail (info->type == E_EWS_ATTACHMENT_INFO_TYPE_INLINED);

	info->data.inlined.data = g_malloc (len);
	memcpy (info->data.inlined.data, data, len);
	info->data.inlined.length = len;
}

const gchar *
e_ews_attachment_info_get_mime_type (EEwsAttachmentInfo *info)
{
	g_return_val_if_fail (info != NULL, NULL);
	g_return_val_if_fail (info->type == E_EWS_ATTACHMENT_INFO_TYPE_INLINED, NULL);

	return info->data.inlined.mime_type;
}

void
e_ews_attachment_info_set_mime_type (EEwsAttachmentInfo *info,
				     const gchar *mime_type)
{
	g_return_if_fail (info != NULL);
	g_return_if_fail (info->type == E_EWS_ATTACHMENT_INFO_TYPE_INLINED);

	g_free (info->data.inlined.mime_type);
	info->data.inlined.mime_type = g_strdup (mime_type);
}

const gchar *
e_ews_attachment_info_get_filename (EEwsAttachmentInfo *info)
{
	g_return_val_if_fail (info != NULL, NULL);
	g_return_val_if_fail (info->type == E_EWS_ATTACHMENT_INFO_TYPE_INLINED, NULL);

	return info->data.inlined.filename;
}

void
e_ews_attachment_info_set_filename (EEwsAttachmentInfo *info,
				    const gchar *filename)
{
	g_return_if_fail (info != NULL);
	g_return_if_fail (info->type == E_EWS_ATTACHMENT_INFO_TYPE_INLINED);

	g_free (info->data.inlined.filename);
	info->data.inlined.filename = g_strdup (filename);
}

const gchar *
e_ews_attachment_info_get_uri (EEwsAttachmentInfo *info)
{
	g_return_val_if_fail (info != NULL, NULL);
	g_return_val_if_fail (info->type == E_EWS_ATTACHMENT_INFO_TYPE_URI, NULL);

	return info->data.uri;
}

void
e_ews_attachment_info_set_uri (EEwsAttachmentInfo *info,
			       const gchar *uri)
{
	g_return_if_fail (info != NULL);
	g_return_if_fail (info->type == E_EWS_ATTACHMENT_INFO_TYPE_URI);

	g_free (info->data.uri);
	info->data.uri = g_strdup (uri);
}

const gchar *
e_ews_attachment_info_get_id (EEwsAttachmentInfo *info)
{
	g_return_val_if_fail (info != NULL, NULL);

	return info->id;
}

void
e_ews_attachment_info_set_id (EEwsAttachmentInfo *info,
			      const gchar *id)
{
	g_return_if_fail (info != NULL);

	if (info->id != id) {
		g_free (info->id);
		info->id = g_strdup (id);
	}
}

/* Connection APIS */

/**
 * e_ews_connection_find
 * @uri: Exchange server uri
 * @username:
 *
 * Find an existing connection for this user/uri, if it exists.
 *
 * Returns: EEwsConnection
 **/
EEwsConnection *
e_ews_connection_find (const gchar *uri,
                       const gchar *username)
{
	EEwsConnection *cnc;
	gchar *hash_key;

	g_mutex_lock (&connecting);

	/* search the connection in our hash table */
	if (loaded_connections_permissions != NULL) {
		hash_key = g_strdup_printf (
			"%s@%s",
			username ? username : "",
			uri);
		cnc = g_hash_table_lookup (
			loaded_connections_permissions, hash_key);
		g_free (hash_key);

		if (E_IS_EWS_CONNECTION (cnc) &&
		    !e_ews_connection_get_disconnected_flag (cnc)) {
			g_object_ref (cnc);
			g_mutex_unlock (&connecting);
			return cnc;
		}
	}

	g_mutex_unlock (&connecting);

	return NULL;
}

/**
 * e_ews_connection_list_existing:
 *
 * Returns: (transfer full) (element-type EEwsConnection): a new #GSList of all currently
 *    opened connections to all servers. Free the returned #GSList with
 *    g_slist_free_full (connections, g_object_unref);
 *    when no longer needed.
 **/
GSList * /* EEwsConnection * */
e_ews_connection_list_existing (void)
{
	GSList *connections = NULL;

	g_mutex_lock (&connecting);

	/* search the connection in our hash table */
	if (loaded_connections_permissions != NULL) {
		GHashTableIter iter;
		gpointer value;

		g_hash_table_iter_init (&iter, loaded_connections_permissions);
		while (g_hash_table_iter_next (&iter, NULL, &value)) {
			if (value && !e_ews_connection_get_disconnected_flag (value))
				connections = g_slist_prepend (connections, g_object_ref (value));
		}
	}

	g_mutex_unlock (&connecting);

	return connections;
}

/**
 * e_ews_connection_new_full
 * @source: corresponding #ESource
 * @uri: Exchange server uri
 * @settings: a #CamelEwsSettings
 * @allow_connection_reuse: whether can return already created connection
 *
 * This does not authenticate to the server. It merely stores the username and password.
 * Authentication happens when a request is made to the server.
 *
 * Returns: EEwsConnection
 **/
EEwsConnection *
e_ews_connection_new_full (ESource *source,
			   const gchar *uri,
			   CamelEwsSettings *settings,
			   gboolean allow_connection_reuse)
{
	CamelNetworkSettings *network_settings;
	EEwsConnection *cnc;
	gchar *hash_key;
	gchar *user;

	if (source)
		g_return_val_if_fail (E_IS_SOURCE (source), NULL);
	g_return_val_if_fail (uri != NULL, NULL);
	g_return_val_if_fail (CAMEL_IS_EWS_SETTINGS (settings), NULL);

	network_settings = CAMEL_NETWORK_SETTINGS (settings);
	user = camel_network_settings_dup_user (network_settings);
	hash_key = g_strdup_printf ("%s@%s", user, uri);
	g_free (user);

	g_mutex_lock (&connecting);

	/* search the connection in our hash table */
	if (allow_connection_reuse && loaded_connections_permissions != NULL) {
		cnc = g_hash_table_lookup (
			loaded_connections_permissions, hash_key);

		if (E_IS_EWS_CONNECTION (cnc) &&
		    !e_ews_connection_get_disconnected_flag (cnc)) {
			g_object_ref (cnc);

			g_free (hash_key);

			g_mutex_unlock (&connecting);
			return cnc;
		}
	}

	/* not found, so create a new connection */
	cnc = g_object_new (E_TYPE_EWS_CONNECTION,
		"settings", settings,
		"source", source,
		NULL);

	cnc->priv->uri = g_strdup (uri);
	cnc->priv->hash_key = hash_key;  /* takes ownership */

	g_free (cnc->priv->impersonate_user);
	if (camel_ews_settings_get_use_impersonation (settings)) {
		cnc->priv->impersonate_user = camel_ews_settings_dup_impersonate_user (settings);
		if (cnc->priv->impersonate_user && !*cnc->priv->impersonate_user) {
			g_free (cnc->priv->impersonate_user);
			cnc->priv->impersonate_user = NULL;
		}
	} else {
		cnc->priv->impersonate_user = NULL;
	}

	e_binding_bind_property (
		settings, "timeout",
		cnc->priv->soup_session, "timeout",
		G_BINDING_SYNC_CREATE);

	if (allow_connection_reuse) {
		/* add the connection to the loaded_connections_permissions hash table */
		if (loaded_connections_permissions == NULL)
			loaded_connections_permissions = g_hash_table_new_full (
				g_str_hash, g_str_equal,
				g_free, NULL);
		g_hash_table_insert (
			loaded_connections_permissions,
			g_strdup (cnc->priv->hash_key), cnc);
	}

	/* free memory */
	g_mutex_unlock (&connecting);
	return cnc;

}

EEwsConnection *
e_ews_connection_new (ESource *source,
		      const gchar *uri,
		      CamelEwsSettings *settings)
{
	return e_ews_connection_new_full (source, uri, settings, TRUE);
}

EEwsConnection *
e_ews_connection_new_for_backend (EBackend *backend,
				  ESourceRegistry *registry,
				  const gchar *uri,
				  CamelEwsSettings *settings)
{
	ESource *source;
	EEwsConnection *cnc;

	g_return_val_if_fail (E_IS_BACKEND (backend), NULL);
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);

	source = e_backend_get_source (backend);
	if (!source)
		return e_ews_connection_new (source, uri, settings);

	g_object_ref (source);

	while (source && !e_source_has_extension (source, E_SOURCE_EXTENSION_COLLECTION) &&
	       e_source_get_parent (source)) {
		ESource *parent;

		parent = e_source_registry_ref_source (registry, e_source_get_parent (source));
		if (!parent) {
			g_clear_object (&source);
			break;
		}

		g_object_unref (source);
		source = parent;
	}

	if (source)
		cnc = e_ews_connection_new (source, uri, settings);
	else
		cnc = e_ews_connection_new (e_backend_get_source (backend), uri, settings);

	g_clear_object (&source);

	return cnc;
}

void
e_ews_connection_update_credentials (EEwsConnection *cnc,
				     const ENamedParameters *credentials)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	if (credentials) {
		const gchar *password;

		/* Update password only if it's provided, otherwise keep the previously set, if any */
		password = e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_PASSWORD);
		if (password && *password)
			e_ews_connection_set_password (cnc, password);

		if (e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_USERNAME)) {
			CamelNetworkSettings *network_settings;

			network_settings = CAMEL_NETWORK_SETTINGS (cnc->priv->settings);
			camel_network_settings_set_user (network_settings, e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_USERNAME));
		}
	} else {
		e_ews_connection_set_password (cnc, NULL);
	}
}

ESourceAuthenticationResult
e_ews_connection_try_credentials_sync (EEwsConnection *cnc,
				       const ENamedParameters *credentials,
				       ESource *use_source,
				       gchar **out_certificate_pem,
				       GTlsCertificateFlags *out_certificate_errors,
				       GCancellable *cancellable,
				       GError **error)
{
	ESourceAuthenticationResult result;
	ESource *source;
	gboolean de_set_source;
	EwsFolderId *fid = NULL;
	GSList *ids = NULL;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), E_SOURCE_AUTHENTICATION_ERROR);

	e_ews_connection_update_credentials (cnc, credentials);

	fid = g_new0 (EwsFolderId, 1);
	fid->id = g_strdup ("inbox");
	fid->is_distinguished_id = TRUE;
	ids = g_slist_append (ids, fid);

	source = e_ews_connection_get_source (cnc);
	if (use_source && use_source != source) {
		cnc->priv->source = g_object_ref (use_source);
		de_set_source = TRUE;
	} else {
		source = NULL;
		de_set_source = FALSE;
	}

	e_ews_connection_get_folder_sync (
		cnc, EWS_PRIORITY_MEDIUM, "Default",
		NULL, ids, NULL, cancellable, &local_error);

	if (de_set_source) {
		g_clear_object (&cnc->priv->source);
		cnc->priv->source = source;
	}

	g_slist_free_full (ids, (GDestroyNotify) e_ews_folder_id_free);

	if (local_error == NULL) {
		result = E_SOURCE_AUTHENTICATION_ACCEPTED;
	} else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) &&
		   e_ews_connection_get_ssl_error_details (cnc, out_certificate_pem, out_certificate_errors)) {
		result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
	} else {
		gboolean auth_failed;

		auth_failed = g_error_matches (
			local_error, EWS_CONNECTION_ERROR,
			EWS_CONNECTION_ERROR_AUTHENTICATION_FAILED);

		if (auth_failed) {
			g_clear_error (&local_error);

			if (camel_ews_settings_get_auth_mechanism (cnc->priv->settings) != EWS_AUTH_TYPE_GSSAPI &&
			    camel_ews_settings_get_auth_mechanism (cnc->priv->settings) != EWS_AUTH_TYPE_OAUTH2 &&
			    (!credentials || !e_named_parameters_exists (credentials, E_SOURCE_CREDENTIAL_PASSWORD))) {
				result = E_SOURCE_AUTHENTICATION_REQUIRED;
			} else {
				result = E_SOURCE_AUTHENTICATION_REJECTED;
			}
		} else {
			g_propagate_error (error, local_error);
			result = E_SOURCE_AUTHENTICATION_ERROR;
		}

		e_ews_connection_set_password (cnc, NULL);
	}

	return result;
}

ESource *
e_ews_connection_get_source (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	return cnc->priv->source;
}

gboolean
e_ews_connection_get_ssl_error_details (EEwsConnection *cnc,
					gchar **out_certificate_pem,
					GTlsCertificateFlags *out_certificate_errors)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (out_certificate_pem != NULL, FALSE);
	g_return_val_if_fail (out_certificate_errors != NULL, FALSE);

	g_mutex_lock (&cnc->priv->property_lock);
	if (!cnc->priv->ssl_info_set) {
		g_mutex_unlock (&cnc->priv->property_lock);
		return FALSE;
	}

	*out_certificate_pem = g_strdup (cnc->priv->ssl_certificate_pem);
	*out_certificate_errors = cnc->priv->ssl_certificate_errors;

	g_mutex_unlock (&cnc->priv->property_lock);

	return TRUE;
}

const gchar *
e_ews_connection_get_uri (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	return cnc->priv->uri;
}

ESoupAuthBearer *
e_ews_connection_ref_bearer_auth (EEwsConnection *cnc)
{
	ESoupAuthBearer *bearer_auth;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	g_mutex_lock (&cnc->priv->property_lock);
	bearer_auth = cnc->priv->bearer_auth;
	if (bearer_auth)
		g_object_ref (bearer_auth);
	g_mutex_unlock (&cnc->priv->property_lock);

	return bearer_auth;
}

void
e_ews_connection_set_bearer_auth (EEwsConnection *cnc,
				  ESoupAuthBearer *bearer_auth)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));
	if (bearer_auth)
		g_return_if_fail (E_IS_SOUP_AUTH_BEARER (bearer_auth));

	g_mutex_lock (&cnc->priv->property_lock);

	if (bearer_auth != cnc->priv->bearer_auth) {
		g_clear_object (&cnc->priv->bearer_auth);
		cnc->priv->bearer_auth = bearer_auth;

		if (cnc->priv->bearer_auth)
			g_object_ref (cnc->priv->bearer_auth);
	}

	g_mutex_unlock (&cnc->priv->property_lock);
}

const gchar *
e_ews_connection_get_password (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	return cnc->priv->password;
}

gchar *
e_ews_connection_dup_password (EEwsConnection *cnc)
{
	const gchar *protected;
	gchar *duplicate;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	g_mutex_lock (&cnc->priv->property_lock);

	protected = e_ews_connection_get_password (cnc);
	duplicate = g_strdup (protected);

	g_mutex_unlock (&cnc->priv->property_lock);

	return duplicate;
}

void
e_ews_connection_set_password (EEwsConnection *cnc,
                               const gchar *password)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	g_mutex_lock (&cnc->priv->property_lock);

	/* Zero-fill the old password before freeing it. */
	if (cnc->priv->password != NULL && *cnc->priv->password != '\0')
		memset (cnc->priv->password, 0, strlen (cnc->priv->password));

	g_free (cnc->priv->password);
	cnc->priv->password = g_strdup (password);

	g_mutex_unlock (&cnc->priv->property_lock);

	g_object_notify (G_OBJECT (cnc), "password");
}

const gchar *
e_ews_connection_get_impersonate_user (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	return cnc->priv->impersonate_user;
}

GProxyResolver *
e_ews_connection_ref_proxy_resolver (EEwsConnection *cnc)
{
	GProxyResolver *proxy_resolver = NULL;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	g_mutex_lock (&cnc->priv->property_lock);

	if (cnc->priv->proxy_resolver != NULL)
		proxy_resolver = g_object_ref (cnc->priv->proxy_resolver);

	g_mutex_unlock (&cnc->priv->property_lock);

	return proxy_resolver;
}

void
e_ews_connection_set_proxy_resolver (EEwsConnection *cnc,
                                     GProxyResolver *proxy_resolver)
{
	gboolean notify = FALSE;

	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	g_mutex_lock (&cnc->priv->property_lock);

	/* Emitting a "notify" signal unnecessarily might have
	 * unwanted side effects like cancelling a SoupMessage.
	 * Only emit if we now have a different GProxyResolver. */

	if (proxy_resolver != cnc->priv->proxy_resolver) {
		g_clear_object (&cnc->priv->proxy_resolver);
		cnc->priv->proxy_resolver = proxy_resolver;

		if (proxy_resolver != NULL)
			g_object_ref (proxy_resolver);

		notify = TRUE;
	}

	g_mutex_unlock (&cnc->priv->property_lock);

	if (notify)
		g_object_notify (G_OBJECT (cnc), "proxy-resolver");
}

CamelEwsSettings *
e_ews_connection_ref_settings (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	return g_object_ref (cnc->priv->settings);
}

SoupSession *
e_ews_connection_ref_soup_session (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	return g_object_ref (cnc->priv->soup_session);
}

gboolean
e_ews_connection_get_backoff_enabled (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);

	return cnc->priv->backoff_enabled;
}

void
e_ews_connection_set_backoff_enabled (EEwsConnection *cnc,
				      gboolean enabled)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	cnc->priv->backoff_enabled = enabled;
}

gboolean
e_ews_connection_get_disconnected_flag (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);

	return cnc->priv->disconnected_flag;
}

void
e_ews_connection_set_disconnected_flag (EEwsConnection *cnc,
					gboolean disconnected_flag)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	cnc->priv->disconnected_flag = disconnected_flag;
}

gchar *
e_ews_connection_dup_last_subscription_id (EEwsConnection *cnc)
{
	gchar *res;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	g_mutex_lock (&cnc->priv->property_lock);

	res = g_strdup (cnc->priv->last_subscription_id);

	g_mutex_unlock (&cnc->priv->property_lock);

	return res;
}

void
e_ews_connection_set_last_subscription_id (EEwsConnection *cnc,
					   const gchar *subscription_id)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	g_mutex_lock (&cnc->priv->property_lock);

	if (g_strcmp0 (subscription_id, cnc->priv->last_subscription_id) != 0) {
		g_free (cnc->priv->last_subscription_id);
		cnc->priv->last_subscription_id = g_strdup (subscription_id);
	}

	g_mutex_unlock (&cnc->priv->property_lock);
}

static xmlDoc *
e_ews_autodiscover_ws_xml (const gchar *email_address)
{
	xmlDoc *doc;
	xmlNode *node;
	xmlNs *ns;

	doc = xmlNewDoc ((xmlChar *) "1.0");
	node = xmlNewDocNode (doc, NULL, (xmlChar *)"Autodiscover", NULL);
	xmlDocSetRootElement (doc, node);
	ns = xmlNewNs (
		node,
		(xmlChar *)"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006", NULL);

	node = xmlNewChild (node, ns, (xmlChar *)"Request", NULL);
	xmlNewChild (
		node, ns, (xmlChar *)"EMailAddress",
		(xmlChar *) email_address);
	xmlNewChild (
		node, ns, (xmlChar *)"AcceptableResponseSchema",
		(xmlChar *)"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a");

	return doc;
}

struct _autodiscover_data {
	EEwsConnection *cnc;
	xmlOutputBuffer *buf;
	SoupMessage *msgs[6];

	GCancellable *cancellable;
	gulong cancel_id;

	GError *error;
	gchar *redirect_addr;
	gchar *redirect_url;
	gint n_redirects;

	/* Results */
	gchar *as_url;
	gchar *oab_url;
};

static void
autodiscover_data_free (struct _autodiscover_data *ad)
{
	xmlOutputBufferClose (ad->buf);

	if (ad->cancellable != NULL) {
		g_cancellable_disconnect (ad->cancellable, ad->cancel_id);
		g_object_unref (ad->cancellable);
	}

	/* Unref the connection after the cancellable is disconnected,
	   to avoid race condition when the connection is freed inside
	   the g_cancellable_disconnect() function, which holds the
	   cancellable lock and blocks all other threads, while at
	   the same time the connection can wait for the finish of
	   its worker thread. */
	g_object_unref (ad->cnc);

	g_clear_error (&ad->error);

	g_free (ad->redirect_addr);
	g_free (ad->redirect_url);
	g_free (ad->as_url);
	g_free (ad->oab_url);

	g_slice_free (struct _autodiscover_data, ad);
}

static void
autodiscover_cancelled_cb (GCancellable *cancellable,
                           EEwsConnection *cnc)
{
	ews_connection_schedule_abort (cnc);
}

/* Frees only the content, not the 'urls' structure itself */
static void
ews_urls_free_content (EwsUrls *urls)
{
	if (!urls)
		return;

	if (urls->as_url)
		xmlFree (urls->as_url);
	urls->as_url = NULL;

	if (urls->oab_url)
		xmlFree (urls->oab_url);
	urls->oab_url = NULL;
}

static gboolean
e_ews_discover_prepare_messages_and_send (GSimpleAsyncResult *simple,
					  const gchar *email_address,
					  const gchar *override_url,
					  GError **error);

/* Called when each soup message completes */
static void
autodiscover_response_cb (SoupSession *session,
                          SoupMessage *msg,
                          gpointer data)

{
	GSimpleAsyncResult *simple = data;
	struct _autodiscover_data *ad;
	EwsUrls exch_urls, expr_urls;
	guint status = msg->status_code;
	xmlDoc *doc;
	xmlNode *node;
	gchar *str;
	gint idx;
	GError *error = NULL;

	memset (&exch_urls, 0, sizeof (EwsUrls));
	memset (&expr_urls, 0, sizeof (EwsUrls));

	ad = g_simple_async_result_get_op_res_gpointer (simple);

	for (idx = 0; idx < 6; idx++) {
		if (ad->msgs[idx] == msg)
			break;
	}
	if (idx == 6 || (idx == 5 && !ad->msgs[5])) {
		/* We already got removed (cancelled). Do nothing */
		goto unref;
	}

	ad->msgs[idx] = NULL;

	if (status != 200) {
		gboolean expired = FALSE;
		gchar *service_url = NULL;

		if (e_ews_connection_utils_check_x_ms_credential_headers (msg, NULL, &expired, &service_url) && expired) {
			e_ews_connection_utils_expired_password_to_error (service_url, &error);
		} else {
			g_set_error (
				&error, SOUP_HTTP_ERROR, status,
				"%d %s", status, msg->reason_phrase);

			if (status == SOUP_STATUS_SSL_FAILED)
				ews_connection_check_ssl_error (ad->cnc, msg);
		}

		g_free (service_url);

		goto failed;
	}

	e_ews_debug_dump_raw_soup_response (msg);
	doc = xmlReadMemory (
		msg->response_body->data,
		msg->response_body->length,
		"autodiscover.xml", NULL, 0);
	if (!doc) {
		g_set_error (
			&error, EWS_CONNECTION_ERROR, -1,
			_("Failed to parse autodiscover response XML"));
		goto failed;
	}
	node = xmlDocGetRootElement (doc);
	if (strcmp ((gchar *) node->name, "Autodiscover")) {
		g_set_error (
			&error, EWS_CONNECTION_ERROR, -1,
			_("Failed to find <Autodiscover> element"));
		goto failed;
	}
	for (node = node->children; node; node = node->next) {
		if (node->type == XML_ELEMENT_NODE &&
		    !strcmp ((gchar *) node->name, "Response"))
			break;
	}
	if (!node) {
		g_set_error (
			&error, EWS_CONNECTION_ERROR, -1,
			_("Failed to find <Response> element"));
		goto failed;
	}
	for (node = node->children; node; node = node->next) {
		if (node->type == XML_ELEMENT_NODE &&
		    !strcmp ((gchar *) node->name, "Account"))
			break;
	}
	if (!node) {
		g_set_error (
			&error, EWS_CONNECTION_ERROR, -1,
			_("Failed to find <Account> element"));
		goto failed;
	}

	str = autodiscover_dup_element_value (node, "RedirectAddr");
	if (str) {
		g_free (ad->redirect_addr);
		ad->redirect_addr = str;
	}

	str = autodiscover_dup_element_value (node, "RedirectUrl");
	if (str) {
		g_free (ad->redirect_url);
		ad->redirect_url = str;
	}

	for (node = node->children; node; node = node->next) {
		if (node->type == XML_ELEMENT_NODE &&
		    !strcmp ((gchar *) node->name, "Protocol")) {
			xmlChar *protocol_type = autodiscover_get_protocol_type (node);

			if (g_strcmp0 ((const gchar *) protocol_type, "EXCH") == 0) {
				ews_urls_free_content (&exch_urls);
				autodiscover_parse_protocol (node, &exch_urls);
			} else if (g_strcmp0 ((const gchar *) protocol_type, "EXPR") == 0) {
				ews_urls_free_content (&expr_urls);
				autodiscover_parse_protocol (node, &expr_urls);

				/* EXPR has precedence, thus stop once found both there */
				if (expr_urls.as_url && expr_urls.oab_url) {
					xmlFree (protocol_type);
					break;
				}
			}

			if (protocol_type)
				xmlFree (protocol_type);
		}
	}

	/* Make the <OABUrl> optional */
	if (!exch_urls.as_url && !expr_urls.as_url) {
		ews_urls_free_content (&exch_urls);
		ews_urls_free_content (&expr_urls);
		g_set_error (
			&error, EWS_CONNECTION_ERROR, -1,
			_("Failed to find <ASUrl> in autodiscover response"));
		goto failed;
	}

	/* We have a good response; cancel all the others */
	for (idx = 0; idx < 6; idx++) {
		if (ad->msgs[idx]) {
			SoupMessage *m = ad->msgs[idx];
			ad->msgs[idx] = NULL;
			ews_connection_schedule_cancel_message (ad->cnc, m);
		}
	}

	if (expr_urls.as_url) {
		if (ad->as_url)
			g_free (ad->as_url);
		ad->as_url = g_strdup ((gchar *) expr_urls.as_url);
	} else if (exch_urls.as_url) {
		if (ad->as_url)
			g_free (ad->as_url);
		ad->as_url = g_strdup ((gchar *) exch_urls.as_url);
	}

	if (expr_urls.as_url && expr_urls.oab_url) {
		if (ad->oab_url)
			g_free (ad->oab_url);
		ad->oab_url = g_strdup ((gchar *) expr_urls.oab_url);
	} else if (!expr_urls.as_url && exch_urls.oab_url) {
		if (ad->oab_url)
			g_free (ad->oab_url);
		ad->oab_url = g_strdup ((gchar *) exch_urls.oab_url);
	}

	ews_urls_free_content (&exch_urls);
	ews_urls_free_content (&expr_urls);

	goto exit;

 failed:
	for (idx = 0; idx < 6; idx++) {
		if (ad->msgs[idx]) {
			/* Preserve any Unauthorized/SSL failed errors */
			if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_NONE) &&
			    !g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) &&
			    !g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) &&
			    (!ad->error ||
			    g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
			    g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) ||
			    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_NONE) ||
			    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_RESOLVE) ||
			    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_RESOLVE_PROXY) ||
			    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT) ||
			    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT_PROXY))) {
				g_clear_error (&ad->error);
				ad->error = error;
				error = NULL;
			} else {
				g_clear_error (&error);
			}

			/* There's another request outstanding.
			 * Hope that it has better luck. */
			goto unref;
		}
	}

	/* Preserve any Unauthorized/SSL failed errors */
	if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_NONE) &&
	    !g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) &&
	    !g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) &&
	    (!ad->error ||
	    g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
	    g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) ||
	    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_NONE) ||
	    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_RESOLVE) ||
	    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_RESOLVE_PROXY) ||
	    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT) ||
	    g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT_PROXY))) {
		g_clear_error (&ad->error);
		ad->error = error;
		error = NULL;
	}

	g_clear_error (&error);

	if (!g_cancellable_is_cancelled (ad->cancellable) &&
	    (!ad->as_url || !ad->oab_url) && ad->n_redirects < 11 &&
	    (ad->redirect_url || ad->redirect_addr) &&
	    !g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) &&
	    !g_error_matches (ad->error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
		CamelEwsSettings *settings = NULL;
		gboolean re_scheduled;
		const gchar *host_url;
		gchar *redirect_addr, *redirect_url;
		GError *local_error;

		redirect_addr = ad->redirect_addr;
		redirect_url = ad->redirect_url;
		local_error = ad->error;

		/* To avoid infinite recursion */
		ad->redirect_addr = NULL;
		ad->redirect_url = NULL;
		ad->n_redirects++;
		ad->error = NULL;

		host_url = redirect_url;
		settings = e_ews_connection_ref_settings (ad->cnc);

		if (!host_url)
			host_url = camel_ews_settings_get_hosturl (settings);

		re_scheduled = e_ews_discover_prepare_messages_and_send (simple, redirect_addr, host_url, NULL);

		g_clear_object (&settings);

		g_free (redirect_addr);
		g_free (redirect_url);

		if (re_scheduled) {
			g_clear_error (&local_error);
			goto unref;
		}

		ad->error = local_error;
	}

	/* FIXME: We're actually returning the *last* error here,
	 * and in some cases (stupid firewalls causing timeouts)
	 * that's going to be the least interesting one. We probably
	 * want the *first* error */
	g_simple_async_result_take_error (simple, ad->error);

	ad->error = NULL;

 exit:
	g_simple_async_result_complete_in_idle (simple);

 unref:
	/* This function is processed within e_ews_soup_thread() and the 'simple'
	 * holds reference to EEwsConnection. For cases when this is the last
	 * reference to 'simple' the unref would cause crash, because of g_thread_join()
	 * in connection's dispose, trying to wait on the end of itself, thus it's
	 * safer to unref the 'simple' in a dedicated thread.
	*/
	e_ews_connection_utils_unref_in_thread (simple);
}

static void
post_restarted (SoupMessage *msg,
		gpointer data)
{
	xmlOutputBuffer *buf = data;

	/* Not all restarts are due to a redirect; some are for auth */
	if (msg->status_code == 401)
		return;

	/* In violation of RFC2616, libsoup will change a POST request to
	 * a GET on receiving a 302 redirect. */
	printf ("Working around libsoup bug with redirect\n");
	g_object_set (msg, SOUP_MESSAGE_METHOD, "POST", NULL);

	soup_message_set_request (
		msg, "text/xml; charset=utf-8", SOUP_MEMORY_COPY,
		(gchar *)
			#ifdef LIBXML2_NEW_BUFFER
			xmlOutputBufferGetContent (buf), xmlOutputBufferGetSize (buf)
			#else
			buf->buffer->content, buf->buffer->use
			#endif
		);
}

static SoupMessage *
e_ews_get_msg_for_url (EEwsConnection *cnc,
		       const gchar *url,
                       xmlOutputBuffer *buf,
                       GError **error)
{
	SoupMessage *msg;
	CamelEwsSettings *settings;

	if (url == NULL) {
		g_set_error_literal (
			error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
			_("URL cannot be NULL"));
		return NULL;
	}

	msg = soup_message_new (buf != NULL ? "POST" : "GET", url);
	if (!msg) {
		g_set_error (
			error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
			_("URL “%s” is not valid"), url);
		return NULL;
	}

	if (cnc->priv->source)
		e_soup_ssl_trust_connect (msg, cnc->priv->source);

	e_ews_message_attach_chunk_allocator (msg);

	settings = e_ews_connection_ref_settings (cnc);
	e_ews_message_set_user_agent_header (msg, settings);
	g_clear_object (&settings);

	if (buf != NULL) {
		soup_message_set_request (
			msg, "text/xml; charset=utf-8", SOUP_MEMORY_COPY,
			(gchar *)
			#ifdef LIBXML2_NEW_BUFFER
			xmlOutputBufferGetContent (buf), xmlOutputBufferGetSize (buf)
			#else
			buf->buffer->content, buf->buffer->use
			#endif
			);
		g_signal_connect (
			msg, "restarted",
			G_CALLBACK (post_restarted), buf);
	}

	e_ews_debug_dump_raw_soup_request (msg);

	return msg;
}

static void
autodiscover_srv_record_resolved_cb (GObject *source,
				     GAsyncResult *result,
				     gpointer user_data)
{
	GList *targets, *link;
	GSimpleAsyncResult *simple = user_data;
	struct _autodiscover_data *ad;
	gchar *new_uri = NULL;
	gboolean success;

	ad = g_simple_async_result_get_op_res_gpointer (simple);

	g_return_if_fail (ad != NULL);

	targets = g_resolver_lookup_service_finish (G_RESOLVER (source), result, NULL);

	success = ad->msgs[5] && targets;

	for (link = targets; link && success; link = g_list_next (link)) {
		GSrvTarget *target = link->data;
		const gchar *hostname;

		hostname = g_srv_target_get_hostname (target);

		switch (g_srv_target_get_port (target)) {
		case 80:
			link = NULL;
			new_uri = g_strdup_printf ("http://%s/autodiscover/autodiscover.xml", hostname);
			break;
		case 443:
			link = NULL;
			new_uri = g_strdup_printf ("https://%s/autodiscover/autodiscover.xml", hostname);
			break;
		}
	}

	g_list_free_full (targets, (GDestroyNotify) g_srv_target_free);

	if (new_uri && success) {
		SoupURI *suri;

		suri = soup_uri_new (new_uri);
		if (suri) {
			soup_message_set_uri (ad->msgs[5], suri);
			/* The autodiscover_response_cb will free the 'simple' */
			ews_connection_schedule_queue_message (ad->cnc, ad->msgs[5], autodiscover_response_cb, simple);
			soup_uri_free (suri);
		} else {
			success = FALSE;
		}
	} else {
		success = FALSE;
	}

	if (!success) {
		/* The callback also frees the 'simple' */
		autodiscover_response_cb (NULL, ad->msgs[5], simple);
	}

	g_free (new_uri);
}

gboolean
e_ews_autodiscover_ws_url_sync (ESource *source,
				CamelEwsSettings *settings,
                                const gchar *email_address,
                                const gchar *password,
				gchar **out_certificate_pem,
				GTlsCertificateFlags *out_certificate_errors,
                                GCancellable *cancellable,
                                GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (CAMEL_IS_EWS_SETTINGS (settings), FALSE);
	g_return_val_if_fail (email_address != NULL, FALSE);
	g_return_val_if_fail (password != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_autodiscover_ws_url (source, settings, email_address, password, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_autodiscover_ws_url_finish (settings, result, out_certificate_pem, out_certificate_errors, error);

	e_async_closure_free (closure);

	return success;
}

static gboolean
e_ews_discover_prepare_messages_and_send (GSimpleAsyncResult *simple,
					  const gchar *email_address,
					  const gchar *override_url,
					  GError **error)
{
	SoupURI *soup_uri = NULL;
	gboolean use_secure = TRUE;
	gboolean is_outlook = FALSE;
	gchar *url1, *url2, *url3, *url4;
	const gchar *url5, *domain = NULL;
	struct _autodiscover_data *ad;
	GError *local_error = NULL;

	ad = g_simple_async_result_get_op_res_gpointer (simple);
	g_return_val_if_fail (ad != NULL, FALSE);

	if (email_address) {
		xmlDoc *doc;

		if (ad->buf)
			xmlOutputBufferClose (ad->buf);

		doc = e_ews_autodiscover_ws_xml (email_address);
		ad->buf = xmlAllocOutputBuffer (NULL);
		xmlNodeDumpOutput (ad->buf, doc, xmlDocGetRootElement (doc), 0, 1, NULL);
		xmlOutputBufferFlush (ad->buf);

		xmlFreeDoc (doc);

		domain = strchr (email_address, '@');
		if (domain)
			domain++;
	}

	g_return_val_if_fail (ad->buf != NULL, FALSE);
	g_return_val_if_fail ((domain && *domain) || (override_url && *override_url), FALSE);

	url1 = NULL;
	url2 = NULL;
	url3 = NULL;
	url4 = NULL;
	url5 = NULL;

	if (override_url)
		soup_uri = soup_uri_new (override_url);

	if (soup_uri) {
		const gchar *host = soup_uri_get_host (soup_uri);
		const gchar *scheme = soup_uri_get_scheme (soup_uri);

		use_secure = g_strcmp0 (scheme, "https") == 0;

		url1 = g_strdup_printf ("http%s://%s/autodiscover/autodiscover.xml", use_secure ? "s" : "", host);
		url2 = g_strdup_printf ("http%s://autodiscover.%s/autodiscover/autodiscover.xml", use_secure ? "s" : "", host);

		is_outlook = host && g_ascii_strcasecmp (host, "outlook.office365.com") == 0;

		/* outlook.office365.com has its autodiscovery at outlook.com */
		if (is_outlook && domain && g_ascii_strcasecmp (domain, "outlook.com") != 0) {
			url5 = "https://outlook.com/autodiscover/autodiscover.xml";
		} else if (!is_outlook && domain) {
			#define ON_MICROSOFT_COM_TEXT "onmicrosoft.com"
			gint len = strlen (domain);
			gint onmslen = strlen (ON_MICROSOFT_COM_TEXT);

			if (len >= onmslen) {
				const gchar *test_domain;

				test_domain = domain + len - onmslen;

				/* onmicrosoft.com addresses might be handled on the outlook.com/office365.com as well */
				if (g_ascii_strcasecmp (test_domain, ON_MICROSOFT_COM_TEXT) == 0 &&
				    (len == onmslen || (len > onmslen && domain[len - onmslen - 1] == '.')))
					url5 = "https://outlook.com/autodiscover/autodiscover.xml";
			}
			#undef ON_MICROSOFT_COM_TEXT
		}

		soup_uri_free (soup_uri);
	}

	is_outlook = is_outlook || (domain && g_ascii_strcasecmp (domain, "outlook.com") == 0);

	if (domain) {
		url3 = g_strdup_printf ("http%s://%s/autodiscover/autodiscover.xml", use_secure ? "s" : "", domain);
		url4 = g_strdup_printf ("http%s://autodiscover.%s/autodiscover/autodiscover.xml", use_secure ? "s" : "", domain);
	}

	/* Passing a NULL URL string returns NULL. */
	ad->msgs[0] = e_ews_get_msg_for_url (ad->cnc, url1, ad->buf, &local_error);
	ad->msgs[1] = e_ews_get_msg_for_url (ad->cnc, url2, ad->buf, local_error ? NULL : &local_error);
	ad->msgs[2] = e_ews_get_msg_for_url (ad->cnc, url3, ad->buf, local_error ? NULL : &local_error);
	ad->msgs[3] = e_ews_get_msg_for_url (ad->cnc, url4, ad->buf, local_error ? NULL : &local_error);
	ad->msgs[4] = e_ews_get_msg_for_url (ad->cnc, url5, ad->buf, local_error ? NULL : &local_error);

	if (!is_outlook && domain && (ad->msgs[0] || ad->msgs[1] || ad->msgs[2] || ad->msgs[3] || ad->msgs[4])) {
		gchar *tmp;

		tmp = g_strdup_printf ("http%s://%s/", use_secure ? "s" : "", domain);

		/* Fake SoupMessage, for the autodiscovery with SRV record help */
		ad->msgs[5] = e_ews_get_msg_for_url (ad->cnc, tmp, ad->buf, local_error ? NULL : &local_error);

		if (ad->msgs[5]) {
			g_resolver_lookup_service_async (g_resolver_get_default (), "autodiscover", "tcp", domain, ad->cancellable,
				autodiscover_srv_record_resolved_cb, g_object_ref (simple));
		}

		g_free (tmp);
	} else {
		ad->msgs[5] = NULL;
	}

	if (local_error && (ad->msgs[0] || ad->msgs[1] || ad->msgs[2] || ad->msgs[3] || ad->msgs[4]))
		g_clear_error (&local_error);

	/* These have to be submitted only after they're both set in ad->msgs[]
	 * or there will be races with fast completion */
	if (ad->msgs[0] != NULL)
		ews_connection_schedule_queue_message (ad->cnc, ad->msgs[0], autodiscover_response_cb, g_object_ref (simple));
	if (ad->msgs[1] != NULL)
		ews_connection_schedule_queue_message (ad->cnc, ad->msgs[1], autodiscover_response_cb, g_object_ref (simple));
	if (ad->msgs[2] != NULL)
		ews_connection_schedule_queue_message (ad->cnc, ad->msgs[2], autodiscover_response_cb, g_object_ref (simple));
	if (ad->msgs[3] != NULL)
		ews_connection_schedule_queue_message (ad->cnc, ad->msgs[3], autodiscover_response_cb, g_object_ref (simple));
	if (ad->msgs[4] != NULL)
		ews_connection_schedule_queue_message (ad->cnc, ad->msgs[4], autodiscover_response_cb, g_object_ref (simple));

	g_free (url1);
	g_free (url2);
	g_free (url3);
	g_free (url4);

	if (local_error) {
		g_propagate_error (error, local_error);
		return FALSE;
	}

	return TRUE;
}

void
e_ews_autodiscover_ws_url (ESource *source,
			   CamelEwsSettings *settings,
                           const gchar *email_address,
                           const gchar *password,
                           GCancellable *cancellable,
                           GAsyncReadyCallback callback,
                           gpointer user_data)
{
	GSimpleAsyncResult *simple;
	struct _autodiscover_data *ad;
	const gchar *domain;
	const gchar *host_url;
	GError *error = NULL;

	g_return_if_fail (CAMEL_IS_EWS_SETTINGS (settings));
	g_return_if_fail (email_address != NULL);
	g_return_if_fail (password != NULL);

	simple = g_simple_async_result_new (
		G_OBJECT (settings), callback,
		user_data, e_ews_autodiscover_ws_url);

	domain = strchr (email_address, '@');
	/* if it's non-NULL, then domain[0] == '@' */
	if (!domain || !domain[1]) {
		g_simple_async_result_set_error (
			simple, EWS_CONNECTION_ERROR, -1,
			"%s", _("Email address is missing a domain part"));
		g_simple_async_result_complete_in_idle (simple);
		g_object_unref (simple);
		return;
	}

	/*
	 * http://msdn.microsoft.com/en-us/library/ee332364.aspx says we are
	 * supposed to try $domain and then autodiscover.$domain. But some
	 * people have broken firewalls on the former which drop packets
	 * instead of rejecting connections, and make the request take ages
	 * to time out. So run both queries in parallel and let the fastest
	 * (successful) one win.
	 */
	ad = g_slice_new0 (struct _autodiscover_data);
	ad->cnc = e_ews_connection_new (source, domain + 1, settings); /* Fake URI, it's not used here */
	g_object_set (ad->cnc->priv->soup_session, SOUP_SESSION_TIMEOUT, 20, NULL);
	e_ews_connection_set_password (ad->cnc, password);

	if (G_IS_CANCELLABLE (cancellable)) {
		ad->cancellable = g_object_ref (cancellable);
		ad->cancel_id = g_cancellable_connect (
			ad->cancellable,
			G_CALLBACK (autodiscover_cancelled_cb),
			g_object_ref (ad->cnc),
			g_object_unref);
	}

	g_simple_async_result_set_op_res_gpointer (
		simple, ad, (GDestroyNotify) autodiscover_data_free);

	host_url = camel_ews_settings_get_hosturl (settings);

	if (!e_ews_discover_prepare_messages_and_send (simple, email_address, host_url, &error)) {
		g_simple_async_result_take_error (simple, error);
		g_simple_async_result_complete_in_idle (simple);
	} else {
		g_clear_error (&error);

		/* each request holds a reference to 'simple',
		 * thus remove one, to have it actually freed */
		g_object_unref (simple);
	}
}

static gboolean
has_suffix_icmp (const gchar *text,
                 const gchar *suffix)
{
	gint ii, tlen, slen;

	g_return_val_if_fail (text != NULL, FALSE);
	g_return_val_if_fail (suffix != NULL, FALSE);

	tlen = strlen (text);
	slen = strlen (suffix);

	if (!*text || !*suffix || tlen < slen)
		return FALSE;

	for (ii = 0; ii < slen; ii++) {
		if (g_ascii_tolower (text[tlen - ii - 1]) !=
		    g_ascii_tolower (suffix[slen - ii - 1]))
			break;
	}

	return ii == slen;
}

gboolean
e_ews_autodiscover_ws_url_finish (CamelEwsSettings *settings,
                                  GAsyncResult *result,
				  gchar **out_certificate_pem,
				  GTlsCertificateFlags *out_certificate_errors,
                                  GError **error)
{
	GSimpleAsyncResult *simple;
	struct _autodiscover_data *ad;
	GError *local_error = NULL;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (settings),
		e_ews_autodiscover_ws_url), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	ad = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, &local_error)) {
		if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
			if (!e_ews_connection_get_ssl_error_details (ad->cnc, out_certificate_pem, out_certificate_errors)) {
				if (out_certificate_pem)
					*out_certificate_pem = NULL;
				if (out_certificate_errors)
					*out_certificate_errors = 0;
			}
		}

		g_propagate_error (error, local_error);

		return FALSE;
	}

	g_warn_if_fail (ad->as_url != NULL);
	g_warn_if_fail (ad->oab_url != NULL);

	camel_ews_settings_set_hosturl (settings, ad->as_url);

	if (!has_suffix_icmp (ad->oab_url, "oab.xml")) {
		gchar *tmp;

		if (g_str_has_suffix (ad->oab_url, "/"))
			tmp = g_strconcat (ad->oab_url, "oab.xml", NULL);
		else
			tmp = g_strconcat (ad->oab_url, "/", "oab.xml", NULL);

		camel_ews_settings_set_oaburl (settings, tmp);
		g_free (tmp);
	} else {
		camel_ews_settings_set_oaburl (settings, ad->oab_url);
	}

	return TRUE;
}

struct _oal_req_data {
	EEwsConnection *cnc;
	SoupMessage *soup_message;
	gchar *oal_id;
	gchar *oal_element;

	GSList *oals;
	GSList *elements;
	gchar *etag;

	GCancellable *cancellable;
	gulong cancel_id;

	/* for dowloading oal file */
	gchar *cache_filename;
	GError *error;
	EwsProgressFn progress_fn;
	gpointer progress_data;
	gsize response_size;
	gsize received_size;
};

static void
oal_req_data_free (struct _oal_req_data *data)
{
	/* The SoupMessage is owned by the SoupSession. */
	g_object_unref (data->cnc);

	g_free (data->oal_id);
	g_free (data->oal_element);
	g_free (data->etag);

	g_slist_free_full (data->oals, (GDestroyNotify) ews_oal_free);
	g_slist_free_full (data->elements, (GDestroyNotify) ews_oal_details_free);

	if (data->cancellable != NULL) {
		g_cancellable_disconnect (data->cancellable, data->cancel_id);
		g_object_unref (data->cancellable);
	}

	g_free (data->cache_filename);

	g_slice_free (struct _oal_req_data, data);
}

static gchar *
get_property (xmlNodePtr node_ptr,
              const gchar *name)
{
	xmlChar *xml_s;
	gchar *s;

	xml_s = xmlGetProp (node_ptr, (const xmlChar *) name);
	s = g_strdup ((gchar *) xml_s);
	xmlFree (xml_s);

	return s;
}

static guint32
get_property_as_uint32 (xmlNodePtr node_ptr,
                        const gchar *name)
{
	gchar *s;
	guint32 val = -1;

	s = get_property (node_ptr, name);
	if (s)
		sscanf (s,"%"G_GUINT32_FORMAT, &val);
	g_free (s);

	return val;
}

static gchar *
get_content (xmlNodePtr node_ptr)
{
	xmlChar *xml_s;
	gchar *s;

	xml_s = xmlNodeGetContent (node_ptr);
	s = g_strdup ((gchar *) xml_s);
	xmlFree (xml_s);

	return s;
}

static GSList *
parse_oal_full_details (xmlNode *node,
                        const gchar *element)
{
	GSList *elements = NULL;

	for (node = node->children; node; node = node->next) {
		EwsOALDetails *det;
		if (node->type != XML_ELEMENT_NODE)
			continue;
		if (element && strcmp ((gchar *) node->name, element))
			continue;
		if (!element && strcmp ((gchar *) node->name, "Full") &&
		    strcmp((gchar *) node->name, "Diff"))
			continue;

		det = g_new0 (EwsOALDetails, 1);
		det->type = g_strdup((gchar *) node->name);
		det->seq = get_property_as_uint32 (node, "seq");
		det->ver = get_property_as_uint32 (node, "ver");
		det->size = get_property_as_uint32 (node, "size");
		det->uncompressed_size = get_property_as_uint32 (node, "uncompressedsize");
		det->sha = get_property (node, "uncompressedsize");
		det->filename = g_strstrip (get_content (node));

		elements = g_slist_prepend (elements, det);
		if (element && !strcmp (element, "Full"))
			break;
	}

	return elements;
}

/* this is run in cnc->priv->soup_thread */
static void
oal_response_cb (SoupSession *soup_session,
                 SoupMessage *soup_message,
                 gpointer user_data)
{
	GSimpleAsyncResult *simple;
	struct _oal_req_data *data;
	const gchar *etag;
	xmlDoc *doc;
	xmlNode *node;

	simple = G_SIMPLE_ASYNC_RESULT (user_data);
	data = g_simple_async_result_get_op_res_gpointer (simple);

	ews_connection_check_ssl_error (data->cnc, soup_message);

	if (ews_connection_credentials_failed (data->cnc, soup_message, simple)) {
		goto exit;
	} else if (soup_message->status_code != 200) {
		if (soup_message->status_code == SOUP_STATUS_UNAUTHORIZED &&
		    soup_message->response_headers) {
			const gchar *diagnostics;

			diagnostics = soup_message_headers_get_list (soup_message->response_headers, "X-MS-DIAGNOSTICS");
			if (diagnostics && strstr (diagnostics, "invalid_grant")) {
				g_simple_async_result_set_error (
					simple,
					EWS_CONNECTION_ERROR,
					EWS_CONNECTION_ERROR_ACCESSDENIED,
					"%s", diagnostics);
				goto exit;
			} else if (diagnostics && *diagnostics) {
				g_simple_async_result_set_error (
					simple,
					EWS_CONNECTION_ERROR,
					EWS_CONNECTION_ERROR_AUTHENTICATION_FAILED,
					"%s", diagnostics);
				goto exit;
			}
		}
		g_simple_async_result_set_error (
			simple, SOUP_HTTP_ERROR,
			soup_message->status_code,
			"%d %s",
			soup_message->status_code,
			soup_message->reason_phrase);
		goto exit;
	}

	etag = soup_message_headers_get_one(soup_message->response_headers,
					    "ETag");
	if (etag)
		data->etag = g_strdup(etag);

	e_ews_debug_dump_raw_soup_response (soup_message);

	doc = xmlReadMemory (
		soup_message->response_body->data,
		soup_message->response_body->length,
		"oab.xml", NULL, 0);
	if (doc == NULL) {
		g_simple_async_result_set_error (
			simple, EWS_CONNECTION_ERROR, -1,
			"%s", _("Failed to parse oab XML"));
		goto exit;
	}

	node = xmlDocGetRootElement (doc);
	if (strcmp ((gchar *) node->name, "OAB") != 0) {
		g_simple_async_result_set_error (
			simple, EWS_CONNECTION_ERROR, -1,
			"%s", _("Failed to find <OAB> element\n"));
		goto exit_doc;
	}

	for (node = node->children; node; node = node->next) {
		if (node->type == XML_ELEMENT_NODE && strcmp ((gchar *) node->name, "OAL") == 0) {
			if (data->oal_id == NULL) {
				EwsOAL *oal = g_new0 (EwsOAL, 1);

				oal->id = get_property (node, "id");
				oal->dn = get_property (node, "dn");
				oal->name = get_property (node, "name");

				data->oals = g_slist_prepend (data->oals, oal);
			} else {
				gchar *id = get_property (node, "id");

				if (strcmp (id, data->oal_id) == 0) {
					/* parse details of full_details file */
					data->elements = parse_oal_full_details (node, data->oal_element);

					g_free (id);
					break;
				}

				g_free (id);
			}
		}
	}

	data->oals = g_slist_reverse (data->oals);

 exit_doc:
	xmlFreeDoc (doc);
 exit:
	g_simple_async_result_complete_in_idle (simple);
	/* This is run in cnc->priv->soup_thread, and the cnc is held by simple, thus
	 * for cases when the complete_in_idle is finished before the unref call, when
	 * the cnc will be left with the last reference and thus cannot join the soup_thread
	 * while still in it, the unref is done in a dedicated thread. */
	e_ews_connection_utils_unref_in_thread (simple);
}

static void
ews_cancel_msg (GCancellable *cancellable,
                struct _oal_req_data *data)
{
	ews_connection_schedule_cancel_message (data->cnc, data->soup_message);
}

gboolean
e_ews_connection_get_oal_list_sync (EEwsConnection *cnc,
                                    GSList **oals,
                                    GCancellable *cancellable,
                                    GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_oal_list (
		cnc, cancellable, e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_oal_list_finish (
		cnc, result, oals, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_get_oal_list (EEwsConnection *cnc,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	GSimpleAsyncResult *simple;
	SoupMessage *soup_message;
	struct _oal_req_data *data;
	GError *error = NULL;

	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	soup_message = e_ews_get_msg_for_url (cnc, cnc->priv->uri, NULL, &error);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_oal_list);

	if (!soup_message) {
		g_simple_async_result_take_error (simple, error);
		g_simple_async_result_complete_in_idle (simple);
		return;
	}

	data = g_slice_new0 (struct _oal_req_data);
	data->cnc = g_object_ref (cnc);
	data->soup_message = soup_message;  /* the session owns this */

	if (G_IS_CANCELLABLE (cancellable)) {
		data->cancellable = g_object_ref (cancellable);
		data->cancel_id = g_cancellable_connect (
			data->cancellable,
			G_CALLBACK (ews_cancel_msg),
			data, (GDestroyNotify) NULL);
	}

	g_simple_async_result_set_op_res_gpointer (
		simple, data, (GDestroyNotify) oal_req_data_free);

	ews_connection_schedule_queue_message (cnc, soup_message, oal_response_cb, simple);
}

gboolean
e_ews_connection_get_oal_list_finish (EEwsConnection *cnc,
                                      GAsyncResult *result,
                                      GSList **oals,
                                      GError **error)
{
	GSimpleAsyncResult *simple;
	struct _oal_req_data *data;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_oal_list),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (oals != NULL) {
		*oals = data->oals;
		data->oals = NULL;
	}

	return TRUE;
}

/**
 * e_ews_connection_get_oal_detail 
 * @cnc: 
 * @oal_id: 
 * @oal_element: 
 * @elements: "Full" "Diff" "Template" are the possible values.
 * @cancellable: 
 * @error: 
 * 
 * 
 * Returns: 
 **/
gboolean
e_ews_connection_get_oal_detail_sync (EEwsConnection *cnc,
                                      const gchar *oal_id,
                                      const gchar *oal_element,
				      const gchar *old_etag,
                                      GSList **elements,
				      gchar **etag,
                                      GCancellable *cancellable,
                                      GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_oal_detail (
		cnc, oal_id, oal_element, old_etag,
		cancellable, e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_oal_detail_finish (
		cnc, result, elements, etag, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_get_oal_detail (EEwsConnection *cnc,
                                 const gchar *oal_id,
                                 const gchar *oal_element,
				 const gchar *etag,
                                 GCancellable *cancellable,
                                 GAsyncReadyCallback callback,
                                 gpointer user_data)
{
	GSimpleAsyncResult *simple;
	SoupMessage *soup_message;
	struct _oal_req_data *data;
	gchar *sep;
	GError *error = NULL;

	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	soup_message = e_ews_get_msg_for_url (cnc, cnc->priv->uri, NULL, &error);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_oal_detail);

	if (!soup_message) {
		g_simple_async_result_take_error (simple, error);
		g_simple_async_result_complete_in_idle (simple);
		return;
	}

	if (etag && *etag)
		soup_message_headers_append (soup_message->request_headers,
					     "If-None-Match", etag);

	data = g_slice_new0 (struct _oal_req_data);
	data->cnc = g_object_ref (cnc);
	data->soup_message = soup_message;  /* the session owns this */
	data->oal_id = g_strdup (oal_id);
	data->oal_element = g_strdup (oal_element);

	/* oal_id can be of form "GUID:name", but here is compared only GUID */
	sep = strchr (data->oal_id, ':');
	if (sep)
		*sep = '\0';

	if (G_IS_CANCELLABLE (cancellable)) {
		data->cancellable = g_object_ref (cancellable);
		data->cancel_id = g_cancellable_connect (
			data->cancellable,
			G_CALLBACK (ews_cancel_msg),
			data, (GDestroyNotify) NULL);
	}

	g_simple_async_result_set_op_res_gpointer (
		simple, data, (GDestroyNotify) oal_req_data_free);

	ews_connection_schedule_queue_message (cnc, soup_message, oal_response_cb, simple);
}

gboolean
e_ews_connection_get_oal_detail_finish (EEwsConnection *cnc,
                                        GAsyncResult *result,
                                        GSList **elements,
					gchar **etag,
                                        GError **error)
{
	GSimpleAsyncResult *simple;
	struct _oal_req_data *data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_oal_detail),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (elements != NULL) {
		*elements = data->elements;
		data->elements = NULL;
	}
	if (etag != NULL) {
		*etag = data->etag;
		data->etag = NULL;
	}

	return TRUE;

}

static void
oal_download_response_cb (SoupSession *soup_session,
                          SoupMessage *soup_message,
                          gpointer user_data)
{
	GSimpleAsyncResult *simple;
	struct _oal_req_data *data;

	simple = G_SIMPLE_ASYNC_RESULT (user_data);
	data = g_simple_async_result_get_op_res_gpointer (simple);

	ews_connection_check_ssl_error (data->cnc, soup_message);

	if (ews_connection_credentials_failed (data->cnc, soup_message, simple)) {
		g_unlink (data->cache_filename);
	} else if (soup_message->status_code != 200) {
		g_simple_async_result_set_error (
			simple, SOUP_HTTP_ERROR,
			soup_message->status_code,
			"%d %s",
			soup_message->status_code,
			soup_message->reason_phrase);
		g_unlink (data->cache_filename);

	} else if (data->error != NULL) {
		g_simple_async_result_take_error (simple, data->error);
		data->error = NULL;
		g_unlink (data->cache_filename);
	}

	e_ews_debug_dump_raw_soup_response (soup_message);

	g_simple_async_result_complete_in_idle (simple);
	e_ews_connection_utils_unref_in_thread (simple);
}

static void
ews_soup_got_headers (SoupMessage *msg,
                      gpointer user_data)
{
	struct _oal_req_data *data = (struct _oal_req_data *) user_data;
	const gchar *size;

	size = soup_message_headers_get_one (
		msg->response_headers,
		"Content-Length");

	if (size)
		data->response_size = strtol (size, NULL, 10);
}

static void
ews_soup_restarted (SoupMessage *msg,
                    gpointer user_data)
{
	struct _oal_req_data *data = (struct _oal_req_data *) user_data;

	data->response_size = 0;
	data->received_size = 0;
}

static void
ews_soup_got_chunk (SoupMessage *msg,
                    SoupBuffer *chunk,
                    gpointer user_data)
{
	struct _oal_req_data *data = (struct _oal_req_data *) user_data;
	gint fd;

	if (msg->status_code != 200)
		return;

	data->received_size += chunk->length;

	if (data->response_size && data->progress_fn) {
		gint pc = data->received_size * 100 / data->response_size;
		data->progress_fn (data->progress_data, pc);
	}

	fd = g_open (data->cache_filename, O_RDONLY | O_WRONLY | O_APPEND | O_CREAT, 0600);
	if (fd != -1) {
		if (write (fd, (const gchar *) chunk->data, chunk->length) != chunk->length) {
			g_set_error (
				&data->error, EWS_CONNECTION_ERROR, EWS_CONNECTION_ERROR_UNKNOWN,
				"Failed to write streaming data to file '%s': %s", data->cache_filename, g_strerror (errno));
		}
		close (fd);
	} else {
		g_set_error (
			&data->error, EWS_CONNECTION_ERROR, EWS_CONNECTION_ERROR_UNKNOWN,
			"Failed to open the cache file '%s': %s", data->cache_filename, g_strerror (errno));
	}
}

gboolean
e_ews_connection_download_oal_file_sync (EEwsConnection *cnc,
                                         const gchar *cache_filename,
                                         EwsProgressFn progress_fn,
                                         gpointer progress_data,
                                         GCancellable *cancellable,
                                         GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_download_oal_file (
		cnc, cache_filename,
		progress_fn, progress_data, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_download_oal_file_finish (
		cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_download_oal_file (EEwsConnection *cnc,
                                    const gchar *cache_filename,
                                    EwsProgressFn progress_fn,
                                    gpointer progress_data,
                                    GCancellable *cancellable,
                                    GAsyncReadyCallback callback,
                                    gpointer user_data)
{
	GSimpleAsyncResult *simple;
	SoupMessage *soup_message;
	struct _oal_req_data *data;
	GError *error = NULL;

	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	soup_message = e_ews_get_msg_for_url (cnc, cnc->priv->uri, NULL, &error);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_download_oal_file);

	if (!soup_message) {
		g_simple_async_result_take_error (simple, error);
		g_simple_async_result_complete_in_idle (simple);
		return;
	}

	data = g_slice_new0 (struct _oal_req_data);
	data->cnc = g_object_ref (cnc);
	data->soup_message = soup_message;  /* the session owns this */
	data->cache_filename = g_strdup (cache_filename);
	data->progress_fn = progress_fn;
	data->progress_data = progress_data;

	if (G_IS_CANCELLABLE (cancellable)) {
		data->cancellable = g_object_ref (cancellable);
		data->cancel_id = g_cancellable_connect (
			data->cancellable,
			G_CALLBACK (ews_cancel_msg),
			data, (GDestroyNotify) NULL);
	}

	g_simple_async_result_set_op_res_gpointer (
		simple, data, (GDestroyNotify) oal_req_data_free);

	/*
	 * Don't use streaming-based messages when we are loggin the traffic
	 * to generate trace files for tests
	 */
	if (e_ews_debug_get_log_level () <= 3)
		soup_message_body_set_accumulate (soup_message->response_body, FALSE);

	g_signal_connect (
		soup_message, "got-headers",
		G_CALLBACK (ews_soup_got_headers), data);
	g_signal_connect (
		soup_message, "got-chunk",
		G_CALLBACK (ews_soup_got_chunk), data);
	g_signal_connect (
		soup_message, "restarted",
		G_CALLBACK (ews_soup_restarted), data);

	ews_connection_schedule_queue_message (cnc, soup_message, oal_download_response_cb, simple);
}

gboolean
e_ews_connection_download_oal_file_finish (EEwsConnection *cnc,
                                           GAsyncResult *result,
                                           GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc),
		e_ews_connection_download_oal_file), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

const gchar *
e_ews_connection_get_mailbox (EEwsConnection *cnc)
{
	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);

	if (!cnc->priv->email || !*cnc->priv->email)
		return camel_ews_settings_get_email (cnc->priv->settings);

	return cnc->priv->email;
}

void
e_ews_connection_set_mailbox (EEwsConnection *cnc,
                              const gchar *email)
{
	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));
	g_return_if_fail (email != NULL);

	g_free (cnc->priv->email);
	cnc->priv->email = g_strdup (email);
}

static void
ews_append_additional_props_to_msg (ESoapMessage *msg,
                                    const EEwsAdditionalProps *add_props)
{
	GSList *l;

	if (!add_props)
		return;

	e_soap_message_start_element (msg, "AdditionalProperties", NULL, NULL);

	if (add_props->field_uri) {
		gchar **prop = g_strsplit (add_props->field_uri, " ", 0);
		gint i = 0;

		while (prop[i]) {
			e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", prop[i]);
			i++;
		}

		g_strfreev (prop);
	}

	if (add_props->extended_furis) {
		for (l = add_props->extended_furis; l != NULL; l = g_slist_next (l)) {
			EEwsExtendedFieldURI *ex_furi = l->data;

			e_soap_message_start_element (msg, "ExtendedFieldURI", NULL, NULL);

			if (ex_furi->distinguished_prop_set_id)
				e_soap_message_add_attribute (msg, "DistinguishedPropertySetId", ex_furi->distinguished_prop_set_id, NULL, NULL);

			if (ex_furi->prop_tag)
				e_soap_message_add_attribute (msg, "PropertyTag", ex_furi->prop_tag, NULL, NULL);

			if (ex_furi->prop_set_id)
				e_soap_message_add_attribute (msg, "PropertySetId", ex_furi->prop_set_id, NULL, NULL);

			if (ex_furi->prop_name)
				e_soap_message_add_attribute (msg, "PropertyName", ex_furi->prop_name, NULL, NULL);

			if (ex_furi->prop_id)
				e_soap_message_add_attribute (msg, "PropertyId", ex_furi->prop_id, NULL, NULL);

			if (ex_furi->prop_type)
				e_soap_message_add_attribute (msg, "PropertyType", ex_furi->prop_type, NULL, NULL);

			e_soap_message_end_element (msg);
		}
	}

	if (add_props->indexed_furis) {
		for (l = add_props->indexed_furis; l != NULL; l = g_slist_next (l)) {
			EEwsIndexedFieldURI *in_furi = l->data;

			e_soap_message_start_element (msg, "IndexedFieldURI", NULL, NULL);

			e_soap_message_add_attribute (msg, "FieldURI", in_furi->field_uri, NULL, NULL);
			e_soap_message_add_attribute (msg, "FieldIndex", in_furi->field_index, NULL, NULL);

			e_soap_message_end_element (msg);
		}
	}

	e_soap_message_end_element (msg);
}

static void
ews_write_sort_order_to_msg (ESoapMessage *msg,
                             EwsSortOrder *sort_order)
{
	if (!sort_order)
		return;

	e_soap_message_start_element (msg, "SortOrder", NULL, NULL);
	e_soap_message_start_element (msg, "FieldOrder", NULL, NULL);
	e_soap_message_add_attribute (msg, "Order", sort_order->order, NULL, NULL);

	if (sort_order->uri_type == NORMAL_FIELD_URI)
		e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", (gchar *) sort_order->field_uri);
	else if (sort_order->uri_type == INDEXED_FIELD_URI) {
		EEwsIndexedFieldURI *in_furi = sort_order->field_uri;

		e_soap_message_start_element (msg, "IndexedFieldURI", NULL, NULL);
		e_soap_message_add_attribute (msg, "FieldURI", in_furi->field_uri, NULL, NULL);
		e_soap_message_add_attribute (msg, "FieldIndex", in_furi->field_index, NULL, NULL);
		e_soap_message_end_element (msg);
	} else if (sort_order->uri_type == EXTENDED_FIELD_URI) {
		EEwsExtendedFieldURI *ex_furi = sort_order->field_uri;

		e_soap_message_start_element (msg, "ExtendedFieldURI", NULL, NULL);

		if (ex_furi->distinguished_prop_set_id)
			e_soap_message_add_attribute (msg, "DistinguishedPropertySetId", ex_furi->distinguished_prop_set_id, NULL, NULL);
		if (ex_furi->prop_set_id)
			e_soap_message_add_attribute (msg, "PropertySetId", ex_furi->prop_set_id, NULL, NULL);
		if (ex_furi->prop_name)
			e_soap_message_add_attribute (msg, "PropertyName", ex_furi->prop_name, NULL, NULL);
		if (ex_furi->prop_id)
			e_soap_message_add_attribute (msg, "PropertyId", ex_furi->prop_id, NULL, NULL);
		if (ex_furi->prop_type)
			e_soap_message_add_attribute (msg, "PropertyType", ex_furi->prop_type, NULL, NULL);

		e_soap_message_end_element (msg);
	}

	e_soap_message_end_element (msg);
	e_soap_message_end_element (msg);
}

/**
 * e_ews_connection_sync_folder_items:
 * @cnc: The EWS Connection
 * @pri: The priority associated with the request
 * @last_sync_state: To sync with the previous requests
 * @folder_id: The folder to which the items belong
 * @default_props: Can take one of the values: IdOnly,Default or AllProperties
 * @additional_props: Specify any additional properties to be fetched
 * @max_entries: Maximum number of items to be returned
 * @cancellable: a GCancellable to monitor cancelled operations
 * @callback: Responses are parsed and returned to this callback
 * @user_data: user data passed to callback
 **/
void
e_ews_connection_sync_folder_items (EEwsConnection *cnc,
                                    gint pri,
                                    const gchar *last_sync_state,
                                    const gchar *fid,
                                    const gchar *default_props,
				    const EEwsAdditionalProps *add_props,
                                    guint max_entries,
                                    GCancellable *cancellable,
                                    GAsyncReadyCallback callback,
                                    gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"SyncFolderItems",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);
	e_soap_message_start_element (msg, "ItemShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, default_props);

	ews_append_additional_props_to_msg (msg, add_props);

	e_soap_message_end_element (msg);

	e_soap_message_start_element (msg, "SyncFolderId", "messages", NULL);
	e_ews_message_write_string_parameter_with_attribute (msg, "FolderId", NULL, NULL, "Id", fid);
	e_soap_message_end_element (msg);

	if (last_sync_state)
		e_ews_message_write_string_parameter (msg, "SyncState", "messages", last_sync_state);

	/* Max changes requested */
	e_ews_message_write_int_parameter (msg, "MaxChangesReturned", "messages", max_entries);

	/* Complete the footer and print the request */
	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_sync_folder_items);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, sync_folder_items_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_sync_folder_items_finish (EEwsConnection *cnc,
                                           GAsyncResult *result,
                                           gchar **new_sync_state,
                                           gboolean *includes_last_item,
                                           GSList **items_created,
                                           GSList **items_updated,
                                           GSList **items_deleted,
                                           GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_sync_folder_items),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*new_sync_state = async_data->sync_state;
	*includes_last_item = async_data->includes_last_item;
	*items_created = async_data->items_created;
	*items_updated = async_data->items_updated;
	*items_deleted = async_data->items_deleted;

	return TRUE;
}

gboolean
e_ews_connection_sync_folder_items_sync (EEwsConnection *cnc,
                                         gint pri,
                                         const gchar *old_sync_state,
                                         const gchar *fid,
                                         const gchar *default_props,
					 const EEwsAdditionalProps *add_props,
                                         guint max_entries,
                                         gchar **new_sync_state,
                                         gboolean *includes_last_item,
                                         GSList **items_created,
                                         GSList **items_updated,
                                         GSList **items_deleted,
                                         GCancellable *cancellable,
                                         GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_sync_folder_items (
		cnc, pri, old_sync_state, fid, default_props,
		add_props, max_entries, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_sync_folder_items_finish (
		cnc, result, new_sync_state, includes_last_item,
		items_created, items_updated, items_deleted, error);

	e_async_closure_free (closure);

	return success;
}

static void
ews_append_folder_ids_to_msg (ESoapMessage *msg,
                              const gchar *email,
                              GSList *folder_ids)
{
	GSList *l;

	for (l = folder_ids; l != NULL; l = g_slist_next (l)) {
		const EwsFolderId *fid = l->data;

		e_ews_folder_id_append_to_msg (msg, email, fid);
	}
}

static void
ews_connection_write_only_ids_restriction (ESoapMessage *msg,
					   GPtrArray *only_ids)
{
	guint ii;

	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));
	g_return_if_fail (only_ids && only_ids->len);

	for (ii = 0; ii < only_ids->len; ii++) {
		const gchar *itemid = g_ptr_array_index (only_ids, ii);

		e_soap_message_start_element (msg, "IsEqualTo", NULL, NULL);
		e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", "item:ItemId");
		e_soap_message_start_element (msg, "FieldURIOrConstant", NULL, NULL);
		e_ews_message_write_string_parameter_with_attribute (msg, "Constant", NULL, NULL, "Value", itemid);
		e_soap_message_end_element (msg); /* FieldURIOrConstant */
		e_soap_message_end_element (msg); /* IsEqualTo */
	}
}

/**
 * e_ews_connection_find_folder_items:
 * @cnc: The EWS Connection
 * @pri: The priority associated with the request
 * @fid: The folder id to which the items belong
 * @default_props: Can take one of the values: IdOnly,Default or AllProperties
 * @add_props: Specify any additional properties to be fetched
 * @sort_order: Specific sorting order for items
 * @query: evo query based on which items will be fetched
 * @only_ids: (element-type utf8) (nullable): a gchar * with item IDs, to check with only; can be %NULL
 * @type: type of folder
 * @convert_query_cb: a callback method to convert query to ews restiction
 * @cancellable: a GCancellable to monitor cancelled operations
 * @callback: Responses are parsed and returned to this callback
 * @user_data: user data passed to callback
 **/
void
e_ews_connection_find_folder_items (EEwsConnection *cnc,
                                    gint pri,
                                    EwsFolderId *fid,
                                    const gchar *default_props,
                                    const EEwsAdditionalProps *add_props,
                                    EwsSortOrder *sort_order,
                                    const gchar *query,
				    GPtrArray *only_ids, /* element-type utf8 */
                                    EEwsFolderType type,
                                    EwsConvertQueryCallback convert_query_cb,
                                    GCancellable *cancellable,
                                    GAsyncReadyCallback callback,
                                    gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"FindItem",
			"Traversal",
			"Shallow",
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);
	e_soap_message_start_element (msg, "ItemShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, default_props);

	ews_append_additional_props_to_msg (msg, add_props);

	e_soap_message_end_element (msg);

	/*write restriction message based on query*/
	if (convert_query_cb) {
		e_soap_message_start_element (msg, "Restriction", "messages", NULL);

		if (only_ids && only_ids->len) {
			e_soap_message_start_element (msg, "And", "messages", NULL);
			e_soap_message_start_element (msg, "Or", "messages", NULL);
			ews_connection_write_only_ids_restriction (msg, only_ids);
			e_soap_message_end_element (msg); /* Or */
		}

		convert_query_cb (msg, query, type);

		if (only_ids && only_ids->len)
			e_soap_message_end_element (msg); /* And */

		e_soap_message_end_element (msg); /* Restriction */
	} else if (only_ids && only_ids->len) {
		e_soap_message_start_element (msg, "Restriction", "messages", NULL);
		ews_connection_write_only_ids_restriction (msg, only_ids);
		e_soap_message_end_element (msg);
	}

	if (sort_order)
		ews_write_sort_order_to_msg (msg, sort_order);

	e_soap_message_start_element (msg, "ParentFolderIds", "messages", NULL);

	if (fid->is_distinguished_id)
		e_ews_message_write_string_parameter_with_attribute (msg, "DistinguishedFolderId", NULL, NULL, "Id", fid->id);
	else
		e_ews_message_write_string_parameter_with_attribute (msg, "FolderId", NULL, NULL, "Id", fid->id);

	e_soap_message_end_element (msg);

	/* Complete the footer and print the request */
	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_find_folder_items);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, find_folder_items_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_find_folder_items_finish (EEwsConnection *cnc,
                                           GAsyncResult *result,
                                           gboolean *includes_last_item,
                                           GSList **items,
                                           GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_find_folder_items),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*includes_last_item = async_data->includes_last_item;
	*items = async_data->items;

	return TRUE;
}

gboolean
e_ews_connection_find_folder_items_sync (EEwsConnection *cnc,
                                         gint pri,
                                         EwsFolderId *fid,
                                         const gchar *default_props,
                                         const EEwsAdditionalProps *add_props,
                                         EwsSortOrder *sort_order,
                                         const gchar *query,
					 GPtrArray *only_ids, /* element-type utf8 */
                                         EEwsFolderType type,
                                         gboolean *includes_last_item,
                                         GSList **items,
                                         EwsConvertQueryCallback convert_query_cb,
                                         GCancellable *cancellable,
                                         GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_find_folder_items (
		cnc, pri, fid, default_props,
		add_props, sort_order, query,
		only_ids, type, convert_query_cb, NULL,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_find_folder_items_finish (
		cnc, result, includes_last_item, items, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_sync_folder_hierarchy (EEwsConnection *cnc,
                                        gint pri,
                                        const gchar *sync_state,
                                        GCancellable *cancellable,
                                        GAsyncReadyCallback callback,
                                        gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"SyncFolderHierarchy",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);
	e_soap_message_start_element (msg, "FolderShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, "AllProperties");
	e_soap_message_start_element (msg, "AdditionalProperties", NULL, NULL);
	e_soap_message_start_element (msg, "ExtendedFieldURI", NULL, NULL);
	e_soap_message_add_attribute (msg, "PropertyTag", "4340", NULL, NULL); /* PidTagAttributeHidden */
	e_soap_message_add_attribute (msg, "PropertyType", "Boolean", NULL, NULL);
	e_soap_message_end_element (msg); /* ExtendedFieldURI */
	e_soap_message_end_element (msg); /* AdditionalProperties */
	e_soap_message_end_element (msg); /* FolderShape */

	if (sync_state)
		e_ews_message_write_string_parameter (msg, "SyncState", "messages", sync_state);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_sync_folder_hierarchy);

	async_data = g_slice_new0 (EwsAsyncData);
	async_data->cnc = cnc;
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, sync_hierarchy_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_sync_folder_hierarchy_finish (EEwsConnection *cnc,
                                               GAsyncResult *result,
                                               gchar **sync_state,
                                               gboolean *includes_last_folder,
                                               GSList **folders_created,
                                               GSList **folders_updated,
                                               GSList **folders_deleted,
                                               GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_sync_folder_hierarchy),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*sync_state = async_data->sync_state;
	*includes_last_folder = async_data->includes_last_item;
	*folders_created = async_data->items_created;
	*folders_updated = async_data->items_updated;
	*folders_deleted = async_data->items_deleted;

	return TRUE;
}

gboolean
e_ews_connection_sync_folder_hierarchy_sync (EEwsConnection *cnc,
                                             gint pri,
					     const gchar *old_sync_state,
                                             gchar **new_sync_state,
                                             gboolean *includes_last_folder,
                                             GSList **folders_created,
                                             GSList **folders_updated,
                                             GSList **folders_deleted,
                                             GCancellable *cancellable,
                                             GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_sync_folder_hierarchy (
		cnc, pri, old_sync_state, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_sync_folder_hierarchy_finish (
		cnc, result, new_sync_state,
		includes_last_folder,
		folders_created,
		folders_updated,
		folders_deleted,
		error);

	e_async_closure_free (closure);

	return success;
}

EEwsServerVersion
e_ews_connection_get_server_version (EEwsConnection *cnc)
{
	g_return_val_if_fail (cnc != NULL, E_EWS_EXCHANGE_UNKNOWN);
	g_return_val_if_fail (cnc->priv != NULL, E_EWS_EXCHANGE_UNKNOWN);

	return cnc->priv->version;
}

void
e_ews_connection_set_server_version (EEwsConnection *cnc,
				     EEwsServerVersion version)
{
	g_return_if_fail (cnc != NULL);
	g_return_if_fail (cnc->priv != NULL);

	if (cnc->priv->version != version)
		cnc->priv->version = version;
}

void
e_ews_connection_set_server_version_from_string (EEwsConnection *cnc,
						 const gchar *version)
{
	if (!version)
		cnc->priv->version = E_EWS_EXCHANGE_UNKNOWN;
	else if (g_strcmp0 (version, "Exchange2007") == 0)
		cnc->priv->version = E_EWS_EXCHANGE_2007;
	else if (g_strcmp0 (version, "Exchange2007_SP1") == 0 ||
		 g_str_has_prefix (version, "Exchange2007"))
		cnc->priv->version = E_EWS_EXCHANGE_2007_SP1;
	else if (g_strcmp0 (version, "Exchange2010") == 0)
		cnc->priv->version = E_EWS_EXCHANGE_2010;
	else if (g_strcmp0 (version, "Exchange2010_SP1") == 0)
		cnc->priv->version = E_EWS_EXCHANGE_2010_SP1;
	else if (g_strcmp0 (version, "Exchange2010_SP2") == 0 ||
		 g_str_has_prefix (version, "Exchange2010"))
		cnc->priv->version = E_EWS_EXCHANGE_2010_SP2;
	else
		cnc->priv->version = E_EWS_EXCHANGE_FUTURE;
}

gboolean
e_ews_connection_satisfies_server_version (EEwsConnection *cnc,
					  EEwsServerVersion version)
{
	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (cnc->priv != NULL, FALSE);

	/*
	 * This test always will fail if, for some reason, we were not able to get the server version.
	 * It occurrs intentionally because we don't want to call any function that expects an EWS
	 * Server version higher than 2007 SP1 without be sure we using an EWS Server with version
	 * 2007 SP1 or later.
	 */
	return cnc->priv->version >= version;
}

void
e_ews_connection_get_items (EEwsConnection *cnc,
                            gint pri,
                            const GSList *ids,
                            const gchar *default_props,
			    const EEwsAdditionalProps *add_props,
                            gboolean include_mime,
                            const gchar *mime_directory,
			    EEwsBodyType body_type,
                            ESoapProgressFn progress_fn,
                            gpointer progress_data,
                            GCancellable *cancellable,
                            GAsyncReadyCallback callback,
                            gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *l;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetItem",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	if (progress_fn && progress_data)
		e_soap_message_set_progress_fn (msg, progress_fn, progress_data);

	e_soap_message_start_element (msg, "ItemShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, default_props);

	if (include_mime)
		e_ews_message_write_string_parameter (msg, "IncludeMimeContent", NULL, "true");
	else
		e_ews_message_write_string_parameter (msg, "IncludeMimeContent", NULL, "false");
	if (mime_directory)
		e_soap_message_store_node_data (msg, "MimeContent", mime_directory, TRUE);

	switch (body_type) {
	case E_EWS_BODY_TYPE_BEST:
		e_ews_message_write_string_parameter (msg, "BodyType", NULL, "Best");
		break;
	case E_EWS_BODY_TYPE_HTML:
		e_ews_message_write_string_parameter (msg, "BodyType", NULL, "HTML");
		break;
	case E_EWS_BODY_TYPE_TEXT:
		e_ews_message_write_string_parameter (msg, "BodyType", NULL, "Text");
		break;
	case E_EWS_BODY_TYPE_ANY:
		break;
	}

	ews_append_additional_props_to_msg (msg, add_props);

	e_soap_message_end_element (msg);

	e_soap_message_start_element (msg, "ItemIds", "messages", NULL);

	for (l = ids; l != NULL; l = g_slist_next (l))
		e_ews_message_write_string_parameter_with_attribute (msg, "ItemId", NULL, NULL, "Id", l->data);

	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_items);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, get_items_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_items_finish (EEwsConnection *cnc,
                                   GAsyncResult *result,
                                   GSList **items,
                                   GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_items),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (!async_data->items) {
		g_set_error_literal (error, EWS_CONNECTION_ERROR, EWS_CONNECTION_ERROR_ITEMNOTFOUND, _("No items found"));
		return FALSE;
	}

	*items = async_data->items;

	return TRUE;
}

gboolean
e_ews_connection_get_items_sync (EEwsConnection *cnc,
                                 gint pri,
                                 const GSList *ids,
                                 const gchar *default_props,
				 const EEwsAdditionalProps *add_props,
                                 gboolean include_mime,
                                 const gchar *mime_directory,
				 EEwsBodyType body_type,
                                 GSList **items,
                                 ESoapProgressFn progress_fn,
                                 gpointer progress_data,
                                 GCancellable *cancellable,
                                 GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_items (
		cnc, pri,ids, default_props,
		add_props, include_mime,
		mime_directory, body_type, progress_fn,
		progress_data, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_items_finish (
		cnc, result, items, error);

	e_async_closure_free (closure);

	return success;
}

static const gchar *
ews_delete_type_to_str (EwsDeleteType delete_type)
{
	switch (delete_type) {
		case EWS_HARD_DELETE:
			return "HardDelete";
		case EWS_SOFT_DELETE:
			return "SoftDelete";
		case EWS_MOVE_TO_DELETED_ITEMS:
			return "MoveToDeletedItems";
	}
	return NULL;
}

static const gchar *
ews_send_cancels_to_str (EwsSendMeetingCancellationsType send_cancels)
{
	switch (send_cancels) {
		case EWS_SEND_TO_NONE:
			return "SendToNone";
		case EWS_SEND_ONLY_TO_ALL:
			return "SendOnlyToAll";
		case EWS_SEND_TO_ALL_AND_SAVE_COPY:
			return "SendToAllAndSaveCopy";
	}
	return NULL;
}

static const gchar *
ews_affected_tasks_to_str (EwsAffectedTaskOccurrencesType affected_tasks)
{
	switch (affected_tasks) {
		case EWS_NONE_OCCURRENCES:
			return NULL;
		case EWS_ALL_OCCURRENCES:
			return "AllOccurrences";
		case EWS_SPECIFIED_OCCURRENCE_ONLY:
			return "SpecifiedOccurrenceOnly";
	}
	return NULL;
}

static void
delete_item_response_cb (ESoapResponse *response,
                         GSimpleAsyncResult *simple)
{
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_delete_items (EEwsConnection *cnc,
                               gint pri,
                               const GSList *ids,
                               EwsDeleteType delete_type,
                               EwsSendMeetingCancellationsType send_cancels,
                               EwsAffectedTaskOccurrencesType affected_tasks,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *iter;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"DeleteItem",
			"DeleteType",
			ews_delete_type_to_str (delete_type),
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	if (send_cancels)
		e_soap_message_add_attribute (
			msg, "SendMeetingCancellations",
			ews_send_cancels_to_str (send_cancels), NULL, NULL);

	if (affected_tasks)
		e_soap_message_add_attribute (
			msg, "AffectedTaskOccurrences",
			ews_affected_tasks_to_str (affected_tasks), NULL, NULL);

	e_soap_message_start_element (msg, "ItemIds", "messages", NULL);

	for (iter = ids; iter != NULL; iter = g_slist_next (iter))
		e_ews_message_write_string_parameter_with_attribute (msg, "ItemId", NULL, NULL, "Id", iter->data);

	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_delete_items);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, delete_item_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

void
e_ews_connection_delete_item (EEwsConnection *cnc,
                              gint pri,
                              EwsId *item_id,
                              guint index,
                              EwsDeleteType delete_type,
                              EwsSendMeetingCancellationsType send_cancels,
                              EwsAffectedTaskOccurrencesType affected_tasks,
                              GCancellable *cancellable,
                              GAsyncReadyCallback callback,
                              gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	gchar buffer[32];

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"DeleteItem",
			"DeleteType",
			ews_delete_type_to_str (delete_type),
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	if (send_cancels)
		e_soap_message_add_attribute (
			msg, "SendMeetingCancellations",
			ews_send_cancels_to_str (send_cancels), NULL, NULL);

	if (affected_tasks != EWS_NONE_OCCURRENCES)
		e_soap_message_add_attribute (
			msg, "AffectedTaskOccurrences",
			ews_affected_tasks_to_str (affected_tasks), NULL, NULL);

	e_soap_message_start_element (msg, "ItemIds", "messages", NULL);

	if (index) {
		e_soap_message_start_element (msg, "OccurrenceItemId", NULL, NULL);
		e_soap_message_add_attribute (msg, "RecurringMasterId", item_id->id, NULL, NULL);
		if (item_id->change_key)
			e_soap_message_add_attribute (msg, "ChangeKey", item_id->change_key, NULL, NULL);
		snprintf (buffer, 32, "%u", index);
		e_soap_message_add_attribute (msg, "InstanceIndex", buffer, NULL, NULL);
		e_soap_message_end_element (msg);
	} else {
		e_soap_message_start_element (msg, "ItemId", NULL, NULL);
		e_soap_message_add_attribute (msg, "Id", item_id->id, NULL, NULL);
		if (item_id->change_key)
			e_soap_message_add_attribute (msg, "ChangeKey", item_id->change_key, NULL, NULL);
		e_soap_message_end_element (msg);
	}

	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_delete_items);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, delete_item_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_delete_items_finish (EEwsConnection *cnc,
                                      GAsyncResult *result,
                                      GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_delete_items),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	return TRUE;
}

gboolean
e_ews_connection_delete_items_sync (EEwsConnection *cnc,
                                    gint pri,
                                    const GSList *ids,
                                    EwsDeleteType delete_type,
                                    EwsSendMeetingCancellationsType send_cancels,
                                    EwsAffectedTaskOccurrencesType affected_tasks,
                                    GCancellable *cancellable,
                                    GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_delete_items (
		cnc, pri, ids, delete_type,
		send_cancels, affected_tasks, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_delete_items_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

gboolean
e_ews_connection_delete_item_sync (EEwsConnection *cnc,
                                   gint pri,
                                   EwsId *id,
                                   guint index,
                                   EwsDeleteType delete_type,
                                   EwsSendMeetingCancellationsType send_cancels,
                                   EwsAffectedTaskOccurrencesType affected_tasks,
                                   GCancellable *cancellable,
                                   GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_delete_item (
		cnc, pri, id, index, delete_type,
		send_cancels, affected_tasks, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_delete_items_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

gboolean
e_ews_connection_delete_items_in_chunks_sync (EEwsConnection *cnc,
					      gint pri,
					      const GSList *ids,
					      EwsDeleteType delete_type,
					      EwsSendMeetingCancellationsType send_cancels,
					      EwsAffectedTaskOccurrencesType affected_tasks,
					      GCancellable *cancellable,
					      GError **error)
{
	const GSList *iter;
	guint total_ids = 0, done_ids = 0;
	gboolean success = TRUE;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);

	g_object_ref (cnc);

	iter = ids;

	while (success && iter) {
		guint n_ids;
		const GSList *tmp_iter;

		for (tmp_iter = iter, n_ids = 0; tmp_iter && n_ids < EWS_MOVE_ITEMS_CHUNK_SIZE; tmp_iter = g_slist_next (tmp_iter), n_ids++) {
			/* Only check bounds first, to avoid unnecessary allocations */
		}

		if (tmp_iter) {
			GSList *shorter = NULL;

			if (total_ids == 0)
				total_ids = g_slist_length ((GSList *) ids);

			for (n_ids = 0; iter && n_ids < EWS_MOVE_ITEMS_CHUNK_SIZE; iter = g_slist_next (iter), n_ids++) {
				shorter = g_slist_prepend (shorter, iter->data);
			}

			shorter = g_slist_reverse (shorter);

			success = e_ews_connection_delete_items_sync (cnc, pri, shorter, delete_type, send_cancels,
				affected_tasks, cancellable, error);

			g_slist_free (shorter);

			done_ids += n_ids;
		} else {
			success = e_ews_connection_delete_items_sync (cnc, pri, iter, delete_type, send_cancels,
				affected_tasks, cancellable, error);

			iter = NULL;
			done_ids = total_ids;
		}

		if (total_ids > 0)
			camel_operation_progress (cancellable, 100 * (gdouble) done_ids / (gdouble) total_ids);
	}

	g_object_unref (cnc);

	return success;
}

static xmlXPathObjectPtr
xpath_eval (xmlXPathContextPtr ctx,
	    const gchar *format,
	    ...)
{
	xmlXPathObjectPtr result;
	va_list args;
	gchar *expr;

	if (ctx == NULL)
		return NULL;

	va_start (args, format);
	expr = g_strdup_vprintf (format, args);
	va_end (args);

	result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
	g_free (expr);

	if (result == NULL)
		return NULL;

	if (result->type == XPATH_NODESET && xmlXPathNodeSetIsEmpty (result->nodesetval)) {
		xmlXPathFreeObject (result);
		return NULL;
	}

	return result;
}

static gboolean
element_has_child (ESoapMessage *message,
		   const gchar *path)
{
	xmlDocPtr doc;
	xmlXPathContextPtr xpctx;
	xmlXPathObjectPtr result;
	xmlNodeSetPtr nodeset;
	xmlNodePtr node;
	gboolean ret = FALSE;

	doc = e_soap_message_get_xml_doc (message);
	xpctx = xmlXPathNewContext (doc);

	xmlXPathRegisterNs (
			xpctx,
			(xmlChar *) "s",
			(xmlChar *) "http://schemas.xmlsoap.org/soap/envelope/");

	xmlXPathRegisterNs (
			xpctx,
			(xmlChar *) "m",
			(xmlChar *) "http://schemas.microsoft.com/exchange/services/2006/messages");

	xmlXPathRegisterNs (
			xpctx,
			(xmlChar *) "t",
			(xmlChar *) "http://schemas.microsoft.com/exchange/services/2006/types");

	result = xpath_eval (xpctx, path);

	if (result == NULL)
		goto exit;

	if (!xmlXPathNodeSetGetLength (result->nodesetval))
		goto exit;

	nodeset = result->nodesetval;
	node = nodeset->nodeTab[0];
	if (!node->children)
		goto exit;

	ret = TRUE;

exit:
	xmlXPathFreeObject (result);
	xmlXPathFreeContext (xpctx);
	return ret;
}

void
e_ews_connection_update_items (EEwsConnection *cnc,
                               gint pri,
                               const gchar *conflict_res,
                               const gchar *msg_disposition,
                               const gchar *send_invites,
                               const gchar *folder_id,
                               EEwsRequestCreationCallback create_cb,
                               gpointer create_user_data,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	gboolean success;
	GError *local_error = NULL;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"UpdateItem",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	if (conflict_res)
		e_soap_message_add_attribute (
			msg, "ConflictResolution",
			conflict_res, NULL, NULL);
	if (msg_disposition)
		e_soap_message_add_attribute (
			msg, "MessageDisposition",
			msg_disposition, NULL, NULL);
	if (send_invites)
		e_soap_message_add_attribute (
			msg, "SendMeetingInvitationsOrCancellations",
			send_invites, NULL, NULL);

	if (folder_id) {
		e_soap_message_start_element (msg, "SavedItemFolderId", "messages", NULL);
		e_ews_message_write_string_parameter_with_attribute (
			msg, "FolderId",
			NULL, NULL, "Id", folder_id);
		e_soap_message_end_element (msg);
	}

	e_soap_message_start_element (msg, "ItemChanges", "messages", NULL);

	success = create_cb (msg, create_user_data, &local_error);

	e_soap_message_end_element (msg); /* ItemChanges */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_update_items);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	if (!success) {
		if (local_error)
			g_simple_async_result_take_error (simple, local_error);
		g_simple_async_result_complete_in_idle (simple);
		g_clear_object (&msg);

	/*
	 * We need to check for both namespaces, because, the message is being wrote without use the types
	 * namespace. Maybe it is wrong, but the server doesn't complain about that. But this is the reason
	 * for the first check. The second one, is related to "how it should be" accord with EWS specifications.
	 */
	} else if (!element_has_child (msg, "/s:Envelope/s:Body/m:UpdateItem/m:ItemChanges/ItemChange/Updates") &&
		!element_has_child (msg, "/s:Envelope/s:Body/m:UpdateItem/m:ItemChanges/t:ItemChange/t:Updates")) {
		g_simple_async_result_complete_in_idle (simple);
		g_clear_object (&msg);
	} else {
		e_ews_connection_queue_request (
			cnc, msg, get_items_response_cb,
			pri, cancellable, simple);
	}

	g_object_unref (simple);
}

gboolean
e_ews_connection_update_items_finish (EEwsConnection *cnc,
                                      GAsyncResult *result,
                                      GSList **ids,
                                      GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_update_items),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	/* if there is only one item, then check whether it's an error */
	if (async_data->items && !async_data->items->next) {
		EEwsItem *item = async_data->items->data;

		if (item && e_ews_item_get_item_type (item) == E_EWS_ITEM_TYPE_ERROR) {
			if (error)
				*error = g_error_copy (e_ews_item_get_error (item));

			g_slist_free_full (async_data->items, g_object_unref);
			async_data->items = NULL;

			return FALSE;
		}
	}

	if (ids)
		*ids = async_data->items;
	else
		g_slist_free_full (async_data->items, g_object_unref);

	return TRUE;
}

gboolean
e_ews_connection_update_items_sync (EEwsConnection *cnc,
                                    gint pri,
                                    const gchar *conflict_res,
                                    const gchar *msg_disposition,
                                    const gchar *send_invites,
                                    const gchar *folder_id,
                                    EEwsRequestCreationCallback create_cb,
                                    gpointer create_user_data,
                                    GSList **ids,
                                    GCancellable *cancellable,
                                    GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_update_items (
		cnc, pri, conflict_res,
		msg_disposition, send_invites,
		folder_id, create_cb,
		create_user_data, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_update_items_finish (
		cnc, result, ids, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_create_items (EEwsConnection *cnc,
                               gint pri,
                               const gchar *msg_disposition,
                               const gchar *send_invites,
                               const EwsFolderId *fid,
                               EEwsRequestCreationCallback create_cb,
                               gpointer create_user_data,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	gboolean success;
	GError *local_error = NULL;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"CreateItem",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	if (msg_disposition)
		e_soap_message_add_attribute (
			msg, "MessageDisposition",
			msg_disposition, NULL, NULL);
	if (send_invites)
		e_soap_message_add_attribute (
			msg, "SendMeetingInvitations",
			send_invites, NULL, NULL);

	if (fid) {
		e_soap_message_start_element (msg, "SavedItemFolderId", "messages", NULL);
		e_ews_folder_id_append_to_msg (msg, cnc->priv->email, fid);
		e_soap_message_end_element (msg);
	}

	e_soap_message_start_element (msg, "Items", "messages", NULL);

	success = create_cb (msg, create_user_data, &local_error);

	e_soap_message_end_element (msg); /* Items */

	e_ews_message_write_footer (msg); /* CreateItem */

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_create_items);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	if (success) {
		e_ews_connection_queue_request (
			cnc, msg, get_items_response_cb,
			pri, cancellable, simple);
	} else {
		if (local_error)
			g_simple_async_result_take_error (simple, local_error);
		g_simple_async_result_complete_in_idle (simple);
		g_clear_object (&msg);
	}

	g_object_unref (simple);
}

gboolean
e_ews_connection_create_items_finish (EEwsConnection *cnc,
                                      GAsyncResult *result,
                                      GSList **ids,
                                      GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_create_items),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	/* if there is only one item, then check whether it's an error */
	if (async_data->items && !async_data->items->next) {
		EEwsItem *item = async_data->items->data;

		if (item && e_ews_item_get_item_type (item) == E_EWS_ITEM_TYPE_ERROR) {
			if (error)
				*error = g_error_copy (e_ews_item_get_error (item));

			g_slist_free_full (async_data->items, g_object_unref);
			async_data->items = NULL;

			return FALSE;
		}
	}

	*ids = async_data->items;

	return TRUE;
}

gboolean
e_ews_connection_create_items_sync (EEwsConnection *cnc,
                                    gint pri,
                                    const gchar *msg_disposition,
                                    const gchar *send_invites,
                                    const EwsFolderId *fid,
                                    EEwsRequestCreationCallback create_cb,
                                    gpointer create_user_data,
                                    GSList **ids,
                                    GCancellable *cancellable,
                                    GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_create_items (
		cnc, pri, msg_disposition,
		send_invites, fid,
		create_cb, create_user_data,
		cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_create_items_finish (
		cnc, result, ids, error);

	e_async_closure_free (closure);

	return success;
}

static const gchar *
get_search_scope_str (EwsContactsSearchScope scope)
{
	switch (scope) {
		case EWS_SEARCH_AD:
			return "ActiveDirectory";
		case EWS_SEARCH_AD_CONTACTS:
			return "ActiveDirectoryContacts";
		case EWS_SEARCH_CONTACTS:
			return "Contacts";
		case EWS_SEARCH_CONTACTS_AD:
			return "ContactsActiveDirectory";
		default:
			g_warn_if_reached ();
			return NULL;

	}
}

void
e_ews_connection_resolve_names (EEwsConnection *cnc,
                                gint pri,
                                const gchar *resolve_name,
                                EwsContactsSearchScope scope,
                                GSList *parent_folder_ids,
                                gboolean fetch_contact_data,
                                GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"ResolveNames",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_add_attribute (msg, "SearchScope", get_search_scope_str (scope), NULL, NULL);

	if (fetch_contact_data) {
		e_soap_message_add_attribute (msg, "ReturnFullContactData", "true", NULL, NULL);

		if (e_ews_connection_satisfies_server_version (cnc, E_EWS_EXCHANGE_2010_SP2))
			e_soap_message_add_attribute (msg, "ContactDataShape", "AllProperties", NULL, NULL);
	} else {
		e_soap_message_add_attribute (msg, "ReturnFullContactData", "false", NULL, NULL);
	}

	if (parent_folder_ids) {
		e_soap_message_start_element (msg, "ParentFolderIds", "messages", NULL);
		ews_append_folder_ids_to_msg (msg, cnc->priv->email, parent_folder_ids);
		e_soap_message_end_element (msg);
	}

	e_ews_message_write_string_parameter (msg, "UnresolvedEntry", "messages", resolve_name);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_resolve_names);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, resolve_names_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_resolve_names_finish (EEwsConnection *cnc,
                                       GAsyncResult *result,
                                       GSList **mailboxes,
                                       GSList **contact_items,
                                       gboolean *includes_last_item,
                                       GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_resolve_names),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*includes_last_item = async_data->includes_last_item;

	if (contact_items)
		*contact_items = async_data->items_created;
	else
		e_util_free_nullable_object_slist (async_data->items_created);
	*mailboxes = async_data->items;

	return TRUE;
}

gboolean
e_ews_connection_resolve_names_sync (EEwsConnection *cnc,
                                     gint pri,
                                     const gchar *resolve_name,
                                     EwsContactsSearchScope scope,
                                     GSList *parent_folder_ids,
                                     gboolean fetch_contact_data,
                                     GSList **mailboxes,
                                     GSList **contact_items,
                                     gboolean *includes_last_item,
                                     GCancellable *cancellable,
                                     GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_resolve_names (
		cnc, pri, resolve_name,
		scope, parent_folder_ids,
		fetch_contact_data,
		cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_resolve_names_finish (
		cnc, result,
		mailboxes, contact_items,
		includes_last_item, error);

	e_async_closure_free (closure);

	return success;
}

static void
ews_connection_resolve_by_name (EEwsConnection *cnc,
                                gint pri,
                                const gchar *usename,
                                gboolean is_user_name,
                                gchar **smtp_address,
                                GCancellable *cancellable)
{
	GSList *mailboxes = NULL;
	GSList *contacts = NULL;
	gboolean includes_last_item = FALSE;
	GSList *miter;
	gint len;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (usename != NULL);
	g_return_if_fail (smtp_address != NULL);

	if (!*usename)
		return;

	len = strlen (usename);
	mailboxes = NULL;
	contacts = NULL;

	/* use the first error, not the guess-part error */
	e_ews_connection_resolve_names_sync (
		cnc, pri, usename,
		EWS_SEARCH_AD_CONTACTS, NULL, TRUE, &mailboxes, &contacts,
		&includes_last_item, cancellable, NULL);

	for (miter = mailboxes; miter; miter = miter->next) {
		const EwsMailbox *mailbox = miter->data;
		if (mailbox->email && *mailbox->email && g_strcmp0 (mailbox->routing_type, "EX") != 0
		    && ((!is_user_name && g_str_has_prefix (mailbox->email, usename) && mailbox->email[len] == '@') ||
		    (is_user_name && g_str_equal (usename, mailbox->name)))) {
			*smtp_address = g_strdup (mailbox->email);
			break;
		} else if (contacts && !contacts->next && contacts->data &&
			   e_ews_item_get_item_type (contacts->data) == E_EWS_ITEM_TYPE_CONTACT) {
			EEwsItem *contact_item = contacts->data;
			GHashTable *addresses_hash = e_ews_item_get_email_addresses (contact_item);
			GList *emails = addresses_hash ? g_hash_table_get_values (addresses_hash) : NULL, *iter;
			const gchar *display_name;
			gboolean found = FALSE;

			display_name = e_ews_item_get_display_name (contact_item);
			if (!display_name || !*display_name)
				display_name = e_ews_item_get_fileas (contact_item);

			for (iter = emails; iter && !found; iter = iter->next) {
				const gchar *it_email = iter->data;

				if (it_email && g_str_has_prefix (it_email, "SMTP:")
				    && ((!is_user_name && g_str_has_prefix (it_email, usename) && it_email[len] == '@') ||
				    (is_user_name && display_name && g_str_equal (usename, display_name)))) {
					found = TRUE;
					break;
				}
			}

			g_list_free (emails);

			if (found) {
				gint ii;

				for (ii = 0; ii < g_hash_table_size (addresses_hash); ii++) {
					gchar *key, *value;

					key = g_strdup_printf ("EmailAddress%d", ii + 1);
					value = g_hash_table_lookup (addresses_hash, key);
					g_free (key);

					if (value && g_str_has_prefix (value, "SMTP:")) {
						/* pick the first available SMTP address */
						*smtp_address = g_strdup (value + 5);
						break;
					}
				}
				break;
			}
		}
	}

	g_slist_free_full (mailboxes, (GDestroyNotify) e_ews_mailbox_free);
	e_util_free_nullable_object_slist (contacts);
}

gboolean
e_ews_connection_ex_to_smtp_sync (EEwsConnection *cnc,
                                  gint pri,
                                  const gchar *name,
                                  const gchar *ex_address,
                                  gchar **smtp_address,
                                  GCancellable *cancellable,
                                  GError **error)
{
	GSList *mailboxes = NULL;
	GSList *contacts = NULL;
	gboolean includes_last_item = FALSE;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (ex_address != NULL, FALSE);
	g_return_val_if_fail (smtp_address != NULL, FALSE);

	*smtp_address = NULL;

	e_ews_connection_resolve_names_sync (
		cnc, pri, ex_address,
		EWS_SEARCH_AD_CONTACTS, NULL, TRUE, &mailboxes, &contacts,
		&includes_last_item, cancellable, error);

	/* only one mailbox matches */
	if (mailboxes && !mailboxes->next && mailboxes->data) {
		const EwsMailbox *mailbox = mailboxes->data;
		if (mailbox->email && *mailbox->email && g_strcmp0 (mailbox->routing_type, "EX") != 0) {
			*smtp_address = g_strdup (mailbox->email);
		} else if (contacts && !contacts->next && contacts->data &&
			   e_ews_item_get_item_type (contacts->data) == E_EWS_ITEM_TYPE_CONTACT) {
			EEwsItem *contact_item = contacts->data;
			GHashTable *addresses = e_ews_item_get_email_addresses (contact_item);
			gint ii;

			for (ii = 0; ii < (addresses ? g_hash_table_size (addresses) : 0); ii++) {
				gchar *key, *value;

				key = g_strdup_printf ("EmailAddress%d", ii + 1);
				value = g_hash_table_lookup (addresses, key);
				g_free (key);

				if (value && g_str_has_prefix (value, "SMTP:")) {
					/* pick the first available SMTP address */
					*smtp_address = g_strdup (value + 5);
					break;
				}
			}
		}
	}

	g_slist_free_full (mailboxes, (GDestroyNotify) e_ews_mailbox_free);
	e_util_free_nullable_object_slist (contacts);

	if (!*smtp_address) {
		const gchar *usename;

		usename = strrchr (ex_address, '/');
		if (usename && g_ascii_strncasecmp (usename, "/cn=", 4) == 0) {
			usename += 4;

			/* try to guess from common name of the EX address */
			ews_connection_resolve_by_name (cnc, pri, usename, FALSE, smtp_address, cancellable);
		}

		if (!*smtp_address && name && *name) {
			/* try to guess from mailbox name */
			ews_connection_resolve_by_name (cnc, pri, name, TRUE, smtp_address, cancellable);
		}
	}

	if (*smtp_address)
		g_clear_error (error);

	return *smtp_address != NULL;
}

void
e_ews_connection_expand_dl (EEwsConnection *cnc,
                            gint pri,
                            const EwsMailbox *mb,
                            GCancellable *cancellable,
                            GAsyncReadyCallback callback,
                            gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"ExpandDL",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "Mailbox", "messages", NULL);

	if (mb->item_id) {
		e_soap_message_start_element (msg, "ItemId", NULL, NULL);

		e_soap_message_add_attribute (msg, "Id", mb->item_id->id, NULL, NULL);
		e_soap_message_add_attribute (msg, "ChangeKey", mb->item_id->change_key, NULL, NULL);

		e_soap_message_end_element (msg); /* Mailbox */

	} else if (mb->email)
		e_ews_message_write_string_parameter (msg, "EmailAddress", NULL, mb->email);

	e_soap_message_end_element (msg); /* Mailbox */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_expand_dl);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, expand_dl_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

/* includes_last_item does not make sense as expand_dl does not support recursive 
 * fetch, wierd */
gboolean
e_ews_connection_expand_dl_finish (EEwsConnection *cnc,
                                   GAsyncResult *result,
                                   GSList **mailboxes,
                                   gboolean *includes_last_item,
                                   GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_expand_dl),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*includes_last_item = async_data->includes_last_item;
	*mailboxes = async_data->items;

	return TRUE;

}

gboolean
e_ews_connection_expand_dl_sync (EEwsConnection *cnc,
                                 gint pri,
                                 const EwsMailbox *mb,
                                 GSList **mailboxes,
                                 gboolean *includes_last_item,
                                 GCancellable *cancellable,
                                 GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_expand_dl (
		cnc, pri, mb, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_expand_dl_finish (
		cnc, result, mailboxes, includes_last_item, error);

	e_async_closure_free (closure);

	return success;
}

static void
update_folder_response_cb (ESoapResponse *response,
                           GSimpleAsyncResult *simple)
{
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		subparam = e_soap_parameter_get_next_child (param);
	}
}

void
e_ews_connection_update_folder (EEwsConnection *cnc,
                                gint pri,
                                EEwsRequestCreationCallback create_cb,
                                gpointer create_user_data,
                                GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	gboolean success;
	GError *local_error = NULL;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"UpdateFolder",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "FolderChanges", "messages", NULL);

	success = create_cb (msg, create_user_data, &local_error);

	e_soap_message_end_element (msg); /* FolderChanges */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_update_folder);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	if (success) {
		e_ews_connection_queue_request (
			cnc, msg, update_folder_response_cb,
			pri, cancellable, simple);
	} else {
		if (local_error)
			g_simple_async_result_take_error (simple, local_error);
		g_simple_async_result_complete_in_idle (simple);
		g_clear_object (&msg);
	}

	g_object_unref (simple);
}

gboolean
e_ews_connection_update_folder_finish (EEwsConnection *cnc,
                                       GAsyncResult *result,
                                       GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_update_folder),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	return TRUE;
}

gboolean
e_ews_connection_update_folder_sync (EEwsConnection *cnc,
                                     gint pri,
                                     EEwsRequestCreationCallback create_cb,
                                     gpointer create_user_data,
                                     GCancellable *cancellable,
                                     GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_update_folder (
		cnc, pri, create_cb, create_user_data, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_update_folder_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

static void
move_folder_response_cb (ESoapResponse *response,
                         GSimpleAsyncResult *simple)
{
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_move_folder (EEwsConnection *cnc,
                              gint pri,
                              const gchar *to_folder,
                              const gchar *folder,
                              GCancellable *cancellable,
                              GAsyncReadyCallback callback,
                              gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"MoveFolder",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "ToFolderId", "messages", NULL);
	if (to_folder)
		e_ews_message_write_string_parameter_with_attribute (
			msg, "FolderId", NULL,
			NULL, "Id", to_folder);
	else
		e_ews_message_write_string_parameter_with_attribute (
			msg, "DistinguishedFolderId", NULL,
			NULL, "Id", "msgfolderroot");

	e_soap_message_end_element (msg);

	e_soap_message_start_element (msg, "FolderIds", "messages", NULL);
	e_ews_message_write_string_parameter_with_attribute (
		msg, "FolderId", NULL,
		NULL, "Id", folder);
	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_move_folder);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, move_folder_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_move_folder_finish (EEwsConnection *cnc,
                                     GAsyncResult *result,
                                     GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_move_folder),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	return TRUE;
}

gboolean
e_ews_connection_move_folder_sync (EEwsConnection *cnc,
                                   gint pri,
                                   const gchar *to_folder,
                                   const gchar *folder,
                                   GCancellable *cancellable,
                                   GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_move_folder (
		cnc, pri, to_folder, folder, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_move_folder_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_get_folder (EEwsConnection *cnc,
                             gint pri,
                             const gchar *folder_shape,
                             const EEwsAdditionalProps *add_props,
                             GSList *folder_ids,
                             GCancellable *cancellable,
                             GAsyncReadyCallback callback,
                             gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetFolder",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			TRUE,
			TRUE);

	e_soap_message_start_element (msg, "FolderShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, folder_shape);

	ews_append_additional_props_to_msg (msg, add_props);
	e_soap_message_end_element (msg);

	if (folder_ids) {
		e_soap_message_start_element (msg, "FolderIds", "messages", NULL);
		ews_append_folder_ids_to_msg (msg, cnc->priv->email, folder_ids);
		e_soap_message_end_element (msg);
	}

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_folder);

	async_data = g_slice_new0 (EwsAsyncData);
	async_data->cnc = cnc;
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, get_folder_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_folder_finish (EEwsConnection *cnc,
                                    GAsyncResult *result,
                                    GSList **folders,
                                    GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_folder),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (folders != NULL)
		*folders = async_data->items;
	else
		g_slist_free_full (async_data->items, g_object_unref);

	return TRUE;
}

gboolean
e_ews_connection_get_folder_sync (EEwsConnection *cnc,
                                  gint pri,
                                  const gchar *folder_shape,
                                  const EEwsAdditionalProps *add_props,
                                  GSList *folder_ids,
                                  GSList **folders,
                                  GCancellable *cancellable,
                                  GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_folder (
		cnc, pri, folder_shape, add_props,
		folder_ids, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_folder_finish (
		cnc, result, folders, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_create_folder (EEwsConnection *cnc,
                                gint pri,
                                const gchar *parent_folder_id,
                                gboolean is_distinguished_id,
                                const gchar *folder_name,
                                EEwsFolderType folder_type,
                                GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const gchar *folder_element;
	const gchar *folder_class;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"CreateFolder",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "ParentFolderId", "messages", NULL);

	/* If NULL passed for parent_folder_id, use "msgfolderroot" */
	if (is_distinguished_id || !parent_folder_id) {
		e_soap_message_start_element (msg, "DistinguishedFolderId", NULL, NULL);
		e_soap_message_add_attribute (
				msg, "Id", parent_folder_id ? parent_folder_id : "msgfolderroot", NULL, NULL);
		if (is_distinguished_id && cnc->priv->email) {
			e_soap_message_start_element (msg, "Mailbox", NULL, NULL);
			e_ews_message_write_string_parameter(
					msg, "EmailAddress", NULL, cnc->priv->email);
			e_soap_message_end_element (msg);
		}
		e_soap_message_end_element (msg);
	} else {
		e_ews_message_write_string_parameter_with_attribute (msg, "FolderId", NULL, NULL, "Id", parent_folder_id);
	}

	e_soap_message_end_element (msg);

	switch (folder_type) {
		default:
			g_warn_if_reached ();
			/* fall through */
		case E_EWS_FOLDER_TYPE_MAILBOX:
			folder_element = "Folder";
			folder_class = "IPF.Note";
			break;
		case E_EWS_FOLDER_TYPE_CALENDAR:
			folder_element = "CalendarFolder";
			folder_class = "IPF.Appointment";
			break;
		case E_EWS_FOLDER_TYPE_CONTACTS:
			folder_element = "ContactsFolder";
			folder_class = "IPF.Contact";
			break;
		case E_EWS_FOLDER_TYPE_SEARCH:
			folder_element = "SearchFolder";
			folder_class = "IPF.Note";
			break;
		case E_EWS_FOLDER_TYPE_TASKS:
			folder_element = "TasksFolder";
			folder_class = "IPF.Task";
			break;
		case E_EWS_FOLDER_TYPE_MEMOS:
			folder_element = "Folder";
			folder_class = "IPF.StickyNote";
			break;
	}

	e_soap_message_start_element (msg, "Folders", "messages", NULL);
	e_soap_message_start_element (msg, folder_element, NULL, NULL);
	e_ews_message_write_string_parameter (msg, "FolderClass", NULL, folder_class);
	e_ews_message_write_string_parameter (msg, "DisplayName", NULL, folder_name);

	e_soap_message_end_element (msg);
	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_create_folder);

	async_data = g_slice_new0 (EwsAsyncData);
	async_data->folder_type = folder_type;

	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, create_folder_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_create_folder_finish (EEwsConnection *cnc,
                                       GAsyncResult *result,
                                       EwsFolderId **fid,
                                       GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_create_folder),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*fid = (EwsFolderId *) async_data->items_created->data;
	g_slist_free (async_data->items_created);

	return TRUE;
}

gboolean
e_ews_connection_create_folder_sync (EEwsConnection *cnc,
                                     gint pri,
                                     const gchar *parent_folder_id,
                                     gboolean is_distinguished_id,
                                     const gchar *folder_name,
                                     EEwsFolderType folder_type,
                                     EwsFolderId **folder_id,
                                     GCancellable *cancellable,
                                     GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_create_folder (
		cnc, pri, parent_folder_id,
		is_distinguished_id, folder_name,
		folder_type, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_create_folder_finish (
		cnc, result, folder_id, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_move_items (EEwsConnection *cnc,
                             gint pri,
                             const gchar *folder_id,
                             gboolean docopy,
                             const GSList *ids,
                             GCancellable *cancellable,
                             GAsyncReadyCallback callback,
                             gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *iter;

	g_return_if_fail (cnc != NULL);

	if (docopy)
		msg = e_ews_message_new_with_header (
				cnc->priv->settings,
				cnc->priv->uri,
				cnc->priv->impersonate_user,
				"CopyItem",
				NULL,
				NULL,
				cnc->priv->version,
				E_EWS_EXCHANGE_2007_SP1,
				FALSE,
				TRUE);
	else
		msg = e_ews_message_new_with_header (
				cnc->priv->settings,
				cnc->priv->uri,
				cnc->priv->impersonate_user,
				"MoveItem",
				NULL,
				NULL,
				cnc->priv->version,
				E_EWS_EXCHANGE_2007_SP1,
				FALSE,
				TRUE);

	e_soap_message_start_element (msg, "ToFolderId", "messages", NULL);
	e_soap_message_start_element (msg, "FolderId", NULL, NULL);
	e_soap_message_add_attribute (msg, "Id", folder_id, NULL, NULL);
	e_soap_message_end_element (msg); /* FolderId */
	e_soap_message_end_element (msg); /* ToFolderId */

	e_soap_message_start_element (msg, "ItemIds", "messages", NULL);
	for (iter = ids; iter != NULL; iter = g_slist_next (iter))
		e_ews_message_write_string_parameter_with_attribute (msg, "ItemId", NULL, NULL, "Id", iter->data);
	e_soap_message_end_element (msg); /* ItemIds */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_move_items);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, get_items_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_move_items_finish (EEwsConnection *cnc,
                                    GAsyncResult *result,
                                    GSList **items,
                                    GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_move_items),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	/* if there is only one item, then check whether it's an error */
	if (async_data->items && !async_data->items->next) {
		EEwsItem *item = async_data->items->data;

		if (item && e_ews_item_get_item_type (item) == E_EWS_ITEM_TYPE_ERROR) {
			if (error)
				*error = g_error_copy (e_ews_item_get_error (item));

			g_slist_free_full (async_data->items, g_object_unref);
			async_data->items = NULL;

			return FALSE;
		}
	}

	*items = async_data->items;

	return TRUE;
}

gboolean
e_ews_connection_move_items_sync (EEwsConnection *cnc,
                                  gint pri,
                                  const gchar *folder_id,
                                  gboolean docopy,
                                  const GSList *ids,
                                  GSList **items,
                                  GCancellable *cancellable,
                                  GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_move_items (
		cnc, pri, folder_id, docopy, ids, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_move_items_finish (
		cnc, result, items, error);

	e_async_closure_free (closure);

	return success;
}

gboolean
e_ews_connection_move_items_in_chunks_sync (EEwsConnection *cnc,
					    gint pri,
					    const gchar *folder_id,
					    gboolean docopy,
					    const GSList *ids,
					    GSList **items,
					    GCancellable *cancellable,
					    GError **error)
{
	const GSList *iter;
	guint total_ids = 0, done_ids = 0;
	gboolean success = TRUE;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (items != NULL, FALSE);

	g_object_ref (cnc);

	*items = NULL;
	iter = ids;

	while (success && iter) {
		guint n_ids;
		const GSList *tmp_iter;
		GSList *processed_items = NULL;

		for (tmp_iter = iter, n_ids = 0; tmp_iter && n_ids < EWS_MOVE_ITEMS_CHUNK_SIZE; tmp_iter = g_slist_next (tmp_iter), n_ids++) {
			/* Only check bounds first, to avoid unnecessary allocations */
		}

		if (tmp_iter) {
			GSList *shorter = NULL;

			if (total_ids == 0)
				total_ids = g_slist_length ((GSList *) ids);

			for (n_ids = 0; iter && n_ids < EWS_MOVE_ITEMS_CHUNK_SIZE; iter = g_slist_next (iter), n_ids++) {
				shorter = g_slist_prepend (shorter, iter->data);
			}

			shorter = g_slist_reverse (shorter);

			success = e_ews_connection_move_items_sync (cnc, pri, folder_id, docopy,
				shorter, &processed_items, cancellable, error);

			g_slist_free (shorter);

			done_ids += n_ids;
		} else {
			success = e_ews_connection_move_items_sync (cnc, pri, folder_id, docopy,
				iter, &processed_items, cancellable, error);

			iter = NULL;
			done_ids = total_ids;
		}

		if (processed_items)
			*items = g_slist_concat (*items, processed_items);

		if (total_ids > 0)
			camel_operation_progress (cancellable, 100 * (gdouble) done_ids / (gdouble) total_ids);
	}

	g_object_unref (cnc);

	return success;
}

static void
delete_folder_response_cb (ESoapResponse *response,
                           GSimpleAsyncResult *simple)
{
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

/**
 * e_ews_connection_delete_folder:
 * @cnc:
 * @pri:
 * @folder_id: folder to be deleted
 * @is_distinguished_id:
 * @delete_type: "HardDelete", "SoftDelete", "MoveToDeletedItems"
 * @cancellable:
 * @callback:
 * @user_data:
 **/
void
e_ews_connection_delete_folder (EEwsConnection *cnc,
                                gint pri,
                                const gchar *folder_id,
                                gboolean is_distinguished_id,
                                const gchar *delete_type,
                                GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"DeleteFolder",
			"DeleteType",
			delete_type,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "FolderIds", "messages", NULL);

	e_soap_message_start_element (
			msg,
			is_distinguished_id ? "DistinguishedFolderId" : "FolderId",
			NULL,
			NULL);
	e_soap_message_add_attribute (msg, "Id", folder_id, NULL, NULL);

	/* This element is required for delegate access */
	if (is_distinguished_id && cnc->priv->email) {
		e_soap_message_start_element (msg, "Mailbox", NULL, NULL);
		e_ews_message_write_string_parameter(
				msg, "EmailAddress", NULL, cnc->priv->email);
		e_soap_message_end_element (msg);
	}

	e_soap_message_end_element (msg); /* </DistinguishedFolderId> || </FolderId> */

	e_soap_message_end_element (msg); /* </FolderIds> */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_delete_folder);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, delete_folder_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_delete_folder_finish (EEwsConnection *cnc,
                                       GAsyncResult *result,
                                       GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_delete_folder),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	return TRUE;
}

/**
 * e_ews_connection_delete_folder_sync:
 * @cnc:
 * @pri:
 * @folder_id: folder to be deleted
 * @is_distinguished_id:
 * @delete_type: "HardDelete", "SoftDelete", "MoveToDeletedItems"
 * @cancellable:
 * @error:
 **/
gboolean
e_ews_connection_delete_folder_sync (EEwsConnection *cnc,
                                     gint pri,
                                     const gchar *folder_id,
                                     gboolean is_distinguished_id,
                                     const gchar *delete_type,
                                     GCancellable *cancellable,
                                     GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_delete_folder (
		cnc, pri, folder_id,
		is_distinguished_id,
		delete_type,
		cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_delete_folder_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

static void
empty_folder_response_cb (ESoapResponse *response,
			  GSimpleAsyncResult *simple)
{
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_empty_folder (EEwsConnection *cnc,
			       gint pri,
			       const gchar *folder_id,
			       gboolean is_distinguished_id,
			       const gchar *delete_type,
			       gboolean delete_subfolders,
			       GCancellable *cancellable,
			       GAsyncReadyCallback callback,
			       gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"EmptyFolder",
			"DeleteType",
			delete_type,
			cnc->priv->version,
			E_EWS_EXCHANGE_2010,
			FALSE,
			TRUE);

	e_soap_message_add_attribute (msg, "DeleteSubFolders", delete_subfolders ? "true" : "false", NULL, NULL);

	e_soap_message_start_element (msg, "FolderIds", "messages", NULL);

	e_soap_message_start_element (
			msg,
			is_distinguished_id ? "DistinguishedFolderId" : "FolderId",
			NULL,
			NULL);
	e_soap_message_add_attribute (msg, "Id", folder_id, NULL, NULL);

	/* This element is required for delegate access */
	if (is_distinguished_id && cnc->priv->email) {
		e_soap_message_start_element (msg, "Mailbox", NULL, NULL);
		e_ews_message_write_string_parameter(
				msg, "EmailAddress", NULL, cnc->priv->email);
		e_soap_message_end_element (msg);
	}

	e_soap_message_end_element (msg); /* </DistinguishedFolderId> || </FolderId> */

	e_soap_message_end_element (msg); /* </FolderIds> */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_empty_folder);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, empty_folder_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_empty_folder_finish (EEwsConnection *cnc,
				      GAsyncResult *result,
				      GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_empty_folder),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	return TRUE;
}

gboolean
e_ews_connection_empty_folder_sync (EEwsConnection *cnc,
				    gint pri,
				    const gchar *folder_id,
				    gboolean is_distinguished_id,
				    const gchar *delete_type,
				    gboolean delete_subfolder,
				    GCancellable *cancellable,
				    GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_empty_folder (
		cnc, pri, folder_id,
		is_distinguished_id,
		delete_type,
		delete_subfolder,
		cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_empty_folder_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

static void
ews_handle_create_attachments_param (ESoapParameter *param,
                                     EwsAsyncData *async_data)
{
	/* http://msdn.microsoft.com/en-us/library/aa565877%28v=EXCHG.80%29.aspx */
	ESoapParameter *subparam, *attspara, *last_relevant = NULL, *attparam;

	attspara = e_soap_parameter_get_first_child_by_name (param, "Attachments");

	for (subparam = e_soap_parameter_get_first_child (attspara); subparam != NULL; subparam = e_soap_parameter_get_next_child (subparam)) {
		if (!g_ascii_strcasecmp (e_soap_parameter_get_name (subparam), "FileAttachment")) {
			attparam = e_soap_parameter_get_first_child (subparam);
			last_relevant = attparam;

			async_data->items = g_slist_append (async_data->items, e_soap_parameter_get_property (attparam, "Id"));
		}
	}

	if (last_relevant != NULL) {
		async_data->sync_state = e_soap_parameter_get_property (last_relevant, "RootItemChangeKey");
	}
}

static void
create_attachments_response_cb (ESoapResponse *response,
                                GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "CreateAttachmentResponseMessage"))
			ews_handle_create_attachments_param (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

static gboolean
e_ews_connection_attach_file (ESoapMessage *msg,
                              EEwsAttachmentInfo *info,
			      gboolean contact_photo,
			      GError **error)
{
	EEwsAttachmentInfoType type = e_ews_attachment_info_get_type (info);
	gchar *filename = NULL, *buffer = NULL;
	const gchar *content = NULL, *prefer_filename;
	gsize length;

	switch (type) {
		case E_EWS_ATTACHMENT_INFO_TYPE_URI: {
			/* TODO - handle a situation where the file isnt accessible/other problem with it */
			/* TODO - This is a naive implementation that just uploads the whole content into */
			/*        memory, ie very inefficient */
			const gchar *uri;
			gchar *filepath;
			GError *local_error = NULL;

			uri = e_ews_attachment_info_get_uri (info);

			/* convert uri to actual file path */
			filepath = g_filename_from_uri (uri, NULL, &local_error);
			if (local_error != NULL) {
				g_propagate_error (error, local_error);
				return FALSE;
			}

			g_file_get_contents (filepath, &buffer, &length, &local_error);
			if (local_error != NULL) {
				g_free (filepath);
				g_propagate_error (error, local_error);
				return FALSE;
			}

			content = buffer;

			filename = strrchr (filepath, G_DIR_SEPARATOR);
			filename = filename ? g_strdup (++filename) : g_strdup (filepath);

			g_free (filepath);
			break;
		}
		case E_EWS_ATTACHMENT_INFO_TYPE_INLINED:
			content = e_ews_attachment_info_get_inlined_data (info, &length);
			filename = g_strdup (e_ews_attachment_info_get_filename (info));
			break;
		default:
			g_warning ("Unknown EwsAttachmentInfoType %d", type);
			return FALSE;
	}

	e_soap_message_start_element (msg, "FileAttachment", NULL, NULL);

	prefer_filename = e_ews_attachment_info_get_prefer_filename (info);
	e_ews_message_write_string_parameter (msg, "Name", NULL, prefer_filename ? prefer_filename : filename);
	if (contact_photo)
		e_ews_message_write_string_parameter (msg, "IsContactPhoto", NULL, "true");
	e_soap_message_start_element (msg, "Content", NULL, NULL);
	e_soap_message_write_base64 (msg, content, length);
	e_soap_message_end_element (msg); /* "Content" */
	e_soap_message_end_element (msg); /* "FileAttachment" */

	g_free (filename);
	g_free (buffer);

	return TRUE;
}

void
e_ews_connection_create_attachments (EEwsConnection *cnc,
                                     gint pri,
                                     const EwsId *parent,
                                     const GSList *files,
				     gboolean is_contact_photo,
                                     GCancellable *cancellable,
                                     GAsyncReadyCallback callback,
                                     gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *l;
	GError *local_error = NULL;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (parent != NULL);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_create_attachments);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"CreateAttachment",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "ParentItemId", "messages", NULL);
	e_soap_message_add_attribute (msg, "Id", parent->id, NULL, NULL);
	if (parent->change_key)
		e_soap_message_add_attribute (msg, "ChangeKey", parent->change_key, NULL, NULL);
	e_soap_message_end_element (msg);

	/* start interation over all items to get the attachemnts */
	e_soap_message_start_element (msg, "Attachments", "messages", NULL);

	for (l = files; l != NULL; l = g_slist_next (l))
		if (!e_ews_connection_attach_file (msg, l->data, is_contact_photo, &local_error)) {
			if (local_error != NULL)
				g_simple_async_result_take_error (simple, local_error);
			g_simple_async_result_complete_in_idle (simple);
			g_object_unref (simple);

			return;
		}

	e_soap_message_end_element (msg); /* "Attachments" */

	e_ews_message_write_footer (msg);

	e_ews_connection_queue_request (
		cnc, msg, create_attachments_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_create_attachments_finish (EEwsConnection *cnc,
                                            gchar **change_key,
					    GSList **attachments_ids,
                                            GAsyncResult *result,
                                            GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_create_attachments),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (attachments_ids)
		*attachments_ids = async_data->items;
	else
		g_slist_free_full (async_data->items, g_free);

	if (change_key)
		*change_key = async_data->sync_state;
	else
		g_free (async_data->sync_state);

	return TRUE;
}

gboolean
e_ews_connection_create_attachments_sync (EEwsConnection *cnc,
                                          gint pri,
                                          const EwsId *parent,
                                          const GSList *files,
					  gboolean is_contact_photo,
                                          gchar **change_key,
					  GSList **attachments_ids,
                                          GCancellable *cancellable,
                                          GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean ret;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (parent != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_create_attachments (
		cnc, pri, parent, files, is_contact_photo, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	ret = e_ews_connection_create_attachments_finish (
		cnc, change_key, attachments_ids, result, error);

	e_async_closure_free (closure);

	return ret;
}

/* Delete attachemnts */
static void
ews_handle_root_item_id_param (ESoapParameter *subparam,
                               EwsAsyncData *async_data)
{
	/* http://msdn.microsoft.com/en-us/library/aa580782%28v=EXCHG.80%29.aspx */
	ESoapParameter *attspara;

	attspara = e_soap_parameter_get_first_child_by_name (
		subparam, "RootItemId");

	if (attspara == NULL)
		return;

	async_data->sync_state = e_soap_parameter_get_property (attspara, "RootItemChangeKey");
}

static void
delete_attachments_response_cb (ESoapResponse *response,
                                GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "DeleteAttachmentResponseMessage"))
			ews_handle_root_item_id_param (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_delete_attachments (EEwsConnection *cnc,
                                     gint pri,
                                     const GSList *attachments_ids,
                                     GCancellable *cancellable,
                                     GAsyncReadyCallback callback,
                                     gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *l;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"DeleteAttachment",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	/* start interation over all items to get the attachemnts */
	e_soap_message_start_element (msg, "AttachmentIds", "messages", NULL);

	for (l = attachments_ids; l != NULL; l = l->next) {
		e_ews_message_write_string_parameter_with_attribute (msg, "AttachmentId", NULL, NULL, "Id", l->data);
	}

	e_soap_message_end_element (msg); /* "AttachmentIds" */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_delete_attachments);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, delete_attachments_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_delete_attachments_finish (EEwsConnection *cnc,
                                            GAsyncResult *result,
					    gchar **new_change_key,
                                            GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_delete_attachments),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (new_change_key)
		*new_change_key = async_data->sync_state;
	else
		g_free (async_data->sync_state);

	return TRUE;
}

gboolean
e_ews_connection_delete_attachments_sync (EEwsConnection *cnc,
                                          gint pri,
                                          const GSList *attachments_ids,
					  gchar **new_change_key,
                                          GCancellable *cancellable,
                                          GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean ret;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_delete_attachments (
		cnc, pri, attachments_ids, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	ret = e_ews_connection_delete_attachments_finish (
		cnc, result, new_change_key, error);

	e_async_closure_free (closure);

	return ret;
}

static void
ews_handle_attachments_param (ESoapParameter *param,
                              EwsAsyncData *async_data)
{
	ESoapParameter *subparam, *attspara;
	EEwsAttachmentInfo *info = NULL;
	EEwsItem *item;
	const gchar *name;

	attspara = e_soap_parameter_get_first_child_by_name (param, "Attachments");

	for (subparam = e_soap_parameter_get_first_child (attspara); subparam != NULL; subparam = e_soap_parameter_get_next_child (subparam)) {
		name = e_soap_parameter_get_name (subparam);

		if (!g_ascii_strcasecmp (name, "ItemAttachment")) {
			item = e_ews_item_new_from_soap_parameter (subparam);
			info = e_ews_item_dump_mime_content (item, async_data->directory);
			g_clear_object (&item);

		} else if (!g_ascii_strcasecmp (name, "FileAttachment")) {
			info = e_ews_dump_file_attachment_from_soap_parameter (
					subparam,
					async_data->directory,
					async_data->sync_state);
		}

		if (info)
			async_data->items = g_slist_append (async_data->items, info);

		info = NULL;
	}
}

static void
get_attachments_response_cb (ESoapResponse *response,
                             GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "GetAttachmentResponseMessage"))
			ews_handle_attachments_param (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_get_attachments (EEwsConnection *cnc,
                                  gint pri,
                                  const gchar *uid,
                                  const GSList *ids,
                                  const gchar *cache,
                                  gboolean include_mime,
                                  ESoapProgressFn progress_fn,
                                  gpointer progress_data,
                                  GCancellable *cancellable,
                                  GAsyncReadyCallback callback,
                                  gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *l;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetAttachment",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	/* not sure why I need it, need to check */
	if (progress_fn && progress_data)
		e_soap_message_set_progress_fn (msg, progress_fn, progress_data);

	if (cache)
		e_soap_message_store_node_data (msg, "MimeContent Content", cache, TRUE);

	/* wrtie empty attachments shape, need to discover maybe usefull in some cases*/
	e_soap_message_start_element (msg, "AttachmentShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "IncludeMimeContent", NULL, "true");
	e_soap_message_end_element (msg);

	/* start interation over all items to get the attachemnts */
	e_soap_message_start_element (msg, "AttachmentIds", "messages", NULL);

	for (l = ids; l != NULL; l = g_slist_next (l))
		e_ews_message_write_string_parameter_with_attribute (msg, "AttachmentId", NULL, NULL, "Id", l->data);

	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_attachments);

	async_data = g_slice_new0 (EwsAsyncData);
	async_data->directory = cache;
	async_data->sync_state = (gchar *) uid;
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, get_attachments_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_attachments_finish (EEwsConnection *cnc,
                                         GAsyncResult *result,
                                         GSList **items,
                                         GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_attachments),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (items)
		*items = async_data->items;
	else
		g_slist_free_full (async_data->items, (GDestroyNotify) e_ews_attachment_info_free);

	return TRUE;
}

gboolean
e_ews_connection_get_attachments_sync (EEwsConnection *cnc,
                                       gint pri,
                                       const gchar *uid,
                                       const GSList *ids,
                                       const gchar *cache,
                                       gboolean include_mime,
                                       GSList **items,
                                       ESoapProgressFn progress_fn,
                                       gpointer progress_data,
                                       GCancellable *cancellable,
                                       GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean ret;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_attachments (
		cnc, pri, uid, ids, cache, include_mime,
		progress_fn, progress_data, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	ret = e_ews_connection_get_attachments_finish (
		cnc, result, items, error);

	e_async_closure_free (closure);

	return ret;
}

static void
ews_handle_free_busy_view (ESoapParameter *param,
                           EwsAsyncData *async_data)
{
       /*parse the response to create a free_busy data
	http://msdn.microsoft.com / en - us / library / aa564001 % 28v = EXCHG.140 % 29.aspx */
	ICalComponent *vfb;
	ICalProperty *prop = NULL;
	ICalTimezone *utc_zone = i_cal_timezone_get_utc_timezone ();
	ESoapParameter *viewparam, *eventarray, *event_param, *subparam;
	GTimeVal t_val;
	const gchar *name;
	gchar *value, *new_val = NULL, *summary = NULL, *location = NULL, *id = NULL;

	viewparam = e_soap_parameter_get_first_child_by_name (param, "FreeBusyView");
	if (!viewparam)
		return;
	vfb = i_cal_component_new_vfreebusy ();
	eventarray = e_soap_parameter_get_first_child_by_name (viewparam, "CalendarEventArray");
	for (event_param = eventarray ? e_soap_parameter_get_first_child (eventarray) : NULL;
	     event_param != NULL;
	     event_param = e_soap_parameter_get_next_child (event_param)) {
		ICalPeriod *ipt;

		ipt = i_cal_period_new_null_period ();

		for (subparam = e_soap_parameter_get_first_child (event_param); subparam != NULL; subparam = e_soap_parameter_get_next_child (subparam)) {
			name = e_soap_parameter_get_name (subparam);

			if (!g_ascii_strcasecmp (name, "StartTime")) {
				ICalTime *itt;

				value = e_soap_parameter_get_string_value (subparam);
				/*We are sending UTC timezone and expect server to return in same*/

				/*Remove leading and trailing whitespace*/
				g_strstrip (value);

				if (g_utf8_strlen (value, -1) == 19) {
					/*If server returns time without zone add Z to treat it in UTC*/
					new_val = g_strdup_printf ("%sZ", value);
					g_free (value);
				} else
					new_val = value;

				g_time_val_from_iso8601 (new_val, &t_val);
				g_free (new_val);

				itt = i_cal_time_new_from_timet_with_zone (t_val.tv_sec, 0, utc_zone);
				i_cal_period_set_start (ipt, itt);
				g_clear_object (&itt);

			} else if (!g_ascii_strcasecmp (name, "EndTime")) {
				ICalTime *itt;

				value = e_soap_parameter_get_string_value (subparam);
				/*We are sending UTC timezone and expect server to return in same*/

				/*Remove leading and trailing whitespace*/
				g_strstrip (value);

				if (g_utf8_strlen (value, -1) == 19) {
					/*If server returns time without zone add Z to treat it in UTC*/
					new_val = g_strdup_printf ("%sZ", value);
					g_free (value);
				} else
					new_val = value;

				g_time_val_from_iso8601 (new_val, &t_val);
				g_free (new_val);

				itt = i_cal_time_new_from_timet_with_zone (t_val.tv_sec, 0, utc_zone);
				i_cal_period_set_end (ipt, itt);
				g_clear_object (&itt);

				prop = i_cal_property_new_freebusy (ipt);
			} else if (!g_ascii_strcasecmp (name, "BusyType")) {
				value = e_soap_parameter_get_string_value (subparam);
				if (!strcmp (value, "Busy"))
					i_cal_property_set_parameter_from_string (prop, "FBTYPE", "BUSY");
				else if (!strcmp (value, "Tentative"))
					i_cal_property_set_parameter_from_string (prop, "FBTYPE", "BUSY-TENTATIVE");
				else if (!strcmp (value, "OOF"))
					i_cal_property_set_parameter_from_string (prop, "FBTYPE", "BUSY-UNAVAILABLE");
				else if (!strcmp (value, "Free"))
					i_cal_property_set_parameter_from_string (prop, "FBTYPE", "FREE");
				g_free (value);
			} else if (!g_ascii_strcasecmp (name, "CalendarEventDetails")) {
				ESoapParameter *dparam;

				dparam = e_soap_parameter_get_first_child_by_name (subparam, "ID");
				if (dparam) {
					g_clear_pointer (&id, g_free);
					id = e_soap_parameter_get_string_value (dparam);
				}

				dparam = e_soap_parameter_get_first_child_by_name (subparam, "Subject");
				if (dparam) {
					g_clear_pointer (&summary, g_free);
					summary = e_soap_parameter_get_string_value (dparam);
				}

				dparam = e_soap_parameter_get_first_child_by_name (subparam, "Location");
				if (dparam) {
					g_clear_pointer (&location, g_free);
					location = e_soap_parameter_get_string_value (dparam);
				}
			}
		}

		if (prop) {
			if (id)
				i_cal_property_set_parameter_from_string (prop, "X-EWS-ID", id);
			if (summary)
				i_cal_property_set_parameter_from_string (prop, "X-SUMMARY", summary);
			if (location)
				i_cal_property_set_parameter_from_string (prop, "X-LOCATION", location);
			i_cal_component_take_property (vfb, prop);
			prop = NULL;
		}

		g_clear_pointer (&summary, g_free);
		g_clear_pointer (&location, g_free);
		g_clear_pointer (&id, g_free);
		g_clear_object (&ipt);
	}

	async_data->items = g_slist_append (async_data->items, vfb);
}

static void
get_free_busy_response_cb (ESoapResponse *response,
                           GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "FreeBusyResponseArray", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		ESoapParameter *subsubparam;

		subsubparam = e_soap_parameter_get_first_child_by_name (
			subparam, "ResponseMessage");

		if (subsubparam) {
			if (!ews_get_response_status (subsubparam, &error)) {
				g_simple_async_result_take_error (simple, error);
				return;
			}

			ews_handle_free_busy_view (subparam, async_data);
		}

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_get_free_busy (EEwsConnection *cnc,
                                gint pri,
                                EEwsRequestCreationCallback free_busy_cb,
                                gpointer free_busy_user_data,
                                GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	gboolean success;
	GError *local_error = NULL;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetUserAvailabilityRequest",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	success = free_busy_cb (msg, free_busy_user_data, &local_error);

	e_ews_message_write_footer (msg); /*GetUserAvailabilityRequest  */

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_free_busy);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	if (success) {
		e_ews_connection_queue_request (
			cnc, msg, get_free_busy_response_cb,
			pri, cancellable, simple);
	} else {
		if (local_error)
			g_simple_async_result_take_error (simple, local_error);
		g_simple_async_result_complete_in_idle (simple);
		g_clear_object (&msg);
	}

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_free_busy_finish (EEwsConnection *cnc,
                                       GAsyncResult *result,
                                       GSList **free_busy, /* ICalComponent * */
                                       GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_free_busy),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;
	*free_busy = async_data->items;

	return TRUE;
}

gboolean
e_ews_connection_get_free_busy_sync (EEwsConnection *cnc,
                                     gint pri,
                                     EEwsRequestCreationCallback free_busy_cb,
                                     gpointer free_busy_user_data,
                                     GSList **free_busy, /* ICalComponent * */
                                     GCancellable *cancellable,
                                     GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_free_busy (
		cnc, pri, free_busy_cb,
		free_busy_user_data, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_free_busy_finish (
		cnc, result, free_busy, error);

	e_async_closure_free (closure);

	return success;
}

static EwsPermissionLevel
get_permission_from_string (const gchar *permission)
{
	g_return_val_if_fail (permission != NULL, EwsPermissionLevel_Unknown);

	if (!g_ascii_strcasecmp (permission, "Editor"))
		return EwsPermissionLevel_Editor;
	else if (!g_ascii_strcasecmp (permission, "Author"))
		return EwsPermissionLevel_Author;
	else if (!g_ascii_strcasecmp (permission, "Reviewer"))
		return EwsPermissionLevel_Reviewer;
	else if (!g_ascii_strcasecmp (permission, "Custom"))
		return EwsPermissionLevel_Custom;
	else
		return EwsPermissionLevel_None;

}

static void
ews_handle_delegate_user_param (ESoapParameter *param,
                                EwsAsyncData *async_data)
{
	ESoapParameter *subparam, *node, *child;
	EwsDelegateInfo *data;
	gchar *value;

	node = e_soap_parameter_get_first_child_by_name (param, "DelegateUser");
	if (!node)
		return;

	subparam = e_soap_parameter_get_first_child_by_name (node, "UserId");
	if (!subparam)
		return;

	data = g_new0 (EwsDelegateInfo, 1);
	data->user_id = g_new0 (EwsUserId, 1);

	/*Parse User Id*/

	child = e_soap_parameter_get_first_child_by_name (subparam, "SID");
	data->user_id->sid = e_soap_parameter_get_string_value (child);

	child = e_soap_parameter_get_first_child_by_name (subparam, "PrimarySmtpAddress");
	data->user_id->primary_smtp = e_soap_parameter_get_string_value (child);

	child = e_soap_parameter_get_first_child_by_name (subparam, "DisplayName");
	data->user_id->display_name = e_soap_parameter_get_string_value (child);

	subparam = e_soap_parameter_get_first_child_by_name (node, "DelegatePermissions");

	/*Parse Delegate Permissions*/
	child = e_soap_parameter_get_first_child_by_name (subparam, "CalendarFolderPermissionLevel");
	if (child) {
		value = e_soap_parameter_get_string_value (child);
		data->calendar = get_permission_from_string (value);
		g_free (value);
	}

	child = e_soap_parameter_get_first_child_by_name (subparam, "ContactsFolderPermissionLevel");
	if (child) {
		value = e_soap_parameter_get_string_value (child);
		data->contacts = get_permission_from_string (value);
		g_free (value);
	}

	child = e_soap_parameter_get_first_child_by_name (subparam, "InboxFolderPermissionLevel");
	if (child) {
		value = e_soap_parameter_get_string_value (child);
		data->inbox = get_permission_from_string (value);
		g_free (value);
	}

	child = e_soap_parameter_get_first_child_by_name (subparam, "TasksFolderPermissionLevel");
	if (child) {
		value = e_soap_parameter_get_string_value (child);
		data->tasks = get_permission_from_string (value);
		g_free (value);
	}

	child = e_soap_parameter_get_first_child_by_name (subparam, "NotesFolderPermissionLevel");
	if (child) {
		value = e_soap_parameter_get_string_value (child);
		data->notes = get_permission_from_string (value);
		g_free (value);
	}

	child = e_soap_parameter_get_first_child_by_name (subparam, "JournalFolderPermissionLevel");
	if (child) {
		value = e_soap_parameter_get_string_value (child);
		data->journal = get_permission_from_string (value);
		g_free (value);
	}

	subparam = e_soap_parameter_get_first_child_by_name (node, "ReceiveCopiesOfMeetingMessages");
	if (subparam) {
		value = e_soap_parameter_get_string_value (subparam);
		data->meetingcopies = g_strcmp0 (value, "true") == 0;
		g_free (value);
	}

	subparam = e_soap_parameter_get_first_child_by_name (node, "ViewPrivateItems");
	if (subparam) {
		value = e_soap_parameter_get_string_value (subparam);
		data->view_priv_items = g_strcmp0 (value, "true") == 0;
		g_free (value);
	}

	async_data->items = g_slist_append (async_data->items, data);
}

static void
get_delegate_response_cb (ESoapResponse *response,
                          GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	gchar *value;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (ews_get_response_status (e_soap_response_get_parameter (response), &error))
		param = e_soap_response_get_first_parameter_by_name (
			response, "DeliverMeetingRequests", &error);
	else
		param = NULL;

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	value = e_soap_parameter_get_string_value (param);
	if (g_strcmp0 (value, "DelegatesOnly") == 0)
		async_data->deliver_to = EwsDelegateDeliver_DelegatesOnly;
	else if (g_strcmp0 (value, "DelegatesAndMe") == 0)
		async_data->deliver_to = EwsDelegateDeliver_DelegatesAndMe;
	else if (g_strcmp0 (value, "DelegatesAndSendInformationToMe") == 0)
		async_data->deliver_to = EwsDelegateDeliver_DelegatesAndSendInformationToMe;
	else {
		g_message ("%s: Unknown deliver-to value '%s'", G_STRFUNC, value ? value : "[null]");
		async_data->deliver_to = EwsDelegateDeliver_DelegatesAndSendInformationToMe;
	}
	g_free (value);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", NULL);
	/* it's OK to not have set any delegate */
	if (!param)
		return;

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "DelegateUserResponseMessageType"))
			ews_handle_delegate_user_param (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_get_delegate (EEwsConnection *cnc,
                               gint pri,
                               const gchar *mail_id,
                               gboolean include_permissions,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetDelegate",
			"IncludePermissions",
			include_permissions ? "true" : "false",
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "Mailbox", "messages", NULL);

	e_ews_message_write_string_parameter (msg, "EmailAddress", NULL, mail_id ? mail_id : cnc->priv->email);

	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_delegate);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, get_delegate_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_delegate_finish (EEwsConnection *cnc,
                                      GAsyncResult *result,
                                      EwsDelegateDeliver *deliver_to,
                                      GSList **delegates, /* EwsDelegateInfo * */
                                      GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (delegates != NULL, FALSE);
	g_return_val_if_fail (deliver_to != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_delegate),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*deliver_to = async_data->deliver_to;
	*delegates = async_data->items;
	async_data->items = NULL;

	return TRUE;
}

gboolean
e_ews_connection_get_delegate_sync (EEwsConnection *cnc,
                                    gint pri,
                                    const gchar *mail_id,
                                    gboolean include_permissions,
                                    EwsDelegateDeliver *deliver_to,
                                    GSList **delegates, /* EwsDelegateInfo * */
                                    GCancellable *cancellable,
                                    GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (deliver_to != NULL, FALSE);
	g_return_val_if_fail (delegates != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_delegate (
		cnc, pri, mail_id,
		include_permissions, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_delegate_finish (
		cnc, result, deliver_to, delegates, error);

	e_async_closure_free (closure);

	return success;
}

static void
update_delegate_response_cb (ESoapResponse *response,
                             GSimpleAsyncResult *simple)
{
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	if (ews_get_response_status (e_soap_response_get_parameter (response), &error)) {
		param = e_soap_response_get_first_parameter_by_name (
			response, "ResponseMessages", NULL);
		/* that's OK to not receive any ResponseMessages here */
		if (!param)
			return;
	} else
		param = NULL;

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		subparam = e_soap_parameter_get_next_child (param);
	}
}

static void
set_delegate_permission (ESoapMessage *msg,
                         const gchar *elem_name,
                         EwsPermissionLevel perm_level)
{
	const gchar *level_name = NULL;

	if (perm_level == EwsPermissionLevel_None)
		level_name = "None";
	else if (perm_level == EwsPermissionLevel_Reviewer)
		level_name = "Reviewer";
	else if (perm_level == EwsPermissionLevel_Author)
		level_name = "Author";
	else if (perm_level == EwsPermissionLevel_Editor)
		level_name = "Editor";

	if (!level_name)
		return;

	e_ews_message_write_string_parameter (msg, elem_name, NULL, level_name);
}

void
e_ews_connection_add_delegate (EEwsConnection *cnc,
                               gint pri,
                               const gchar *mail_id,
                               const GSList *delegates, /* EwsDelegateInfo * */
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *iter;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (delegates != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"AddDelegate",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "Mailbox", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "EmailAddress", NULL, mail_id ? mail_id : cnc->priv->email);
	e_soap_message_end_element (msg);

	e_soap_message_start_element (msg, "DelegateUsers", "messages", NULL);
	for (iter = delegates; iter; iter = iter->next) {
		const EwsDelegateInfo *di = iter->data;

		if (!di)
			continue;

		e_soap_message_start_element (msg, "DelegateUser", NULL, NULL);

		e_soap_message_start_element (msg, "UserId", NULL, NULL);
		e_ews_message_write_string_parameter (msg, "PrimarySmtpAddress", NULL, di->user_id->primary_smtp);
		e_soap_message_end_element (msg); /* UserId */

		e_soap_message_start_element (msg, "DelegatePermissions", NULL, NULL);
		set_delegate_permission (msg, "CalendarFolderPermissionLevel", di->calendar);
		set_delegate_permission (msg, "TasksFolderPermissionLevel", di->tasks);
		set_delegate_permission (msg, "InboxFolderPermissionLevel", di->inbox);
		set_delegate_permission (msg, "ContactsFolderPermissionLevel", di->contacts);
		set_delegate_permission (msg, "NotesFolderPermissionLevel", di->notes);
		set_delegate_permission (msg, "JournalFolderPermissionLevel", di->journal);
		e_soap_message_end_element (msg); /* DelegatePermissions */

		e_ews_message_write_string_parameter (
			msg, "ReceiveCopiesOfMeetingMessages", NULL,
			di->meetingcopies ? "true" : "false");
		e_ews_message_write_string_parameter (
			msg, "ViewPrivateItems", NULL,
			di->view_priv_items ? "true" : "false");

		e_soap_message_end_element (msg); /* DelegateUser */
	}

	e_soap_message_end_element (msg); /* DelegateUsers */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_add_delegate);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, update_delegate_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_add_delegate_finish (EEwsConnection *cnc,
                                      GAsyncResult *result,
                                      GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_add_delegate),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	return !g_simple_async_result_propagate_error (simple, error);
}

gboolean
e_ews_connection_add_delegate_sync (EEwsConnection *cnc,
                                    gint pri,
                                    const gchar *mail_id,
                                    const GSList *delegates, /* EwsDelegateInfo * */
                                    GCancellable *cancellable,
                                    GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (delegates != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_add_delegate (
		cnc, pri, mail_id, delegates, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_add_delegate_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_remove_delegate (EEwsConnection *cnc,
                                  gint pri,
                                  const gchar *mail_id,
                                  const GSList *delegate_ids, /* EwsUserId * */
                                  GCancellable *cancellable,
                                  GAsyncReadyCallback callback,
                                  gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *iter;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (delegate_ids != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"RemoveDelegate",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "Mailbox", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "EmailAddress", NULL, mail_id ? mail_id : cnc->priv->email);
	e_soap_message_end_element (msg);

	e_soap_message_start_element (msg, "UserIds", "messages", NULL);
	for (iter = delegate_ids; iter; iter = iter->next) {
		const EwsUserId *user_id = iter->data;

		if (!user_id)
			continue;

		e_soap_message_start_element (msg, "UserId", NULL, NULL);
		e_ews_message_write_string_parameter (msg, "PrimarySmtpAddress", NULL, user_id->primary_smtp);
		e_soap_message_end_element (msg); /* UserId */
	}

	e_soap_message_end_element (msg); /* UserIds */

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_remove_delegate);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, update_delegate_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_remove_delegate_finish (EEwsConnection *cnc,
                                         GAsyncResult *result,
                                         GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_remove_delegate),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	return !g_simple_async_result_propagate_error (simple, error);
}

gboolean
e_ews_connection_remove_delegate_sync (EEwsConnection *cnc,
                                       gint pri,
                                       const gchar *mail_id,
                                       const GSList *delegate_ids, /* EwsUserId * */
                                       GCancellable *cancellable,
                                       GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (delegate_ids != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_remove_delegate (
		cnc, pri, mail_id, delegate_ids, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_remove_delegate_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_update_delegate (EEwsConnection *cnc,
                                  gint pri,
                                  const gchar *mail_id,
                                  EwsDelegateDeliver deliver_to,
                                  const GSList *delegates, /* EwsDelegateInfo * */
                                  GCancellable *cancellable,
                                  GAsyncReadyCallback callback,
                                  gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *iter;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"UpdateDelegate",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "Mailbox", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "EmailAddress", NULL, mail_id ? mail_id : cnc->priv->email);
	e_soap_message_end_element (msg);

	if (delegates) {
		e_soap_message_start_element (msg, "DelegateUsers", "messages", NULL);
		for (iter = delegates; iter; iter = iter->next) {
			const EwsDelegateInfo *di = iter->data;

			if (!di)
				continue;

			e_soap_message_start_element (msg, "DelegateUser", NULL, NULL);

			e_soap_message_start_element (msg, "UserId", NULL, NULL);
			e_ews_message_write_string_parameter (msg, "PrimarySmtpAddress", NULL, di->user_id->primary_smtp);
			e_soap_message_end_element (msg); /* UserId */

			e_soap_message_start_element (msg, "DelegatePermissions", NULL, NULL);
			set_delegate_permission (msg, "CalendarFolderPermissionLevel", di->calendar);
			set_delegate_permission (msg, "TasksFolderPermissionLevel", di->tasks);
			set_delegate_permission (msg, "InboxFolderPermissionLevel", di->inbox);
			set_delegate_permission (msg, "ContactsFolderPermissionLevel", di->contacts);
			set_delegate_permission (msg, "NotesFolderPermissionLevel", di->notes);
			set_delegate_permission (msg, "JournalFolderPermissionLevel", di->journal);
			e_soap_message_end_element (msg); /* DelegatePermissions */

			e_ews_message_write_string_parameter (
				msg, "ReceiveCopiesOfMeetingMessages", NULL,
				di->meetingcopies ? "true" : "false");
			e_ews_message_write_string_parameter (
				msg, "ViewPrivateItems", NULL,
				di->view_priv_items ? "true" : "false");

			e_soap_message_end_element (msg); /* DelegateUser */
		}

		e_soap_message_end_element (msg); /* DelegateUsers */
	}

	e_ews_message_write_string_parameter (
		msg, "DeliverMeetingRequests", "messages",
		deliver_to == EwsDelegateDeliver_DelegatesOnly ? "DelegatesOnly" :
		deliver_to == EwsDelegateDeliver_DelegatesAndMe ? "DelegatesAndMe" :
		"DelegatesAndSendInformationToMe");

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_update_delegate);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, update_delegate_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_update_delegate_finish (EEwsConnection *cnc,
                                         GAsyncResult *result,
                                         GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_update_delegate),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	return !g_simple_async_result_propagate_error (simple, error);
}

gboolean
e_ews_connection_update_delegate_sync (EEwsConnection *cnc,
                                       gint pri,
                                       const gchar *mail_id,
                                       EwsDelegateDeliver deliver_to,
                                       const GSList *delegates, /* EwsDelegateInfo * */
                                       GCancellable *cancellable,
                                       GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_update_delegate (
		cnc, pri, mail_id, deliver_to, delegates, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_update_delegate_finish (cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

static void
get_folder_permissions_response_cb (ESoapResponse *response,
                                    GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "GetFolderResponseMessage")) {
			ESoapParameter *node;

			node = e_soap_parameter_get_first_child_by_name (subparam, "Folders");
			if (node) {
				subparam = node;

				node = e_soap_parameter_get_first_child (subparam);
				if (node && node->name && g_str_has_suffix ((const gchar *) node->name, "Folder")) {
					node = e_soap_parameter_get_first_child_by_name (node, "PermissionSet");
					if (node) {
						async_data->items = e_ews_permissions_from_soap_param (node);
					}
				}
			}

			break;
		}

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_get_folder_permissions (EEwsConnection *cnc,
                                         gint pri,
                                         EwsFolderId *folder_id,
                                         GCancellable *cancellable,
                                         GAsyncReadyCallback callback,
                                         gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (folder_id != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetFolder",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "FolderShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, "IdOnly");
	e_soap_message_start_element (msg, "AdditionalProperties", NULL, NULL);
	e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", "folder:PermissionSet");
	e_soap_message_end_element (msg); /* AdditionalProperties */
	e_soap_message_end_element (msg); /* FolderShape */

	e_soap_message_start_element (msg, "FolderIds", "messages", NULL);
	e_ews_folder_id_append_to_msg (msg, cnc->priv->email, folder_id);
	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_folder_permissions);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, get_folder_permissions_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

/* free permissions with e_ews_permissions_free() */
gboolean
e_ews_connection_get_folder_permissions_finish (EEwsConnection *cnc,
                                                GAsyncResult *result,
                                                GSList **permissions,
                                                GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (permissions != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_folder_permissions),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*permissions = async_data->items;

	return TRUE;
}

/* free permissions with e_ews_permissions_free() */
gboolean
e_ews_connection_get_folder_permissions_sync (EEwsConnection *cnc,
                                              gint pri,
                                              EwsFolderId *folder_id,
                                              GSList **permissions,
                                              GCancellable *cancellable,
                                              GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (folder_id != NULL, FALSE);
	g_return_val_if_fail (permissions != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_folder_permissions (
		cnc, pri, folder_id, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_folder_permissions_finish (
		cnc, result, permissions, error);

	e_async_closure_free (closure);

	return success;
}

void
e_ews_connection_set_folder_permissions (EEwsConnection *cnc,
                                         gint pri,
                                         EwsFolderId *folder_id,
                                         EEwsFolderType folder_type,
                                         const GSList *permissions,
                                         GCancellable *cancellable,
                                         GAsyncReadyCallback callback,
                                         gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	const GSList *iter;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (folder_id != NULL);
	g_return_if_fail (permissions != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"UpdateFolder",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "FolderChanges", "messages", NULL);
	e_ews_message_start_folder_change (msg, cnc->priv->email, folder_id);

	e_soap_message_start_element (msg, "SetFolderField", NULL, NULL);
	e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", "folder:PermissionSet");

	switch (folder_type) {
	default:
	case E_EWS_FOLDER_TYPE_MAILBOX:
		e_soap_message_start_element (msg, "Folder", NULL, NULL);
		break;
	case E_EWS_FOLDER_TYPE_CALENDAR:
		e_soap_message_start_element (msg, "CalendarFolder", NULL, NULL);
		break;
	case E_EWS_FOLDER_TYPE_CONTACTS:
		e_soap_message_start_element (msg, "ContactsFolder", NULL, NULL);
		break;
	case E_EWS_FOLDER_TYPE_SEARCH:
		e_soap_message_start_element (msg, "SearchFolder", NULL, NULL);
		break;
	case E_EWS_FOLDER_TYPE_TASKS:
		e_soap_message_start_element (msg, "TasksFolder", NULL, NULL);
		break;
	}

	e_soap_message_start_element (msg, "PermissionSet", NULL, NULL);
	if (folder_type == E_EWS_FOLDER_TYPE_CALENDAR)
		e_soap_message_start_element (msg, "CalendarPermissions", NULL, NULL);
	else
		e_soap_message_start_element (msg, "Permissions", NULL, NULL);

	for (iter = permissions; iter; iter = iter->next) {
		EEwsPermission *perm = iter->data;
		const gchar *perm_level_name;

		if (!perm)
			continue;

		if (folder_type == E_EWS_FOLDER_TYPE_CALENDAR)
			e_soap_message_start_element (msg, "CalendarPermission", NULL, NULL);
		else
			e_soap_message_start_element (msg, "Permission", NULL, NULL);

		e_soap_message_start_element (msg, "UserId", NULL, NULL);

		switch (perm->user_type) {
		case E_EWS_PERMISSION_USER_TYPE_NONE:
			g_return_if_reached ();
			break;
		case E_EWS_PERMISSION_USER_TYPE_ANONYMOUS:
			e_ews_message_write_string_parameter (msg, "DistinguishedUser", NULL, "Anonymous");
			break;
		case E_EWS_PERMISSION_USER_TYPE_DEFAULT:
			e_ews_message_write_string_parameter (msg, "DistinguishedUser", NULL, "Default");
			break;
		case E_EWS_PERMISSION_USER_TYPE_REGULAR:
			e_ews_message_write_string_parameter (msg, "PrimarySmtpAddress", NULL, perm->primary_smtp);
			break;
		}

		e_soap_message_end_element (msg); /* UserId */

		e_ews_permission_rights_to_level_name (perm->rights);

		perm_level_name = e_ews_permission_rights_to_level_name (perm->rights);

		if (g_strcmp0 (perm_level_name, "Custom") == 0) {
			e_ews_message_write_string_parameter (
				msg, "CanCreateItems", NULL,
				(perm->rights & E_EWS_PERMISSION_BIT_CREATE) != 0 ? "true" : "false");
			e_ews_message_write_string_parameter (
				msg, "CanCreateSubFolders", NULL,
				(perm->rights & E_EWS_PERMISSION_BIT_CREATE_SUBFOLDER) != 0 ? "true" : "false");
			e_ews_message_write_string_parameter (
				msg, "IsFolderOwner", NULL,
				(perm->rights & E_EWS_PERMISSION_BIT_FOLDER_OWNER) != 0 ? "true" : "false");
			e_ews_message_write_string_parameter (
				msg, "IsFolderVisible", NULL,
				(perm->rights & E_EWS_PERMISSION_BIT_FOLDER_VISIBLE) != 0 ? "true" : "false");
			e_ews_message_write_string_parameter (
				msg, "IsFolderContact", NULL,
				(perm->rights & E_EWS_PERMISSION_BIT_FOLDER_CONTACT) != 0 ? "true" : "false");
			e_ews_message_write_string_parameter (
				msg, "EditItems", NULL,
				(perm->rights & E_EWS_PERMISSION_BIT_EDIT_ANY) != 0 ? "All" :
				(perm->rights & E_EWS_PERMISSION_BIT_EDIT_OWNED) != 0 ? "Owned" : "None");
			e_ews_message_write_string_parameter (
				msg, "DeleteItems", NULL,
				(perm->rights & E_EWS_PERMISSION_BIT_DELETE_ANY) != 0 ? "All" :
				(perm->rights & E_EWS_PERMISSION_BIT_DELETE_OWNED) != 0 ? "Owned" : "None");
			if (folder_type == E_EWS_FOLDER_TYPE_CALENDAR)
				e_ews_message_write_string_parameter (
					msg, "ReadItems", NULL,
					(perm->rights & E_EWS_PERMISSION_BIT_READ_ANY) != 0 ? "FullDetails" :
					(perm->rights & E_EWS_PERMISSION_BIT_FREE_BUSY_DETAILED) != 0 ? "TimeAndSubjectAndLocation" :
					(perm->rights & E_EWS_PERMISSION_BIT_FREE_BUSY_SIMPLE) != 0 ? "TimeOnly" : "None");
			else
				e_ews_message_write_string_parameter (
					msg, "ReadItems", NULL,
					(perm->rights & E_EWS_PERMISSION_BIT_READ_ANY) != 0 ? "FullDetails" : "None");
		}

		e_ews_message_write_string_parameter (
			msg,
			folder_type == E_EWS_FOLDER_TYPE_CALENDAR ? "CalendarPermissionLevel" : "PermissionLevel", NULL,
			perm_level_name);

		e_soap_message_end_element (msg); /* Permission/CalendarPermission */
	}

	e_soap_message_end_element (msg); /* Permissions */
	e_soap_message_end_element (msg); /* PermissionSet */
	e_soap_message_end_element (msg); /* Folder/CalendarFolder/... */
	e_soap_message_end_element (msg); /* SetFolderField */

	e_ews_message_end_item_change (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_set_folder_permissions);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, update_folder_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_set_folder_permissions_finish (EEwsConnection *cnc,
                                                GAsyncResult *result,
                                                GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_set_folder_permissions),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	return !g_simple_async_result_propagate_error (simple, error);
}

gboolean
e_ews_connection_set_folder_permissions_sync (EEwsConnection *cnc,
                                              gint pri,
                                              EwsFolderId *folder_id,
                                              EEwsFolderType folder_type,
                                              const GSList *permissions,
                                              GCancellable *cancellable,
                                              GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (folder_id != NULL, FALSE);
	g_return_val_if_fail (permissions != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_set_folder_permissions (
		cnc, pri, folder_id, folder_type, permissions, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_set_folder_permissions_finish (
		cnc, result, error);

	e_async_closure_free (closure);

	return success;
}

static void
get_password_expiration_response_cb (ESoapResponse *response,
                                     GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	gchar *exp_date;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "PasswordExpirationDate", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	exp_date = e_soap_parameter_get_string_value (param);

	async_data->items = g_slist_append (async_data->items, exp_date);
}

/**
 * e_ews_connection_get_password_expiration
 * @cnc:
 * @pri:
 * @mail_id: mail is for which password expiration is requested
 * @cb:
 * @cancellable:
 * @user_data:
 **/
void
e_ews_connection_get_password_expiration (EEwsConnection *cnc,
                                          gint pri,
                                          const gchar *mail_id,
                                          GCancellable *cancellable,
                                          GAsyncReadyCallback callback,
                                          gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetPasswordExpirationDate",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2010_SP2,
			FALSE,
			TRUE);
	e_ews_message_write_string_parameter (msg, "MailboxSmtpAddress", NULL, mail_id ? mail_id : cnc->priv->email);
	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_password_expiration);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, get_password_expiration_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_password_expiration_finish (EEwsConnection *cnc,
                                                 GAsyncResult *result,
                                                 gchar **exp_date,
                                                 GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (exp_date != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_password_expiration),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	g_return_val_if_fail (async_data->items != NULL, FALSE);

	*exp_date = async_data->items->data;
	g_slist_free (async_data->items);

	return TRUE;
}

/**
 * e_ews_connection_get_password_expiration_sync
 * @cnc:
 * @pri:
 * @mail_id: mail id for which password expiration is requested
 * @cancellable:
 * @error:
 **/
gboolean
e_ews_connection_get_password_expiration_sync (EEwsConnection *cnc,
                                               gint pri,
                                               const gchar *mail_id,
                                               gchar **exp_date,
                                               GCancellable *cancellable,
                                               GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (exp_date != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_password_expiration (
		cnc, pri, mail_id, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_password_expiration_finish (
		cnc, result, exp_date, error);

	e_async_closure_free (closure);

	return success;
}

static void
get_folder_info_response_cb (ESoapResponse *response,
                             GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "GetFolderResponseMessage")) {
			ESoapParameter *node;

			node = e_soap_parameter_get_first_child_by_name (subparam, "Folders");
			if (node) {
				EEwsFolder *folder = e_ews_folder_new_from_soap_parameter (node);

				if (folder)
					async_data->items = g_slist_prepend (NULL, folder);
			}

			break;
		}

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_get_folder_info (EEwsConnection *cnc,
                                  gint pri,
                                  const gchar *mail_id,
                                  const EwsFolderId *folder_id,
                                  GCancellable *cancellable,
                                  GAsyncReadyCallback callback,
                                  gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (folder_id != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetFolder",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);

	e_soap_message_start_element (msg, "FolderShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, "Default");
	e_soap_message_start_element (msg, "AdditionalProperties", NULL, NULL);
	e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", "folder:FolderClass");
	e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", "folder:ParentFolderId");
	e_soap_message_end_element (msg); /* AdditionalProperties */
	e_soap_message_end_element (msg); /* FolderShape */

	e_soap_message_start_element (msg, "FolderIds", "messages", NULL);
	e_ews_folder_id_append_to_msg (msg, mail_id, folder_id);
	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_get_folder_info);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, get_folder_info_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_folder_info_finish (EEwsConnection *cnc,
                                         GAsyncResult *result,
                                         EEwsFolder **folder,
                                         GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (folder != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_get_folder_info),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (!async_data->items)
		return FALSE;

	*folder = async_data->items ? async_data->items->data : NULL;

	g_slist_free (async_data->items);
	async_data->items = NULL;

	return TRUE;
}

gboolean
e_ews_connection_get_folder_info_sync (EEwsConnection *cnc,
                                       gint pri,
                                       const gchar *mail_id,
                                       const EwsFolderId *folder_id,
                                       EEwsFolder **folder,
                                       GCancellable *cancellable,
                                       GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), FALSE);
	g_return_val_if_fail (folder_id != NULL, FALSE);
	g_return_val_if_fail (folder != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_folder_info (
		cnc, pri, mail_id, folder_id, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_folder_info_finish (
		cnc, result, folder, error);

	e_async_closure_free (closure);

	return success;
}

static void
ews_handle_root_folder_param_folders (ESoapParameter *subparam,
				      EwsAsyncData *async_data)
{
	ESoapParameter *node, *subparam1;
	gchar *last, *total;
	gint total_items;
	EEwsFolder *folder;
	gboolean includes_last_item;

	node = e_soap_parameter_get_first_child_by_name (subparam, "RootFolder");
	total = e_soap_parameter_get_property (node, "TotalItemsInView");
	total_items = atoi (total);
	g_free (total);
	last = e_soap_parameter_get_property (node, "IncludesLastItemInRange");
	/*
	 * Set the includes_last_item to TRUE as default.
	 * It can avoid an infinite loop in caller, when, for some reason,
	 * we don't receive the last_tag property from the server.
	 */
	includes_last_item = g_strcmp0 (last, "false") != 0;
	g_free (last);

	node = e_soap_parameter_get_first_child_by_name (node, "Folders");
	for (subparam1 = e_soap_parameter_get_first_child (node);
	     subparam1; subparam1 = e_soap_parameter_get_next_child (subparam1)) {
		folder = e_ews_folder_new_from_soap_parameter (subparam1);
		if (!folder) continue;
		async_data->items = g_slist_append (async_data->items, folder);
	}
	async_data->total_items = total_items;
	async_data->includes_last_item = includes_last_item;
}

static void
find_folder_response_cb (ESoapResponse *response,
			 GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "FindFolderResponseMessage"))
			ews_handle_root_folder_param_folders (subparam, async_data);

		subparam = e_soap_parameter_get_next_child (subparam);
	}
}

void
e_ews_connection_find_folder (EEwsConnection *cnc,
			      gint pri,
			      const EwsFolderId *fid,
			      GCancellable *cancellable,
			      GAsyncReadyCallback callback,
			      gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"FindFolder",
			"Traversal",
			"Shallow",
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			FALSE,
			TRUE);
	e_soap_message_start_element (msg, "FolderShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, "Default");
	e_soap_message_start_element (msg, "AdditionalProperties", NULL, NULL);
	e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", "folder:FolderClass");
	e_ews_message_write_string_parameter_with_attribute (msg, "FieldURI", NULL, NULL, "FieldURI", "folder:ChildFolderCount");
	e_soap_message_end_element (msg); /* AdditionalProperties */
	e_soap_message_end_element (msg);

	e_soap_message_start_element (msg, "ParentFolderIds", "messages", NULL);

	if (fid->is_distinguished_id)
		e_ews_message_write_string_parameter_with_attribute (msg, "DistinguishedFolderId", NULL, NULL, "Id", fid->id);
	else
		e_ews_message_write_string_parameter_with_attribute (msg, "FolderId", NULL, NULL, "Id", fid->id);

	e_soap_message_end_element (msg);

	/* Complete the footer and print the request */
	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_find_folder);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	e_ews_connection_queue_request (
		cnc, msg, find_folder_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_find_folder_finish (EEwsConnection *cnc,
				     GAsyncResult *result,
				     gboolean *includes_last_item,
				     GSList **folders,
				     GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_find_folder),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*includes_last_item = async_data->includes_last_item;
	*folders = async_data->items;

	return TRUE;
}

gboolean
e_ews_connection_find_folder_sync (EEwsConnection *cnc,
				   gint pri,
				   const EwsFolderId *fid,
				   gboolean *includes_last_item,
				   GSList **folders,
				   GCancellable *cancellable,
				   GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_find_folder (cnc, pri, fid, cancellable, e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_find_folder_finish (
		cnc, result, includes_last_item, folders, error);

	e_async_closure_free (closure);

	return success;
}

#define EWS_OBJECT_KEY_AUTHS_GATHERED "ews-auths-gathered"

static void
query_auth_methods_response_cb (ESoapResponse *response,
				GSimpleAsyncResult *simple)
{
	ESoapParameter *param;
	GError *error = NULL;

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	/* nothing to read, it should not get this far anyway */
}

static void
ews_connection_gather_auth_methods_cb (SoupMessage *message,
				       GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	const gchar *auths_lst;
	gboolean has_bearer = FALSE;
	gchar **auths;
	gint ii;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	g_return_if_fail (async_data != NULL);

	auths_lst = soup_message_headers_get_list (message->response_headers, "WWW-Authenticate");
	if (!auths_lst)
		return;

	auths = g_strsplit (auths_lst, ",", -1);
	for (ii = 0; auths && auths[ii]; ii++) {
		gchar *auth, *space;

		auth = g_strstrip (g_strdup (auths[ii]));
		if (auth && *auth) {
			space = strchr (auth, ' ');
			if (space)
				*space = '\0';

			has_bearer = has_bearer || g_ascii_strcasecmp (auth, "Bearer") == 0;
			async_data->items = g_slist_prepend (async_data->items, auth);
		} else {
			g_free (auth);
		}
	}

	g_strfreev (auths);

	if (!has_bearer) {
		/* Special-case Office365 OAuth2, because outlook.office365.com doesn't advertise Bearer */
		async_data->items = g_slist_prepend (async_data->items, g_strdup ("Bearer"));
	}

	g_object_set_data (G_OBJECT (simple), EWS_OBJECT_KEY_AUTHS_GATHERED, GINT_TO_POINTER (1));

	soup_message_set_status_full (message, SOUP_STATUS_CANCELLED, "EWS auths gathered");
}

/* Note: This only works if the connection is not connected yet */
void
e_ews_connection_query_auth_methods (EEwsConnection *cnc,
				     gint pri,
				     GCancellable *cancellable,
				     GAsyncReadyCallback callback,
				     gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);

	/* use some simple operation to get WWW-Authenticate headers from the server */
	msg = e_ews_message_new_with_header (
			cnc->priv->settings,
			cnc->priv->uri,
			cnc->priv->impersonate_user,
			"GetFolder",
			NULL,
			NULL,
			cnc->priv->version,
			E_EWS_EXCHANGE_2007_SP1,
			TRUE,
			TRUE);

	e_soap_message_start_element (msg, "FolderShape", "messages", NULL);
	e_ews_message_write_string_parameter (msg, "BaseShape", NULL, "IdOnly");
	e_soap_message_end_element (msg);

	e_soap_message_start_element (msg, "FolderIds", "messages", NULL);
	e_ews_message_write_string_parameter_with_attribute (msg, "DistinguishedFolderId", NULL, NULL, "Id", "inbox");
	e_soap_message_end_element (msg);

	e_ews_message_write_footer (msg);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data,
		e_ews_connection_query_auth_methods);

	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (
		simple, async_data, (GDestroyNotify) async_data_free);

	soup_message_add_header_handler (SOUP_MESSAGE (msg), "got-headers", "WWW-Authenticate",
		G_CALLBACK (ews_connection_gather_auth_methods_cb), simple);

	e_ews_connection_queue_request (
		cnc, msg, query_auth_methods_response_cb,
		pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_query_auth_methods_finish (EEwsConnection *cnc,
					    GAsyncResult *result,
					    GSList **auth_methods,
					    GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (auth_methods != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (cnc), e_ews_connection_query_auth_methods),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (simple), EWS_OBJECT_KEY_AUTHS_GATHERED)) != 1 &&
	    g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	*auth_methods = g_slist_reverse (async_data->items);

	return TRUE;
}

/* Note: This only works if the connection is not connected yet */
gboolean
e_ews_connection_query_auth_methods_sync (EEwsConnection *cnc,
					  gint pri,
					  GSList **auth_methods,
					  GCancellable *cancellable,
					  GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_query_auth_methods (cnc, pri, cancellable, e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_query_auth_methods_finish (
		cnc, result, auth_methods, error);

	e_async_closure_free (closure);

	return success;
}

static void
ews_connection_build_subscribed_folders_list (gpointer key,
					      gpointer value,
					      gpointer user_data)
{
	GSList *folders = value, *l;
	EEwsConnection *cnc = user_data;

	for (l = folders; l != NULL; l = l->next) {
		if (g_slist_find_custom (cnc->priv->subscribed_folders, l->data, (GCompareFunc) g_strcmp0) == NULL) {
			cnc->priv->subscribed_folders =
				g_slist_prepend (cnc->priv->subscribed_folders, g_strdup (l->data));
		}
	}
}

static void
ews_connection_subscription_id_changed_cb (EEwsNotification *notification,
					   const gchar *subscription_id,
					   gpointer user_data)
{
	EEwsConnection *cnc = user_data;

	g_return_if_fail (E_IS_EWS_CONNECTION (cnc));

	NOTIFICATION_LOCK (cnc);

	if (cnc->priv->notification == notification)
		g_signal_emit (cnc, signals[SUBSCRIPTION_ID_CHANGED], 0, subscription_id, NULL);

	NOTIFICATION_UNLOCK (cnc);
}

static gpointer
ews_connection_notification_start_thread (gpointer user_data)
{
	GWeakRef *weakref = user_data;
	EEwsConnection *cnc;

	g_return_val_if_fail (weakref != NULL, NULL);

	cnc = g_weak_ref_get (weakref);

	if (cnc && !e_ews_connection_get_disconnected_flag (cnc)) {
		gchar *last_subscription_id = e_ews_connection_dup_last_subscription_id (cnc);

		NOTIFICATION_LOCK (cnc);

		if (cnc->priv->subscribed_folders) {
			g_warn_if_fail (cnc->priv->notification == NULL);
			g_clear_object (&cnc->priv->notification);

			cnc->priv->notification = e_ews_notification_new (cnc, last_subscription_id);

			/* The 'notification' assumes ownership of the 'last_subscription_id' */
			last_subscription_id = NULL;

			g_signal_connect_object (cnc->priv->notification, "subscription-id-changed",
				G_CALLBACK (ews_connection_subscription_id_changed_cb), cnc, 0);

			e_ews_notification_start_listening_sync (cnc->priv->notification, cnc->priv->subscribed_folders);
		}

		NOTIFICATION_UNLOCK (cnc);

		g_free (last_subscription_id);
	}

	g_clear_object (&cnc);
	e_weak_ref_free (weakref);

	return NULL;
}

static gboolean
ews_connection_notification_delay_cb (gpointer user_data)
{
	GWeakRef *weakref = user_data;
	EEwsConnection *cnc;

	if (g_source_is_destroyed (g_main_current_source ()))
		return FALSE;

	g_return_val_if_fail (weakref != NULL, FALSE);

	cnc = g_weak_ref_get (weakref);

	if (cnc) {
		NOTIFICATION_LOCK (cnc);

		if (cnc->priv->notification_delay_id == g_source_get_id (g_main_current_source ())) {
			cnc->priv->notification_delay_id = 0;

			if (cnc->priv->subscribed_folders) {
				g_thread_unref (g_thread_new (NULL, ews_connection_notification_start_thread,
					e_weak_ref_new (cnc)));
			}
		}

		NOTIFICATION_UNLOCK (cnc);

		g_object_unref (cnc);
	}

	return FALSE;
}

/*
 * Enables server notification on a folder (or a set of folders).
 * The events we are listen for notifications are: Copied, Created, Deleted, Modified and Moved.
 *
 * As we have only one subscription per connection, for every enable_notifications_sync() call,
 * we do:
 * - Check if we already are subscribed
 * - If we are already subscribed:
 *  - Stop to send events regards to the already subscribed folders' list
 *  - Unsubscribe the already subscribed folders' list
 * - Add the user folders' lst to the hash table if subscribed folders
 * - Create a new list of the folders to subscribe for, based in the hash table of subscribed folders
 * - Subscribed to listen notifications for the folders
 * - Start to send events regards to the newly subscribed folders' list
 *
 * Pair function for this one is e_ews_connection_disable_notifications_sync() and we do
 * something really similar for every disable_notifications_sync() call.
 *
 * The notification is received to the caller with the "server-notification" signal.
 * Note that the signal is used for each notification, without distinction on the
 * enabled object.
 */
void
e_ews_connection_enable_notifications_sync (EEwsConnection *cnc,
					    GSList *folders,
					    guint *subscription_key)
{
	GSList *new_folders = NULL, *l, *flink;
	gint subscriptions_size;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (cnc->priv != NULL);
	g_return_if_fail (cnc->priv->version >= E_EWS_EXCHANGE_2010_SP1);
	g_return_if_fail (folders != NULL);

	NOTIFICATION_LOCK (cnc);
	subscriptions_size = g_hash_table_size (cnc->priv->subscriptions);

	if (subscriptions_size == G_MAXUINT - 1)
		goto exit;

	for (flink = folders; flink; flink = g_slist_next (flink)) {
		for (l = cnc->priv->subscribed_folders; l; l = g_slist_next (l)) {
			if (g_strcmp0 (l->data, flink->data) == 0)
				break;
		}

		if (!l)
			break;
	}

	/* All requested folders are already subscribed */
	if (!flink && cnc->priv->notification)
		goto exit;

	if (subscriptions_size > 0) {
		if (cnc->priv->notification) {
			e_ews_notification_stop_listening_sync (cnc->priv->notification);
			g_clear_object (&cnc->priv->notification);
		}

		g_slist_free_full (cnc->priv->subscribed_folders, g_free);
		cnc->priv->subscribed_folders = NULL;
	}

	while (g_hash_table_contains (cnc->priv->subscriptions, GINT_TO_POINTER (notification_key))) {
		notification_key++;
		if (notification_key == 0)
			notification_key++;
	}

	for (l = folders; l != NULL; l = l->next)
		new_folders = g_slist_prepend (new_folders, g_strdup (l->data));

	g_hash_table_insert (cnc->priv->subscriptions, GINT_TO_POINTER (notification_key), new_folders);
	new_folders = NULL;

	g_hash_table_foreach (cnc->priv->subscriptions, ews_connection_build_subscribed_folders_list, cnc);

	if (cnc->priv->notification_delay_id)
		g_source_remove (cnc->priv->notification_delay_id);

	cnc->priv->notification_delay_id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 5,
		ews_connection_notification_delay_cb, e_weak_ref_new (cnc), (GDestroyNotify) e_weak_ref_free);

exit:
	*subscription_key = notification_key;
	notification_key++;
	if (notification_key == 0)
		notification_key++;

	NOTIFICATION_UNLOCK (cnc);
}

void
e_ews_connection_disable_notifications_sync (EEwsConnection *cnc,
					     guint subscription_key)
{
	g_return_if_fail (cnc != NULL);
	g_return_if_fail (cnc->priv != NULL);

	NOTIFICATION_LOCK (cnc);
	if (cnc->priv->notification == NULL)
		goto exit;

	if (!g_hash_table_remove (cnc->priv->subscriptions, GINT_TO_POINTER (subscription_key)))
		goto exit;

	e_ews_notification_stop_listening_sync (cnc->priv->notification);

	g_slist_free_full (cnc->priv->subscribed_folders, g_free);
	cnc->priv->subscribed_folders = NULL;

	g_hash_table_foreach (cnc->priv->subscriptions, ews_connection_build_subscribed_folders_list, cnc);
	if (cnc->priv->subscribed_folders != NULL && !e_ews_connection_get_disconnected_flag (cnc)) {
		e_ews_notification_start_listening_sync (cnc->priv->notification, cnc->priv->subscribed_folders);
	} else {
		g_clear_object (&cnc->priv->notification);
	}

exit:
	NOTIFICATION_UNLOCK (cnc);
}

static EEwsCalendarTo *
ews_get_to (ESoapParameter *node)
{
	EEwsCalendarTo *to = NULL;
	ESoapParameter *param;
	gchar *kind = NULL;
	gchar *value = NULL;
	gboolean success = FALSE;

	param = e_soap_parameter_get_first_child_by_name (node, "To");
	if (param == NULL)
		goto exit;

	kind = e_soap_parameter_get_property (param, "Kind");
	if (kind == NULL)
		goto exit;

	value = e_soap_parameter_get_string_value (param);
	if (value == NULL)
		goto exit;

	success = TRUE;

exit:
	if (success) {
		to = e_ews_calendar_to_new ();
		to->kind = kind;
		to->value = value;
	} else {
		g_free (kind);
		g_free (value);
	}

	return to;
}

static EEwsCalendarPeriod *
ews_get_period (ESoapParameter *node)
{
	EEwsCalendarPeriod *period = NULL;
	gchar *bias = NULL;
	gchar *name = NULL;
	gchar *id = NULL;

	bias = e_soap_parameter_get_property (node, "Bias");
	name = e_soap_parameter_get_property (node, "Name");
	id = e_soap_parameter_get_property (node, "Id");

	if (bias == NULL || name == NULL || id == NULL) {
		g_free (bias);
		g_free (name);
		g_free (id);

		return NULL;
	}

	period = e_ews_calendar_period_new ();
	period->bias = bias;
	period->name = name;
	period->id = id;

	return period;
}

static GSList *
ews_get_periods_list (ESoapParameter *node)
{
	ESoapParameter *param;
	GSList *periods = NULL;

	for (param = e_soap_parameter_get_first_child_by_name (node, "Period");
	     param != NULL;
	     param = e_soap_parameter_get_next_child_by_name (param, "Period")) {
		EEwsCalendarPeriod *period;

		period = ews_get_period (param);
		if (period != NULL) {
			periods = g_slist_prepend (periods, period);
		} else {
			g_slist_free_full (periods, (GDestroyNotify) e_ews_calendar_period_free);
			return NULL;
		}
	}

	periods = g_slist_reverse (periods);
	return periods;
}

static EEwsCalendarAbsoluteDateTransition *
ews_get_absolute_date_transition (ESoapParameter *node)
{
	ESoapParameter *param;
	EEwsCalendarAbsoluteDateTransition *absolute_date_transition = NULL;
	EEwsCalendarTo *to = NULL;
	gchar *date_time = NULL;
	gboolean success = FALSE;

	param = e_soap_parameter_get_first_child_by_name (node, "To");
	if (param != NULL)
		to = ews_get_to (param);

	if (to == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "DateTime");
	if (param != NULL)
		date_time = e_soap_parameter_get_string_value (param);

	if (date_time == NULL)
		goto exit;

	success = TRUE;

exit:
	if (success) {
		absolute_date_transition = e_ews_calendar_absolute_date_transition_new ();
		absolute_date_transition->to = to;
		absolute_date_transition->date_time = date_time;
	} else {
		e_ews_calendar_to_free (to);
		g_free (date_time);
	}

	return absolute_date_transition;
}

static EEwsCalendarRecurringDateTransition *
ews_get_recurring_date_transition (ESoapParameter *node)
{
	ESoapParameter *param;
	EEwsCalendarRecurringDateTransition *recurring_date_transition = NULL;
	EEwsCalendarTo *to = NULL;
	gchar *time_offset = NULL;
	gchar *month = NULL;
	gchar *day = NULL;
	gboolean success = FALSE;

	to = ews_get_to (node);
	if (to == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "TimeOffset");
	if (param != NULL)
		time_offset = e_soap_parameter_get_string_value (param);

	if (time_offset == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "Month");
	if (param != NULL)
		month = e_soap_parameter_get_string_value (param);

	if (month == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "Day");
	if (param != NULL)
		day = e_soap_parameter_get_string_value (param);

	if (day == NULL)
		goto exit;

	success = TRUE;

exit:
	if (success) {
		recurring_date_transition = e_ews_calendar_recurring_date_transition_new ();
		recurring_date_transition->to = to;
		recurring_date_transition->time_offset = time_offset;
		recurring_date_transition->month = month;
		recurring_date_transition->day = day;
	} else {
		e_ews_calendar_to_free (to);
		g_free (time_offset);
		g_free (month);
		g_free (day);
	}

	return recurring_date_transition;
}

static EEwsCalendarRecurringDayTransition *
ews_get_recurring_day_transition (ESoapParameter *node)
{
	ESoapParameter *param;
	EEwsCalendarRecurringDayTransition *recurring_day_transition = NULL;
	EEwsCalendarTo *to = NULL;
	gchar *time_offset = NULL;
	gchar *month = NULL;
	gchar *day_of_week = NULL;
	gchar *occurrence = NULL;
	gboolean success = FALSE;

	to = ews_get_to (node);
	if (to == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "TimeOffset");
	if (param != NULL)
		time_offset = e_soap_parameter_get_string_value (param);

	if (time_offset == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "Month");
	if (param != NULL)
		month = e_soap_parameter_get_string_value (param);

	if (month == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "DayOfWeek");
	if (param != NULL)
		day_of_week = e_soap_parameter_get_string_value (param);

	if (day_of_week == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "Occurrence");
	if (param != NULL)
		occurrence = e_soap_parameter_get_string_value (param);

	if (occurrence == NULL)
		goto exit;

	success = TRUE;

exit:
	if (success) {
		recurring_day_transition = e_ews_calendar_recurring_day_transition_new ();
		recurring_day_transition->to = to;
		recurring_day_transition->time_offset = time_offset;
		recurring_day_transition->month = month;
		recurring_day_transition->day_of_week = day_of_week;
		recurring_day_transition->occurrence = occurrence;
	} else {
		e_ews_calendar_to_free (to);
		g_free (time_offset);
		g_free (month);
		g_free (day_of_week);
		g_free (occurrence);
	}

	return recurring_day_transition;
}

static GSList *
ews_get_absolute_date_transitions_list (ESoapParameter *node)
{
	ESoapParameter *param;
	GSList *absolute_date_transitions = NULL;

	for (param = e_soap_parameter_get_first_child_by_name (node, "AbsoluteDateTransition");
	     param != NULL;
	     param = e_soap_parameter_get_next_child_by_name (param, "AbsoluteDateTransition")) {
		EEwsCalendarAbsoluteDateTransition *absolute_date_transition;

		absolute_date_transition = ews_get_absolute_date_transition (param);
		if (absolute_date_transition != NULL) {
			absolute_date_transitions =
				g_slist_prepend (absolute_date_transitions, absolute_date_transition);
		} else {
			g_slist_free_full (
				absolute_date_transitions,
				(GDestroyNotify) e_ews_calendar_absolute_date_transition_free);
			return NULL;
		}
	}

	absolute_date_transitions = g_slist_reverse (absolute_date_transitions);
	return absolute_date_transitions;
}

static GSList *
ews_get_recurring_day_transitions_list (ESoapParameter *node)
{
	ESoapParameter *param;
	GSList *recurring_day_transitions = NULL;

	for (param = e_soap_parameter_get_first_child_by_name (node, "RecurringDayTransition");
	     param != NULL;
	     param = e_soap_parameter_get_next_child_by_name (param, "RecurringDayTransition")) {
		EEwsCalendarRecurringDayTransition *recurring_day_transition;

		recurring_day_transition = ews_get_recurring_day_transition (param);
		if (recurring_day_transition != NULL) {
			recurring_day_transitions =
				g_slist_prepend (recurring_day_transitions, recurring_day_transition);
		} else {
			g_slist_free_full (
				recurring_day_transitions,
				(GDestroyNotify) e_ews_calendar_recurring_day_transition_free);
			return NULL;
		}
	}

	recurring_day_transitions = g_slist_reverse (recurring_day_transitions);
	return recurring_day_transitions;
}

static GSList *
ews_get_recurring_date_transitions_list (ESoapParameter *node)
{
	ESoapParameter *param;
	GSList *recurring_date_transitions = NULL;

	for (param = e_soap_parameter_get_first_child_by_name (node, "RecurringDateTransition");
	     param != NULL;
	     param = e_soap_parameter_get_next_child_by_name (param, "RecurringDateTransition")) {
		EEwsCalendarRecurringDateTransition *recurring_date_transition;

		recurring_date_transition = ews_get_recurring_date_transition (param);
		if (recurring_date_transition != NULL) {
			recurring_date_transitions =
				g_slist_prepend (recurring_date_transitions, recurring_date_transition);
		} else {
			g_slist_free_full (
				recurring_date_transitions,
				(GDestroyNotify) e_ews_calendar_recurring_date_transition_free);
			return NULL;
		}
	}

	recurring_date_transitions = g_slist_reverse (recurring_date_transitions);
	return recurring_date_transitions;
}

static EEwsCalendarTransitionsGroup *
ews_get_transitions_group (ESoapParameter *node)
{
	EEwsCalendarTransitionsGroup *tg = NULL;
	EEwsCalendarTo *transition = NULL;
	ESoapParameter *param = NULL;
	gchar *id = NULL;
	GSList *absolute_date_transitions = NULL;
	GSList *recurring_date_transitions = NULL;
	GSList *recurring_day_transitions = NULL;

	id = e_soap_parameter_get_property (node, "Id");
	if (id == NULL)
		return NULL;

	param = e_soap_parameter_get_first_child_by_name (node, "Transition");
	if (param != NULL)
		transition = ews_get_to (param);

	absolute_date_transitions = ews_get_absolute_date_transitions_list (node);
	recurring_date_transitions = ews_get_recurring_date_transitions_list (node);
	recurring_day_transitions = ews_get_recurring_day_transitions_list (node);

	tg = e_ews_calendar_transitions_group_new ();
	tg->id = id;
	tg->transition = transition;
	tg->absolute_date_transitions = absolute_date_transitions;
	tg->recurring_date_transitions = recurring_date_transitions;
	tg->recurring_day_transitions = recurring_day_transitions;

	return tg;
}

static GSList *
ews_get_transitions_groups_list (ESoapParameter *node)
{
	ESoapParameter *param;
	GSList *transitions_groups = NULL;

	for (param = e_soap_parameter_get_first_child_by_name (node, "TransitionsGroup");
	     param != NULL;
	     param = e_soap_parameter_get_next_child_by_name (param, "TransitionsGroup")) {
		EEwsCalendarTransitionsGroup *tg;

		tg = ews_get_transitions_group (param);
		if (tg != NULL) {
			transitions_groups = g_slist_prepend (transitions_groups, tg);
		} else {
			g_slist_free_full (transitions_groups, (GDestroyNotify) e_ews_calendar_transitions_group_free);
			return NULL;
		}
	}

	transitions_groups = g_slist_reverse (transitions_groups);
	return transitions_groups;
}

static EEwsCalendarTransitions *
ews_get_transitions (ESoapParameter *node)
{
	ESoapParameter *param;
	EEwsCalendarTransitions *transitions = NULL;
	EEwsCalendarTo *transition = NULL;
	GSList *absolute_date_transitions = NULL;
	GSList *recurring_date_transitions = NULL;
	GSList *recurring_day_transitions = NULL;

	param = e_soap_parameter_get_first_child_by_name (node, "Transition");
	if (param != NULL)
		transition = ews_get_to (param);

	if (transition == NULL)
		return NULL;

	absolute_date_transitions = ews_get_absolute_date_transitions_list (node);
	recurring_day_transitions = ews_get_recurring_day_transitions_list (node);
	recurring_date_transitions = ews_get_recurring_date_transitions_list (node);

	transitions = e_ews_calendar_transitions_new ();
	transitions->transition = transition;
	transitions->absolute_date_transitions = absolute_date_transitions;
	transitions->recurring_day_transitions = recurring_day_transitions;
	transitions->recurring_date_transitions = recurring_date_transitions;

	return transitions;
}

static EEwsCalendarTimeZoneDefinition *
ews_get_time_zone_definition (ESoapParameter *node)
{
	ESoapParameter *param;
	gchar *name = NULL;
	gchar *id = NULL;
	GSList *periods = NULL;
	GSList *transitions_groups = NULL;
	EEwsCalendarTransitions *transitions = NULL;
	EEwsCalendarTimeZoneDefinition *tzd = NULL;
	gboolean success = FALSE;

	name = e_soap_parameter_get_property (node, "Name");
	if (name == NULL)
		goto exit;

	id = e_soap_parameter_get_property (node, "Id");
	if (id == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "Periods");
	if (param != NULL)
		periods = ews_get_periods_list (param);
	if (periods == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "TransitionsGroups");
	if (param != NULL)
		transitions_groups = ews_get_transitions_groups_list (param);
	if (transitions_groups == NULL)
		goto exit;

	param = e_soap_parameter_get_first_child_by_name (node, "Transitions");
	if (param != NULL)
		transitions = ews_get_transitions (param);
	if (transitions == NULL)
		goto exit;

	success = TRUE;

exit:
	if (success) {
		tzd = e_ews_calendar_time_zone_definition_new ();
		tzd->name = name;
		tzd->id = id;
		tzd->periods = periods;
		tzd->transitions_groups = transitions_groups;
		tzd->transitions = transitions;
	} else {
		g_free (name);
		g_free (id);
		g_slist_free_full (periods, (GDestroyNotify) e_ews_calendar_period_free);
		g_slist_free_full (transitions_groups, (GDestroyNotify) e_ews_calendar_transitions_group_free);
		e_ews_calendar_transitions_free (transitions);
	}

	return tzd;
}

static void
get_server_time_zones_response_cb (ESoapResponse *response,
				   GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	ESoapParameter *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "ResponseMessages", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child (param);

	while (subparam != NULL) {
		const gchar *name = (const gchar *) subparam->name;

		if (!ews_get_response_status (subparam, &error)) {
			g_simple_async_result_take_error (simple, error);
			return;
		}

		if (E_EWS_CONNECTION_UTILS_CHECK_ELEMENT (name, "GetServerTimeZonesResponseMessage")) {
			ESoapParameter *node, *node2;

			node = e_soap_parameter_get_first_child_by_name (subparam, "TimeZoneDefinitions");
			if (node != NULL) {
				node2 = e_soap_parameter_get_first_child_by_name (node, "TimeZoneDefinition");
				if (node2 != NULL) {
					EEwsCalendarTimeZoneDefinition *tzd;

					tzd = ews_get_time_zone_definition (node2);
					if (tzd != NULL)
						async_data->tzds = g_slist_prepend (async_data->tzds, tzd);
				}
			}
		}

		subparam = e_soap_parameter_get_next_child (subparam);
	}

	async_data->tzds = g_slist_reverse (async_data->tzds);
}

void
e_ews_connection_get_server_time_zones (EEwsConnection *cnc,
					gint pri,
					GSList *msdn_locations,
					GCancellable *cancellable,
					GAsyncReadyCallback callback,
					gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	GSList *l;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (cnc->priv != NULL);

	simple = g_simple_async_result_new (
		G_OBJECT (cnc), callback, user_data, e_ews_connection_get_server_time_zones);
	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (simple, async_data, (GDestroyNotify) async_data_free);

	/*
	 * EWS server version earlier than 2010 doesn't have support to "GetServerTimeZones".
	 * So, if the API is called with an older Exchange's version, let's just fail silently.
	 */
	if (!e_ews_connection_satisfies_server_version (cnc, E_EWS_EXCHANGE_2010_SP1)) {
		g_simple_async_result_complete_in_idle (simple);
		g_object_unref (simple);
		return;
	}

	msg = e_ews_message_new_with_header (
		cnc->priv->settings,
		cnc->priv->uri,
		cnc->priv->impersonate_user,
		"GetServerTimeZones",
		"ReturnFullTimeZoneData",
		"true",
		cnc->priv->version,
		E_EWS_EXCHANGE_2010,
		FALSE,
		TRUE);

	e_soap_message_start_element (msg, "Ids", "messages", NULL);
	for (l = msdn_locations; l != NULL; l = l->next)
		e_ews_message_write_string_parameter_with_attribute (msg, "Id", NULL, l->data, NULL, NULL);
	e_soap_message_end_element (msg); /* Ids */

	e_ews_message_write_footer (msg); /* Complete the footer and print the request */

	e_ews_connection_queue_request (cnc, msg, get_server_time_zones_response_cb, pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_server_time_zones_finish (EEwsConnection *cnc,
					       GAsyncResult *result,
					       GSList **tzds, /*EEwsCalendarTimeZoneDefinition */
					       GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (result, G_OBJECT (cnc), e_ews_connection_get_server_time_zones),
		FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (async_data->tzds == NULL)
		return FALSE;

	if (tzds != NULL)
		*tzds = async_data->tzds;
	else
		g_slist_free_full (
			async_data->tzds,
			(GDestroyNotify) e_ews_calendar_time_zone_definition_free);

	return TRUE;
}

gboolean
e_ews_connection_get_server_time_zones_sync (EEwsConnection *cnc,
					     gint pri,
					     GSList *msdn_locations,
					     GSList **tzds, /* EEwsCalendarTimeZoneDefinition */
					     GCancellable *cancellable,
					     GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_server_time_zones (
		cnc, pri, msdn_locations, cancellable, e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_server_time_zones_finish (cnc, result, tzds, error);

	e_async_closure_free (closure);

	return success;
}

static void
get_user_photo_response_cb (ESoapResponse *response,
			    GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (
		response, "PictureData", &error);

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	async_data->custom_data = e_soap_parameter_get_string_value (param);
	if (async_data->custom_data && !*async_data->custom_data) {
		g_free (async_data->custom_data);
		async_data->custom_data = NULL;
	}
}

void
e_ews_connection_get_user_photo (EEwsConnection *cnc,
				 gint pri,
				 const gchar *email,
				 EEwsSizeRequested size_requested,
				 GCancellable *cancellable,
				 GAsyncReadyCallback callback,
				 gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	gchar *tmp;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (cnc->priv != NULL);
	g_return_if_fail (email != NULL);

	simple = g_simple_async_result_new (G_OBJECT (cnc), callback, user_data, e_ews_connection_get_user_photo);
	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (simple, async_data, (GDestroyNotify) async_data_free);

	/*
	 * EWS server version earlier than 2013 doesn't have support to "GetUserPhoto".
	 */
	if (!e_ews_connection_satisfies_server_version (cnc, E_EWS_EXCHANGE_2013)) {
		g_simple_async_result_complete_in_idle (simple);
		g_object_unref (simple);
		return;
	}

	msg = e_ews_message_new_with_header (
		cnc->priv->settings,
		cnc->priv->uri,
		cnc->priv->impersonate_user,
		"GetUserPhoto",
		NULL,
		NULL,
		cnc->priv->version,
		E_EWS_EXCHANGE_2013,
		FALSE,
		TRUE);

	e_soap_message_start_element (msg, "Email", "messages", NULL);
	e_soap_message_write_string (msg, email);
	e_soap_message_end_element (msg); /* Email */

	e_soap_message_start_element (msg, "SizeRequested", "messages", NULL);
	tmp = g_strdup_printf ("HR%dx%d", (gint) size_requested, size_requested);
	e_soap_message_write_string (msg, tmp);
	g_free (tmp);
	e_soap_message_end_element (msg); /* SizeRequested */

	e_ews_message_write_footer (msg); /* Complete the footer and print the request */

	e_ews_connection_queue_request (cnc, msg, get_user_photo_response_cb, pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_user_photo_finish (EEwsConnection *cnc,
					GAsyncResult *result,
					gchar **out_picture_data, /* base64-encoded */
					GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (result, G_OBJECT (cnc), e_ews_connection_get_user_photo),
		FALSE);
	g_return_val_if_fail (out_picture_data != NULL, FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (!async_data->custom_data)
		return FALSE;

	*out_picture_data = async_data->custom_data;
	async_data->custom_data = NULL;

	return TRUE;
}

gboolean
e_ews_connection_get_user_photo_sync (EEwsConnection *cnc,
				      gint pri,
				      const gchar *email,
				      EEwsSizeRequested size_requested,
				      gchar **out_picture_data, /* base64-encoded */
				      GCancellable *cancellable,
				      GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_user_photo (
		cnc, pri, email, size_requested, cancellable, e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_user_photo_finish (cnc, result, out_picture_data, error);

	e_async_closure_free (closure);

	return success;
}

static void
get_user_configuration_response_cb (ESoapResponse *response,
				    GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param, *subparam;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (response, "ResponseMessages", &error);

	if (param) {
		param = e_soap_parameter_get_first_child_by_name (param, "GetUserConfigurationResponseMessage");
		if (!param) {
			g_set_error (&error,
				SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED,
				"Missing <%s> in SOAP response", "GetUserConfigurationResponseMessage");
		}
	}

	if (param) {
		param = e_soap_parameter_get_first_child_by_name (param, "UserConfiguration");
		if (!param) {
			g_set_error (&error,
				SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED,
				"Missing <%s> in SOAP response", "UserConfiguration");
		}
	}

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	subparam = e_soap_parameter_get_first_child_by_name (param, "ItemId");
	if (subparam) {
		gchar *id, *changekey;

		id = e_soap_parameter_get_property (subparam, "Id");
		changekey = e_soap_parameter_get_property (subparam, "ChangeKey");

		/* Encoded as: Id + "\n" + ChangeKey */
		async_data->custom_data = g_strconcat (id ? id : "", "\n", changekey, NULL);

		g_free (changekey);
		g_free (id);
	}

	if (!subparam) {
		subparam = e_soap_parameter_get_first_child_by_name (param, "Dictionary");
		if (subparam)
			async_data->custom_data = e_soap_response_dump_parameter (response, subparam);
	}

	if (!subparam) {
		subparam = e_soap_parameter_get_first_child_by_name (param, "XmlData");
		if (subparam) {
			async_data->custom_data = e_soap_parameter_get_string_value (subparam);
		}
	}

	if (!subparam) {
		subparam = e_soap_parameter_get_first_child_by_name (param, "BinaryData");
		if (subparam) {
			async_data->custom_data = e_soap_parameter_get_string_value (subparam);
		}
	}

	if (async_data->custom_data && !*async_data->custom_data) {
		g_free (async_data->custom_data);
		async_data->custom_data = NULL;
	}
}

void
e_ews_connection_get_user_configuration (EEwsConnection *cnc,
					 gint pri,
					 const EwsFolderId *fid,
					 const gchar *config_name,
					 EEwsUserConfigurationProperties props,
					 GCancellable *cancellable,
					 GAsyncReadyCallback callback,
					 gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;
	EwsFolderId local_fid;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (cnc->priv != NULL);
	g_return_if_fail (fid != NULL);
	g_return_if_fail (config_name != NULL);

	simple = g_simple_async_result_new (G_OBJECT (cnc), callback, user_data, e_ews_connection_get_user_configuration);
	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (simple, async_data, (GDestroyNotify) async_data_free);

	/* EWS server version earlier than 2010 doesn't support it. */
	if (!e_ews_connection_satisfies_server_version (cnc, E_EWS_EXCHANGE_2010)) {
		g_simple_async_result_complete_in_idle (simple);
		g_object_unref (simple);
		return;
	}

	local_fid = *fid;
	local_fid.change_key = NULL;

	msg = e_ews_message_new_with_header (
		cnc->priv->settings,
		cnc->priv->uri,
		cnc->priv->impersonate_user,
		"GetUserConfiguration",
		NULL,
		NULL,
		cnc->priv->version,
		E_EWS_EXCHANGE_2010,
		FALSE,
		TRUE);

	e_soap_message_start_element (msg, "UserConfigurationName", "messages", NULL);
	e_soap_message_add_attribute (msg, "Name", config_name, NULL, NULL);

	e_ews_folder_id_append_to_msg (msg, cnc->priv->email, &local_fid);

	e_soap_message_end_element (msg); /* UserConfigurationName */

	e_soap_message_start_element (msg, "UserConfigurationProperties", "messages", NULL);

	switch (props) {
	case E_EWS_USER_CONFIGURATION_PROPERTIES_ID:
		e_soap_message_write_string (msg, "Id");
		break;
	case E_EWS_USER_CONFIGURATION_PROPERTIES_DICTIONARY:
		e_soap_message_write_string (msg, "Dictionary");
		break;
	case E_EWS_USER_CONFIGURATION_PROPERTIES_XMLDATA:
		e_soap_message_write_string (msg, "XmlData");
		break;
	case E_EWS_USER_CONFIGURATION_PROPERTIES_BINARYDATA:
		e_soap_message_write_string (msg, "BinaryData");
		break;
	/* case E_EWS_USER_CONFIGURATION_PROPERTIES_ALL:
		e_soap_message_write_string (msg, "All");
		break; */
	default:
		e_soap_message_write_string (msg, "Unknown");
		break;
	}

	e_soap_message_end_element (msg); /* UserConfigurationProperties */

	e_ews_message_write_footer (msg);

	e_ews_connection_queue_request (cnc, msg, get_user_configuration_response_cb, pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_get_user_configuration_finish (EEwsConnection *cnc,
						GAsyncResult *result,
						gchar **out_properties,
						GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (result, G_OBJECT (cnc), e_ews_connection_get_user_configuration),
		FALSE);
	g_return_val_if_fail (out_properties != NULL, FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (!async_data->custom_data)
		return FALSE;

	*out_properties = async_data->custom_data;
	async_data->custom_data = NULL;

	return TRUE;
}

gboolean
e_ews_connection_get_user_configuration_sync (EEwsConnection *cnc,
					      gint pri,
					      const EwsFolderId *fid,
					      const gchar *config_name,
					      EEwsUserConfigurationProperties props,
					      gchar **out_properties,
					      GCancellable *cancellable,
					      GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_get_user_configuration (
		cnc, pri, fid, config_name, props, cancellable, e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_get_user_configuration_finish (cnc, result, out_properties, error);

	e_async_closure_free (closure);

	return success;
}

static void
convert_id_response_cb (ESoapResponse *response,
			GSimpleAsyncResult *simple)
{
	EwsAsyncData *async_data;
	ESoapParameter *param;
	GError *error = NULL;

	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	param = e_soap_response_get_first_parameter_by_name (response, "ResponseMessages", &error);

	if (param) {
		param = e_soap_parameter_get_first_child_by_name (param, "ConvertIdResponseMessage");
		if (!param) {
			g_set_error (&error,
				SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED,
				"Missing <%s> in SOAP response", "ConvertIdResponseMessage");
		}
	}

	if (param) {
		param = e_soap_parameter_get_first_child_by_name (param, "AlternateId");
		if (!param) {
			g_set_error (&error,
				SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED,
				"Missing <%s> in SOAP response", "AlternateId");
		}
	}

	/* Sanity check */
	g_return_if_fail (
		(param != NULL && error == NULL) ||
		(param == NULL && error != NULL));

	if (error != NULL) {
		g_simple_async_result_take_error (simple, error);
		return;
	}

	async_data->custom_data = e_soap_parameter_get_property (param, "Id");
}

void
e_ews_connection_convert_id (EEwsConnection *cnc,
			     gint pri,
			     const gchar *email,
			     const gchar *folder_id,
			     const gchar *from_format,
			     const gchar *to_format,
			     GCancellable *cancellable,
			     GAsyncReadyCallback callback,
			     gpointer user_data)
{
	ESoapMessage *msg;
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_if_fail (cnc != NULL);
	g_return_if_fail (cnc->priv != NULL);
	g_return_if_fail (email != NULL);
	g_return_if_fail (folder_id != NULL);
	g_return_if_fail (from_format != NULL);
	g_return_if_fail (to_format != NULL);

	simple = g_simple_async_result_new (G_OBJECT (cnc), callback, user_data, e_ews_connection_convert_id);
	async_data = g_slice_new0 (EwsAsyncData);
	g_simple_async_result_set_op_res_gpointer (simple, async_data, (GDestroyNotify) async_data_free);

	/* EWS server version earlier than 2007 SP1 doesn't support it. */
	if (!e_ews_connection_satisfies_server_version (cnc, E_EWS_EXCHANGE_2007_SP1)) {
		g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR, "%s", _("Requires at least Microsoft Exchange 2007 SP1 server"));
		g_simple_async_result_complete_in_idle (simple);
		g_object_unref (simple);
		return;
	}

	msg = e_ews_message_new_with_header (
		cnc->priv->settings,
		cnc->priv->uri,
		cnc->priv->impersonate_user,
		"ConvertId",
		"DestinationFormat",
		to_format,
		cnc->priv->version,
		E_EWS_EXCHANGE_2007_SP1,
		FALSE,
		TRUE);

	e_soap_message_start_element (msg, "SourceIds", "messages", NULL);
	e_soap_message_start_element (msg, "AlternateId", NULL, NULL);

	e_soap_message_add_attribute (msg, "Id", folder_id, NULL, NULL);
	e_soap_message_add_attribute (msg, "Format", from_format, NULL, NULL);
	e_soap_message_add_attribute (msg, "Mailbox", email, NULL, NULL);

	e_soap_message_end_element (msg); /* AlternateId */
	e_soap_message_end_element (msg); /* SourceIds */

	e_ews_message_write_footer (msg);

	e_ews_connection_queue_request (cnc, msg, convert_id_response_cb, pri, cancellable, simple);

	g_object_unref (simple);
}

gboolean
e_ews_connection_convert_id_finish (EEwsConnection *cnc,
				    GAsyncResult *result,
				    gchar **out_converted_id,
				    GError **error)
{
	GSimpleAsyncResult *simple;
	EwsAsyncData *async_data;

	g_return_val_if_fail (cnc != NULL, FALSE);
	g_return_val_if_fail (
		g_simple_async_result_is_valid (result, G_OBJECT (cnc), e_ews_connection_convert_id),
		FALSE);
	g_return_val_if_fail (out_converted_id != NULL, FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_data = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (!async_data->custom_data)
		return FALSE;

	*out_converted_id = async_data->custom_data;
	async_data->custom_data = NULL;

	return TRUE;
}

gboolean
e_ews_connection_convert_id_sync (EEwsConnection *cnc,
				  gint pri,
				  const gchar *email,
				  const gchar *folder_id,
				  const gchar *from_format,
				  const gchar *to_format,
				  gchar **out_converted_id,
				  GCancellable *cancellable,
				  GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (cnc != NULL, FALSE);

	closure = e_async_closure_new ();

	e_ews_connection_convert_id (
		cnc, pri, email, folder_id, from_format, to_format, cancellable, e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_ews_connection_convert_id_finish (cnc, result, out_converted_id, error);

	e_async_closure_free (closure);

	return success;
}
