balsa r8070 - in trunk: libbalsa src
- From: pawels svn gnome org
- To: svn-commits-list gnome org
- Subject: balsa r8070 - in trunk: libbalsa src
- Date: Sun, 8 Feb 2009 16:58:00 +0000 (UTC)
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]