balsa r8070 - in trunk: libbalsa src



Author: pawels
Date: Sun Feb  8 16:58:00 2009
New Revision: 8070
URL: http://svn.gnome.org/viewvc/balsa?rev=8070&view=rev

Log:
Add files missing in last commit.

Added:
   trunk/libbalsa/rfc2445.c
   trunk/libbalsa/rfc2445.h
   trunk/src/balsa-mime-widget-vcalendar.c
   trunk/src/balsa-mime-widget-vcalendar.h

Added: trunk/libbalsa/rfc2445.c
==============================================================================
--- (empty file)
+++ trunk/libbalsa/rfc2445.c	Sun Feb  8 16:58:00 2009
@@ -0,0 +1,744 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * VCalendar (RFC 2445) stuff
+ * Copyright (C) 2009 Albrecht Dreß<albrecht dress arcor de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option) 
+ * any later version.
+ *  
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
+ * GNU General Public License for more details.
+ *  
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include "libbalsa.h"
+#include "rfc2445.h"
+
+
+/* participant roles as defined by RFC 2446 */
+typedef enum {
+    VCAL_ROLE_UNKNOWN = 0,
+    VCAL_ROLE_CHAIR,
+    VCAL_ROLE_REQ_PART,
+    VCAL_ROLE_OPT_PART,
+    VCAL_ROLE_NON_PART
+} LibBalsaVCalRole;
+
+
+static GObjectClass *libbalsa_vcal_parent_class = NULL;
+static GObjectClass *libbalsa_vevent_parent_class = NULL;
+
+
+/* LibBalsaAddress extra object data */
+#define RFC2445_ROLE            "RFC2445:Role"
+#define RFC2445_PARTSTAT        "RFC2445:PartStat"
+#define RFC2445_RSVP            "RFC2445:RSVP"
+
+
+static void libbalsa_vcal_class_init(LibBalsaVCalClass * klass);
+static void libbalsa_vcal_init(LibBalsaVCal * self);
+static void libbalsa_vcal_finalize(LibBalsaVCal * self);
+
+static void libbalsa_vevent_class_init(LibBalsaVEventClass * klass);
+static void libbalsa_vevent_init(LibBalsaVEvent * self);
+static void libbalsa_vevent_finalize(LibBalsaVEvent * self);
+
+static LibBalsaAddress *cal_address_2445_to_lbaddress(const gchar * uri,
+						      gchar ** attributes,
+						      gboolean
+						      is_organizer);
+
+/* conversion helpers */
+static time_t date_time_2445_to_time_t(const gchar * date_time);
+static gchar *time_t_to_date_time_2445(time_t ttime);
+static gchar *text_2445_unescape(const gchar * text);
+static gchar *text_2445_escape(const gchar * text);
+static const gchar *vcal_role_to_str(LibBalsaVCalRole role);
+static LibBalsaVCalMethod vcal_str_to_method(const gchar * method);
+static LibBalsaVCalRole vcal_str_to_role(const gchar * role);
+static LibBalsaVCalPartStat vcal_str_to_part_stat(const gchar * pstat);
+
+
+static struct {
+    gchar *str_2445;
+    gchar *hr_text;
+} pstats[] = {
+    { NULL, N_("unknown") },
+    { "NEEDS-ACTION", N_("needs action") },
+    { "ACCEPTED", N_("accepted") },
+    { "DECLINED", N_("declined") },
+    { "TENTATIVE", N_("tentatively accepted") },
+    { "DELEGATED", N_("delegated") },
+    { "COMPLETED", N_("completed") },
+    { "IN-PROCESS", N_("in process") }
+};
+
+
+/* --- VCal GObject stuff --- */
+GType
+libbalsa_vcal_get_type(void)
+{
+    static GType libbalsa_vcal_type = 0;
+
+    if (!libbalsa_vcal_type) {
+	static const GTypeInfo libbalsa_vcal_type_info = {
+	    sizeof(LibBalsaVCalClass),	/* class_size */
+	    NULL,		/* base_init */
+	    NULL,		/* base_finalize */
+	    (GClassInitFunc) libbalsa_vcal_class_init,	/* class_init */
+	    NULL,		/* class_finalize */
+	    NULL,		/* class_data */
+	    sizeof(LibBalsaVCal),	/* instance_size */
+	    0,			/* n_preallocs */
+	    (GInstanceInitFunc) libbalsa_vcal_init,	/* instance_init */
+	    /* no value_table */
+	};
+
+	libbalsa_vcal_type =
+	    g_type_register_static(G_TYPE_OBJECT, "LibBalsaVCal",
+				   &libbalsa_vcal_type_info, 0);
+    }
+
+    return libbalsa_vcal_type;
+}
+
+
+static void
+libbalsa_vcal_class_init(LibBalsaVCalClass * klass)
+{
+    GObjectClass *gobject_klass = G_OBJECT_CLASS(klass);
+
+    libbalsa_vcal_parent_class = g_type_class_peek(G_TYPE_OBJECT);
+    gobject_klass->finalize = (GObjectFinalizeFunc) libbalsa_vcal_finalize;
+}
+
+
+static void
+libbalsa_vcal_init(LibBalsaVCal * self)
+{
+    self->method = ITIP_UNKNOWN;
+    self->vevent = NULL;
+}
+
+
+static void
+libbalsa_vcal_finalize(LibBalsaVCal * self)
+{
+    g_return_if_fail(self != NULL);
+    if (self->vevent) {
+	g_list_foreach(self->vevent, (GFunc) g_object_unref, NULL);
+	g_list_free(self->vevent);
+    }
+
+    libbalsa_vcal_parent_class->finalize(G_OBJECT(self));
+}
+
+
+LibBalsaVCal *
+libbalsa_vcal_new(void)
+{
+    return LIBBALSA_VCAL(g_object_new(LIBBALSA_TYPE_VCAL, NULL));
+}
+
+
+/* --- VEvent GObject stuff --- */
+GType
+libbalsa_vevent_get_type(void)
+{
+    static GType libbalsa_vevent_type = 0;
+
+    if (!libbalsa_vevent_type) {
+	static const GTypeInfo libbalsa_vevent_type_info = {
+	    sizeof(LibBalsaVEventClass),	/* class_size */
+	    NULL,		/* base_init */
+	    NULL,		/* base_finalize */
+	    (GClassInitFunc) libbalsa_vevent_class_init,	/* class_init */
+	    NULL,		/* class_finalize */
+	    NULL,		/* class_data */
+	    sizeof(LibBalsaVEvent),	/* instance_size */
+	    0,			/* n_preallocs */
+	    (GInstanceInitFunc) libbalsa_vevent_init,	/* instance_init */
+	    /* no value_table */
+	};
+
+	libbalsa_vevent_type =
+	    g_type_register_static(G_TYPE_OBJECT, "LibBalsaVEvent",
+				   &libbalsa_vevent_type_info, 0);
+    }
+
+    return libbalsa_vevent_type;
+}
+
+
+static void
+libbalsa_vevent_class_init(LibBalsaVEventClass * klass)
+{
+    GObjectClass *gobject_klass = G_OBJECT_CLASS(klass);
+
+    libbalsa_vevent_parent_class = g_type_class_peek(G_TYPE_OBJECT);
+    gobject_klass->finalize =
+	(GObjectFinalizeFunc) libbalsa_vevent_finalize;
+}
+
+
+static void
+libbalsa_vevent_init(LibBalsaVEvent * self)
+{
+    self->start = self->end = self->stamp = (time_t) - 1;
+}
+
+
+static void
+libbalsa_vevent_finalize(LibBalsaVEvent * self)
+{
+    g_return_if_fail(self != NULL);
+
+    if (self->organizer)
+	g_object_unref(self->organizer);
+    if (self->attendee) {
+	g_list_foreach(self->attendee, (GFunc) g_object_unref, NULL);
+	g_list_free(self->attendee);
+    }
+    g_free(self->uid);
+    g_free(self->summary);
+    g_free(self->location);
+    g_free(self->description);
+
+    libbalsa_vevent_parent_class->finalize(G_OBJECT(self));
+}
+
+
+LibBalsaVEvent *
+libbalsa_vevent_new(void)
+{
+    return LIBBALSA_VEVENT(g_object_new(LIBBALSA_TYPE_VEVENT, NULL));
+}
+
+
+#define STR_REPL_2445_TXT(s, v)                 \
+    do {                                        \
+        g_free(s);                              \
+        s = text_2445_unescape(v);              \
+    } while (0)
+
+/* parse a text/calendar part and convert it into a LibBalsaVCal object */
+LibBalsaVCal *
+libbalsa_vcal_new_from_body(LibBalsaMessageBody * body)
+{
+    LibBalsaVCal *retval;
+    gchar *charset;
+    gchar *method;
+    gchar *vcal_buf;
+    gchar *p;
+    gchar **lines;
+    LibBalsaVEvent *event;
+    int k;
+
+    g_return_val_if_fail(body != NULL, NULL);
+
+    /* get the method parameter which is not required, but we ignore all parts
+     * which don't have it */
+    method = libbalsa_message_body_get_parameter(body, "method");
+    g_return_val_if_fail(method != NULL, NULL);
+
+    /* get the body buffer */
+    if (libbalsa_message_body_get_content(body, &vcal_buf, NULL) <= 0)
+	return NULL;
+
+    /* check if the body has a charset (default is utf-8, see '2445, sect 4.1.4) */
+    charset = libbalsa_message_body_get_parameter(body, "charset");
+    if (charset && g_ascii_strcasecmp(charset, "utf-8")) {
+	gsize written;
+	gchar *conv_buf;
+
+	conv_buf =
+	    g_convert(vcal_buf, -1, "utf-8", charset, NULL, &written,
+		      NULL);
+	g_free(vcal_buf);
+	vcal_buf = conv_buf;
+    }
+    g_free(charset);
+    if (!vcal_buf)
+	return NULL;
+
+    /* o.k., create a new object */
+    retval = libbalsa_vcal_new();
+    retval->method = vcal_str_to_method(method);
+    g_free(method);
+
+    /* unfold the body and split into lines */
+    p = strchr(vcal_buf, '\n');
+    while (p) {
+	if (p[1] == '\t' || p[1] == ' ')
+	    memmove(p, p + 2, strlen(p + 2) + 1);
+	p = strchr(p + 1, '\n');
+    }
+    lines = g_strsplit(vcal_buf, "\n", -1);
+    g_free(vcal_buf);
+
+    /* scan lines to extract vevent(s) */
+    event = NULL;
+    for (k = 0; lines[k]; k++) {
+	if (!event) {
+	    if (!g_ascii_strcasecmp("BEGIN:VEVENT", lines[k]))
+		event = libbalsa_vevent_new();
+	} else {
+	    gchar *value = strchr(lines[k], ':');
+	    gchar **entry;
+
+	    if (value)
+		*value++ = '\0';
+	    entry = g_strsplit(lines[k], ";", -1);
+	    if (!g_ascii_strcasecmp(entry[0], "END")) {
+		retval->vevent = g_list_append(retval->vevent, event);
+		event = NULL;
+	    } else if (!g_ascii_strcasecmp(entry[0], "DTSTART"))
+		event->start = date_time_2445_to_time_t(value);
+	    else if (!g_ascii_strcasecmp(entry[0], "DTEND"))
+		event->end = date_time_2445_to_time_t(value);
+	    else if (!g_ascii_strcasecmp(entry[0], "DTSTAMP"))
+		event->stamp = date_time_2445_to_time_t(value);
+	    else if (!g_ascii_strcasecmp(entry[0], "UID"))
+		STR_REPL_2445_TXT(event->uid, value);
+	    else if (!g_ascii_strcasecmp(entry[0], "SUMMARY"))
+		STR_REPL_2445_TXT(event->summary, value);
+	    else if (!g_ascii_strcasecmp(entry[0], "LOCATION"))
+		STR_REPL_2445_TXT(event->location, value);
+	    else if (!g_ascii_strcasecmp(entry[0], "DESCRIPTION"))
+		STR_REPL_2445_TXT(event->description, value);
+	    else if (!g_ascii_strcasecmp(entry[0], "ORGANIZER")) {
+		if (event->organizer)
+		    g_object_unref(event->organizer);
+		event->organizer =
+		    cal_address_2445_to_lbaddress(value, entry + 1, TRUE);
+	    } else if (!g_ascii_strcasecmp(entry[0], "ATTENDEE"))
+		event->attendee =
+		    g_list_prepend(event->attendee,
+				   cal_address_2445_to_lbaddress(value,
+								 entry + 1,
+								 FALSE));
+	    g_strfreev(entry);
+	}
+    }
+    g_strfreev(lines);
+    if (event)
+	g_object_unref(event);
+
+    return retval;
+}
+
+
+/* return a rfc 2445 attendee (i.e. a LibBalsaAddress w/ extra information)
+ * as a human-readable string */
+gchar *
+libbalsa_vcal_attendee_to_str(LibBalsaAddress * person)
+{
+    GString *retval;
+    gchar *str;
+    LibBalsaVCalRole role;
+    LibBalsaVCalPartStat pstat;
+
+    g_return_val_if_fail(LIBBALSA_IS_ADDRESS(person), NULL);
+
+    retval = g_string_new("");
+
+    role =
+	(LibBalsaVCalRole) g_object_get_data(G_OBJECT(person),
+					     RFC2445_ROLE);
+    if (role != VCAL_ROLE_UNKNOWN)
+	g_string_printf(retval, "%s ", vcal_role_to_str(role));
+
+    str = libbalsa_address_to_gchar(person, -1);
+    retval = g_string_append(retval, str);
+    g_free(str);
+
+    pstat =
+	(LibBalsaVCalPartStat) g_object_get_data(G_OBJECT(person),
+						 RFC2445_PARTSTAT);
+    if (pstat != VCAL_PSTAT_UNKNOWN)
+	g_string_append_printf(retval, " (%s)",
+			       libbalsa_vcal_part_stat_to_str(pstat));
+
+    return g_string_free(retval, FALSE);
+}
+
+
+/* check if a rfc 2445 attendee (i.e. a LibBalsaAddress w/ extra information)
+ * has the RSVP flag ("please reply") set */
+gboolean
+libbalsa_vcal_attendee_rsvp(LibBalsaAddress * person)
+{
+    g_return_val_if_fail(LIBBALSA_IS_ADDRESS(person), FALSE);
+    return (gboolean) 
+	GPOINTER_TO_INT(g_object_get_data(G_OBJECT(person), RFC2445_RSVP));
+}
+
+
+/* return a new buffer containing a proper reply to an event for a new
+ * participant status */
+gchar *
+libbalsa_vevent_reply(const LibBalsaVEvent * event, const gchar * sender,
+		      LibBalsaVCalPartStat new_stat)
+{
+    GString *retval;
+    gchar *buf;
+    gssize p;
+    gchar *eol;
+
+    /* check for allowed new state and sender */
+    g_return_val_if_fail((new_stat == VCAL_PSTAT_ACCEPTED ||
+			  new_stat == VCAL_PSTAT_DECLINED ||
+			  new_stat == VCAL_PSTAT_TENTATIVE) &&
+			 sender != NULL, NULL);
+
+    /* build the part */
+    retval = g_string_new("BEGIN:VCALENDAR\nVERSION:2.0\n"
+			  "PRODID:-//GNOME//Balsa " BALSA_VERSION "//EN\n"
+			  "METHOD:REPLY\nBEGIN:VEVENT\n");
+    buf = time_t_to_date_time_2445(time(NULL));
+    g_string_append_printf(retval, "DTSTAMP:%s\n", buf);
+    g_free(buf);
+    g_string_append_printf(retval, "ATTENDEE;PARTSTAT=%s:mailto:%s\n";,
+			   pstats[(int) new_stat].str_2445, sender);
+    if (event->uid)
+	g_string_append_printf(retval, "UID:%s\n", event->uid);
+    if (event->organizer && event->organizer->address_list)
+	g_string_append_printf(retval, "ORGANIZER:mailto:%s\n";,
+			       (gchar *) event->organizer->address_list->
+			       data);
+    if (event->summary) {
+	buf = text_2445_escape(event->summary);
+	g_string_append_printf(retval, "SUMMARY:%s\n", buf);
+	g_free(buf);
+    }
+    if (event->description) {
+	buf = text_2445_escape(event->description);
+	g_string_append_printf(retval, "DESCRIPTION:%s\n", buf);
+	g_free(buf);
+    }
+    if (event->start != (time_t) - 1) {
+	buf = time_t_to_date_time_2445(event->start);
+	g_string_append_printf(retval, "DTSTART:%s\n", buf);
+	g_free(buf);
+    }
+    if (event->end != (time_t) - 1) {
+	buf = time_t_to_date_time_2445(event->end);
+	g_string_append_printf(retval, "DTEND:%s\n", buf);
+	g_free(buf);
+    }
+    retval = g_string_append(retval, "END:VEVENT\nEND:VCALENDAR");
+
+    /* fold */
+    p = 0;
+    eol = strchr(retval->str, '\n');
+    while (eol) {
+	if (eol - (retval->str + p) > 74) {
+	    retval = g_string_insert(retval, p + 74, "\n ");
+	    p += 76;
+	} else
+	    p = eol - retval->str + 1;
+	eol = strchr(retval->str + p, '\n');
+    }
+
+    /* done */
+    return g_string_free(retval, FALSE);
+}
+
+
+/* -- rfc 2445 parser helper functions -- */
+
+#define FILL_TM(tm, buf, idx)                   \
+    do {                                        \
+        tm = atoi(buf + idx);                   \
+        buf[idx] = '\0';                        \
+    } while (0)
+
+/* convert a rfc 2445 time string into a time_t value */
+// FIXME - what about entries containing a TZID?
+static time_t
+date_time_2445_to_time_t(const gchar * date_time)
+{
+    gint len;
+    struct tm tm_buf;
+    char strbuf[17];
+    gboolean is_utc;
+    time_t the_time;
+    struct tm utc_tm;
+    time_t utc_time;
+    gint diff_min;
+
+    g_return_val_if_fail(date_time != NULL, (time_t) - 1);
+    len = strlen(date_time);
+
+    /* must be yyyymmddThhmmssZ? */
+    if (len < 15 || len > 16 || date_time[8] != 'T' ||
+	(len == 16 && date_time[15] != 'Z'))
+	return (time_t) - 1;
+
+    /* fill for conversion... */
+    strcpy(strbuf, date_time);	/* safe, due to checks above */
+    if (len == 16) {
+	is_utc = TRUE;
+	strbuf[15] = '\0';
+    }
+    FILL_TM(tm_buf.tm_sec, strbuf, 13);
+    FILL_TM(tm_buf.tm_min, strbuf, 11);
+    FILL_TM(tm_buf.tm_hour, strbuf, 9);
+    FILL_TM(tm_buf.tm_mday, strbuf, 6);
+    FILL_TM(tm_buf.tm_mon, strbuf, 4);
+    FILL_TM(tm_buf.tm_year, strbuf, 0);
+    tm_buf.tm_mon--;
+    tm_buf.tm_year -= 1900;
+    tm_buf.tm_isdst = -1;
+    the_time = mktime(&tm_buf);
+
+    /* return value if local time was requested */
+    if (len == 15)
+	return the_time;
+
+    /* adjust for utc */
+    gmtime_r(&the_time, &utc_tm);
+    utc_tm.tm_isdst = -1;
+    utc_time = mktime(&utc_tm);
+    diff_min = (utc_time - the_time) / 60;
+    tm_buf.tm_min -= diff_min % 60;
+    tm_buf.tm_hour -= diff_min / 60;
+    tm_buf.tm_isdst = -1;
+    the_time = mktime(&tm_buf);
+
+    return the_time;
+}
+
+
+static gchar *
+time_t_to_date_time_2445(time_t ttime)
+{
+    gchar *retval = g_malloc(17);
+    struct tm tm;
+
+    gmtime_r(&ttime, &tm);
+    strftime(retval, 17, "%Y%m%dT%H%M%SZ", &tm);
+    return retval;
+}
+
+
+/* unescape a text from rfc 2445 format to a plain string */
+static gchar *
+text_2445_unescape(const gchar * text)
+{
+    gchar *retval;
+    gchar *p;
+
+    g_return_val_if_fail(text != NULL, NULL);
+    retval = g_strdup(text);
+    p = strchr(retval, '\\');
+    while (p) {
+	if (p[1] == 'n')
+	    p[1] = '\n';
+	memmove(p, p + 1, strlen(p + 1) + 1);
+	p = strchr(retval, '\\');
+    }
+    return retval;
+}
+
+
+/* unescape a text from rfc 2445 format to a plain string */
+static gchar *
+text_2445_escape(const gchar * text)
+{
+    GString *retval;
+    static const gchar *do_escape = "\\;,\n";
+    const gchar *esc_p;
+
+    g_return_val_if_fail(text != NULL, NULL);
+    retval = g_string_new(text);
+    for (esc_p = do_escape; *esc_p; esc_p++) {
+	gchar *enc_p = strchr(retval->str, *esc_p);
+
+	while (enc_p) {
+	    gssize inspos = enc_p - retval->str;
+
+	    if (*enc_p == '\n')
+		*enc_p = 'n';
+	    retval = g_string_insert_c(retval, inspos, '\\');
+	    enc_p = strchr(retval->str + inspos + 2, *esc_p);
+	}
+    }
+    return g_string_free(retval, FALSE);
+}
+
+
+/* extract a rfc 2445 mailto address and into a LibBalsaAddress and add the
+ * extra information (rfc 2445 attributes) as data to the returned object. */
+static LibBalsaAddress *
+cal_address_2445_to_lbaddress(const gchar * uri, gchar ** attributes,
+			      gboolean is_organizer)
+{
+    LibBalsaAddress *retval;
+
+    /* must be a mailto: uri */
+    if (g_ascii_strncasecmp("mailto:";, uri, 7))
+	return NULL;
+
+    retval = libbalsa_address_new();
+    retval->address_list = g_list_prepend(NULL, g_strdup(uri + 7));
+    if (!is_organizer)
+	g_object_set_data(G_OBJECT(retval), RFC2445_ROLE,
+			  (gpointer) VCAL_ROLE_REQ_PART);
+
+    if (attributes) {
+	int n;
+	gchar *the_attr;
+
+	/* scan attributes for extra information */
+	for (n = 0; (the_attr = attributes[n]); n++) {
+	    if (!g_ascii_strncasecmp(the_attr, "CN=", 3))
+		retval->full_name = g_strdup(the_attr + 3);
+	    else if (!g_ascii_strncasecmp(the_attr, "ROLE=", 5))
+		g_object_set_data(G_OBJECT(retval), RFC2445_ROLE,
+				  (gpointer) vcal_str_to_role(the_attr + 5));
+	    else if (!g_ascii_strncasecmp(the_attr, "PARTSTAT=", 9))
+		g_object_set_data(G_OBJECT(retval), RFC2445_PARTSTAT,
+				  (gpointer) vcal_str_to_part_stat(the_attr + 9));
+	    else if (!g_ascii_strncasecmp(the_attr, "RSVP=", 5))
+		g_object_set_data(G_OBJECT(retval), RFC2445_RSVP,
+				  GINT_TO_POINTER(! g_ascii_strcasecmp(the_attr + 5, "TRUE")));
+	}
+    }
+    return retval;
+}
+
+
+/* -- conversion helpers string <--> enumeration -- */
+
+/* convert the passed method string into the enumeration */
+static LibBalsaVCalMethod
+vcal_str_to_method(const gchar * method)
+{
+    static struct {
+	gchar *meth_id;
+	LibBalsaVCalMethod meth;
+    } meth_list[] = {
+	{ "PUBLISH", ITIP_PUBLISH },
+        { "REQUEST", ITIP_REQUEST },
+        { "REPLY", ITIP_REPLY },
+        { "CANCEL", ITIP_CANCEL },
+        { NULL, ITIP_UNKNOWN}
+    };
+
+    gint n;
+
+    for (n = 0;
+	 meth_list[n].meth_id
+	 && g_ascii_strcasecmp(method, meth_list[n].meth_id); n++);
+    return meth_list[n].meth;
+}
+
+
+/* return a rfc 2445 method as human-readable string */
+const gchar *
+libbalsa_vcal_method_to_str(LibBalsaVCalMethod method)
+{
+    static gchar *methods[] = {
+        N_("unknown"),
+	N_("Event Notification"),
+	N_("Event Request"),
+	N_("Reply to Event Request"),
+	N_("Event Cancellation"),
+    };
+
+    g_return_val_if_fail(method >= ITIP_UNKNOWN && method <= ITIP_CANCEL,
+			 NULL);
+    return methods[(int) method];
+
+}
+
+
+/* return a rfc 2445 role as human-readable string */
+static const gchar *
+vcal_role_to_str(LibBalsaVCalRole role)
+{
+    static gchar *roles[] = {
+        N_("unknown"),
+	N_("chair"),
+	N_("required participant"),
+	N_("optional participant"),
+	N_("non-participant, information only")
+    };
+
+    g_return_val_if_fail(role >= VCAL_ROLE_UNKNOWN
+			 && role <= VCAL_ROLE_NON_PART, NULL);
+    return roles[(int) role];
+}
+
+
+/* convert the passed role string into the enumeration */
+static LibBalsaVCalRole
+vcal_str_to_role(const gchar * role)
+{
+    static struct {
+	gchar *role_id;
+	LibBalsaVCalRole role;
+    } role_list[] = {
+	{ "CHAIR", VCAL_ROLE_CHAIR },
+        { "REQ-PARTICIPANT", VCAL_ROLE_REQ_PART },
+        { "OPT-PARTICIPANT", VCAL_ROLE_OPT_PART },
+        { "NON-PARTICIPANT", VCAL_ROLE_NON_PART },
+        { NULL, VCAL_ROLE_UNKNOWN}
+    };
+    gint n;
+
+    for (n = 0;
+	 role_list[n].role_id
+	 && g_ascii_strcasecmp(role, role_list[n].role_id); n++);
+    return role_list[n].role;
+}
+
+
+/* return a rfc 2445 participant status as human-readable string */
+const gchar *
+libbalsa_vcal_part_stat_to_str(LibBalsaVCalPartStat pstat)
+{
+    g_return_val_if_fail(pstat >= VCAL_PSTAT_UNKNOWN
+			 && pstat <= VCAL_PSTAT_IN_PROCESS, NULL);
+    return pstats[(int) pstat].hr_text;
+
+}
+
+
+/* convert the passed participant status string into the enumeration */
+static LibBalsaVCalPartStat
+vcal_str_to_part_stat(const gchar * pstat)
+{
+    static struct {
+	gchar *pstat_id;
+	LibBalsaVCalPartStat pstat;
+    } pstat_list[] = {
+	{ "NEEDS-ACTION", VCAL_PSTAT_NEEDS_ACTION },
+        { "ACCEPTED", VCAL_PSTAT_ACCEPTED},
+        { "DECLINED", VCAL_PSTAT_DECLINED },
+        { "TENTATIVE", VCAL_PSTAT_TENTATIVE },
+        { "DELEGATED", VCAL_PSTAT_DELEGATED },
+        { "COMPLETED", VCAL_PSTAT_COMPLETED },
+        { "IN-PROCESS", VCAL_PSTAT_IN_PROCESS },
+        { NULL, VCAL_PSTAT_UNKNOWN }
+    };
+    gint n;
+
+    for (n = 0;
+	 pstat_list[n].pstat_id
+	 && g_ascii_strcasecmp(pstat, pstat_list[n].pstat_id); n++);
+    return pstat_list[n].pstat;
+}

Added: trunk/libbalsa/rfc2445.h
==============================================================================
--- (empty file)
+++ trunk/libbalsa/rfc2445.h	Sun Feb  8 16:58:00 2009
@@ -0,0 +1,135 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * VCalendar (RFC 2445) stuff
+ * Copyright (C) 2009 Albrecht Dreß<albrecht dress arcor de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option) 
+ * any later version.
+ *  
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
+ * GNU General Public License for more details.
+ *  
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
+ * 02111-1307, USA.
+ */
+
+#ifndef __RFC2445_H__
+#define __RFC2445_H__
+
+
+#include <glib.h>
+#include <glib-object.h>
+#include <time.h>
+#include "body.h"
+#include "address.h"
+
+
+G_BEGIN_DECLS
+
+/* a VCalendar object description as GObject */
+#define LIBBALSA_TYPE_VCAL            (libbalsa_vcal_get_type())
+#define LIBBALSA_VCAL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), LIBBALSA_TYPE_VCAL, LibBalsaVCal))
+#define LIBBALSA_VCAL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), LIBBALSA_TYPE_VCAL, LibBalsaVCalClass))
+#define LIBBALSA_IS_VCAL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), LIBBALSA_TYPE_VCAL))
+#define LIBBALSA_IS_VCAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), LIBBALSA_TYPE_VCAL))
+#define LIBBALSA_VCAL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), LIBBALSA_TYPE_VCAL, LibBalsaVCalClass))
+
+typedef struct _LibBalsaVCal LibBalsaVCal;
+typedef struct _LibBalsaVCalClass LibBalsaVCalClass;
+
+
+/* a VEvent object description as GObject */
+#define LIBBALSA_TYPE_VEVENT            (libbalsa_vevent_get_type())
+#define LIBBALSA_VEVENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), LIBBALSA_TYPE_VEVENT, LibBalsaVEvent))
+#define LIBBALSA_VEVENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), LIBBALSA_TYPE_VEVENT, LibBalsaVEventClass))
+#define LIBBALSA_IS_VEVENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), LIBBALSA_TYPE_VEVENT))
+#define LIBBALSA_IS_VEVENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), LIBBALSA_TYPE_VEVENT))
+#define LIBBALSA_VEVENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), LIBBALSA_TYPE_VEVENT, LibBalsaVEventClass))
+
+typedef struct _LibBalsaVEvent LibBalsaVEvent;
+typedef struct _LibBalsaVEventClass LibBalsaVEventClass;
+
+
+/* methods as defined by RFC 2446 */
+typedef enum {
+    ITIP_UNKNOWN = 0,
+    ITIP_PUBLISH,
+    ITIP_REQUEST,
+    ITIP_REPLY,
+    ITIP_CANCEL
+} LibBalsaVCalMethod;
+
+/* participation status as defined by RFC 2445
+ * (note: includes constants for VTODO) */
+typedef enum {
+    VCAL_PSTAT_UNKNOWN = 0,
+    VCAL_PSTAT_NEEDS_ACTION,
+    VCAL_PSTAT_ACCEPTED,
+    VCAL_PSTAT_DECLINED,
+    VCAL_PSTAT_TENTATIVE,
+    VCAL_PSTAT_DELEGATED,
+    VCAL_PSTAT_COMPLETED,
+    VCAL_PSTAT_IN_PROCESS
+} LibBalsaVCalPartStat;
+
+
+struct _LibBalsaVCal {
+    GObject parent;
+
+    /* method */
+    LibBalsaVCalMethod method;
+
+    /* linked list of VEVENT entries */
+    GList *vevent;
+};
+
+
+struct _LibBalsaVEvent {
+    GObject parent;
+
+    LibBalsaAddress *organizer;
+    GList *attendee;
+    time_t stamp;
+    time_t start;
+    time_t end;
+    gchar *uid;
+    gchar *summary;
+    gchar *location;
+    gchar *description;
+};
+
+
+struct _LibBalsaVCalClass {
+    GObjectClass parent;
+};
+
+
+struct _LibBalsaVEventClass {
+    GObjectClass parent;
+};
+
+
+GType libbalsa_vcal_get_type(void);
+LibBalsaVCal *libbalsa_vcal_new(void);
+LibBalsaVCal *libbalsa_vcal_new_from_body(LibBalsaMessageBody * body);
+
+GType libbalsa_vevent_get_type(void);
+LibBalsaVEvent *libbalsa_vevent_new(void);
+gchar *libbalsa_vevent_reply(const LibBalsaVEvent * event,
+			     const gchar * sender,
+			     LibBalsaVCalPartStat new_stat);
+
+gchar *libbalsa_vcal_attendee_to_str(LibBalsaAddress * person);
+gboolean libbalsa_vcal_attendee_rsvp(LibBalsaAddress * person);
+const gchar *libbalsa_vcal_method_to_str(LibBalsaVCalMethod method);
+const gchar *libbalsa_vcal_part_stat_to_str(LibBalsaVCalPartStat pstat);
+
+G_END_DECLS
+
+#endif				/* __RFC2445_H__ */

Added: trunk/src/balsa-mime-widget-vcalendar.c
==============================================================================
--- (empty file)
+++ trunk/src/balsa-mime-widget-vcalendar.c	Sun Feb  8 16:58:00 2009
@@ -0,0 +1,301 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ * Copyright (C) 1997-2001 Stuart Parmenter and others,
+ *                         See the file AUTHORS for a list.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *  
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
+ * GNU General Public License for more details.
+ *  
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "libbalsa.h"
+#include "rfc2445.h"
+#include "send.h"
+#include "balsa-app.h"
+#include <glib/gi18n.h>
+#include "balsa-mime-widget.h"
+#include "balsa-mime-widget-callbacks.h"
+#include "balsa-mime-widget-vcalendar.h"
+
+
+static GtkWidget *balsa_vevent_widget(LibBalsaVEvent * event,
+				      gboolean may_reply,
+				      InternetAddress * sender);
+static void vevent_reply(GObject * button, GtkWidget * box);
+
+
+BalsaMimeWidget *
+balsa_mime_widget_new_vcalendar(BalsaMessage * bm,
+				LibBalsaMessageBody * mime_body,
+				const gchar * content_type, gpointer data)
+{
+    LibBalsaVCal *vcal_obj;
+    BalsaMimeWidget *mw;
+    GtkWidget *label;
+    gchar *text;
+    GList *l;
+    LibBalsaMessage *lbm = bm->message;
+    gboolean may_reply = FALSE;
+    InternetAddress *sender = NULL;;
+
+    g_return_val_if_fail(mime_body != NULL, NULL);
+    g_return_val_if_fail(content_type != NULL, NULL);
+    g_return_val_if_fail(lbm != NULL, NULL);
+
+    vcal_obj = libbalsa_vcal_new_from_body(mime_body);
+    if (!vcal_obj)
+	return NULL;
+
+    mw = g_object_new(BALSA_TYPE_MIME_WIDGET, NULL);
+    mw->widget = gtk_vbox_new(FALSE, 12);
+
+    text = g_strdup_printf(_("This is an iTIP calendar \"%s\" message."),
+			   libbalsa_vcal_method_to_str(vcal_obj->method));
+    label = gtk_label_new(text);
+    g_free(text);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(mw->widget), label);
+
+    /* a reply may be created only for unread requests */
+    if ((vcal_obj->method == ITIP_REQUEST) &&
+	LIBBALSA_MESSAGE_IS_UNREAD(lbm)) {
+	may_reply = TRUE;
+	if (lbm->headers) {
+	    if (lbm->headers->reply_to)
+		sender = lbm->headers->reply_to->address;
+	    else if (lbm->headers && lbm->headers->from)
+		sender = lbm->headers->from->address;
+	} else if (lbm->sender)
+	    sender = lbm->sender->address;
+    }
+
+    /* add events */
+    for (l = vcal_obj->vevent; l; l = g_list_next(l)) {
+	GtkWidget *event =
+	    balsa_vevent_widget((LibBalsaVEvent *) l->data, may_reply,
+				sender);
+	gtk_container_add(GTK_CONTAINER(mw->widget), event);
+    }
+
+    gtk_widget_show_all(mw->widget);
+    g_object_unref(vcal_obj);
+
+    return mw;
+}
+
+#define TABLE_ATTACH(t,str,label)                                       \
+    do {                                                                \
+        if (str) {                                                      \
+            GtkWidget *lbl = gtk_label_new(label);                      \
+            gtk_table_attach(t, lbl, 0, 1, row, row+1,                  \
+                             GTK_FILL, GTK_FILL, 4, 2);                 \
+            gtk_misc_set_alignment(GTK_MISC(lbl), 1.0, 0.0);            \
+            gtk_table_attach(table, lbl = gtk_label_new(str),           \
+                             1, 2, row, row + 1,                        \
+                             GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND,  \
+                             4, 2);                                     \
+            gtk_misc_set_alignment(GTK_MISC(lbl), 0.0, 0.0);            \
+            row++;                                                      \
+        }                                                               \
+    } while (0)
+
+#define TABLE_ATTACH_DATE(t,date,label)                                 \
+    do {                                                                \
+        if (date != (time_t) -1) {                                      \
+            gchar * _dstr =                                             \
+                libbalsa_date_to_utf8(&date, balsa_app.date_string);    \
+            TABLE_ATTACH(table, _dstr, label);                          \
+            g_free(_dstr);                                              \
+        }                                                               \
+    } while (0)
+
+#define TABLE_ATTACH_ADDRESS(t,addr,label)                              \
+    do {                                                                \
+        if (addr) {                                                     \
+            gchar * _astr = libbalsa_vcal_attendee_to_str(addr);        \
+            TABLE_ATTACH(table, _astr, label);                          \
+            g_free(_astr);                                              \
+        }                                                               \
+    } while (0)
+
+static GtkWidget *
+balsa_vevent_widget(LibBalsaVEvent * event, gboolean may_reply,
+		    InternetAddress * sender)
+{
+    GtkTable *table;
+    int row = 0;
+    gboolean reply_widget = FALSE;
+
+    table = GTK_TABLE(gtk_table_new(7, 2, FALSE));
+    TABLE_ATTACH(table, event->summary, _("Summary"));
+    TABLE_ATTACH_ADDRESS(table, event->organizer, _("Organizer"));
+    TABLE_ATTACH_DATE(table, event->start, _("Start"));
+    TABLE_ATTACH_DATE(table, event->end, _("End"));
+    TABLE_ATTACH(table, event->location, _("Location"));
+    if (event->attendee) {
+	GList *att;
+	GString *all_atts = NULL;
+
+	for (att = event->attendee; att; att = g_list_next(att)) {
+	    LibBalsaAddress *lba = LIBBALSA_ADDRESS(att->data);
+	    gchar *this_att = libbalsa_vcal_attendee_to_str(lba);
+
+	    if (all_atts)
+		g_string_append_printf(all_atts, "\n%s", this_att);
+	    else
+		all_atts = g_string_new(this_att);
+	    g_free(this_att);
+
+	    if (may_reply && libbalsa_vcal_attendee_rsvp(lba)) {
+		InternetAddress *ia = internet_address_new();
+
+		internet_address_set_addr(ia,
+					  (const gchar *) lba->address_list->data);
+		if (libbalsa_ia_rfc2821_equal(balsa_app.current_ident->ia, ia))
+		    reply_widget = TRUE;
+		internet_address_unref(ia);
+	    }
+	}
+	TABLE_ATTACH(table, all_atts->str,
+		     event->attendee->next ? _("Attendees") : _("Attendee"));
+	g_string_free(all_atts, TRUE);
+    }
+    TABLE_ATTACH(table, event->description, _("Description"));
+
+    if (sender && reply_widget) {
+	GtkWidget *box = gtk_vbox_new(FALSE, 6);
+	GtkWidget *label;
+	GtkWidget *bbox;
+	GtkWidget *button;
+
+	/* add the callback data to the event object */
+	g_object_ref(G_OBJECT(event));
+	internet_address_ref(sender);
+	g_object_set_data_full(G_OBJECT(event), "ev:sender",
+			       internet_address_to_string(sender, FALSE),
+			       (GDestroyNotify) g_free);
+
+	/* pack everything into a box */
+	gtk_container_add(GTK_CONTAINER(box), GTK_WIDGET(table));
+	label =
+	    gtk_label_new(_("The sender asks you for a reply to this request:"));
+	gtk_container_add(GTK_CONTAINER(box), label);
+	bbox = gtk_hbutton_box_new();
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox),
+				  GTK_BUTTONBOX_SPREAD);
+	gtk_container_add(GTK_CONTAINER(box), bbox);
+
+	button = gtk_button_new_with_label(_("Accept"));
+	g_object_set_data_full(G_OBJECT(button), "event", event,
+                               (GDestroyNotify) g_object_unref);
+	g_object_set_data(G_OBJECT(button), "mode",
+			  (gpointer) VCAL_PSTAT_ACCEPTED);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(vevent_reply), bbox);
+	gtk_container_add(GTK_CONTAINER(bbox), button);
+
+	button = gtk_button_new_with_label(_("Accept tentatively"));
+	g_object_set_data(G_OBJECT(button), "event", event);
+	g_object_set_data(G_OBJECT(button), "mode",
+			  (gpointer) VCAL_PSTAT_TENTATIVE);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(vevent_reply), bbox);
+	gtk_container_add(GTK_CONTAINER(bbox), button);
+
+	button = gtk_button_new_with_label(_("Decline"));
+	g_object_set_data(G_OBJECT(button), "event", event);
+	g_object_set_data(G_OBJECT(button), "mode",
+			  (gpointer) VCAL_PSTAT_DECLINED);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(vevent_reply), bbox);
+	gtk_container_add(GTK_CONTAINER(bbox), button);
+
+	return box;
+    } else
+	return GTK_WIDGET(table);
+}
+
+static void
+vevent_reply(GObject * button, GtkWidget * box)
+{
+    LibBalsaVEvent *event =
+	LIBBALSA_VEVENT(g_object_get_data(button, "event"));
+    LibBalsaVCalPartStat pstat =
+	(LibBalsaVCalPartStat) g_object_get_data(button, "mode");
+    gchar *rcpt;
+    LibBalsaMessage *message;
+    LibBalsaMessageBody *body;
+    gchar *dummy;
+    gchar **params;
+    GError *error = NULL;
+    LibBalsaMsgCreateResult result;
+
+    g_return_if_fail(event != NULL);
+    rcpt = (gchar *) g_object_get_data(G_OBJECT(event), "ev:sender");
+    g_return_if_fail(rcpt != NULL);
+
+    /* make the button box insensitive... */
+    gtk_widget_set_sensitive(box, FALSE);
+
+    /* create a message with the header set from the incoming message */
+    message = libbalsa_message_new();
+    dummy = internet_address_to_string(balsa_app.current_ident->ia, FALSE);
+    message->headers->from = internet_address_parse_string(dummy);
+    g_free(dummy);
+    message->headers->to_list = internet_address_parse_string(rcpt);
+    message->headers->date = time(NULL);
+
+    /* create the message subject */
+    dummy = g_strdup_printf("%s: %s",
+			    event->summary ? event->summary : _("iTip Calendar Request"),
+			    libbalsa_vcal_part_stat_to_str(pstat));
+    libbalsa_message_set_subject(message, dummy);
+    g_free(dummy);
+
+    /* the only message part is the calendar object */
+    body = libbalsa_message_body_new(message);
+    body->buffer =
+	libbalsa_vevent_reply(event,
+			      internet_address_get_addr(balsa_app.current_ident->ia),
+			      pstat);
+    body->charset = g_strdup("utf-8");
+    body->content_type = g_strdup("text/calendar");
+    libbalsa_message_append_part(message, body);
+
+    /* set the text/calendar parameters */
+    params = g_new(gchar *, 3);
+    params[0] = g_strdup("method");
+    params[1] = g_strdup("reply");
+    params[2] = NULL;
+    message->parameters = g_list_prepend(message->parameters, params);
+
+#if ENABLE_ESMTP
+    result = libbalsa_message_send(message, balsa_app.outbox, NULL,
+				   balsa_find_sentbox_by_url,
+				   balsa_app.current_ident->smtp_server,
+				   FALSE, balsa_app.debug, &error);
+#else
+    result = libbalsa_message_send(message, balsa_app.outbox, NULL,
+				   balsa_find_sentbox_by_url,
+				   FALSE, balsa_app.debug, &error);
+#endif
+    if (result != LIBBALSA_MESSAGE_CREATE_OK)
+	libbalsa_information(LIBBALSA_INFORMATION_ERROR,
+			     _("Sending the iTip calendar reply failed: %s"),
+			     error ? error->message : "?");
+    if (error)
+	g_error_free(error);
+    g_object_unref(G_OBJECT(message));
+}

Added: trunk/src/balsa-mime-widget-vcalendar.h
==============================================================================
--- (empty file)
+++ trunk/src/balsa-mime-widget-vcalendar.h	Sun Feb  8 16:58:00 2009
@@ -0,0 +1,38 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ * Copyright (C) 1997-2001 Stuart Parmenter and others,
+ *                         See the file AUTHORS for a list.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option) 
+ * any later version.
+ *  
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
+ * GNU General Public License for more details.
+ *  
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
+ * 02111-1307, USA.
+ */
+
+#ifndef __BALSA_MIME_WIDGET_VCALENDAR_H__
+#define __BALSA_MIME_WIDGET_VCALENDAR_H__
+
+#include "balsa-app.h"
+#include "balsa-message.h"
+#include "balsa-mime-widget.h"
+
+G_BEGIN_DECLS
+
+BalsaMimeWidget * balsa_mime_widget_new_vcalendar(BalsaMessage * bm,
+                                                  LibBalsaMessageBody * mime_body,
+                                                  const gchar * content_type,
+                                                  gpointer data);
+
+G_END_DECLS
+
+#endif				/* __BALSA_MIME_WIDGET_VCALENDAR_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]