/* sunone-connection.c
 *
 * Copyright (C) 2002-2004 Sun Microsystems, Inc
 *
 * AUTHORS
 *     Jack Jia <jack.jia@sun.com>
 *     Harry Lu <harry.lu@sun.com>
 *     Alfred Peng <alfred.peng@sun.com>
 *     Jedy Wang <jedy.wang@sun.com>
 *     Rodrigo Moya <rodrigo@ximian.com>
 *
 */

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

#include <string.h>
#include <libsoup/soup.h>
#include <libedataserver/e-time-utils.h>

#include "sunone-util.h"
#include "sunone-connection.h"
#include "sunone-message.h"
#include "calendar/cal-backend-wcap.h"

#define REFRESH_INTERVAL 5 * 60 * 1000
#define IS_CONNECTED(connection,return_value) {if (!connection->priv->wcap_session_id) return return_value;}
#define NEW_URI(uri) {uri = soup_uri_new (connection->priv->server_uri); \
		if (uri && uri->path) {g_free (uri->path); uri->path = NULL;} \
		if (uri && uri->query) {g_free (uri->query); uri->query = NULL;}}
#define IS_CNC_WCAP_2_0(connection) ( !strncmp (connection->priv->wcap_version, "2.0", 3) )

struct _SunOneConnectionPrivate {
	SoupSession *soup_session;

	char *server_uri;
	char *user;
	char *uid;
	char *password;
	char *wcap_session_id;
	char *wcap_version;
	SunOneConnectionPreferences *prefs;
	GHashTable *calendar_properties;
	guint timeout_id;
	gboolean is_open;
	GHashTable *wcaps;
};

static gchar *get_acls_string (GList *acls);
static gboolean reconnect_timeout_cb (gpointer user_data);

static void sunone_connection_class_init (SunOneConnectionClass *klass);
static void sunone_connection_init (SunOneConnection *connection);
static void sunone_connection_dispose (GObject *object);
static void sunone_connection_finalize (GObject *object);
static guint sunone_connection_storeevents_by_mail (SunOneConnection *connection,
															const char *calid, icalcomponent *icalcomp,
															SunOneMethod method, SunOneModType mod,
															gboolean expand, gboolean allday,
															icalcomponent **return_icalcomp, gboolean mail,
															const char *account_email);

static GObjectClass *parent_class = NULL;

static char *
query_encode (const char *part, gboolean escape_unsafe,
	      const char *escape_extra)
{
	char *work, *p;

	/* worst case scenario = 3 times the initial */
	p = work = g_malloc (3 * strlen (part) + 1);

	while (*part) {
		if (((guchar) *part >= 127) || ((guchar) *part <= ' ') ||
		    (escape_unsafe && strchr ("\"%#<>{}|\\^~[]`", *part)) ||
		    (escape_extra && strchr (escape_extra, *part))) {
			sprintf (p, "%%%.02hX", (guchar) *part++);
			p += 3;
		} else
			*p++ = *part++;
	}
	*p = '\0';
	
	return work;
}

static void
append_to_query (SoupURI *uri, const char *param, const char *value)
{
	char *tmp, *enc;
	
	if (!value)
		return;

	enc = query_encode (value, FALSE, " &:+");
	tmp = g_strdup_printf ("%s&%s=%s", uri->query, param, enc);
	g_free (enc);

	g_free (uri->query);
	uri->query = tmp;
}

static void
append_to_query_int (SoupURI *uri, const char *param, int value)
{
	char *tmp;
	
	tmp = g_strdup_printf ("%s&%s=%d", uri->query, param, value);
	g_free (uri->query);
	uri->query = tmp;
}

static char *
get_stringv (const char **stringv)
{
	const char **s;
	char *string;

	string = NULL;
	for (s = stringv; *s != NULL; s++) {
		char *tmp = string;

		if (string) {
			string = g_strconcat (string, ";", *s, NULL);
			g_free (tmp);
		} else {
			string = g_strdup (*s);
		}
	}

	return string;
}


static void
append_to_query_stringv (SoupURI *uri, const char *param, const char **stringv)
{
	char *string;
	
	string = get_stringv (stringv);
	if (string)
		append_to_query (uri, param, string);
	else 
		append_to_query (uri, param, "");
				
	if (string)
		g_free (string);
}

static void
append_to_query_text (SoupURI *uri, const char *param, ECalComponentText *text)
{
	if (text->value)
		append_to_query (uri, param, text->value);
	else
		append_to_query (uri, param, "");
}

static void
append_to_query_text_list (SoupURI *uri, const char *param, GSList *text_list)
{
	GSList *l;
	char *string;
	
	string = NULL;
	for (l = text_list; l != NULL; l = l->next) {
		ECalComponentText *text = l->data;
		char *tmp = string;

		if (string) {
			string = g_strconcat (string, ";", text->value, NULL);
			g_free (tmp);
		} else {
			string = g_strdup (text->value);
		}
	}	
	append_to_query (uri, param, string ? string : "");
	e_cal_component_free_text_list (text_list);

	if (string)
		g_free (string);
}

static void
append_to_query_datetime (SoupURI *uri, const char *param, ECalComponentDateTime *dt, gboolean zero)
{
	char *string;

	if (!dt->value || !icaltime_is_valid_time (*dt->value)) {
		if (zero) 
			append_to_query (uri, param, "0");
		return;
	}
	
	string = icaltime_as_ical_string (*dt->value);
	append_to_query (uri, param, string);	
	g_free (string);
	e_cal_component_free_datetime (dt);
}

static void
append_to_query_attendees (SoupURI *uri, const char *param, GSList *attendee_list, SunOneConnection *connection, const char *calid, const char *email)
{
	GSList *l;
	GString *string;

	string = g_string_new (NULL);
	for (l = attendee_list; l != NULL; l = l->next) {
		ECalComponentAttendee *attendee = l->data;

		/* PARTSTAT */
		switch (attendee->status) {
		case ICAL_PARTSTAT_NEEDSACTION:
			g_string_append (string, "PARTSTAT=NEEDS-ACTION");
			break;
		case ICAL_PARTSTAT_ACCEPTED:
			g_string_append (string, "PARTSTAT=ACCEPTED");
			break;
		case ICAL_PARTSTAT_DECLINED:
			g_string_append (string, "PARTSTAT=DECLINED");
			break;
		case ICAL_PARTSTAT_TENTATIVE:
			g_string_append (string, "PARTSTAT=TENTATIVE");
			break;
		case ICAL_PARTSTAT_DELEGATED:
			g_string_append (string, "PARTSTAT=DELEGATED");
			break;
		case ICAL_PARTSTAT_COMPLETED:
			g_string_append (string, "PARTSTAT=COMPLETED");
			break;
		case ICAL_PARTSTAT_INPROCESS:
			g_string_append (string, "PARTSTAT=INPROCESS");
			break;
		default:
			g_string_append (string, "PARTSTAT=NEEDS-ACTION");
		}
		
		/* CUTYPE */
		switch (attendee->cutype) {
		case ICAL_CUTYPE_INDIVIDUAL:
			g_string_append (string, "^CUTYPE=INDIVIDUAL");
			break;
		case ICAL_CUTYPE_GROUP:
			g_string_append (string, "^CUTYPE=GROUP");
			break;
		case ICAL_CUTYPE_RESOURCE:
			g_string_append (string, "^CUTYPE=RESOURCE");
			break;
		case ICAL_CUTYPE_ROOM:
			g_string_append (string, "^CUTYPE=ROOM");
			break;
		default:
			g_string_append (string, "^CUTYPE=INDIVIDUAL");
		}

		/* MEMBER */
		if (attendee->member)
			g_string_append_printf (string, "^MEMBER=%s", attendee->member);

		/* ROLE */
		switch (attendee->role) {
		case ICAL_ROLE_CHAIR:
			g_string_append (string, "^ROLE=CHAIR");
			break;
		case ICAL_ROLE_REQPARTICIPANT:
			g_string_append (string, "^ROLE=REQ-PARTICIPANT");
			break;
		case ICAL_ROLE_OPTPARTICIPANT:
			g_string_append (string, "^ROLE=OPT-PARTICIPANT");
			break;
		case ICAL_ROLE_NONPARTICIPANT:
			g_string_append (string, "^ROLE=NON-PARTICIPANT");
			break;
		default:
			g_string_append (string, "^ROLE=REQ-PARTICIPANT");
		}

		/* RSVP */
		g_string_append_printf (string, "^RSVP=%s", attendee->rsvp ? "TRUE" : "FALSE");

		/* DELEGATED-TO */
		if (attendee->delto)
			g_string_append_printf (string, "^DELEGATED-TO=%s", attendee->delto);

		/* DELEGATED-FROM */
		if (attendee->delfrom)
			g_string_append_printf (string, "^DELEGATED-FROM=%s", attendee->delfrom);

		/* SENT-BY */
		if (attendee->sentby)
			g_string_append_printf (string, "^SENT-BY=%s", attendee->sentby);

		/* CN */
		if (attendee->cn)
			g_string_append_printf (string, "^CN=%s", attendee->cn);
		
		/* LANG */
		if (attendee->language)
			g_string_append_printf (string, "^LANGUAGE=%s", attendee->language);

		/* Email Address */		
		if (!strncasecmp (attendee->value, "mailto:", 7)) {
			if (strchr (attendee->value, '@')) {
				/* mailto a real email address,
				 * if account is in the attendees,
				 * change its email to calid
				 */
				if(!strcasecmp (email, attendee->value + 7)) {
					g_string_append_printf (string, "^%s;", calid);
				} else
					g_string_append_printf (string, "^%s;", attendee->value);
			} else
				/* mailto, but a calid, strip it */
				g_string_append_printf (string, "^%s;", attendee->value + 7);

		} else
			g_string_append_printf (string, "^%s;", attendee->value);			

	}
	if (string->len > 0) {
		char *enc;
		g_string_truncate (string, string->len - 1);
		enc = query_encode (string->str, FALSE, " ");
		append_to_query (uri, param, enc);
		g_free (enc);
	}	
	e_cal_component_free_attendee_list (attendee_list);
	g_string_free (string, TRUE);
}

static void
append_to_query_exdate_list (SoupURI *uri, const char *param, GSList *dt_list)
{
	GSList *l;
	char *string;

	string = NULL;
	for (l = dt_list; l != NULL; l = l->next) {
		ECalComponentDateTime *dt = l->data;
		char *tmp = string;
		char *tstr;
		
		if (!dt->value)
			continue;
		tstr = icaltime_as_ical_string (*dt->value);
		
		if (string) {
			string = g_strconcat (string, ";", tstr, NULL);
			g_free (tmp);
		} else {
			string = g_strdup (tstr);
		}
		g_free (tstr);
	}
	if (string)
		append_to_query (uri, param, string);
	e_cal_component_free_exdate_list (dt_list);
	g_free (string);
}

static void
append_to_query_recur_list (SoupURI *uri, const char *param, GSList *r_list)
{
	GSList *l;
	char *string;

	string = NULL;
	for (l = r_list; l != NULL; l = l->next) {
		struct icalrecurrencetype *recur = l->data;
		char *tmp = string, *enc;

		enc = query_encode (icalrecurrencetype_as_string (recur), FALSE, ";=");
		if (string) {
			string = g_strconcat (string, ";\"", enc, "\"", NULL);
			g_free (tmp);
		} else {
			string = g_strdup_printf ("\"%s\"", enc);
		}
		g_free (enc);
	}
	if (string)
		append_to_query (uri, param, string);
	e_cal_component_free_recur_list (r_list);
	g_free (string);
}

static void
append_to_query_classification (SoupURI *uri, const char *param, ECalComponentClassification class)
{
	switch (class) {
	case E_CAL_COMPONENT_CLASS_PUBLIC:
		append_to_query (uri, param, "PUBLIC");
		break;
	case E_CAL_COMPONENT_CLASS_PRIVATE:
		append_to_query (uri, param, "PRIVATE");
		break;
	case E_CAL_COMPONENT_CLASS_CONFIDENTIAL:
		append_to_query (uri, param, "CONFIDENTIAL");
		break;
	default:
		break;
	}
}


static void
append_to_query_status (SoupURI *uri, const char *param, icalproperty_status status)
{
	switch (status) {
	case ICAL_STATUS_CONFIRMED:
		append_to_query_int (uri, param, 0);
		break;
	case ICAL_STATUS_CANCELLED:
		append_to_query_int (uri, param, 1);
		break;
	case ICAL_STATUS_TENTATIVE:
		append_to_query_int (uri, param, 2);
		break;
	case ICAL_STATUS_NEEDSACTION:
		append_to_query_int (uri, param, 3);
		break;
	case ICAL_STATUS_COMPLETED:
		append_to_query_int (uri, param, 4);
		break;
	case ICAL_STATUS_INPROCESS:
		append_to_query_int (uri, param, 5);
		break;
	case ICAL_STATUS_DRAFT:
		append_to_query_int (uri, param, 6);
		break;
	case ICAL_STATUS_FINAL:
		append_to_query_int (uri, param, 7);
		break;
	default:
		break;
	}
}

static void
append_to_query_compstate (SoupURI *uri, const char *param, SunOneCompState state)
{
	const char *statev[7];
	int i;

	i = 0;
	if (state == REQUEST_ALL
	    || state & (REPLY_DECLINED & REPLY_ACCEPTED & REQUEST_COMPLETED & REQUEST_NEEDS_ACTION 
			& REQUEST_NEEDS_NOACTION & REQUEST_PENDING & REQUEST_WAITFORREPLY)) {
		statev[i++] = "ALL";
		goto append;
	}
	
	if (state & REPLY_DECLINED)
		statev[i++] = "REPLY-DECLINED";
	if (state & REPLY_ACCEPTED)
		statev[i++] = "REPLY-ACCEPTED";
	if (state & REQUEST_COMPLETED)
		statev[i++] = "REQUEST-COMPLETED";
	if (state & REQUEST_NEEDS_ACTION)
		statev[i++] = "REQUEST-NEEDS-ACTION";
	if (state & REQUEST_NEEDS_NOACTION)
		statev[i++] = "REQUEST-NEEDSNOACTION";
	if (state & REQUEST_PENDING)
		statev[i++] = "REQUEST-PENDING";
	if (state & REQUEST_WAITFORREPLY)
		statev[i++] = "REQUEST-WAITFORREPLY";

 append:
	statev[i] = NULL;
	if (i > 0)
		append_to_query_stringv (uri, param, statev);
}

static void
append_to_query_geo (SoupURI *uri, const char *param, struct icalgeotype *geo)
{
	char *tmp;
	
	if (!geo)
		return;
	
	tmp = g_strdup_printf ("%s&%s=%f;%f", uri->query, param, geo->lat, geo->lon);
	g_free (uri->query);
	uri->query = tmp;
}

static void
append_to_query_alarms (SoupURI *uri, SunOneConnection *connection, ECalComponent *comp)
{
	GList *auids, *l;
	ECalComponentAlarm *alarm;
	ECalComponentAlarmAction action;
	ECalComponentAlarmTrigger trigger;
	char *atype;
	char *dtstring;

	if (!e_cal_component_has_alarms (comp)) {
		
		append_to_query (uri, "alarmAudio", "");
		append_to_query (uri, "alarmPopup", "");
		append_to_query (uri, "alarmStart", "");
		append_to_query (uri, "alarmEmails", "");
		append_to_query (uri, "alarmDescription", "");

		return;
	}
		
	auids = e_cal_component_get_alarm_uids (comp);
	for (l = auids; l != NULL; l = l->next) {
		const char *auid = l->data;
		
		alarm = e_cal_component_get_alarm (comp, auid);

		e_cal_component_alarm_get_action (alarm, &action);
		switch (action) {
		case E_CAL_COMPONENT_ALARM_AUDIO:
			atype = "alarmAudio";
			break;
		case E_CAL_COMPONENT_ALARM_DISPLAY:
			atype = "alarmPopup";
			break;
		case E_CAL_COMPONENT_ALARM_EMAIL:
			atype = "alarmStart";
			break;
		case E_CAL_COMPONENT_ALARM_PROCEDURE:
		default:
			atype = NULL;
		}

		e_cal_component_alarm_get_trigger (alarm, &trigger);
		if (trigger.type == E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE)
			dtstring = icaltime_as_ical_string (trigger.u.abs_time);
		else
			dtstring =
				g_strdup (icaldurationtype_as_ical_string (trigger.u.rel_duration));
		
		if (atype != NULL && dtstring != NULL) {
			ECalComponentText text;
			
			append_to_query (uri, atype, dtstring);
			
			e_cal_component_alarm_get_description (alarm, &text);
			if (text.value)
				append_to_query (uri, "alarmDescription", text.value);

			if (action == E_CAL_COMPONENT_ALARM_EMAIL) {
				if (e_cal_component_alarm_has_attendees (alarm)) {
					GSList *attendee_list, *l;
					GString *string;
				
					e_cal_component_alarm_get_attendee_list (alarm, &attendee_list);
				
					string = g_string_new (NULL);
					for (l = attendee_list; l != NULL; l = l->next) {
						ECalComponentAttendee *attendee = l->data;

						/* Email Address */
						if (!attendee->value)
							continue;

						if (!strncasecmp (attendee->value, "mailto:", 7))
							g_string_append_printf (string, "%s;", attendee->value + 7);
						else
							g_string_append_printf (string, "%s;", attendee->value);
					}
					if (string->len > 0) {
						g_string_truncate (string, string->len - 1);
						append_to_query (uri, "alarmEmails", string->str);
					}	
					e_cal_component_free_attendee_list (attendee_list);
					g_string_free (string, TRUE);
				}
			}
		}
		g_free (dtstring);
		
		e_cal_component_alarm_free (alarm);
	}
	cal_obj_uid_list_free (auids);
}

static char *
type_to_string (SunOneCompType type) 
{
	switch (type) {
	case TYPE_TODO:
		return "todo";
	case TYPE_EVENT:
		return "event";
	case TYPE_ALL:
		return "all";
		
	}

	return "all";
}

G_DEFINE_TYPE (SunOneConnection, sunone_connection, G_TYPE_OBJECT)

static void
sunone_connection_class_init (SunOneConnectionClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	parent_class = g_type_class_ref (G_TYPE_OBJECT);

	object_class->dispose  = sunone_connection_dispose;
	object_class->finalize = sunone_connection_finalize;
}

static void
sunone_connection_init (SunOneConnection *object)
{
	SunOneConnection *connection = SUNONE_CONNECTION (object);
	SunOneConnectionPrivate *priv;

	connection->priv = g_new0 (SunOneConnectionPrivate, 1);
	priv = connection->priv;

	priv->soup_session = soup_session_sync_new ();
	priv->calendar_properties = g_hash_table_new (g_str_hash, g_str_equal);
	priv->timeout_id = -1;
	priv->is_open = FALSE;
	priv->wcaps = g_hash_table_new (g_str_hash, g_str_equal);
}


static gboolean
parse_session_object (SunOneConnection *connection, SoupMessage *msg)
{
	SunOneConnectionPrivate *priv = connection->priv;
	icalcomponent *icalcomp;
	icalcomponent_kind kind;
	icalproperty *icalprop;

	g_return_val_if_fail (msg != NULL, FALSE);

	icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
	if (!icalcomp)
		return FALSE;

	kind = icalcomponent_isa (icalcomp);
	if (kind != ICAL_VCALENDAR_COMPONENT) {
		icalcomponent_free (icalcomp);
		return FALSE;
	}

	/* look for the properties */
	icalprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);
	while (icalprop) {
		const char *x_name, *x_val;

		x_name = icalproperty_get_x_name (icalprop);
		x_val = icalproperty_get_x (icalprop);

		if (!strcmp (x_name, "X-NSCP-WCAP-SESSION-ID")) {
			if (priv->wcap_session_id)
				g_free (priv->wcap_session_id);
			priv->wcap_session_id = g_strdup (x_val);
		}

		icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY);
	}

	icalcomponent_free (icalcomp);

	return priv->wcap_session_id != NULL;
}

static guint
sunone_login (SunOneConnection *connection, gboolean refresh)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *login_uri;
	SoupMessage *msg;
	icalcomponent *icalcomp;
	guint error_code = SOUP_STATUS_CANT_CONNECT;
	char *encode_password = NULL;
	
	/* build the URI to connect to the server */
	NEW_URI (login_uri);
	if (!login_uri) {
		g_message ("'%s' is an invalid URI", priv->server_uri);
		return SOUP_STATUS_BAD_REQUEST;
	}

	/* escape some chars in password */
	if (priv->password)
		encode_password = query_encode (priv->password, FALSE, "=+&");

	login_uri->path = g_strdup ("/login.wcap");
	login_uri->query = g_strdup_printf ("user=%s&password=%s&fmt-out=text/calendar",
						  priv->user ? priv->user : "",
						  encode_password ? encode_password : "");

	if (encode_password)
		g_free (encode_password);

	/* send the connection request to the server */
	msg = sunone_message_new_from_uri (login_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);

	error_code = msg->status_code;
	if (SUNONE_ERROR_IS_SUCCESSFUL (error_code)) {
		if (parse_session_object (connection, msg)) {
			if (priv->timeout_id == -1)
				priv->timeout_id = g_timeout_add (REFRESH_INTERVAL,
									(GSourceFunc) reconnect_timeout_cb,
									connection);
			priv->is_open = TRUE;
		}
	}
	/* parse the returned data */
	icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
	g_object_unref (msg);
	soup_uri_free (login_uri);
	if (icalcomp) {
		icalproperty *icalprop =
			icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);

		while (icalprop) {
			const char *x_name, *x_val;

			x_name = icalproperty_get_x_name (icalprop);
			x_val = icalproperty_get_x (icalprop);
			/*
			 * if user id is different from user name,
			 * we have to remember user id and compare
			 * it with primary owner of the folder to decide
			 * if user is the owner of the folder.
			 */
			if (!strcmp (x_name, "X-NSCP-WCAP-USER-ID") &&
				priv->user && x_val && strcmp (priv->user, x_val)) {
				priv->uid = g_strdup (x_val);
				fprintf (stderr, "uid:%s\n", x_val);
			}
			icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY);
		}	
		icalcomponent_free (icalcomp);
	}

	return error_code;
}

static guint
sunone_logout (SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *logout_uri;
	SoupMessage *msg;
	guint error_code;

	if (priv->timeout_id != -1) {
		g_source_remove (priv->timeout_id);
		priv->timeout_id = -1;
	}

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);
	
	/* build the URI to connect to the server */
	NEW_URI (logout_uri);
	if (!logout_uri) {
		g_message ("'%s' is an invalid URI", priv->server_uri);
		return FALSE;
	}

	logout_uri->path = g_strdup ("/logout.wcap");
	logout_uri->query = g_strdup_printf ("id=%s&fmt-out=text/calendar", priv->wcap_session_id);

	/* send the connection request to the server */
	msg = sunone_message_new_from_uri (logout_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);

	error_code = msg->status_code;
	if (SUNONE_ERROR_IS_SUCCESSFUL (error_code)) {
		if (priv->wcap_session_id)
			g_free (priv->wcap_session_id);
		priv->wcap_session_id = NULL;
		priv->is_open = FALSE;
	}
	
	g_object_unref (msg);
	soup_uri_free (logout_uri);

	return error_code;
}

static gboolean
reconnect_timeout_cb (gpointer user_data)
{
	SunOneConnection *connection = (SunOneConnection *) user_data;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), FALSE);

	sunone_login (connection, TRUE);

	return TRUE;
}

static void
free_calprops (gpointer key, gpointer value, gpointer user_data)
{
	g_free (key);
	sunone_connection_free_calprops (value);
}

static void
free_wcaps (gpointer key, gpointer value, gpointer user_data)
{
	g_free (key);
}

static void
sunone_connection_dispose (GObject *object)
{
	SunOneConnection *connection = (SunOneConnection *) object;
	SunOneConnectionPrivate *priv = connection->priv;

	g_return_if_fail (IS_SUNONE_CONNECTION (connection));

	sunone_logout (connection);

	/* free memory */
	if (priv) {
		if (priv->server_uri) {
			g_free (priv->server_uri);
			priv->server_uri = NULL;
		}
		if (priv->user) {
			g_free (priv->user);
			priv->user = NULL;
		}
		if (priv->uid) {
			g_free (priv->uid);
			priv->uid = NULL;
		}
		if (priv->password) {
			g_free (priv->password);
			priv->password = NULL;
		}
		if (priv->wcap_session_id) {
			g_free (priv->wcap_session_id);
			priv->wcap_session_id = NULL;
		}
		if (priv->wcap_version) {
			g_free (priv->wcap_version);
			priv->wcap_version = NULL;
		}
		if (priv->prefs) {
			sunone_connection_free_preferences (priv->prefs);
			priv->prefs = NULL;
		}
		if (priv->calendar_properties) {
			g_hash_table_foreach (priv->calendar_properties, (GHFunc) free_calprops, NULL);
			g_hash_table_destroy (priv->calendar_properties);
			priv->calendar_properties = NULL;
		}
		if (priv->wcaps) {
			g_hash_table_foreach (priv->wcaps, (GHFunc)free_wcaps, NULL);
			g_hash_table_destroy (priv->wcaps);
			priv->wcaps = NULL;
		}

		g_free (priv);
		connection->priv = NULL;
	}

	if (G_OBJECT_CLASS (parent_class)->dispose)
		(* G_OBJECT_CLASS (parent_class)->dispose) (object);
		
}

static void
sunone_connection_finalize (GObject *object)
{
	if (G_OBJECT_CLASS (parent_class)->finalize)
		(* G_OBJECT_CLASS (parent_class)->finalize) (object);
}

SunOneConnection *
sunone_connection_new (const char *server_uri, const char *proxy_uri,
		       const char *user,
		       const char *password)
{
	SunOneConnection *connection;
	SunOneConnectionPrivate *priv;
	SoupURI *suri;

	connection = g_object_new (SUNONE_CONNECTION_TYPE, NULL);
	priv = connection->priv;
	priv->server_uri = g_strdup (server_uri);
	priv->user = g_strdup (user);
	priv->password = g_strdup (password);
	priv->is_open = FALSE;
	if (proxy_uri && priv->soup_session) {
		suri = soup_uri_new (proxy_uri);
		g_object_set (G_OBJECT (priv->soup_session), SOUP_SESSION_PROXY_URI, suri, NULL);
		soup_uri_free (suri);
	}
	
	return connection;
}

guint
sunone_connection_login (SunOneConnection *connection)
{
	return sunone_login (connection, FALSE);
}

guint 
sunone_connection_logout (SunOneConnection *connection)
{
	return sunone_logout (connection);
}

gboolean
sunone_connection_is_open (SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), FALSE);
	return priv->is_open;
}

static void
copy_subscription_item (gpointer item, gpointer user_data)
{
	SunOneCalendarSubscription *copy, *orig;
	GList **list = (GList **) user_data;

	orig = (SunOneCalendarSubscription *) item;
	if (!orig)
		return;

	copy = g_new0 (SunOneCalendarSubscription, 1);
	copy->calid = g_strdup (orig->calid);
	copy->display_name = g_strdup (orig->display_name);

	*list = g_list_append (*list, copy);
}

static SunOneConnectionPreferences *
clone_prefs (SunOneConnectionPreferences *prefs)
{
	SunOneConnectionPreferences *copy;

	copy = g_new0 (SunOneConnectionPreferences, 1);
	copy->common_name = g_strdup (prefs->common_name);
	copy->given_name = g_strdup (prefs->given_name);
	copy->mail = g_strdup (prefs->mail);
	copy->preferred_language = g_strdup (prefs->preferred_language);
	copy->surname = g_strdup (prefs->surname);
	copy->default_calendar = g_strdup (prefs->default_calendar);
	copy->timezone = g_strdup (prefs->timezone);
	g_list_foreach (prefs->subscriptions, (GFunc) copy_subscription_item, &copy->subscriptions);
	copy->freebusy = g_strdup (prefs->freebusy);
	copy->allow_change_password = prefs->allow_change_password;
	copy->allow_create_calendars = prefs->allow_create_calendars;
	copy->allow_delete_calendars = prefs->allow_delete_calendars;
	copy->allow_public_writable_calendars = prefs->allow_public_writable_calendars;

	return copy;
}

static gboolean
already_in_subscriptions (GList *subscriptions, const char *calid)
{
	GList *l;

	for (l = subscriptions; l != NULL; l = l->next) {
		SunOneCalendarSubscription *sub = l->data;
		if (!strcmp (sub->calid, calid)) 
			return TRUE;
	}

	return FALSE;
}

static void
parse_subscriptions (SunOneConnection *connection, const gchar *val)
{
	SunOneConnectionPrivate *priv = connection->priv;
	gchar **items;
	gchar **parts;
	SunOneCalendarSubscription *sub;
	gint n = 0;

	items = g_strsplit (val, ",", 0);
	if (!items)
		return;

	while (items[n]) {
		parts = g_strsplit (items[n], "$", 0);

		if (parts) {
			if (parts[0] != NULL && !already_in_subscriptions (priv->prefs->subscriptions, parts[0])) {
				sub = g_new0 (SunOneCalendarSubscription, 1);
				sub->calid = g_strdup (parts[0]);
				if (parts[1] != NULL)
					sub->display_name = g_strdup (parts[1]);
				else
					sub->display_name = g_strdup (parts[0]);

				priv->prefs->subscriptions = g_list_append (
					priv->prefs->subscriptions, sub);
			}

			g_strfreev (parts);
		}

		n++;
	}

	g_strfreev (items);
}

#define STRING_TO_BOOLEAN(str) ((str) && !strcmp ((str), "yes") ? TRUE : FALSE)

const char *
sunone_connection_get_wcap_version(SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), NULL);
	return (const char *)priv->wcap_version;
}

void
sunone_connection_set_wcap_version (SunOneConnection *connection, const char *wcap_version)
{
	SunOneConnectionPrivate *priv = connection->priv;

	g_return_if_fail (IS_SUNONE_CONNECTION (connection));
	priv->wcap_version = g_strdup (wcap_version);
}

const char *
sunone_connection_get_user(SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), NULL);
	return (const char *)priv->user;
}

const char *
sunone_connection_get_uid(SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), NULL);
	return (const char *)priv->uid;
}

void
sunone_connection_set_user (SunOneConnection *connection, const char *user)
{
	SunOneConnectionPrivate *priv = connection->priv;

	g_return_if_fail (IS_SUNONE_CONNECTION (connection));

	g_free (priv->user);
	priv->user = g_strdup (user);
}

static void
check_default_calendar (SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;
	GList *l;
	SunOneCalendarSubscription *sub;
	gboolean found = FALSE;

	/* Not great but it is possible to have an empty icsCalendar value */
	if (!priv->prefs->default_calendar)
		priv->prefs->default_calendar = g_strdup (priv->user);

	/* Make sure default_calendar is in subscriptions */
	for (l = priv->prefs->subscriptions; l!= NULL; l = l->next) {
		sub = l->data;
		if (!strcmp (priv->prefs->default_calendar, sub->calid)) {
			found = TRUE;
			break;
		}
	}

	if (!found) {
		sub = g_new0 (SunOneCalendarSubscription, 1);
		sub->calid = g_strdup (priv->prefs->default_calendar);
		sub->display_name = g_strdup (priv->prefs->default_calendar);
		priv->prefs->subscriptions = g_list_append (
					priv->prefs->subscriptions, sub);
	}
}

SunOneConnectionPreferences *
sunone_connection_get_preferences (SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *get_uri;
	SoupMessage *msg;
	guint ret;
	icalcomponent *icalcomp;
	icalproperty *icalprop;
	SunOneConnectionPreferences *clone;
	static GStaticMutex mutex = G_STATIC_MUTEX_INIT;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), NULL);

	IS_CONNECTED (connection, NULL);
	g_static_mutex_lock (&mutex);

	if (priv->prefs) {
		sunone_connection_free_preferences (priv->prefs);
		priv->prefs = NULL;
	}

	/* prepare the URI */
	NEW_URI (get_uri);
	get_uri->path = g_strdup ("/get_userprefs.wcap");
	get_uri->query = g_strdup_printf (
		"id=%s&fmt-out=text/calendar",
		priv->wcap_session_id);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (get_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	ret = msg->status_code;

	soup_uri_free (get_uri);

	if (!SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		g_object_unref (msg);
		g_static_mutex_unlock (&mutex);
		return NULL;
	}

	/* parse the returned data */
	icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
	g_object_unref (msg);
	if (!icalcomp) {
		g_static_mutex_unlock (&mutex);
		return NULL;
	}

	priv->prefs = g_new0 (SunOneConnectionPreferences, 1);

	icalprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);
	while (icalprop) {
		const char *x_name, *x_val;

		x_name = icalproperty_get_x_name (icalprop);
		x_val = icalproperty_get_x (icalprop);

		if (!strcmp (x_name, "X-NSCP-WCAP-PREF-cn"))
			priv->prefs->common_name = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-givenName"))
			priv->prefs->given_name = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-mail"))
			priv->prefs->mail = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-preferredlanguage"))
			priv->prefs->preferred_language = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-sn"))
			priv->prefs->surname = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-icsCalendar"))
			priv->prefs->default_calendar = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-icsTimezone"))
			priv->prefs->timezone = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-icsSubscribed"))
			parse_subscriptions (connection, x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-icsFreeBusy"))
			priv->prefs->freebusy = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-PREF-ceDefaultAlarmEmail"))
			priv->prefs->alarm_mail = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-SERVER-PREF-ceSingleCalendarTZID"))
			priv->prefs->single_calendar_tzid = STRING_TO_BOOLEAN (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-SERVER-PREF-allowchangepassword"))
			priv->prefs->allow_change_password = STRING_TO_BOOLEAN (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-SERVER-PREF-allowcreatecalendars"))
			priv->prefs->allow_create_calendars = STRING_TO_BOOLEAN (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-SERVER-PREF-allowdeletecalendars"))
			priv->prefs->allow_delete_calendars = STRING_TO_BOOLEAN (x_val);
		else if (!strcmp (x_name, "X-NSCP-WCAP-SERVER-PREF-allowpublicwritablecalendars"))
			priv->prefs->allow_public_writable_calendars = STRING_TO_BOOLEAN (x_val);
		
		icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY);
	}

	icalcomponent_free (icalcomp);

	check_default_calendar (connection);
	clone = clone_prefs (priv->prefs);
	g_static_mutex_unlock (&mutex);

	return clone;
}

static gchar *
replace_colons (const gchar *s)
{
        gint i;
        gchar *tmp;
                                                                                        
        tmp = g_strdup (s);
        for (i = 0; i < strlen (tmp); i++) {
                if (tmp[i] == ':')
                        tmp[i] = '^';
        }
                                                                                        
        return tmp;
}

static gchar *
subscriptions_to_string (SunOneConnection *connection, const gchar *default_calendar, GList *list)
{
	GString *str;
	gchar *retval;
	gchar *tmp;
	SunOneCalendarSubscription *sub;
	GList *l;
	gboolean first_time;
                                                                                        
	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), NULL);

	/* add the other subscriptions */
	first_time = TRUE;
	str = g_string_new ("");
        for (l = list; l != NULL; l = l->next) {
                sub = l->data;
                if (!sub)
                        continue;

		if (!strcmp (sub->calid, default_calendar))
			continue;

		tmp = replace_colons (sub->calid);
		if (first_time) {
			str = g_string_append (str, "&add_attrs=icsSubscribed=");
			first_time = FALSE;
		} else
			str = g_string_append (str, ":icsSubscribed=");

		str = g_string_append (str, tmp);
		g_free (tmp);
		str = g_string_append (str, "$");
		if (sub->display_name && *sub->display_name) {
			tmp = replace_colons (sub->display_name);
			str = g_string_append (str, tmp);
			g_free (tmp);
		}
        }

	retval = query_encode (str->str, TRUE, ",");
	g_string_free (str, TRUE);

	return retval;
}

guint
sunone_connection_set_preferences (SunOneConnection *connection, SunOneConnectionPreferences *prefs)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *set_uri;
	SoupMessage *msg;
	gchar *s, *s1 = NULL;
	guint retval;
	GList *l;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (prefs != NULL, SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* first, set subscriptions to the default calendar */
	NEW_URI (set_uri);
	set_uri->path = g_strdup ("/set_userprefs.wcap");

	for (l = prefs->subscriptions; l != NULL; l = l->next) {
		SunOneCalendarSubscription *sub = l->data;

		if (!strcmp (sub->calid, prefs->default_calendar)) {
			s1 = g_strdup_printf ("%s$%s", sub->calid, sub->display_name);
			s = replace_colons (s1);
			g_free (s1);
			s1 = query_encode (s, TRUE, ",");

			set_uri->query = g_strdup_printf (
				"id=%s&fmt-out=text/calendar&convertCalid=1&set_attrs=icsSubscribed=%s",
				priv->wcap_session_id,
				s1);

			msg = sunone_message_new_from_uri (set_uri, SOUP_METHOD_GET);
			sunone_message_send (connection, msg);
			retval = msg->status_code;

			g_free (s);
			g_free (s1);
			g_object_unref (msg);
			soup_uri_free (set_uri);

			if (!SUNONE_ERROR_IS_SUCCESSFUL (retval))
				return retval;
			break;
		}
	}

	/* prepare the URI */
	NEW_URI (set_uri);
	set_uri->path = g_strdup ("/set_userprefs.wcap");

	/* FIXME: we only set subscriptions so far */
	s = subscriptions_to_string (connection, prefs->default_calendar, prefs->subscriptions);
	if (!s) {
		soup_uri_free (set_uri);
		return SOUP_STATUS_MALFORMED;
	}

	set_uri->query = g_strdup_printf ("id=%s&fmt-out=text/calendar&convertCalid=1%s",
						priv->wcap_session_id,
						s);
	g_free (s);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (set_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);

	retval = msg->status_code;
	if (SUNONE_ERROR_IS_SUCCESSFUL (retval)) {
		sunone_connection_free_preferences (priv->prefs);
		priv->prefs = clone_prefs (prefs);
	} else
		g_warning ("Error: %s", msg->reason_phrase);

	g_object_unref (msg);
	soup_uri_free (set_uri);

	return retval;
}

void
sunone_connection_free_subscription (SunOneCalendarSubscription *sub)
{
	if (!sub)
		return;
	
	g_free (sub->calid);
	g_free (sub->display_name);
	g_free (sub);
}

void
sunone_connection_free_preferences (SunOneConnectionPreferences *prefs)
{
	if (!prefs)
		return;

	g_free (prefs->common_name);
	g_free (prefs->given_name);
	g_free (prefs->mail);
	g_free (prefs->preferred_language);
	g_free (prefs->surname);
	g_free (prefs->default_calendar);
	g_free (prefs->timezone);
	g_free (prefs->freebusy);
	g_free (prefs->alarm_mail);

	g_list_foreach (prefs->subscriptions, (GFunc) sunone_connection_free_subscription, NULL);
	g_list_free (prefs->subscriptions);

	g_free (prefs);
}

static void
add_ace (GList **acls, SunOneACEUserType utype, SunOneACEContext context, SunOneACEPermission perms)
{
	SunOneACE *ace;

	ace = sunone_ace_new ();
	sunone_ace_set_user_type (ace, utype);
	sunone_ace_set_context (ace, context);
	sunone_ace_set_permissions (ace, perms);
	sunone_ace_set_access_type (ace, SUNONE_ACE_ACCESSTYPE_GRANT);

	*acls = g_list_append (*acls, ace);
}

guint
sunone_connection_createcalendar (SunOneConnection *connection, const gchar *calid, gchar **real_calid)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *suri;
	SoupMessage *msg;
	guint retval;
	GList *acls = NULL;
	gchar *aclstr, *calidtmp = NULL;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* set ACLS to the same value used in the Web UI */
	add_ace (&acls, SUNONE_ACE_USERTYPE_OWNERS, SUNONE_ACE_CONTEXT_CALENDAR_COMPONENTS,
		 SUNONE_ACE_PERMISSION_WRITE | SUNONE_ACE_PERMISSION_DELETE |
		 SUNONE_ACE_PERMISSION_REPLY | SUNONE_ACE_PERMISSION_INVITE | SUNONE_ACE_PERMISSION_CANCEL);
	add_ace (&acls, SUNONE_ACE_USERTYPE_OWNERS, SUNONE_ACE_CONTEXT_ENTIRE_CALENDAR,
		 SUNONE_ACE_PERMISSION_READ | SUNONE_ACE_PERMISSION_SCHEDULE | SUNONE_ACE_PERMISSION_FREEBUSY);
	add_ace (&acls, SUNONE_ACE_USERTYPE_ALL_USERS, SUNONE_ACE_CONTEXT_ENTIRE_CALENDAR,
		 SUNONE_ACE_PERMISSION_SCHEDULE | SUNONE_ACE_PERMISSION_FREEBUSY);
	add_ace (&acls, SUNONE_ACE_USERTYPE_ALL_USERS, SUNONE_ACE_CONTEXT_CALENDAR_COMPONENTS, SUNONE_ACE_PERMISSION_NONE);
	add_ace (&acls, SUNONE_ACE_USERTYPE_ALL_USERS, SUNONE_ACE_CONTEXT_CALENDAR_PROPERTIES, SUNONE_ACE_PERMISSION_READ);

	aclstr = get_acls_string (acls);

	/* prepare the URI */
	NEW_URI (suri);
	suri->path = g_strdup ("/createcalendar.wcap");
	suri->query = g_strdup_printf (
		"id=%s&calid=%s&fmt-out=text/calendar&set_calprops=1%s",
		priv->wcap_session_id,
		calid, aclstr);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (suri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (retval)) {
		icalcomponent *icalcomp;

		/* parse the response to get the calid as generated in the server */
		icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (icalcomp) {
			icalproperty *icalprop;

			icalprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);
			while (icalprop) {
				const gchar *x_name, *x_val;

				x_name = icalproperty_get_x_name (icalprop);
				x_val = icalproperty_get_x (icalprop);

				if (!strcmp (x_name, "X-NSCP-CALPROPS-RELATIVE-CALID")) {
					calidtmp = g_strdup (x_val);
					break;
				}

				icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY);
			}

			icalcomponent_free (icalcomp);
		}
	}

	g_object_unref (msg);
	soup_uri_free (suri);
	g_free (aclstr);
	g_list_foreach (acls, (GFunc) g_object_unref, NULL);
	g_list_free (acls);

	if (SUNONE_ERROR_IS_SUCCESSFUL (retval)) {
		SunOneConnectionPreferences *prefs;

		/* auto-subscribe to new folder */
		prefs = sunone_connection_get_preferences (connection);
		if (prefs) {
			SunOneCalendarSubscription *sub;

			sub = g_new0 (SunOneCalendarSubscription, 1);
			sub->calid = g_strdup (calidtmp);
			sub->display_name = g_strdup (calid);

			prefs->subscriptions = g_list_append (prefs->subscriptions, sub);
			if (!SUNONE_ERROR_IS_SUCCESSFUL (sunone_connection_set_preferences (connection, prefs)))
				g_warning ("Could not autosubscribe to folder %s", calid);

			sunone_connection_free_preferences (prefs);

			if (real_calid)
				*real_calid = calidtmp;
			else
				g_free (calidtmp);
		} else {
			g_warning ("Could not get preferences for calid %s", calid);
			g_free (calidtmp);
		}
	} else
		g_free (calidtmp);

	return retval;
}

guint
sunone_connection_deletecalendar (SunOneConnection *connection, const gchar *calid)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *suri;
	SoupMessage *msg;
	guint retval;
	SunOneConnectionPreferences *prefs;
	SunOneCalendarSubscription *sub = NULL;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	prefs = sunone_connection_get_preferences (connection);
	if (prefs) {
		GList *l;
		gboolean found = FALSE;

		/* don't allow removal of the default calendar */
		if (!strcmp (calid, prefs->default_calendar)) {
			sunone_connection_free_preferences (prefs);
			return SUNONE_ERROR_ACCESS_DENIED;
		}


		for (l = prefs->subscriptions; l != NULL; l = l->next) {
			sub = l->data;
			if (!strcmp (calid, sub->calid)) {
				found = TRUE;
				break;
			}
		}

		if (!found) {
			sunone_connection_free_preferences (prefs);
			return SUNONE_ERROR_CALENDAR_DOES_NOT_EXIST;
		}
	} else {
		g_warning ("Could not get preferences for connection");
		return SUNONE_ERROR_CALENDAR_DOES_NOT_EXIST;
	}

	/* prepare the URI */
	NEW_URI (suri);
	suri->path = g_strdup ("/deletecalendar.wcap");
	suri->query = g_strdup_printf (
		"id=%s&calid=%s&fmt-out=text/calendar",
		priv->wcap_session_id,
		sub->calid);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (suri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	g_object_unref (msg);
	soup_uri_free (suri);

	if (SUNONE_ERROR_IS_SUCCESSFUL (retval)) {
		/* auto-unsubscribe from folder */
		prefs->subscriptions = g_list_remove (prefs->subscriptions, sub);
		if (!SUNONE_ERROR_IS_SUCCESSFUL (sunone_connection_set_preferences (connection, prefs)))
			g_warning ("Could not autounsubscribe from folder %s", calid);
	}

	sunone_connection_free_preferences (prefs);

	return retval;
}

static SunOneCalendarProperties *
clone_calprops (SunOneCalendarProperties *props)
{
	SunOneCalendarProperties *copy;
	GList *l;

	copy = g_new0 (SunOneCalendarProperties, 1);

	copy->relative_calid = g_strdup (props->relative_calid);
	copy->display_name = g_strdup (props->display_name);
	copy->last_modified = props->last_modified;
	copy->created = props->created;
	copy->language = g_strdup (props->language);
	copy->primary_owner = g_strdup (props->primary_owner);
	for (l = props->owners; l != NULL; l = l->next)
		copy->owners = g_list_append (copy->owners, g_strdup (l->data));
	copy->timezone = g_strdup (props->timezone);
	copy->is_resource = props->is_resource;
	copy->acls = NULL;

	for (l = props->acls; l != NULL; l = l->next)
		copy->acls = g_list_append (copy->acls, sunone_ace_clone (SUNONE_ACE (l->data)));

	return copy;
}

static SunOneCalendarProperties *
calprops_from_icalcomp (icalcomponent *icalcomp)
{
	SunOneCalendarProperties *calprops;
	icalproperty *icalprop;

	calprops = g_new0 (SunOneCalendarProperties, 1);

	icalprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);
	while (icalprop) {
		const gchar *x_name, *x_val;

		x_name = icalproperty_get_x_name (icalprop);
		x_val = icalproperty_get_x (icalprop);

		if (!strcmp (x_name, "X-NSCP-CALPROPS-RELATIVE-CALID"))
			calprops->relative_calid = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-CALPROPS-LAST-MODIFIED"))
			calprops->last_modified = icaltime_from_string (x_val);
		else if (!strcmp (x_name, "X-NSCP-CALPROPS-CREATED"))
			calprops->created = icaltime_from_string (x_val);
		else if (!strcmp (x_name, "X-NSCP-CALPROPS-NAME"))
			calprops->display_name = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-CALPROPS-LANGUAGE"))
			calprops->language = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-CALPROPS-PRIMARY-OWNER"))
			calprops->primary_owner = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-CALPROPS-OWNERS"))
			calprops->owners = g_list_append (calprops->owners, g_strdup (x_val));
		else if (!strcmp (x_name, "X-NSCP-CALPROPS-TZID"))
			calprops->timezone = g_strdup (x_val);
		else if (!strcmp (x_name, "X-NSCP-CALPROPS-ACCESS-CONTROL-ENTRY")) {
			SunOneACE *ace;

			ace = sunone_ace_new_from_string (x_val);
			if (IS_SUNONE_ACE (ace))
				calprops->acls = g_list_append (calprops->acls, ace);
		} else if (!strcmp (x_name, "X-NSCP-CALPROPS-READ")) {
		} else if (!strcmp (x_name, "X-NSCP-CALPROPS-WRITE")) {
		} else if (!strcmp (x_name, "X-NSCP-CALPROPS-RESOURCE"))
			calprops->is_resource = !strcmp (x_val, "1") ? TRUE : FALSE;

		icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY);
	}

	return calprops;
}

SunOneCalendarProperties *
sunone_connection_get_calprops (SunOneConnection *connection, const gchar *calid, gboolean use_cache)
{
	SunOneConnectionPrivate *priv = connection->priv;
	gchar *calid_hash = NULL;
	SunOneCalendarProperties *calprops = NULL;
	SoupURI *get_uri;
	SoupMessage *msg;
	icalcomponent *icalcomp;
	SunOneCalendarProperties *clone;
	static GStaticMutex mutex = G_STATIC_MUTEX_INIT;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), NULL);
	g_return_val_if_fail (calid != NULL, NULL);

	IS_CONNECTED (connection, NULL);
	g_static_mutex_lock (&mutex);
	
	if (!g_hash_table_lookup_extended (priv->calendar_properties, calid,
					   (gpointer *) &calid_hash, (gpointer *) &calprops) || !use_cache) {
		/* prepare the URI */
		NEW_URI (get_uri);
		get_uri->path = g_strdup ("/get_calprops.wcap");
		get_uri->query = g_strdup_printf ("id=%s&fmt-out=text/calendar&calid=%s",
							priv->wcap_session_id,
							calid);

		/* send the request to the server */
		msg = sunone_message_new_from_uri (get_uri, SOUP_METHOD_GET);
		sunone_message_send (connection, msg);

		soup_uri_free (get_uri);

		if (!SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
			g_warning ("Error: %s", msg->reason_phrase);
			g_object_unref (msg);
			g_static_mutex_unlock (&mutex);
			return NULL;
		}

		/* parse the returned data */
		icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		g_object_unref (msg);
		if (!icalcomp) {
			g_static_mutex_unlock (&mutex);
			return NULL;
		}
	
		/* free old calprops from hashtable */	
		if (calid_hash) {
			g_hash_table_remove (priv->calendar_properties, calid_hash);
			free_calprops (calid_hash, calprops, NULL);
		}
		

		calprops = calprops_from_icalcomp (icalcomp);
		g_hash_table_insert (priv->calendar_properties, g_strdup (calid), calprops);

		icalcomponent_free (icalcomp);
	}

	clone = clone_calprops (calprops);
	g_static_mutex_unlock (&mutex);

	return clone;
}

static gchar *
get_acls_string (GList *acls)
{
	GString *str = NULL;
	gchar *retval;
	GList *l;

	g_return_val_if_fail (acls != NULL, NULL);

	for (l = acls; l != NULL; l = l->next) {
		gchar *acestr;
		SunOneACE *ace = SUNONE_ACE (l->data);

		acestr = sunone_ace_to_string (ace);
		if (acestr) {
			if (!str)
				str = g_string_new ("&acl=");
			else
				str = g_string_append (str, ";");
			str = g_string_append (str, acestr);

			g_free (acestr);
		}
	}

	retval = str->str;
	g_string_free (str, FALSE);

	return retval;
}

static gchar *
get_owners_string (GList *owners)
{
	GString *str = NULL;
	GList *l;

	for (l = owners; l != NULL; l = l->next) {
		if (!str)
			str = g_string_new ((const gchar *) l->data);
		else {
			str = g_string_append (str, ";");
			str = g_string_append (str, (const gchar *) l->data);
		}
	}

	if (str) {
		char *retval = str->str;
		g_string_free (str, FALSE);

		return retval;
	}

	return NULL;
}

guint
sunone_connection_set_calprops (SunOneConnection *connection,
				const gchar *calid,
				SunOneCalendarProperties *calprops)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *set_uri;
	SoupMessage *msg;
	guint retval;
	gchar *s;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* prepare the URI */
	NEW_URI (set_uri);
	set_uri->path = g_strdup ("/set_calprops.wcap");
	s = get_acls_string (calprops->acls);
	set_uri->query = g_strdup_printf (
		"id=%s&calid=%s&fmt-out=text/calendar%s",
		priv->wcap_session_id,
		calid,
		s ? s : "");
	g_free (s);

	if (calprops->display_name != NULL)
		append_to_query (set_uri, "name", calprops->display_name);

	s = get_owners_string (calprops->owners);
	append_to_query (set_uri, "owners", s ? s :"");
	g_free (s);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (set_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		gchar *calid_hash;
		SunOneCalendarProperties *calprops_hash;

		if (g_hash_table_lookup_extended (priv->calendar_properties, calid,
						  (gpointer *) &calid_hash, (gpointer *) &calprops_hash)) {
			g_hash_table_remove (priv->calendar_properties, calid);
			g_free (calid_hash);
			sunone_connection_free_calprops (calprops_hash);
		}

		g_hash_table_insert (priv->calendar_properties, g_strdup (calid), 
				     clone_calprops (calprops));
	}

	g_object_unref (msg);
	soup_uri_free (set_uri);

	return retval;
}

GList *
sunone_connection_list_calids (SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *get_uri;
	SoupMessage *msg;
	icalcomponent *icalcomp, *subcomp;
	SunOneCalendarProperties *calprops;
	GList *calids = NULL;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), NULL);

	IS_CONNECTED (connection, NULL);

	/* prepare the URI */
	NEW_URI (get_uri);
	get_uri->path = g_strdup ("/search_calprops.wcap");
	get_uri->query = g_strdup_printf ("id=%s&fmt-out=text/calendar&calid=1&searchOpts=0&search-string=",
						priv->wcap_session_id);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (get_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);

	soup_uri_free (get_uri);

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (icalcomp) {
			subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VCALENDAR_COMPONENT);
			while (subcomp) {
				calprops = calprops_from_icalcomp (subcomp);
				if (calprops)
					calids = g_list_append (calids, calprops);

				subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VCALENDAR_COMPONENT);
			}

			icalcomponent_free (icalcomp);
		} else
			g_warning ("Error: could not parse response's body");
	} else
		g_warning ("Error: %s", msg->reason_phrase);

	g_object_unref (msg);

	return calids;
}

GList *
sunone_connection_search_calids (SunOneConnection *connection, SunOneSearchField field, SunOneSearchBy by, const char *search_str)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *get_uri;
	SoupMessage *msg;
	icalcomponent *icalcomp, *subcomp;
	SunOneCalendarProperties *calprops;
	GList *calids = NULL;
	gint calid = 0, name = 0, primary_owner = 0;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), NULL);

	IS_CONNECTED (connection, NULL);

	switch (field) {
	case NAME_OR_ID:
		calid = name = 1;
		break;
	case NAME:
		name = 1;
		break;
	case PRIMARY_OWNER:
		primary_owner = 1;
		break;
	case ID:	
		calid = 1;
		break;
	}
	/* prepare the URI */
	NEW_URI (get_uri);
	get_uri->path = g_strdup ("/search_calprops.wcap");
	get_uri->query = g_strdup_printf ("id=%s&fmt-out=text/calendar&calid=%d&name=%d&primaryOwner=%d&searchOpts=%d&search-string=%s", priv->wcap_session_id, calid, name, primary_owner, by, search_str);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (get_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);

	soup_uri_free (get_uri);

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		char *tmp_str;
		char *vcal_str;

		tmp_str = g_strndup (msg->response_body->data, msg->response_body->length);
		if (tmp_str) {
			vcal_str = g_strdup_printf ("BEGIN:VCALENDAR\n%s\nEND:VCALENDAR\n", tmp_str);
			g_free (tmp_str);
			if (vcal_str) {
				icalcomp = sunone_util_icalparser_parse_string (vcal_str, strlen (vcal_str));
				g_free (vcal_str);
				if (icalcomp) {
					subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VCALENDAR_COMPONENT);
					while (subcomp) {
						/* Because we wrap the result in a vcalender,
						 * we always get at least one sub component in icalcompoent.
						 * So we use ICAL_X_PROPERTY to identify if this sub component
						 * is the only one in search result or it is just fake.
						 */
						if (icalcomponent_get_first_property (subcomp, ICAL_X_PROPERTY) != NULL) {
							calprops = calprops_from_icalcomp (subcomp);
							if (calprops)
								calids = g_list_append (calids, calprops);
						}

						subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VCALENDAR_COMPONENT);
					}

					icalcomponent_free (icalcomp);
				} else
					g_warning ("Error: could not parse response's body");
			}
		}
	} else
		g_warning ("Error: %s", msg->reason_phrase);

	g_object_unref (msg);

	return calids;
}

void
sunone_connection_free_calprops (SunOneCalendarProperties *calprops)
{
	g_return_if_fail (calprops != NULL);

	g_free (calprops->relative_calid);
	g_free (calprops->display_name);
	g_free (calprops->language);
	g_free (calprops->primary_owner);
	g_list_foreach (calprops->owners, (GFunc) g_free, NULL);
	g_list_free (calprops->owners);
	g_free (calprops->timezone);

	g_list_foreach (calprops->acls, (GFunc) g_object_unref, NULL);
	g_list_free (calprops->acls);

	g_free (calprops);
}

guint
sunone_connection_get_all_timezones (SunOneConnection *connection,
				     icalcomponent **icalcomp)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* prepare the URI */
	NEW_URI (fetch_uri);
	fetch_uri->path = g_strdup ("/get_all_timezones.wcap");
	fetch_uri->query = g_strdup_printf (
		"id=%s&fmt-out=text/calendar",
		priv->wcap_session_id);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		*icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!*icalcomp)
			retval = SOUP_STATUS_MALFORMED;
	}

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	return retval;
}


guint
sunone_connection_deletetodos_by_id (SunOneConnection *connection,
				     const char *calid,
				     const char *uid,
				     const char *rid,
				     SunOneModType mod)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* prepare the URI */
	NEW_URI (fetch_uri);
	fetch_uri->path = g_strdup ("/deletetodos_by_id.wcap");
	fetch_uri->query = g_strdup_printf (
		"id=%s&calid=%s&uid=%s&rid=%s&mod=%d&fmt-out=text/calendar",
		priv->wcap_session_id, calid,
		uid, rid, mod);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	return retval;
}

guint
sunone_connection_deleteevents_by_id (SunOneConnection *connection,
				      const char *calid,
				      const char *uid,
				      const char *rid,
				      SunOneModType mod)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* prepare the URI */
	NEW_URI (fetch_uri);
	fetch_uri->path = g_strdup ("/deleteevents_by_id.wcap");
	fetch_uri->query = g_strdup_printf (
		"id=%s&calid=%s&uid=%s&rid=%s&mod=%d&fmt-out=text/calendar",
		priv->wcap_session_id, calid,
		uid, rid, mod);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	return retval;
}

static time_t
parse_http_date (const char *date)
{
	/* Do not internationalize. */
	static const char *months[12] = {
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
	};
	struct tm tm;
	char *p;

	if (strlen (date) < 29 || date[3] != ',' || date[4] != ' ')
		return -1;

	memset (&tm, 0, sizeof (tm));
	p = (char *)date + 5;

	tm.tm_mday = strtol (p, &p, 10);
	p++;
	for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
		if (!strncmp (p, months[tm.tm_mon], 3))
			break;
	}
	p += 3;

	tm.tm_year = strtol (p, &p, 10) - 1900;

	tm.tm_hour = strtol (p, &p, 10);
	p++;
	tm.tm_min  = strtol (p, &p, 10);
	p++;
	tm.tm_sec  = strtol (p, &p, 10);

	return e_mktime_utc (&tm);
}

guint
sunone_connection_fetchcomponents_by_lastmod (SunOneConnection *connection,
					      const char *calid,
					      struct icaltimetype dtstart,
					      struct icaltimetype dtend,
					      SunOneCompType type,
					      SunOneCompState state,
					      icalcomponent **icalcomp,
					      time_t *server_time)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;
	char *tsstr;
	char *testr;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* prepare the URI */
	NEW_URI (fetch_uri);
	if (!icaltime_is_null_time (dtstart))
		tsstr = icaltime_as_ical_string (dtstart);
	else
		tsstr = g_strdup ("0");
	if (!icaltime_is_null_time (dtend))
		testr = icaltime_as_ical_string (dtend);
	else
		testr = g_strdup ("0");
	fetch_uri->path = g_strdup ("/fetchcomponents_by_lastmod.wcap");
	fetch_uri->query = g_strdup_printf (
		"id=%s&calid=%s&dtstart=%s&dtend=%s&component-type=%s&relativealarm=4&fmt-out=text/calendar&compressed=1",
		priv->wcap_session_id,
		calid,
		tsstr, testr,
		type_to_string (type));
	g_free (tsstr);
	g_free (testr);

	append_to_query_compstate (fetch_uri, "compstate", state);
	
	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		*icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!*icalcomp)
			retval = SOUP_STATUS_MALFORMED;

		if (server_time) {
			const char *date;
			
			date = soup_message_headers_get (msg->response_headers, "Date");
			*server_time = parse_http_date (date);
			if (*server_time < 0)
				*server_time = 0;
		}		
	}

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	return retval;
}

guint
sunone_connection_fetch_deletedcomponents (SunOneConnection *connection,
				const char *calid,
				struct icaltimetype dtstart,
				struct icaltimetype dtend,
				SunOneCompType type,
				icalcomponent **icalcomp)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;
	char *tsstr;
	char *testr;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* prepare the URI */
	NEW_URI (fetch_uri);
	if (!icaltime_is_null_time (dtstart))
		tsstr = icaltime_as_ical_string (dtstart);
	else
		tsstr = g_strdup ("0");
	if (!icaltime_is_null_time (dtend))
		testr = icaltime_as_ical_string (dtend);
	else
		testr = g_strdup ("0");
	fetch_uri->path = g_strdup ("/fetch_deletedcomponents.wcap");
	fetch_uri->query = g_strdup_printf (
		"id=%s&calid=%s&dtstart=%s&dtend=%s&component-type=%s&fmt-out=text/calendar&recurring=0",
		priv->wcap_session_id,
		calid,
		tsstr, testr,
		type_to_string (type));
	g_free (tsstr);
	g_free (testr);

	
	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		*icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!*icalcomp)
			retval = SOUP_STATUS_MALFORMED;
	}

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	return retval;
}

guint
sunone_connection_get_freebusy (SunOneConnection *connection,
				const char *calid,
				struct icaltimetype dtstart,
				struct icaltimetype dtend,
				icalcomponent **icalcomp)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;
	char *tsstr;
	char *testr;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);

	/* prepare the URI */
	NEW_URI (fetch_uri);
	if (!icaltime_is_null_time (dtstart))
		tsstr = icaltime_as_ical_string (dtstart);
	else
		tsstr = g_strdup ("0");
	if (!icaltime_is_null_time (dtend))
		testr = icaltime_as_ical_string (dtend);
	else
		testr = g_strdup ("0");
	fetch_uri->path = g_strdup ("/get_freebusy.wcap");
	fetch_uri->query = g_strdup_printf (
		"id=%s&calid=%s&dtstart=%s&dtend=%s&tzid=UTC&tzidout=UTC&busyonly=1&fmt-out=text/calendar&compressed=1",
		priv->wcap_session_id,
		calid,
		tsstr, testr);
	g_free (tsstr);
	g_free (testr);

	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		*icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!*icalcomp)
			retval = SOUP_STATUS_MALFORMED;
	}

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	/* For WCAP 3.0, maybe calid is a mail address, try again */
	if (!IS_CNC_WCAP_2_0(connection) && !SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		char *tsstr;
		char *testr;

		/* prepare the URI */
		NEW_URI (fetch_uri);
		if (!icaltime_is_null_time (dtstart))
			tsstr = icaltime_as_ical_string (dtstart);
		else
			tsstr = g_strdup ("0");
		if (!icaltime_is_null_time (dtend))
			testr = icaltime_as_ical_string (dtend);
		else
			testr = g_strdup ("0");
		fetch_uri->path = g_strdup ("/get_freebusy.wcap");
		fetch_uri->query = g_strdup_printf (
			"id=%s&mail=%s&dtstart=%s&dtend=%s&tzid=UTC&tzidout=UTC&busyonly=1&fmt-out=text/calendar&compressed=1",
			priv->wcap_session_id,
			calid,
			tsstr, testr);
		g_free (tsstr);
		g_free (testr);

		/* send the request to the server */
		msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
		sunone_message_send (connection, msg);
		retval = msg->status_code;

		if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
			*icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
			if (!*icalcomp)
				retval = SOUP_STATUS_MALFORMED;
		}

		g_object_unref (msg);
		soup_uri_free (fetch_uri);
	}

	return retval;
	
}

guint
sunone_connection_import (SunOneConnection *connection, const char *calid, icalcomponent *icalcomp)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *uri;
	SoupMessage *msg;
	guint retval;
	char *ical_string, *import_msg, *content_type, *err;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);
	g_return_val_if_fail (icalcomp != NULL, SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	ical_string = icalcomponent_as_ical_string (icalcomp);
	if (!ical_string)
		return SOUP_STATUS_CANCELLED;

	/* prepare the URI */
	NEW_URI (uri);
	uri->path = g_strdup ("/import.wcap");
	uri->query = g_strdup_printf (
		"id=%s&calid=%s&content-in=text/calendar",
		priv->wcap_session_id,
		calid);

	/* prepare message */
	import_msg = g_strdup_printf ("-----------------------------33111928916708\r\nContent-Disposition: form-data; name=\"Upload\"; filename=\"ical1.ics\"\r\n\r\n%s-----------------------------33111928916708--", ical_string);
	
	content_type = "multipart/form-data;boundary=---------------------------33111928916708";	
	msg = sunone_message_new_full_from_uri (uri, SOUP_METHOD_POST, content_type, SOUP_MEMORY_TAKE,
						import_msg, strlen (import_msg));

	/* send the request to the server */
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) 
		retval = msg->status_code;

	if (msg->response_body->data) {
		if (IS_CNC_WCAP_2_0 (connection)) {
			err = strstr (msg->response_body->data, "var errno=");
			err += 10;
		}
		else {
			err = strstr (msg->response_body->data, "X-NSCP-WCAP-ERRNO:");
			err += 18;
		}

		if (err) {
			retval = 1001 + atoi (err);
		} else { 
			retval = SUNONE_ERROR_COMMAND_FAILED;
		}
	} else {
		retval = SUNONE_ERROR_COMMAND_FAILED;
	}

	g_object_unref (msg);
	soup_uri_free (uri);

	return retval;
}

static void
remove_all_but (icalcomponent *icalcomp, icalcomponent_kind kind)
{
	icalcomponent_kind child_kind;
	icalcomponent *subcomp;

	subcomp = icalcomponent_get_first_component (icalcomp, ICAL_ANY_COMPONENT);
	while (subcomp) {
		child_kind = icalcomponent_isa (subcomp);
		if (child_kind != ICAL_VTIMEZONE_COMPONENT 
		    && child_kind != ICAL_VALARM_COMPONENT
		    && child_kind != kind) {
			icalcomponent_remove_component (icalcomp, subcomp);
			icalcomponent_free (subcomp);
		}
		
		subcomp = icalcomponent_get_next_component (icalcomp, ICAL_ANY_COMPONENT);
	}
}

static guint
sunone_connection_storeevents_by_mail (SunOneConnection *connection, const char *calid,
				   icalcomponent *icalcomp, SunOneMethod method, SunOneModType mod, gboolean expand,
				   gboolean allday, icalcomponent **return_icalcomp, gboolean mail, const char *account_email)
{
	SunOneConnectionPrivate *priv = connection->priv;
	ECalComponent *comp;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;
	icalcomponent *real_icalcomp;
	icalproperty *prop;
	GSList *slist;
	ECalComponentText text;
	ECalComponentDateTime dt;
	ECalComponentOrganizer organizer;
	ECalComponentClassification class;
	ECalComponentRange recur;
	ECalComponentTransparency trans;
	struct icalgeotype *geo = NULL;
	icalproperty_status status;
	const char *const_string;
	int *num;
	
	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);
	g_return_val_if_fail (icalcomp != NULL, SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	real_icalcomp = icalcomponent_new_clone (icalcomp);
	remove_all_but (real_icalcomp, ICAL_VEVENT_COMPONENT);

	comp = e_cal_component_new ();
	e_cal_component_set_icalcomponent (comp, real_icalcomp);
	
	/* prepare the URI */
	NEW_URI (fetch_uri);
	fetch_uri->path = g_strdup ("/storeevents.wcap" );
	fetch_uri->query = g_strdup_printf (
		"id=%s&calid=%s&fetch=1&method=%d&mod=%d&replace=1&rchange=%s&compressed=1&fmt-out=text/calendar",
		priv->wcap_session_id, calid, method, mod, expand ? "1" : "0");

	/* Alarms */
	append_to_query_alarms (fetch_uri, connection, comp);
	
	/* Attachments */
	/* FIXME */

	/* Attendees */
	if (e_cal_component_has_attendees (comp)) {
		e_cal_component_get_attendee_list (comp, &slist);
		append_to_query_attendees (fetch_uri, "attendees", slist, connection, calid, account_email);
	}
	
	/* Categories */
	e_cal_component_get_categories (comp, &const_string);
	append_to_query (fetch_uri, "categories", const_string ? const_string : "");

	/* Contacts */
	e_cal_component_get_contact_list (comp, &slist);
	append_to_query_text_list (fetch_uri, "contacts", slist);
	
	/* Description */
	e_cal_component_get_description_list (comp, &slist);
	if (slist)
		append_to_query_text (fetch_uri, "desc", slist->data);
	else
		append_to_query (fetch_uri, "desc", "");

	/* Dtend */
	e_cal_component_get_dtend (comp, &dt);
	append_to_query_datetime (fetch_uri, "dtend", &dt, FALSE);

	/* Dtstart */
	e_cal_component_get_dtstart (comp, &dt);
	append_to_query_datetime (fetch_uri, "dtstart", &dt, FALSE);
	
	/* Exdates */
	e_cal_component_get_exdate_list (comp, &slist);
	append_to_query_exdate_list (fetch_uri, "exdates", slist);

	/* Exrules */
	e_cal_component_get_exrule_list (comp, &slist);
	append_to_query_recur_list (fetch_uri, "exrules", slist);

	/* Transparency for wcap 3.0 */
	if (!IS_CNC_WCAP_2_0(connection)) {
		e_cal_component_get_transparency (comp, &trans);
		append_to_query (fetch_uri, "transparent", trans == E_CAL_COMPONENT_TRANSP_TRANSPARENT ? "1":"0");
	}

	/* Geo */
	e_cal_component_get_geo (comp, &geo);
	append_to_query_geo (fetch_uri, "geo", geo);
	
	/* Classification */
	e_cal_component_get_classification (comp, &class);
	append_to_query_classification (fetch_uri, "icsClass", class);

	/* URL */
	e_cal_component_get_url (comp, &const_string);
	if (const_string)
		append_to_query (fetch_uri, "icsUrl", const_string);
	else
		append_to_query (fetch_uri, "icsUrl", "");

	/* isAllDay */
	append_to_query (fetch_uri, "isAllDay", allday ? "1" : "0");

	/* Language */
	/* FIXME */

	/* Location */
	e_cal_component_get_location (comp, &const_string);
	if (const_string)
		append_to_query (fetch_uri, "location", const_string);
	else
		append_to_query (fetch_uri, "location", "");
	
	/* OrgUID for wcap 2.0, OrgCalid for wcap 3.0 */
	if (e_cal_component_has_organizer (comp)) {
		e_cal_component_get_organizer (comp, &organizer);
		if (!strncasecmp (organizer.value, "mailto:", 7))
			organizer.value += 7;
		if (IS_CNC_WCAP_2_0(connection))
			append_to_query (fetch_uri, "orgUID", organizer.value);
		else {
			if (!mail) {
				append_to_query (fetch_uri, "orgCalid", organizer.value);
			} else {
				append_to_query (fetch_uri, "orgMail", priv->prefs->mail);
			}
		}
	}
	
	/* Priority */
	e_cal_component_get_priority (comp, &num);
	if (num) {
		append_to_query_int (fetch_uri, "priority", *num);	
		e_cal_component_free_priority (num);
	}
	
	/* RDates */
	/* FIXME */

	/* Relatedto */
	/* FIXME */

	/* Resources */
	/* FIXME */
	
	/* Rid */
	if (e_cal_component_is_instance (comp)) {
		e_cal_component_get_recurid (comp, &recur);
		append_to_query_datetime (fetch_uri, "rid", &recur.datetime, FALSE);
	}
	
	/* Rrules */
	e_cal_component_get_rrule_list (comp, &slist);
	append_to_query_recur_list (fetch_uri, "rrules", slist);

	/* Sequence */
	e_cal_component_get_sequence (comp, &num);
	if (num) {
		append_to_query_int (fetch_uri, "seq", *num);
		e_cal_component_free_sequence (num);
	}

	/* Status */
	e_cal_component_get_status (comp, &status);
	append_to_query_status (fetch_uri, "status", status);

	/* Summary */
	e_cal_component_get_summary (comp, &text);
	append_to_query_text (fetch_uri, "summary", &text);

	/* UID */
	e_cal_component_get_uid (comp, &const_string);
	append_to_query (fetch_uri, "uid", const_string);
	
	/* tzid */
	prop = icalcomponent_get_first_property (real_icalcomp, ICAL_X_PROPERTY);
	while (prop) {
		const char *x_name, *x_val;

		x_name = icalproperty_get_x_name (prop);
		x_val = icalproperty_get_x (prop);

		if (!strcmp (x_name, "X-NSCP-DTSTART-TZID")  && strcmp (x_val, "UTC"))
			append_to_query (fetch_uri, "tzid", x_val);
		
		prop = icalcomponent_get_next_property (real_icalcomp, ICAL_X_PROPERTY);
	}

	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code) && return_icalcomp) {
		*return_icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!*return_icalcomp)
			retval = SOUP_STATUS_MALFORMED;
	}

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	/* will free real_icalcomp when comp is freed */
	g_object_unref (comp);

	return retval;
}

guint
sunone_connection_storeevents (SunOneConnection *connection, const char *calid, icalcomponent *icalcomp, 
			       SunOneMethod method, SunOneModType mod, gboolean expand, gboolean allday,
			       icalcomponent **return_icalcomp, const char *account_email)
{
	SunOneConnectionPrivate *priv = connection->priv;
	guint retval;

	retval = sunone_connection_storeevents_by_mail (connection, calid, icalcomp, method, mod,
															expand, allday, return_icalcomp, FALSE, account_email);

	if (!SUNONE_ERROR_IS_SUCCESSFUL (retval)
			&& !IS_CNC_WCAP_2_0 (connection)
			&& priv->prefs->mail) {
		retval = sunone_connection_storeevents_by_mail (connection, calid, icalcomp, method, mod,
																expand, allday, return_icalcomp, TRUE, account_email);
	}

	return retval;
}

guint
sunone_connection_storetodos (SunOneConnection *connection, const char *calid, icalcomponent *icalcomp, 
			      SunOneMethod method, SunOneModType mod, gboolean expand, gboolean allday,
			      icalcomponent **return_icalcomp, const char *email)
{
	SunOneConnectionPrivate *priv = connection->priv;
	ECalComponent *comp;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;
	icalcomponent *real_icalcomp;
	icalproperty *prop;
	GSList *slist;
	ECalComponentText text;
	ECalComponentDateTime dt;
	ECalComponentOrganizer organizer;
	ECalComponentClassification class;
	ECalComponentRange recur;
	struct icaltimetype *t;
	struct icalgeotype *geo = NULL;
	icalproperty_status status;
	const char *const_string;
	int *num;
	char *tstr;
	
	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);
	g_return_val_if_fail (icalcomp != NULL, SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	real_icalcomp = icalcomponent_new_clone (icalcomp);
	remove_all_but (real_icalcomp, ICAL_VEVENT_COMPONENT);

	comp = e_cal_component_new ();
	e_cal_component_set_icalcomponent (comp, real_icalcomp);
	
	/* prepare the URI */
	NEW_URI (fetch_uri);
	fetch_uri->path = g_strdup ("/storetodos.wcap" );
	fetch_uri->query = g_strdup_printf (
		"id=%s&calid=%s&fetch=1&method=%d&mod=%d&replace=1&rchange=%s&compressed=1&fmt-out=text/calendar",
		priv->wcap_session_id, calid, method, mod, expand ? "1" : "0");

	/* Alarms */
	append_to_query_alarms (fetch_uri, connection, comp);

	/* Attendees */
	if (e_cal_component_has_attendees (comp)) {
		e_cal_component_get_attendee_list (comp, &slist);
		append_to_query_attendees (fetch_uri, "attendees", slist, connection, calid, email);
	}

	/* Categories */
	e_cal_component_get_categories (comp, &const_string);
	append_to_query (fetch_uri, "categories", const_string ? const_string : "");

	/* Contacts */
	e_cal_component_get_contact_list (comp, &slist);
	append_to_query_text_list (fetch_uri, "contacts", slist);

	/* Completed */
	e_cal_component_get_completed (comp, &t);
	if (t)
		tstr = icaltime_as_ical_string (*t);
	else
		tstr = g_strdup ("0");
	append_to_query (fetch_uri, "completed", tstr);
	g_free (tstr);
	
	/* Description */
	e_cal_component_get_description_list (comp, &slist);
	if (slist)
		append_to_query_text (fetch_uri, "desc", slist->data);
	else
		append_to_query (fetch_uri, "desc", "");

	/* Dtstart */
	e_cal_component_get_dtstart (comp, &dt);
	append_to_query_datetime (fetch_uri, "dtstart", &dt, FALSE);

	/* Due */
	e_cal_component_get_due (comp, &dt);
	append_to_query_datetime (fetch_uri, "due", &dt, TRUE);
	
	/* Exdates */
	e_cal_component_get_exdate_list (comp, &slist);
	append_to_query_exdate_list (fetch_uri, "exdates", slist);

	/* Exrules */
	e_cal_component_get_exrule_list (comp, &slist);
	append_to_query_recur_list (fetch_uri, "exrules", slist);

	/* Geo */
	e_cal_component_get_geo (comp, &geo);
	append_to_query_geo (fetch_uri, "geo", geo);
	
	/* Classification */
	e_cal_component_get_classification (comp, &class);
	append_to_query_classification (fetch_uri, "icsClass", class);

	/* URL */
	e_cal_component_get_url (comp, &const_string);
	if (const_string)
		append_to_query (fetch_uri, "icsUrl", const_string);
	else
		append_to_query (fetch_uri, "icsUrl", "");

	/* isAllDay */
	append_to_query (fetch_uri, "isAllDay", allday ? "1" : "0");

	/* Language */
	/* FIXME */

	/* Location */
	e_cal_component_get_location (comp, &const_string);
	append_to_query (fetch_uri, "location", const_string);

	/* OrgUID for wcap 2.0, OrgCalid for wcap 3.0 */
	if (e_cal_component_has_organizer (comp)) {
		e_cal_component_get_organizer (comp, &organizer);
		if (!strncasecmp (organizer.value, "mailto:", 7))
			organizer.value += 7;
		if (IS_CNC_WCAP_2_0(connection))
			append_to_query (fetch_uri, "orgUID", organizer.value);
		else
			append_to_query (fetch_uri, "orgCalid", organizer.value);
	}	
	
	/* Percent */
	e_cal_component_get_percent (comp, &num);
	if (num) {
		append_to_query_int (fetch_uri, "percent", *num);
		e_cal_component_free_percent (num);
	} else {
		/*When one marks a completed task as uncompleted, evolution will remove
		 *percent property, this would cause problem on some calendar servers,
		 *so we need to set percent property to 0
		 */
		append_to_query_int (fetch_uri, "percent", 0);
	}
	
	/* Priority */
	e_cal_component_get_priority (comp, &num);
	if (num) {
		append_to_query_int (fetch_uri, "priority", *num);
		e_cal_component_free_priority (num);
	}
	
	/* RDates */
	/* FIXME */

	/* Relatedto */
	/* FIXME */

	/* Resources */
	/* FIXME */
	
	/* Rid */
	if (e_cal_component_is_instance (comp)) {
		e_cal_component_get_recurid (comp, &recur);
		append_to_query_datetime (fetch_uri, "rid", &recur.datetime, FALSE);
	}

	/* Rrules */
	e_cal_component_get_rrule_list (comp, &slist);
	append_to_query_recur_list (fetch_uri, "rrules", slist);

	/* Sequence */
	e_cal_component_get_sequence (comp, &num);
	if (num) {
		append_to_query_int (fetch_uri, "seq", *num);
		e_cal_component_free_sequence (num);
	}

	/* Status */
	e_cal_component_get_status (comp, &status);
	append_to_query_status (fetch_uri, "status", status);

	/* Summary */
	e_cal_component_get_summary (comp, &text);
	append_to_query_text (fetch_uri, "summary", &text);

	/* UID */
	e_cal_component_get_uid (comp, &const_string);
	append_to_query (fetch_uri, "uid", const_string);
	
	/* tzid */
	prop = icalcomponent_get_first_property (real_icalcomp, ICAL_X_PROPERTY);
	while (prop) {
		const char *x_name, *x_val;

		x_name = icalproperty_get_x_name (prop);
		x_val = icalproperty_get_x (prop);

		if (!strcmp (x_name, "X-NSCP-DTSTART-TZID"))
			append_to_query (fetch_uri, "tzid", x_val);
		
		prop = icalcomponent_get_next_property (real_icalcomp, ICAL_X_PROPERTY);
	}

	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code) && return_icalcomp) {
		*return_icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!*return_icalcomp)
			retval = SOUP_STATUS_MALFORMED;
	}

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	/* will free real_icalcomp when comp is freed */
	g_object_unref (comp);

	return retval;
}

guint
sunone_connection_verifyevents_by_ids (SunOneConnection *connection,
				       const char *calid,
				       const char **uidv,
				       const char **ridv,
				       icalcomponent **icalcomp)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	char *post_string, *content_type;
	guint retval;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);
	g_return_val_if_fail (calid != NULL, SUNONE_ERROR_ILLEGAL_CALID_NAME);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* prepare the URI */
	NEW_URI (fetch_uri);
	fetch_uri->path = g_strdup ("/verifyevents_by_ids.wcap");
	fetch_uri->query = g_strdup_printf ("id=%s&calid=%s&fmt-out=text/calendar",
		priv->wcap_session_id,
		calid);


	append_to_query_stringv (fetch_uri, "uid", uidv);
	append_to_query_stringv (fetch_uri, "rid", ridv);

	post_string = fetch_uri->query;
	fetch_uri->query = NULL;
	
	content_type = "application/x-www-form-urlencoded";
	/* send the request to the server */
	msg = sunone_message_new_full_from_uri (fetch_uri, SOUP_METHOD_POST, content_type, SOUP_MEMORY_TAKE,
		post_string, strlen (post_string));

	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		*icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!*icalcomp)
			retval = SOUP_STATUS_MALFORMED;
	}

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	return retval;
}


guint
sunone_connection_verifytodos_by_ids (SunOneConnection *connection, 
				      const char *calid,
				      const char **uidv,
				      const char **ridv,
				      icalcomponent **icalcomp)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	char *post_string, *content_type;
	guint retval;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);
	
	/* prepare the URI */
	NEW_URI (fetch_uri);
	fetch_uri->path = g_strdup ("/verifytodos_by_ids.wcap");
	fetch_uri->query = g_strdup_printf (
		"id=%s&calid=%s&fmt-out=text/calendar",
		priv->wcap_session_id,
		calid);

	append_to_query_stringv (fetch_uri, "uid", uidv);
	append_to_query_stringv (fetch_uri, "rid", ridv);

	post_string = fetch_uri->query;
	fetch_uri->query = NULL;
	
	content_type = "application/x-www-form-urlencoded";
	/* send the request to the server */
	msg = sunone_message_new_full_from_uri (fetch_uri, SOUP_METHOD_POST, content_type, SOUP_MEMORY_TAKE,
		post_string, strlen (post_string));

	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		*icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!*icalcomp)
			retval = SOUP_STATUS_MALFORMED;
	}

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	return retval;
}

void
sunone_connection_add_wcap (SunOneConnection *connection, const char *key, gpointer wcap)
{
	SunOneConnectionPrivate *priv = connection->priv;

	if (g_hash_table_lookup(priv->wcaps, key)) {
		return;
	}
	
	g_hash_table_insert (priv->wcaps, g_strdup (key), wcap);
}

gpointer
sunone_connection_get_wcap (SunOneConnection *connection, const char *key)
{
	SunOneConnectionPrivate *priv = connection->priv;

	return g_hash_table_lookup (priv->wcaps, key);
}

guint
sunone_connection_version (SunOneConnection *connection)
{
	SunOneConnectionPrivate *priv = connection->priv;
	SoupURI *fetch_uri;
	SoupMessage *msg;
	guint retval;
	icalcomponent *icalcomp;
	icalproperty *icalprop;

	g_return_val_if_fail (IS_SUNONE_CONNECTION (connection), SOUP_STATUS_CANCELLED);

	IS_CONNECTED (connection, SOUP_STATUS_CANT_CONNECT);

	/* prepare the URI */
	NEW_URI (fetch_uri);
	fetch_uri->path = g_strdup ("/version.wcap");
	fetch_uri->query = g_strdup_printf (
		"fmt-out=text/calendar");

	/* send the request to the server */
	msg = sunone_message_new_from_uri (fetch_uri, SOUP_METHOD_GET);
	sunone_message_send (connection, msg);
	retval = msg->status_code;

	if (SUNONE_ERROR_IS_SUCCESSFUL (msg->status_code)) {
		icalcomp = sunone_util_icalparser_parse_string (msg->response_body->data, msg->response_body->length);
		if (!icalcomp)
			retval = SOUP_STATUS_MALFORMED;
		else {
			icalprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);
			while (icalprop) {
				const char *x_name, *x_val;
				x_name = icalproperty_get_x_name (icalprop);
				x_val = icalproperty_get_x (icalprop);

				if (!strcmp (x_name, "X-NSCP-WCAPVERSION"))
					priv->wcap_version = g_strdup (x_val);
				icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY);
			}
			icalcomponent_free (icalcomp);
		}
       	}

	if (!priv->wcap_version)
		priv->wcap_version = g_strdup ("2.0");

	g_object_unref (msg);
	soup_uri_free (fetch_uri);

	return retval;
}

guint
sunone_message_send (SunOneConnection *connection, SoupMessage *msg)
{
	SunOneConnectionPrivate *priv = connection->priv;
	guint status;

	setup_message (msg);
	status = soup_session_send_message (priv->soup_session, msg);
	if (!SOUP_STATUS_IS_TRANSPORT_ERROR(status))
		parse_server_response (msg, &status);

	return status;
}

guint
sunone_message_send_no_parse (SunOneConnection *connection, SoupMessage *msg)
{
	SunOneConnectionPrivate *priv = connection->priv;

	setup_message (msg);
	return soup_session_send_message (priv->soup_session, msg);
}
