aboutsummaryrefslogtreecommitdiffstats
path: root/em-format
diff options
context:
space:
mode:
Diffstat (limited to 'em-format')
-rw-r--r--em-format/Makefile.am28
-rw-r--r--em-format/em-format-quote.c585
-rw-r--r--em-format/em-format-quote.h81
-rw-r--r--em-format/em-format.c2016
-rw-r--r--em-format/em-format.h402
-rw-r--r--em-format/em-stripsig-filter.c164
-rw-r--r--em-format/em-stripsig-filter.h57
7 files changed, 3333 insertions, 0 deletions
diff --git a/em-format/Makefile.am b/em-format/Makefile.am
new file mode 100644
index 0000000000..953364482f
--- /dev/null
+++ b/em-format/Makefile.am
@@ -0,0 +1,28 @@
+emformatincludedir = $(privincludedir)/em-format
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/widgets \
+ $(EVOLUTION_MAIL_CFLAGS)
+
+privsolib_LTLIBRARIES = libemformat.la
+
+emformatinclude_HEADERS = \
+ em-format.h \
+ em-format-quote.h \
+ em-stripsig-filter.h
+
+libemformat_la_SOURCES = \
+ $(emformatinclude_HEADERS) \
+ em-format.c \
+ em-format-quote.c \
+ em-stripsig-filter.c
+
+libemformat_la_LDFLAGS = $(NO_UNDEFINED)
+
+libemformat_la_LIBADD = \
+ $(top_builddir)/e-util/libeutil.la \
+ $(top_builddir)/shell/libeshell.la \
+ $(EVOLUTION_MAIL_LIBS)
+
+-include $(top_srcdir)/git.mk
diff --git a/em-format/em-format-quote.c b/em-format/em-format-quote.c
new file mode 100644
index 0000000000..88b11c3ac2
--- /dev/null
+++ b/em-format/em-format-quote.c
@@ -0,0 +1,585 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <camel/camel-iconv.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-mime-filter-tohtml.h>
+#include <camel/camel-mime-filter-enriched.h>
+#include <camel/camel-string-utils.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-url.h>
+
+#include <glib/gi18n.h>
+#include <gconf/gconf-client.h>
+
+#include "em-stripsig-filter.h"
+#include "em-format-quote.h"
+
+struct _EMFormatQuotePrivate {
+ gint dummy;
+};
+
+static void emfq_format_clone(EMFormat *, CamelFolder *, const gchar *, CamelMimeMessage *, EMFormat *);
+static void emfq_format_error(EMFormat *emf, CamelStream *stream, const gchar *txt);
+static void emfq_format_message(EMFormat *, CamelStream *, CamelMimePart *, const EMFormatHandler *);
+static void emfq_format_source(EMFormat *, CamelStream *, CamelMimePart *);
+static void emfq_format_attachment(EMFormat *, CamelStream *, CamelMimePart *, const gchar *, const EMFormatHandler *);
+
+static void emfq_builtin_init(EMFormatQuoteClass *efhc);
+
+static gpointer parent_class;
+
+static void
+emfq_init(GObject *o)
+{
+ EMFormatQuote *emfq =(EMFormatQuote *) o;
+
+ /* we want to convert url's etc */
+ emfq->text_html_flags = CAMEL_MIME_FILTER_TOHTML_PRE | CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS
+ | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES;
+}
+
+static void
+emfq_finalize (GObject *object)
+{
+ EMFormatQuote *emfq =(EMFormatQuote *) object;
+
+ if (emfq->stream)
+ camel_object_unref(emfq->stream);
+ g_free(emfq->credits);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+emfq_base_init(EMFormatQuoteClass *emfqclass)
+{
+ emfq_builtin_init(emfqclass);
+}
+
+static void
+emfq_class_init (EMFormatQuoteClass *class)
+{
+ GObjectClass *object_class;
+ EMFormatClass *format_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EMFormatQuotePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = emfq_finalize;
+
+ format_class = EM_FORMAT_CLASS (class);
+ format_class->format_clone = emfq_format_clone;
+ format_class->format_error = emfq_format_error;
+ format_class->format_source = emfq_format_source;
+ format_class->format_attachment = emfq_format_attachment;
+}
+
+GType
+em_format_quote_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EMFormatQuoteClass),
+ (GBaseInitFunc) emfq_base_init,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) emfq_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMFormatQuote),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) emfq_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ EM_TYPE_FORMAT, "EMFormatQuote", &type_info, 0);
+ }
+
+ return type;
+}
+
+EMFormatQuote *
+em_format_quote_new (const gchar *credits,
+ CamelStream *stream,
+ guint32 flags)
+{
+ EMFormatQuote *emfq;
+
+ emfq = g_object_new (EM_TYPE_FORMAT_QUOTE, NULL);
+
+ emfq->credits = g_strdup (credits);
+ emfq->stream = stream;
+ camel_object_ref (stream);
+ emfq->flags = flags;
+
+ return emfq;
+}
+
+static void
+emfq_format_empty_line(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ camel_stream_printf(stream, "<br>\n");
+}
+
+static void
+emfq_format_clone(EMFormat *emf, CamelFolder *folder, const gchar *uid, CamelMimeMessage *msg, EMFormat *src)
+{
+ EMFormatQuote *emfq = (EMFormatQuote *) emf;
+ const EMFormatHandler *handle;
+ GConfClient *gconf;
+
+ /* Chain up to parent's format_clone() method. */
+ EM_FORMAT_CLASS (parent_class)->format_clone (emf, folder, uid, msg, src);
+
+ gconf = gconf_client_get_default ();
+ camel_stream_reset(emfq->stream);
+ if (gconf_client_get_bool(gconf, "/apps/evolution/mail/composer/top_signature", NULL))
+ emfq_format_empty_line(emf, emfq->stream, (CamelMimePart *)msg, NULL);
+ g_object_unref (gconf);
+ handle = em_format_find_handler(emf, "x-evolution/message/prefix");
+ if (handle)
+ handle->handler(emf, emfq->stream, (CamelMimePart *)msg, handle);
+ handle = em_format_find_handler(emf, "x-evolution/message/rfc822");
+ if (handle)
+ handle->handler(emf, emfq->stream, (CamelMimePart *)msg, handle);
+
+ camel_stream_flush(emfq->stream);
+
+ g_signal_emit_by_name(emf, "complete");
+}
+
+static void
+emfq_format_error(EMFormat *emf, CamelStream *stream, const gchar *txt)
+{
+ /* FIXME: should we even bother writing error text for quoting? probably not... */
+}
+
+static void
+emfq_format_text_header (EMFormatQuote *emfq, CamelStream *stream, const gchar *label, const gchar *value, guint32 flags, gint is_html)
+{
+ const gchar *html;
+ gchar *mhtml = NULL;
+
+ if (value == NULL)
+ return;
+
+ while (*value == ' ')
+ value++;
+
+ if (!is_html)
+ html = mhtml = camel_text_to_html (value, 0, 0);
+ else
+ html = value;
+
+ if (flags & EM_FORMAT_HEADER_BOLD)
+ camel_stream_printf (stream, "<b>%s</b>: %s<br>", label, html);
+ else
+ camel_stream_printf (stream, "%s: %s<br>", label, html);
+
+ g_free (mhtml);
+}
+
+static const gchar *addrspec_hdrs[] = {
+ "Sender", "From", "Reply-To", "To", "Cc", "Bcc",
+ "Resent-Sender", "Resent-from", "Resent-Reply-To",
+ "Resent-To", "Resent-cc", "Resent-Bcc", NULL
+};
+
+#if 0
+/* FIXME: include Sender and Resent-* headers too? */
+/* For Translators only: The following strings are used in the header table in the preview pane */
+static gchar *i18n_hdrs[] = {
+ N_("From"), N_("Reply-To"), N_("To"), N_("Cc"), N_("Bcc")
+};
+#endif
+
+static void
+emfq_format_address (GString *out, struct _camel_header_address *a)
+{
+ guint32 flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES;
+ gchar *name, *mailto, *addr;
+
+ while (a) {
+ if (a->name)
+ name = camel_text_to_html (a->name, flags, 0);
+ else
+ name = NULL;
+
+ switch (a->type) {
+ case CAMEL_HEADER_ADDRESS_NAME:
+ if (name && *name) {
+ gchar *real, *mailaddr;
+
+ g_string_append_printf (out, "%s &lt;", name);
+ /* rfc2368 for mailto syntax and url encoding extras */
+ if ((real = camel_header_encode_phrase ((guchar *)a->name))) {
+ mailaddr = g_strdup_printf ("%s <%s>", real, a->v.addr);
+ g_free (real);
+ mailto = camel_url_encode (mailaddr, "?=&()");
+ g_free (mailaddr);
+ } else {
+ mailto = camel_url_encode (a->v.addr, "?=&()");
+ }
+ } else {
+ mailto = camel_url_encode (a->v.addr, "?=&()");
+ }
+ addr = camel_text_to_html (a->v.addr, flags, 0);
+ g_string_append_printf (out, "<a href=\"mailto:%s\">%s</a>", mailto, addr);
+ g_free (mailto);
+ g_free (addr);
+
+ if (name && *name)
+ g_string_append (out, "&gt;");
+ break;
+ case CAMEL_HEADER_ADDRESS_GROUP:
+ g_string_append_printf (out, "%s: ", name);
+ emfq_format_address (out, a->v.members);
+ g_string_append_printf (out, ";");
+ break;
+ default:
+ g_warning ("Invalid address type");
+ break;
+ }
+
+ g_free (name);
+
+ a = a->next;
+ if (a)
+ g_string_append (out, ", ");
+ }
+}
+
+static void
+canon_header_name (gchar *name)
+{
+ gchar *inptr = name;
+
+ /* canonicalise the header name... first letter is
+ * capitalised and any letter following a '-' also gets
+ * capitalised */
+
+ if (*inptr >= 'a' && *inptr <= 'z')
+ *inptr -= 0x20;
+
+ inptr++;
+
+ while (*inptr) {
+ if (inptr[-1] == '-' && *inptr >= 'a' && *inptr <= 'z')
+ *inptr -= 0x20;
+ else if (*inptr >= 'A' && *inptr <= 'Z')
+ *inptr += 0x20;
+
+ inptr++;
+ }
+}
+
+static void
+emfq_format_header (EMFormat *emf, CamelStream *stream, CamelMedium *part, const gchar *namein, guint32 flags, const gchar *charset)
+{
+ CamelMimeMessage *msg = (CamelMimeMessage *) part;
+ EMFormatQuote *emfq = (EMFormatQuote *) emf;
+ gchar *name, *buf, *value = NULL;
+ const gchar *txt, *label;
+ gboolean addrspec = FALSE;
+ gint is_html = FALSE;
+ gint i;
+
+ name = g_alloca (strlen (namein) + 1);
+ strcpy (name, namein);
+ canon_header_name (name);
+
+ for (i = 0; addrspec_hdrs[i]; i++) {
+ if (!strcmp (name, addrspec_hdrs[i])) {
+ addrspec = TRUE;
+ break;
+ }
+ }
+
+ label = _(name);
+
+ if (addrspec) {
+ struct _camel_header_address *addrs;
+ GString *html;
+
+ if (!(txt = camel_medium_get_header (part, name)))
+ return;
+
+ buf = camel_header_unfold (txt);
+ if (!(addrs = camel_header_address_decode (txt, emf->charset ? emf->charset : emf->default_charset))) {
+ g_free (buf);
+ return;
+ }
+
+ g_free (buf);
+
+ html = g_string_new ("");
+ emfq_format_address (html, addrs);
+ camel_header_address_unref (addrs);
+ txt = value = html->str;
+ g_string_free (html, FALSE);
+ flags |= EM_FORMAT_HEADER_BOLD;
+ is_html = TRUE;
+ } else if (!strcmp (name, "Subject")) {
+ txt = camel_mime_message_get_subject (msg);
+ label = _("Subject");
+ flags |= EM_FORMAT_HEADER_BOLD;
+ } else if (!strcmp (name, "X-Evolution-Mailer")) { /* pseudo-header */
+ if (!(txt = camel_medium_get_header (part, "x-mailer")))
+ if (!(txt = camel_medium_get_header (part, "user-agent")))
+ if (!(txt = camel_medium_get_header (part, "x-newsreader")))
+ if (!(txt = camel_medium_get_header (part, "x-mimeole")))
+ return;
+
+ txt = value = camel_header_format_ctext (txt, charset);
+
+ label = _("Mailer");
+ flags |= EM_FORMAT_HEADER_BOLD;
+ } else if (!strcmp (name, "Date") || !strcmp (name, "Resent-Date")) {
+ if (!(txt = camel_medium_get_header (part, name)))
+ return;
+
+ flags |= EM_FORMAT_HEADER_BOLD;
+ } else {
+ txt = camel_medium_get_header (part, name);
+ buf = camel_header_unfold (txt);
+ txt = value = camel_header_decode_string (txt, charset);
+ g_free (buf);
+ }
+
+ emfq_format_text_header (emfq, stream, label, txt, flags, is_html);
+
+ g_free (value);
+}
+
+static void
+emfq_format_headers (EMFormatQuote *emfq, CamelStream *stream, CamelMedium *part)
+{
+ EMFormat *emf = (EMFormat *) emfq;
+ CamelContentType *ct;
+ const gchar *charset;
+ EMFormatHeader *h;
+
+ if (!part)
+ return;
+
+ ct = camel_mime_part_get_content_type ((CamelMimePart *) part);
+ charset = camel_content_type_param (ct, "charset");
+ charset = camel_iconv_charset_name (charset);
+
+ /* dump selected headers */
+ h = (EMFormatHeader *) emf->header_list.head;
+ while (h->next) {
+ emfq_format_header (emf, stream, part, h->name, h->flags, charset);
+ h = h->next;
+ }
+
+ camel_stream_printf(stream, "<br>\n");
+}
+
+static void
+emfq_format_message_prefix(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ EMFormatQuote *emfq = (EMFormatQuote *) emf;
+
+ if (emfq->credits)
+ camel_stream_printf(stream, "%s<br>\n", emfq->credits);
+}
+
+static void
+emfq_format_message(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ EMFormatQuote *emfq = (EMFormatQuote *) emf;
+
+ if (emfq->flags & EM_FORMAT_QUOTE_CITE)
+ camel_stream_printf(stream, "<!--+GtkHTML:<DATA class=\"ClueFlow\" key=\"orig\" value=\"1\">-->\n"
+ "<blockquote type=cite>\n");
+
+ if (((CamelMimePart *)emf->message) != part) {
+ camel_stream_printf(stream, "%s</br>\n", _("-------- Forwarded Message --------"));
+ emfq_format_headers (emfq, stream, (CamelMedium *)part);
+ } else if (emfq->flags & EM_FORMAT_QUOTE_HEADERS)
+ emfq_format_headers (emfq, stream, (CamelMedium *)part);
+
+ em_format_part (emf, stream, part);
+
+ if (emfq->flags & EM_FORMAT_QUOTE_CITE)
+ camel_stream_write_string(stream, "</blockquote><!--+GtkHTML:<DATA class=\"ClueFlow\" clear=\"orig\">-->");
+}
+
+static void
+emfq_format_source(EMFormat *emf, CamelStream *stream, CamelMimePart *part)
+{
+ CamelStreamFilter *filtered_stream;
+ CamelMimeFilter *html_filter;
+
+ filtered_stream = camel_stream_filter_new_with_stream ((CamelStream *) stream);
+ html_filter = camel_mime_filter_tohtml_new (CAMEL_MIME_FILTER_TOHTML_CONVERT_NL
+ | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES
+ | CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT, 0);
+ camel_stream_filter_add(filtered_stream, html_filter);
+ camel_object_unref(html_filter);
+
+ em_format_format_text(emf, (CamelStream *)filtered_stream, (CamelDataWrapper *)part);
+ camel_object_unref(filtered_stream);
+}
+
+static void
+emfq_format_attachment(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const gchar *mime_type, const EMFormatHandler *handle)
+{
+ if (handle && em_format_is_inline(emf, emf->part_id->str, part, handle)) {
+ gchar *text, *html;
+
+ camel_stream_write_string(stream,
+ "<table border=1 cellspacing=0 cellpadding=0><tr><td><font size=-1>\n");
+
+ /* output some info about it */
+ text = em_format_describe_part(part, mime_type);
+ html = camel_text_to_html(text, ((EMFormatQuote *)emf)->text_html_flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0);
+ camel_stream_write_string(stream, html);
+ g_free(html);
+ g_free(text);
+
+ camel_stream_write_string(stream, "</font></td></tr></table>");
+
+ handle->handler(emf, stream, part, handle);
+ }
+}
+
+#include <camel/camel-medium.h>
+#include <camel/camel-mime-part.h>
+#include <camel/camel-multipart.h>
+#include <camel/camel-url.h>
+
+static void
+emfq_text_plain(EMFormatQuote *emfq, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info)
+{
+ CamelStreamFilter *filtered_stream;
+ CamelMimeFilter *html_filter;
+ CamelMimeFilter *sig_strip;
+ CamelContentType *type;
+ const gchar *format;
+ guint32 rgb = 0x737373, flags;
+
+ if (!part)
+ return;
+
+ flags = emfq->text_html_flags;
+
+ /* Check for RFC 2646 flowed text. */
+ type = camel_mime_part_get_content_type(part);
+ if (camel_content_type_is(type, "text", "plain")
+ && (format = camel_content_type_param(type, "format"))
+ && !g_ascii_strcasecmp(format, "flowed"))
+ flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED;
+
+ filtered_stream = camel_stream_filter_new_with_stream(stream);
+
+ if (emfq->flags != 0) {
+ sig_strip = em_stripsig_filter_new ();
+ camel_stream_filter_add (filtered_stream, sig_strip);
+ camel_object_unref (sig_strip);
+ }
+
+ html_filter = camel_mime_filter_tohtml_new(flags, rgb);
+ camel_stream_filter_add(filtered_stream, html_filter);
+ camel_object_unref(html_filter);
+
+ em_format_format_text((EMFormat *)emfq, (CamelStream *)filtered_stream, (CamelDataWrapper *)part);
+ camel_stream_flush((CamelStream *)filtered_stream);
+ camel_object_unref(filtered_stream);
+}
+
+static void
+emfq_text_enriched(EMFormatQuote *emfq, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info)
+{
+ CamelStreamFilter *filtered_stream;
+ CamelMimeFilter *enriched;
+ CamelDataWrapper *dw;
+ guint32 flags = 0;
+
+ dw = camel_medium_get_content_object((CamelMedium *)part);
+
+ if (!strcmp(info->mime_type, "text/richtext")) {
+ flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT;
+ camel_stream_write_string(stream, "\n<!-- text/richtext -->\n");
+ } else {
+ camel_stream_write_string(stream, "\n<!-- text/enriched -->\n");
+ }
+
+ enriched = camel_mime_filter_enriched_new(flags);
+ filtered_stream = camel_stream_filter_new_with_stream (stream);
+ camel_stream_filter_add(filtered_stream, enriched);
+ camel_object_unref(enriched);
+
+ camel_stream_write_string(stream, "<br><hr><br>");
+ em_format_format_text((EMFormat *)emfq, (CamelStream *)filtered_stream, (CamelDataWrapper *)part);
+ camel_object_unref(filtered_stream);
+}
+
+static void
+emfq_text_html(EMFormat *emf, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info)
+{
+ camel_stream_write_string(stream, "\n<!-- text/html -->\n");
+ em_format_format_text(emf, stream, (CamelDataWrapper *)part);
+}
+
+static void
+emfq_ignore(EMFormat *emf, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info)
+{
+ /* NOOP */
+}
+
+static EMFormatHandler type_builtin_table[] = {
+ { (gchar *) "text/plain", (EMFormatFunc)emfq_text_plain },
+ { (gchar *) "text/enriched", (EMFormatFunc)emfq_text_enriched },
+ { (gchar *) "text/richtext", (EMFormatFunc)emfq_text_enriched },
+ { (gchar *) "text/html", (EMFormatFunc)emfq_text_html },
+/* { (gchar *) "multipart/related",(EMFormatFunc)emfq_multipart_related },*/
+ { (gchar *) "message/external-body", (EMFormatFunc)emfq_ignore },
+ { (gchar *) "multipart/appledouble", (EMFormatFunc)emfq_ignore },
+
+ /* internal evolution types */
+ { (gchar *) "x-evolution/evolution-rss-feed", (EMFormatFunc)emfq_text_html },
+ { (gchar *) "x-evolution/message/rfc822", (EMFormatFunc)emfq_format_message },
+ { (gchar *) "x-evolution/message/prefix", (EMFormatFunc)emfq_format_message_prefix },
+};
+
+static void
+emfq_builtin_init(EMFormatQuoteClass *efhc)
+{
+ gint i;
+
+ for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++)
+ em_format_class_add_handler((EMFormatClass *)efhc, &type_builtin_table[i]);
+}
diff --git a/em-format/em-format-quote.h b/em-format/em-format-quote.h
new file mode 100644
index 0000000000..5b010e1da5
--- /dev/null
+++ b/em-format/em-format-quote.h
@@ -0,0 +1,81 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef EM_FORMAT_QUOTE_H
+#define EM_FORMAT_QUOTE_H
+
+#include <camel/camel-stream.h>
+#include "em-format.h"
+
+/* Standard GObject macros */
+#define EM_TYPE_FORMAT_QUOTE \
+ (em_format_quote_get_type ())
+#define EM_FORMAT_QUOTE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), EM_TYPE_FORMAT_QUOTE, EMFormatQuote))
+#define EM_FORMAT_QUOTE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), EM_TYPE_FORMAT_QUOTE, EMFormatQuoteClass))
+#define EM_IS_FORMAT_QUOTE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), EM_TYPE_FORMAT_QUOTE))
+#define EM_IS_FORMAT_QUOTE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), EM_TYPE_FORMAT_QUOTE))
+#define EM_FORMAT_QUOTE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), EM_TYPE_FORMAT_QUOTE, EMFormatQuoteClass))
+
+#define EM_FORMAT_QUOTE_CITE (1<<0)
+#define EM_FORMAT_QUOTE_HEADERS (1<<1)
+
+G_BEGIN_DECLS
+
+typedef struct _EMFormatQuote EMFormatQuote;
+typedef struct _EMFormatQuoteClass EMFormatQuoteClass;
+typedef struct _EMFormatQuotePrivate EMFormatQuotePrivate;
+
+struct _EMFormatQuote {
+ EMFormat format;
+
+ EMFormatQuotePrivate *priv;
+
+ gchar *credits;
+ CamelStream *stream;
+ guint32 flags;
+
+ guint32 text_html_flags;
+ guint32 citation_colour;
+};
+
+struct _EMFormatQuoteClass {
+ EMFormatClass format_class;
+};
+
+GType em_format_quote_get_type (void);
+EMFormatQuote * em_format_quote_new (const gchar *credits,
+ CamelStream *stream,
+ guint32 flags);
+
+G_END_DECLS
+
+#endif /* EM_FORMAT_QUOTE_H */
diff --git a/em-format/em-format.c b/em-format/em-format.c
new file mode 100644
index 0000000000..71cd977b0a
--- /dev/null
+++ b/em-format/em-format.c
@@ -0,0 +1,2016 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include <libedataserver/e-msgport.h>
+#include <camel/camel-stream.h>
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-multipart.h>
+#include <camel/camel-multipart-encrypted.h>
+#include <camel/camel-multipart-signed.h>
+#include <camel/camel-medium.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-gpg-context.h>
+#include <camel/camel-smime-context.h>
+#include <camel/camel-string-utils.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-stream-null.h>
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-mime-filter-charset.h>
+#include <camel/camel-mime-filter-windows.h>
+#include <camel/camel-mime-filter-pgp.h>
+
+#include "em-format.h"
+#include "e-util/e-util.h"
+#include "shell/e-shell.h"
+#include "shell/e-shell-settings.h"
+
+#define d(x)
+
+/* Used to cache various data/info for redraws
+ The validity stuff could be cached at a higher level but this is easier
+ This absolutely relies on the partid being _globally unique_
+ This is still kind of yucky, we should maintian a full tree of all this data,
+ along with/as part of the puri tree */
+struct _EMFormatCache {
+ CamelCipherValidity *valid; /* validity copy */
+ CamelMimePart *secured; /* encrypted subpart */
+
+ guint state:2; /* inline state */
+
+ gchar partid[1];
+};
+
+#define INLINE_UNSET (0)
+#define INLINE_ON (1)
+#define INLINE_OFF (2)
+
+static void emf_builtin_init(EMFormatClass *);
+
+static const EMFormatHandler *emf_find_handler(EMFormat *emf, const gchar *mime_type);
+static void emf_format_clone(EMFormat *emf, CamelFolder *folder, const gchar *uid, CamelMimeMessage *msg, EMFormat *emfsource);
+static void emf_format_secure(EMFormat *emf, CamelStream *stream, CamelMimePart *part, CamelCipherValidity *valid);
+static gboolean emf_busy(EMFormat *emf);
+enum {
+ EMF_COMPLETE,
+ EMF_LAST_SIGNAL
+};
+
+static gpointer parent_class;
+static guint signals[EMF_LAST_SIGNAL];
+
+static void
+emf_free_cache(struct _EMFormatCache *efc)
+{
+ if (efc->valid)
+ camel_cipher_validity_free(efc->valid);
+ if (efc->secured)
+ camel_object_unref(efc->secured);
+ g_free(efc);
+}
+
+static struct _EMFormatCache *
+emf_insert_cache(EMFormat *emf, const gchar *partid)
+{
+ struct _EMFormatCache *new;
+
+ new = g_malloc0(sizeof(*new)+strlen(partid));
+ strcpy(new->partid, partid);
+ g_hash_table_insert(emf->inline_table, new->partid, new);
+
+ return new;
+}
+
+static void
+emf_finalize (GObject *object)
+{
+ EMFormat *emf = EM_FORMAT (object);
+
+ if (emf->session)
+ camel_object_unref (emf->session);
+
+ g_hash_table_destroy (emf->inline_table);
+
+ em_format_clear_headers(emf);
+ camel_cipher_validity_free(emf->valid);
+ g_free(emf->charset);
+ g_free (emf->default_charset);
+ g_string_free(emf->part_id, TRUE);
+
+ /* FIXME: check pending jobs */
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+emf_base_init (EMFormatClass *class)
+{
+ class->type_handlers = g_hash_table_new (g_str_hash, g_str_equal);
+ emf_builtin_init (class);
+}
+
+static void
+emf_class_init (EMFormatClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = emf_finalize;
+
+ class->find_handler = emf_find_handler;
+ class->format_clone = emf_format_clone;
+ class->format_secure = emf_format_secure;
+ class->busy = emf_busy;
+
+ signals[EMF_COMPLETE] = g_signal_new (
+ "complete",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EMFormatClass, complete),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ class->type_handlers = g_hash_table_new (g_str_hash, g_str_equal);
+ emf_builtin_init (class);
+}
+
+static void
+emf_init (EMFormat *emf)
+{
+ EShell *shell;
+ EShellSettings *shell_settings;
+
+ emf->inline_table = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) emf_free_cache);
+ emf->composer = FALSE;
+ emf->print = FALSE;
+ e_dlist_init(&emf->header_list);
+ em_format_default_headers(emf);
+ emf->part_id = g_string_new("");
+
+ shell = e_shell_get_default ();
+ shell_settings = e_shell_get_shell_settings (shell);
+
+ emf->session = e_shell_settings_get_pointer (shell_settings, "mail-session");
+ g_return_if_fail (emf->session != NULL);
+
+ camel_object_ref (emf->session);
+}
+
+GType
+em_format_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EMFormatClass),
+ (GBaseInitFunc) emf_base_init,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) emf_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EMFormat),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) emf_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ G_TYPE_OBJECT, "EMFormat", &type_info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * em_format_class_add_handler:
+ * @emfc: EMFormatClass
+ * @info: Callback information.
+ *
+ * Add a mime type handler to this class. This is only used by
+ * implementing classes. The @info.old pointer will automatically be
+ * setup to point to the old hanlder if one was already set. This can
+ * be used for overrides a fallback.
+ *
+ * When a mime type described by @info is encountered, the callback will
+ * be invoked. Note that @info may be extended by sub-classes if
+ * they require additional context information.
+ *
+ * Use a mime type of "foo/ *" to insert a fallback handler for type "foo".
+ **/
+void
+em_format_class_add_handler(EMFormatClass *emfc, EMFormatHandler *info)
+{
+ d(printf("adding format handler to '%s' '%s'\n", g_type_name_from_class((GTypeClass *)emfc), info->mime_type));
+ info->old = g_hash_table_lookup(emfc->type_handlers, info->mime_type);
+ g_hash_table_insert(emfc->type_handlers, (gpointer) info->mime_type, info);
+}
+
+struct _class_handlers {
+ EMFormatClass *old;
+ EMFormatClass *new;
+};
+static void
+merge_missing (gpointer key, gpointer value, gpointer userdata)
+{
+ struct _class_handlers *classes = (struct _class_handlers *) userdata;
+ EMFormatHandler *info, *oldinfo;
+
+ oldinfo = (EMFormatHandler *) value;
+ info = g_hash_table_lookup (classes->new->type_handlers, key);
+ if (!info) {
+ /* Might be from a plugin */
+ g_hash_table_insert (classes->new->type_handlers, key, value);
+ }
+
+}
+
+void
+em_format_merge_handler(EMFormat *new, EMFormat *old)
+{
+ EMFormatClass *oldc = (EMFormatClass *)G_OBJECT_GET_CLASS(old);
+ EMFormatClass *newc = (EMFormatClass *)G_OBJECT_GET_CLASS(new);
+ struct _class_handlers fclasses;
+
+ fclasses.old = oldc;
+ fclasses.new = newc;
+
+ g_hash_table_foreach (oldc->type_handlers, merge_missing, &fclasses);
+
+}
+
+/**
+ * em_format_class_remove_handler:
+ * @emfc:
+ * @info:
+ *
+ * Remove a handler. @info must be a value which was previously
+ * added.
+ **/
+void
+em_format_class_remove_handler(EMFormatClass *emfc, EMFormatHandler *info)
+{
+ EMFormatHandler *current;
+
+ /* TODO: thread issues? */
+
+ current = g_hash_table_lookup(emfc->type_handlers, info->mime_type);
+ if (current == info) {
+ current = info->old;
+ if (current)
+ g_hash_table_insert(emfc->type_handlers, (gpointer) current->mime_type, current);
+ else
+ g_hash_table_remove(emfc->type_handlers, info->mime_type);
+ } else {
+ while (current && current->old != info)
+ current = current->old;
+ g_return_if_fail(current != NULL);
+ current->old = info->old;
+ }
+}
+
+const EMFormatHandler *
+em_format_find_handler (EMFormat *emf,
+ const gchar *mime_type)
+{
+ EMFormatClass *class;
+
+ g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
+ g_return_val_if_fail (mime_type != NULL, NULL);
+
+ class = EM_FORMAT_GET_CLASS (emf);
+ g_return_val_if_fail (class->find_handler != NULL, NULL);
+ return class->find_handler (emf, mime_type);
+}
+
+/**
+ * em_format_find_handler:
+ * @emf:
+ * @mime_type:
+ *
+ * Find a format handler by @mime_type.
+ *
+ * Return value: NULL if no handler is available.
+ **/
+static const EMFormatHandler *
+emf_find_handler(EMFormat *emf, const gchar *mime_type)
+{
+ EMFormatClass *emfc = (EMFormatClass *)G_OBJECT_GET_CLASS(emf);
+
+ return g_hash_table_lookup(emfc->type_handlers, mime_type);
+}
+
+/**
+ * em_format_fallback_handler:
+ * @emf:
+ * @mime_type:
+ *
+ * Try to find a format handler based on the major type of the @mime_type.
+ *
+ * The subtype is replaced with "*" and a lookup performed.
+ *
+ * Return value:
+ **/
+const EMFormatHandler *
+em_format_fallback_handler(EMFormat *emf, const gchar *mime_type)
+{
+ gchar *mime, *s;
+
+ s = strchr(mime_type, '/');
+ if (s == NULL)
+ mime = (gchar *)mime_type;
+ else {
+ gsize len = (s-mime_type)+1;
+
+ mime = alloca(len+2);
+ strncpy(mime, mime_type, len);
+ strcpy(mime+len, "*");
+ }
+
+ return em_format_find_handler(emf, mime);
+}
+
+/**
+ * em_format_add_puri:
+ * @emf:
+ * @size:
+ * @cid: Override the autogenerated content id.
+ * @part:
+ * @func:
+ *
+ * Add a pending-uri handler. When formatting parts that reference
+ * other parts, a pending-uri (PURI) can be used to track the reference.
+ *
+ * @size is used to allocate the structure, so that it can be directly
+ * subclassed by implementors.
+ *
+ * @cid can be used to override the key used to retreive the PURI, if NULL,
+ * then the content-location and the content-id of the @part are stored
+ * as lookup keys for the part.
+ *
+ * FIXME: This may need a free callback.
+ *
+ * Return value: A new PURI, with a referenced copy of @part, and the cid
+ * always set. The uri will be set if one is available. Clashes
+ * are resolved by forgetting the old PURI in the global index.
+ **/
+EMFormatPURI *
+em_format_add_puri(EMFormat *emf, gsize size, const gchar *cid, CamelMimePart *part, EMFormatPURIFunc func)
+{
+ EMFormatPURI *puri;
+ const gchar *tmp;
+
+ d(printf("adding puri for part: %s\n", emf->part_id->str));
+
+ if (size < sizeof(*puri)) {
+ g_warning (
+ "size (%" G_GSIZE_FORMAT
+ ") less than size of puri\n", size);
+ size = sizeof (*puri);
+ }
+
+ puri = g_malloc0(size);
+
+ puri->format = emf;
+ puri->func = func;
+ puri->use_count = 0;
+ puri->cid = g_strdup(cid);
+ puri->part_id = g_strdup(emf->part_id->str);
+
+ if (part) {
+ camel_object_ref(part);
+ puri->part = part;
+ }
+
+ if (part != NULL && cid == NULL) {
+ tmp = camel_mime_part_get_content_id(part);
+ if (tmp)
+ puri->cid = g_strdup_printf("cid:%s", tmp);
+ else
+ puri->cid = g_strdup_printf("em-no-cid:%s", emf->part_id->str);
+
+ d(printf("built cid '%s'\n", puri->cid));
+
+ /* not quite same as old behaviour, it also put in the relative uri and a fallback for no parent uri */
+ tmp = camel_mime_part_get_content_location(part);
+ puri->uri = NULL;
+ if (tmp == NULL) {
+ /* no location, don't set a uri at all, html parts do this themselves */
+ } else {
+ if (strchr(tmp, ':') == NULL && emf->base != NULL) {
+ CamelURL *uri;
+
+ uri = camel_url_new_with_base(emf->base, tmp);
+ puri->uri = camel_url_to_string(uri, 0);
+ camel_url_free(uri);
+ } else {
+ puri->uri = g_strdup(tmp);
+ }
+ }
+ }
+
+ g_return_val_if_fail (puri->cid != NULL, NULL);
+ g_return_val_if_fail (emf->pending_uri_level != NULL, NULL);
+ g_return_val_if_fail (emf->pending_uri_table != NULL, NULL);
+
+ e_dlist_addtail(&emf->pending_uri_level->uri_list, (EDListNode *)puri);
+
+ if (puri->uri)
+ g_hash_table_insert(emf->pending_uri_table, puri->uri, puri);
+ g_hash_table_insert(emf->pending_uri_table, puri->cid, puri);
+
+ return puri;
+}
+
+/**
+ * em_format_push_level:
+ * @emf:
+ *
+ * This is used to build a heirarchy of visible PURI objects based on
+ * the structure of the message. Used by multipart/alternative formatter.
+ *
+ * FIXME: This could probably also take a uri so it can automaticall update
+ * the base location.
+ **/
+void
+em_format_push_level(EMFormat *emf)
+{
+ struct _EMFormatPURITree *purilist;
+
+ d(printf("em_format_push_level\n"));
+ purilist = g_malloc0(sizeof(*purilist));
+ e_dlist_init(&purilist->children);
+ e_dlist_init(&purilist->uri_list);
+ purilist->parent = emf->pending_uri_level;
+ if (emf->pending_uri_tree == NULL) {
+ emf->pending_uri_tree = purilist;
+ } else {
+ e_dlist_addtail(&emf->pending_uri_level->children, (EDListNode *)purilist);
+ }
+ emf->pending_uri_level = purilist;
+}
+
+/**
+ * em_format_pull_level:
+ * @emf:
+ *
+ * Drop a level of visibility back to the parent. Note that
+ * no PURI values are actually freed.
+ **/
+void
+em_format_pull_level(EMFormat *emf)
+{
+ d(printf("em_format_pull_level\n"));
+ emf->pending_uri_level = emf->pending_uri_level->parent;
+}
+
+/**
+ * em_format_find_visible_puri:
+ * @emf:
+ * @uri:
+ *
+ * Search for a PURI based on the visibility defined by :push_level()
+ * and :pull_level().
+ *
+ * Return value:
+ **/
+EMFormatPURI *
+em_format_find_visible_puri(EMFormat *emf, const gchar *uri)
+{
+ EMFormatPURI *pw;
+ struct _EMFormatPURITree *ptree;
+
+ d(printf("checking for visible uri '%s'\n", uri));
+
+ ptree = emf->pending_uri_level;
+ while (ptree) {
+ pw = (EMFormatPURI *)ptree->uri_list.head;
+ while (pw->next) {
+ d(printf(" pw->uri = '%s' pw->cid = '%s\n", pw->uri?pw->uri:"", pw->cid));
+ if ((pw->uri && !strcmp(pw->uri, uri)) || !strcmp(pw->cid, uri))
+ return pw;
+ pw = pw->next;
+ }
+ ptree = ptree->parent;
+ }
+
+ return NULL;
+}
+
+/**
+ * em_format_find_puri:
+ * @emf:
+ * @uri:
+ *
+ * Search for a PURI based on a uri. Both the content-id
+ * and content-location are checked.
+ *
+ * Return value:
+ **/
+EMFormatPURI *
+
+em_format_find_puri(EMFormat *emf, const gchar *uri)
+{
+ return g_hash_table_lookup(emf->pending_uri_table, uri);
+}
+
+static void
+emf_clear_puri_node(struct _EMFormatPURITree *node)
+{
+ {
+ EMFormatPURI *pw, *pn;
+
+ /* clear puri's at this level */
+ pw = (EMFormatPURI *)node->uri_list.head;
+ pn = pw->next;
+ while (pn) {
+ d(printf ("freeing pw %p format:%p\n", pw, pw->format));
+ if (pw->free)
+ pw->free(pw);
+ g_free(pw->uri);
+ g_free(pw->cid);
+ g_free(pw->part_id);
+ if (pw->part)
+ camel_object_unref(pw->part);
+ g_free(pw);
+ pw = pn;
+ pn = pn->next;
+ }
+ }
+
+ {
+ struct _EMFormatPURITree *cw, *cn;
+
+ /* clear child nodes */
+ cw = (struct _EMFormatPURITree *)node->children.head;
+ cn = cw->next;
+ while (cn) {
+ emf_clear_puri_node(cw);
+ cw = cn;
+ cn = cn->next;
+ }
+ }
+
+ g_free(node);
+}
+
+/**
+ * em_format_clear_puri_tree:
+ * @emf:
+ *
+ * For use by implementors to clear out the message structure
+ * data.
+ **/
+void
+em_format_clear_puri_tree(EMFormat *emf)
+{
+ d(printf("clearing pending uri's\n"));
+
+ if (emf->pending_uri_table) {
+ g_hash_table_destroy(emf->pending_uri_table);
+ emf_clear_puri_node(emf->pending_uri_tree);
+ emf->pending_uri_level = NULL;
+ emf->pending_uri_tree = NULL;
+ }
+ emf->pending_uri_table = g_hash_table_new(g_str_hash, g_str_equal);
+ em_format_push_level(emf);
+}
+
+/* use mime_type == NULL to force showing as application/octet-stream */
+void
+em_format_part_as(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const gchar *mime_type)
+{
+ const EMFormatHandler *handle = NULL;
+ const gchar *snoop_save = emf->snoop_mime_type, *tmp;
+ CamelURL *base_save = emf->base, *base = NULL;
+ gchar *basestr = NULL;
+
+ d(printf("format_part_as()\n"));
+
+ emf->snoop_mime_type = NULL;
+
+ /* RFC 2110, we keep track of content-base, and absolute content-location headers
+ This is actually only required for html, but, *shrug* */
+ tmp = camel_medium_get_header((CamelMedium *)part, "Content-Base");
+ if (tmp == NULL) {
+ tmp = camel_mime_part_get_content_location(part);
+ if (tmp && strchr(tmp, ':') == NULL)
+ tmp = NULL;
+ } else {
+ tmp = basestr = camel_header_location_decode(tmp);
+ }
+ d(printf("content-base is '%s'\n", tmp?tmp:"<unset>"));
+ if (tmp
+ && (base = camel_url_new(tmp, NULL))) {
+ emf->base = base;
+ d(printf("Setting content base '%s'\n", tmp));
+ }
+ g_free(basestr);
+
+ if (mime_type != NULL) {
+ if (g_ascii_strcasecmp(mime_type, "application/octet-stream") == 0) {
+ emf->snoop_mime_type = mime_type = em_format_snoop_type(part);
+ if (mime_type == NULL)
+ mime_type = "application/octet-stream";
+ }
+
+ handle = em_format_find_handler(emf, mime_type);
+ if (handle == NULL)
+ handle = em_format_fallback_handler(emf, mime_type);
+
+ if (handle != NULL
+ && !em_format_is_attachment(emf, part)) {
+ d(printf("running handler for type '%s'\n", mime_type));
+ handle->handler(emf, stream, part, handle);
+ goto finish;
+ }
+ d(printf("this type is an attachment? '%s'\n", mime_type));
+ } else {
+ mime_type = "application/octet-stream";
+ }
+
+ ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_attachment(emf, stream, part, mime_type, handle);
+finish:
+ emf->base = base_save;
+ emf->snoop_mime_type = snoop_save;
+
+ if (base)
+ camel_url_free(base);
+}
+
+void
+em_format_part(EMFormat *emf, CamelStream *stream, CamelMimePart *part)
+{
+ gchar *mime_type;
+ CamelDataWrapper *dw;
+
+ dw = camel_medium_get_content_object((CamelMedium *)part);
+ mime_type = camel_data_wrapper_get_mime_type(dw);
+ if (mime_type) {
+ camel_strdown(mime_type);
+ em_format_part_as(emf, stream, part, mime_type);
+ g_free(mime_type);
+ } else
+ em_format_part_as(emf, stream, part, "text/plain");
+}
+
+static void
+emf_clone_inlines(gpointer key, gpointer val, gpointer data)
+{
+ struct _EMFormatCache *emfc = val, *new;
+
+ new = emf_insert_cache((EMFormat *)data, emfc->partid);
+ new->state = emfc->state;
+ if (emfc->valid)
+ new->valid = camel_cipher_validity_clone(emfc->valid);
+ if (emfc->secured)
+ camel_object_ref((new->secured = emfc->secured));
+}
+
+static void
+emf_format_clone(EMFormat *emf, CamelFolder *folder, const gchar *uid, CamelMimeMessage *msg, EMFormat *emfsource)
+{
+ em_format_clear_puri_tree(emf);
+
+ if (emf != emfsource) {
+ g_hash_table_remove_all(emf->inline_table);
+ if (emfsource) {
+ struct _EMFormatHeader *h;
+
+ /* We clone the current state here */
+ g_hash_table_foreach(emfsource->inline_table, emf_clone_inlines, emf);
+ emf->mode = emfsource->mode;
+ g_free(emf->charset);
+ emf->charset = g_strdup(emfsource->charset);
+ g_free (emf->default_charset);
+ emf->default_charset = g_strdup (emfsource->default_charset);
+
+ em_format_clear_headers(emf);
+ for (h = (struct _EMFormatHeader *)emfsource->header_list.head; h->next; h = h->next)
+ em_format_add_header(emf, h->name, h->flags);
+ }
+ }
+
+ /* what a mess */
+ if (folder != emf->folder) {
+ if (emf->folder)
+ camel_object_unref(emf->folder);
+ if (folder)
+ camel_object_ref(folder);
+ emf->folder = folder;
+ }
+
+ if (uid != emf->uid) {
+ g_free(emf->uid);
+ emf->uid = g_strdup(uid);
+ }
+
+ if (msg != emf->message) {
+ if (emf->message)
+ camel_object_unref(emf->message);
+ if (msg)
+ camel_object_ref(msg);
+ emf->message = msg;
+ }
+
+ g_string_truncate(emf->part_id, 0);
+ if (folder != NULL)
+ /* TODO build some string based on the folder name/location? */
+ g_string_append_printf(emf->part_id, ".%p", (gpointer) folder);
+ if (uid != NULL)
+ g_string_append_printf(emf->part_id, ".%s", uid);
+}
+
+static void
+emf_format_secure(EMFormat *emf, CamelStream *stream, CamelMimePart *part, CamelCipherValidity *valid)
+{
+ CamelCipherValidity *save = emf->valid_parent;
+ gint len;
+
+ /* Note that this also requires support from higher up in the class chain
+ - validity needs to be cleared when you start output
+ - also needs to be cleared (but saved) whenever you start a new message. */
+
+ if (emf->valid == NULL) {
+ emf->valid = valid;
+ } else {
+ camel_dlist_addtail(&emf->valid_parent->children, (CamelDListNode *)valid);
+ camel_cipher_validity_envelope(emf->valid_parent, valid);
+ }
+
+ emf->valid_parent = valid;
+
+ len = emf->part_id->len;
+ g_string_append_printf(emf->part_id, ".secured");
+ em_format_part(emf, stream, part);
+ g_string_truncate(emf->part_id, len);
+
+ emf->valid_parent = save;
+}
+
+static gboolean
+emf_busy(EMFormat *emf)
+{
+ return FALSE;
+}
+
+/**
+ * em_format_format_clone:
+ * @emf: an #EMFormat
+ * @folder: a #CamelFolder or %NULL
+ * @uid: Message UID or %NULL
+ * @msg: a #CamelMimeMessage or %NULL
+ * @emfsource: Used as a basis for user-altered layout, e.g. inline viewed
+ * attachments.
+ *
+ * Format a message @msg. If @emfsource is non NULL, then the status of
+ * inlined expansion and so forth is copied direction from @emfsource.
+ *
+ * By passing the same value for @emf and @emfsource, you can perform
+ * a display refresh, or it can be used to generate an identical layout,
+ * e.g. to print what the user has shown inline.
+ **/
+void
+em_format_format_clone (EMFormat *emf,
+ CamelFolder *folder,
+ const gchar *uid,
+ CamelMimeMessage *message,
+ EMFormat *source)
+{
+ EMFormatClass *class;
+
+ g_return_if_fail (EM_IS_FORMAT (emf));
+ g_return_if_fail (folder == NULL || CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message == NULL || CAMEL_IS_MIME_MESSAGE (message));
+ g_return_if_fail (source == NULL || EM_IS_FORMAT (source));
+
+ class = EM_FORMAT_GET_CLASS (emf);
+ g_return_if_fail (class->format_clone != NULL);
+ class->format_clone (emf, folder, uid, message, source);
+}
+
+void
+em_format_format (EMFormat *emf,
+ CamelFolder *folder,
+ const gchar *uid,
+ CamelMimeMessage *message)
+{
+ /* em_format_format_clone() will check the arguments. */
+ em_format_format_clone (emf, folder, uid, message, NULL);
+}
+
+void
+em_format_redraw (EMFormat *emf)
+{
+ g_return_if_fail (EM_IS_FORMAT (emf));
+
+ em_format_format_clone (
+ emf, emf->folder, emf->uid, emf->message, emf);
+}
+
+/**
+ * em_format_set_mode:
+ * @emf:
+ * @type:
+ *
+ * Set display mode, EM_FORMAT_SOURCE, EM_FORMAT_ALLHEADERS, or
+ * EM_FORMAT_NORMAL.
+ **/
+void
+em_format_set_mode(EMFormat *emf, em_format_mode_t type)
+{
+ if (emf->mode == type)
+ return;
+
+ emf->mode = type;
+
+ /* force redraw if type changed afterwards */
+ if (emf->message)
+ em_format_redraw(emf);
+}
+
+/**
+ * em_format_set_charset:
+ * @emf:
+ * @charset:
+ *
+ * set override charset on formatter. message will be redisplayed if
+ * required.
+ **/
+void
+em_format_set_charset(EMFormat *emf, const gchar *charset)
+{
+ if ((emf->charset && charset && g_ascii_strcasecmp(emf->charset, charset) == 0)
+ || (emf->charset == NULL && charset == NULL)
+ || (emf->charset == charset))
+ return;
+
+ g_free(emf->charset);
+ emf->charset = g_strdup(charset);
+
+ if (emf->message)
+ em_format_redraw(emf);
+}
+
+/**
+ * em_format_set_default_charset:
+ * @emf:
+ * @charset:
+ *
+ * Set the fallback, default system charset to use when no other charsets
+ * are present. Message will be redisplayed if required (and sometimes redisplayed
+ * when it isn't).
+ **/
+void
+em_format_set_default_charset(EMFormat *emf, const gchar *charset)
+{
+ if ((emf->default_charset && charset && g_ascii_strcasecmp(emf->default_charset, charset) == 0)
+ || (emf->default_charset == NULL && charset == NULL)
+ || (emf->default_charset == charset))
+ return;
+
+ g_free(emf->default_charset);
+ emf->default_charset = g_strdup(charset);
+
+ if (emf->message && emf->charset == NULL)
+ em_format_redraw(emf);
+}
+
+/**
+ * em_format_clear_headers:
+ * @emf:
+ *
+ * Clear the list of headers to be displayed. This will force all headers to
+ * be shown.
+ **/
+void
+em_format_clear_headers(EMFormat *emf)
+{
+ EMFormatHeader *eh;
+
+ while ((eh = (EMFormatHeader *)e_dlist_remhead(&emf->header_list)))
+ g_free(eh);
+}
+
+/* note: also copied in em-mailer-prefs.c */
+static const struct {
+ const gchar *name;
+ guint32 flags;
+} default_headers[] = {
+ { N_("From"), EM_FORMAT_HEADER_BOLD },
+ { N_("Reply-To"), EM_FORMAT_HEADER_BOLD },
+ { N_("To"), EM_FORMAT_HEADER_BOLD },
+ { N_("Cc"), EM_FORMAT_HEADER_BOLD },
+ { N_("Bcc"), EM_FORMAT_HEADER_BOLD },
+ { N_("Subject"), EM_FORMAT_HEADER_BOLD },
+ { N_("Date"), EM_FORMAT_HEADER_BOLD },
+ { N_("Newsgroups"), EM_FORMAT_HEADER_BOLD },
+ { N_("Face"), 0 },
+};
+
+/**
+ * em_format_default_headers:
+ * @emf:
+ *
+ * Set the headers to show to the default list.
+ *
+ * From, Reply-To, To, Cc, Bcc, Subject and Date.
+ **/
+void
+em_format_default_headers(EMFormat *emf)
+{
+ gint i;
+
+ em_format_clear_headers(emf);
+ for (i=0; i<sizeof(default_headers)/sizeof(default_headers[0]); i++)
+ em_format_add_header(emf, default_headers[i].name, default_headers[i].flags);
+}
+
+/**
+ * em_format_add_header:
+ * @emf:
+ * @name: The name of the header, as it will appear during output.
+ * @flags: EM_FORMAT_HEAD_* defines to control display attributes.
+ *
+ * Add a specific header to show. If any headers are set, they will
+ * be displayed in the order set by this function. Certain known
+ * headers included in this list will be shown using special
+ * formatting routines.
+ **/
+void em_format_add_header(EMFormat *emf, const gchar *name, guint32 flags)
+{
+ EMFormatHeader *h;
+
+ h = g_malloc(sizeof(*h) + strlen(name));
+ h->flags = flags;
+ strcpy(h->name, name);
+ e_dlist_addtail(&emf->header_list, (EDListNode *)h);
+}
+
+/**
+ * em_format_is_attachment:
+ * @emf:
+ * @part: Part to check.
+ *
+ * Returns true if the part is an attachment.
+ *
+ * A part is not considered an attachment if it is a
+ * multipart, or a text part with no filename. It is used
+ * to determine if an attachment header should be displayed for
+ * the part.
+ *
+ * Content-Disposition is not checked.
+ *
+ * Return value: TRUE/FALSE
+ **/
+gint em_format_is_attachment(EMFormat *emf, CamelMimePart *part)
+{
+ /*CamelContentType *ct = camel_mime_part_get_content_type(part);*/
+ CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)part);
+
+ if (!dw)
+ return 0;
+
+ /*printf("checking is attachment %s/%s\n", ct->type, ct->subtype);*/
+ return !(camel_content_type_is (dw->mime_type, "multipart", "*")
+ || camel_content_type_is(dw->mime_type, "application", "x-pkcs7-mime")
+ || camel_content_type_is(dw->mime_type, "application", "pkcs7-mime")
+ || camel_content_type_is(dw->mime_type, "application", "x-inlinepgp-signed")
+ || camel_content_type_is(dw->mime_type, "application", "x-inlinepgp-encrypted")
+ || camel_content_type_is(dw->mime_type, "x-evolution", "evolution-rss-feed")
+ || (camel_content_type_is (dw->mime_type, "text", "*")
+ && camel_mime_part_get_filename(part) == NULL));
+}
+
+/**
+ * em_format_is_inline:
+ * @emf:
+ * @part:
+ * @partid: format->part_id part id of this part.
+ * @handle: handler for this part
+ *
+ * Returns true if the part should be displayed inline. Any part with
+ * a Content-Disposition of inline, or if the @handle has a default
+ * inline set, will be shown inline.
+ *
+ * :set_inline() called on the same part will override any calculated
+ * value.
+ *
+ * Return value:
+ **/
+gint em_format_is_inline(EMFormat *emf, const gchar *partid, CamelMimePart *part, const EMFormatHandler *handle)
+{
+ struct _EMFormatCache *emfc;
+ const gchar *tmp;
+
+ if (handle == NULL)
+ return FALSE;
+
+ emfc = g_hash_table_lookup(emf->inline_table, partid);
+ if (emfc && emfc->state != INLINE_UNSET)
+ return emfc->state & 1;
+
+ /* some types need to override the disposition, e.g. application/x-pkcs7-mime */
+ if (handle->flags & EM_FORMAT_HANDLER_INLINE_DISPOSITION)
+ return TRUE;
+
+ tmp = camel_mime_part_get_disposition(part);
+ if (tmp)
+ return g_ascii_strcasecmp(tmp, "inline") == 0;
+
+ /* otherwise, use the default for this handler type */
+ return (handle->flags & EM_FORMAT_HANDLER_INLINE) != 0;
+}
+
+/**
+ * em_format_set_inline:
+ * @emf:
+ * @partid: id of part
+ * @state:
+ *
+ * Force the attachment @part to be expanded or hidden explictly to match
+ * @state. This is used only to record the change for a redraw or
+ * cloned layout render and does not force a redraw.
+ **/
+void em_format_set_inline(EMFormat *emf, const gchar *partid, gint state)
+{
+ struct _EMFormatCache *emfc;
+
+ emfc = g_hash_table_lookup(emf->inline_table, partid);
+ if (emfc == NULL) {
+ emfc = emf_insert_cache(emf, partid);
+ } else if (emfc->state != INLINE_UNSET && (emfc->state & 1) == state)
+ return;
+
+ emfc->state = state?INLINE_ON:INLINE_OFF;
+
+ if (emf->message)
+ em_format_redraw(emf);
+}
+
+void
+em_format_format_attachment (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *mime_part,
+ const gchar *mime_type,
+ const struct _EMFormatHandler *info)
+{
+ EMFormatClass *class;
+
+ g_return_if_fail (EM_IS_FORMAT (emf));
+ g_return_if_fail (CAMEL_IS_STREAM (stream));
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+ g_return_if_fail (mime_type != NULL);
+ g_return_if_fail (info != NULL);
+
+ class = EM_FORMAT_GET_CLASS (emf);
+ g_return_if_fail (class->format_attachment != NULL);
+ class->format_attachment (emf, stream, mime_part, mime_type, info);
+}
+
+void
+em_format_format_error (EMFormat *emf,
+ CamelStream *stream,
+ const gchar *format,
+ ...)
+{
+ EMFormatClass *class;
+ gchar *errmsg;
+ va_list ap;
+
+ g_return_if_fail (EM_IS_FORMAT (emf));
+ g_return_if_fail (CAMEL_IS_STREAM (stream));
+ g_return_if_fail (format != NULL);
+
+ class = EM_FORMAT_GET_CLASS (emf);
+ g_return_if_fail (class->format_error != NULL);
+
+ va_start (ap, format);
+ errmsg = g_strdup_vprintf (format, ap);
+ class->format_error (emf, stream, errmsg);
+ g_free (errmsg);
+ va_end (ap);
+}
+
+void
+em_format_format_secure (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *mime_part,
+ CamelCipherValidity *valid)
+{
+ EMFormatClass *class;
+
+ g_return_if_fail (EM_IS_FORMAT (emf));
+ g_return_if_fail (CAMEL_IS_STREAM (stream));
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+ g_return_if_fail (valid != NULL);
+
+ class = EM_FORMAT_GET_CLASS (emf);
+ g_return_if_fail (class->format_secure != NULL);
+ class->format_secure (emf, stream, mime_part, valid);
+
+ if (emf->valid_parent == NULL && emf->valid != NULL) {
+ camel_cipher_validity_free (emf->valid);
+ emf->valid = NULL;
+ }
+}
+
+void
+em_format_format_source (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *mime_part)
+{
+ EMFormatClass *class;
+
+ g_return_if_fail (EM_IS_FORMAT (emf));
+ g_return_if_fail (CAMEL_IS_STREAM (stream));
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+
+ class = EM_FORMAT_GET_CLASS (emf);
+ g_return_if_fail (class->format_source != NULL);
+ class->format_source (emf, stream, mime_part);
+}
+
+gboolean
+em_format_busy (EMFormat *emf)
+{
+ EMFormatClass *class;
+
+ g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE);
+
+ class = EM_FORMAT_GET_CLASS (emf);
+ g_return_val_if_fail (class->busy != NULL, FALSE);
+ return class->busy (emf);
+}
+
+/* should this be virtual? */
+void
+em_format_format_content(EMFormat *emf, CamelStream *stream, CamelMimePart *part)
+{
+ CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)part);
+
+ if (camel_content_type_is (dw->mime_type, "text", "*"))
+ em_format_format_text(emf, stream, (CamelDataWrapper *)part);
+ else
+ camel_data_wrapper_decode_to_stream(dw, stream);
+}
+
+/**
+ * em_format_format_content:
+ * @emf:
+ * @stream: Where to write the converted text
+ * @part: Part whose container is to be formatted
+ *
+ * Decode/output a part's content to @stream.
+ **/
+void
+em_format_format_text(EMFormat *emf, CamelStream *stream, CamelDataWrapper *dw)
+{
+ CamelStreamFilter *filter_stream;
+ CamelMimeFilterCharset *filter;
+ const gchar *charset = NULL;
+ CamelMimeFilterWindows *windows = NULL;
+ CamelStream *mem_stream = NULL;
+ gsize size;
+ gsize max;
+ GConfClient *gconf;
+
+ if (emf->charset) {
+ charset = emf->charset;
+ } else if (dw->mime_type
+ && (charset = camel_content_type_param (dw->mime_type, "charset"))
+ && g_ascii_strncasecmp(charset, "iso-8859-", 9) == 0) {
+ CamelStream *null;
+
+ /* Since a few Windows mailers like to claim they sent
+ * out iso-8859-# encoded text when they really sent
+ * out windows-cp125#, do some simple sanity checking
+ * before we move on... */
+
+ null = camel_stream_null_new();
+ filter_stream = camel_stream_filter_new_with_stream(null);
+ camel_object_unref(null);
+
+ windows = (CamelMimeFilterWindows *)camel_mime_filter_windows_new(charset);
+ camel_stream_filter_add(filter_stream, (CamelMimeFilter *)windows);
+
+ camel_data_wrapper_decode_to_stream(dw, (CamelStream *)filter_stream);
+ camel_stream_flush((CamelStream *)filter_stream);
+ camel_object_unref(filter_stream);
+
+ charset = camel_mime_filter_windows_real_charset (windows);
+ } else if (charset == NULL) {
+ charset = emf->default_charset;
+ }
+
+ mem_stream = (CamelStream *)camel_stream_mem_new ();
+ filter_stream = camel_stream_filter_new_with_stream(mem_stream);
+
+ if ((filter = camel_mime_filter_charset_new_convert(charset, "UTF-8"))) {
+ camel_stream_filter_add(filter_stream, (CamelMimeFilter *) filter);
+ camel_object_unref(filter);
+ }
+
+ max = -1;
+
+ gconf = gconf_client_get_default ();
+ if (gconf_client_get_bool (gconf, "/apps/evolution/mail/display/force_message_limit", NULL)) {
+ max = gconf_client_get_int (gconf, "/apps/evolution/mail/display/message_text_part_limit", NULL);
+ if (max == 0)
+ max = -1;
+ }
+ g_object_unref (gconf);
+
+ size = camel_data_wrapper_decode_to_stream(emf->mode == EM_FORMAT_SOURCE ? (CamelDataWrapper *)dw: camel_medium_get_content_object((CamelMedium *)dw), (CamelStream *)filter_stream);
+ camel_stream_flush((CamelStream *)filter_stream);
+ camel_object_unref(filter_stream);
+ camel_stream_reset (mem_stream);
+
+ if (max == -1 || size == -1 || size < (max * 1024) || emf->composer) {
+ camel_stream_write_to_stream(mem_stream, (CamelStream *)stream);
+ camel_stream_flush((CamelStream *)stream);
+ } else {
+ ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_optional(emf, stream, (CamelMimePart *)dw, mem_stream);
+ }
+
+ if (windows)
+ camel_object_unref(windows);
+}
+
+/**
+ * em_format_describe_part:
+ * @part:
+ * @mimetype:
+ *
+ * Generate a simple textual description of a part, @mime_type represents the
+ * the content.
+ *
+ * Return value:
+ **/
+gchar *
+em_format_describe_part(CamelMimePart *part, const gchar *mime_type)
+{
+ GString *stext;
+ const gchar *filename, *description;
+ gchar *content_type, *desc;
+
+ stext = g_string_new("");
+ content_type = g_content_type_from_mime_type (mime_type);
+ desc = g_content_type_get_description (content_type ? content_type : mime_type);
+ g_free (content_type);
+ g_string_append_printf (stext, _("%s attachment"), desc ? desc : mime_type);
+ g_free (desc);
+ if ((filename = camel_mime_part_get_filename (part)))
+ g_string_append_printf(stext, " (%s)", filename);
+ if ((description = camel_mime_part_get_description(part)) &&
+ (*description != 0) &&
+ !(filename && (strcmp(filename, description) == 0)))
+ g_string_append_printf(stext, ", \"%s\"", description);
+
+ return g_string_free (stext, FALSE);
+}
+
+/* ********************************************************************** */
+
+#ifdef ENABLE_SMIME
+static void
+emf_application_xpkcs7mime(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ CamelCipherContext *context;
+ CamelException *ex;
+ CamelMimePart *opart;
+ CamelCipherValidity *valid;
+ struct _EMFormatCache *emfc;
+
+ /* should this perhaps run off a key of ".secured" ? */
+ emfc = g_hash_table_lookup(emf->inline_table, emf->part_id->str);
+ if (emfc && emfc->valid) {
+ em_format_format_secure(emf, stream, emfc->secured, camel_cipher_validity_clone(emfc->valid));
+ return;
+ }
+
+ ex = camel_exception_new();
+
+ context = camel_smime_context_new(emf->session);
+
+ opart = camel_mime_part_new();
+ valid = camel_cipher_decrypt(context, part, opart, ex);
+ if (valid == NULL) {
+ em_format_format_error(emf, stream, "%s", ex->desc?ex->desc:_("Could not parse S/MIME message: Unknown error"));
+ em_format_part_as(emf, stream, part, NULL);
+ } else {
+ if (emfc == NULL)
+ emfc = emf_insert_cache(emf, emf->part_id->str);
+
+ emfc->valid = camel_cipher_validity_clone(valid);
+ camel_object_ref((emfc->secured = opart));
+
+ em_format_format_secure(emf, stream, opart, valid);
+ }
+
+ camel_object_unref(opart);
+ camel_object_unref(context);
+ camel_exception_free(ex);
+}
+#endif
+
+/* RFC 1740 */
+static void
+emf_multipart_appledouble(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part);
+ CamelMimePart *mime_part;
+ gint len;
+
+ if (!CAMEL_IS_MULTIPART(mp)) {
+ em_format_format_source(emf, stream, part);
+ return;
+ }
+
+ mime_part = camel_multipart_get_part(mp, 1);
+ if (mime_part) {
+ /* try the data fork for something useful, doubtful but who knows */
+ len = emf->part_id->len;
+ g_string_append_printf(emf->part_id, ".appledouble.1");
+ em_format_part(emf, stream, mime_part);
+ g_string_truncate(emf->part_id, len);
+ } else
+ em_format_format_source(emf, stream, part);
+
+}
+
+/* RFC ??? */
+static void
+emf_multipart_mixed(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part);
+ gint i, nparts, len;
+
+ if (!CAMEL_IS_MULTIPART(mp)) {
+ em_format_format_source(emf, stream, part);
+ return;
+ }
+
+ len = emf->part_id->len;
+ nparts = camel_multipart_get_number(mp);
+ for (i = 0; i < nparts; i++) {
+ part = camel_multipart_get_part(mp, i);
+ g_string_append_printf(emf->part_id, ".mixed.%d", i);
+ em_format_part(emf, stream, part);
+ g_string_truncate(emf->part_id, len);
+ }
+}
+
+/* RFC 1740 */
+static void
+emf_multipart_alternative(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part);
+ gint i, nparts, bestid = 0;
+ CamelMimePart *best = NULL;
+
+ if (!CAMEL_IS_MULTIPART(mp)) {
+ em_format_format_source(emf, stream, part);
+ return;
+ }
+
+ /* as per rfc, find the last part we know how to display */
+ nparts = camel_multipart_get_number(mp);
+ for (i = 0; i < nparts; i++) {
+ CamelContentType *type;
+ gchar *mime_type;
+
+ /* is it correct to use the passed in *part here? */
+ part = camel_multipart_get_part(mp, i);
+
+ if (!part)
+ continue;
+
+ type = camel_mime_part_get_content_type (part);
+ mime_type = camel_content_type_simple (type);
+
+ camel_strdown (mime_type);
+
+ /*if (want_plain && !strcmp (mime_type, "text/plain"))
+ return part;*/
+
+ if (em_format_find_handler(emf, mime_type)
+ || (best == NULL && em_format_fallback_handler(emf, mime_type))) {
+ best = part;
+ bestid = i;
+ }
+
+ g_free(mime_type);
+ }
+
+ if (best) {
+ gint len = emf->part_id->len;
+
+ g_string_append_printf(emf->part_id, ".alternative.%d", bestid);
+ em_format_part(emf, stream, best);
+ g_string_truncate(emf->part_id, len);
+ } else
+ emf_multipart_mixed(emf, stream, part, info);
+}
+
+static void
+emf_multipart_encrypted(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ CamelCipherContext *context;
+ CamelException *ex;
+ const gchar *protocol;
+ CamelMimePart *opart;
+ CamelCipherValidity *valid;
+ CamelMultipartEncrypted *mpe;
+ struct _EMFormatCache *emfc;
+
+ /* should this perhaps run off a key of ".secured" ? */
+ emfc = g_hash_table_lookup(emf->inline_table, emf->part_id->str);
+ if (emfc && emfc->valid) {
+ em_format_format_secure(emf, stream, emfc->secured, camel_cipher_validity_clone(emfc->valid));
+ return;
+ }
+
+ mpe = (CamelMultipartEncrypted*)camel_medium_get_content_object((CamelMedium *)part);
+ if (!CAMEL_IS_MULTIPART_ENCRYPTED(mpe)) {
+ em_format_format_error(emf, stream, _("Could not parse MIME message. Displaying as source."));
+ em_format_format_source(emf, stream, part);
+ return;
+ }
+
+ /* Currently we only handle RFC2015-style PGP encryption. */
+ protocol = camel_content_type_param(((CamelDataWrapper *)mpe)->mime_type, "protocol");
+ if (!protocol || g_ascii_strcasecmp (protocol, "application/pgp-encrypted") != 0) {
+ em_format_format_error(emf, stream, _("Unsupported encryption type for multipart/encrypted"));
+ em_format_part_as(emf, stream, part, "multipart/mixed");
+ return;
+ }
+
+ ex = camel_exception_new();
+ context = camel_gpg_context_new(emf->session);
+ opart = camel_mime_part_new();
+ valid = camel_cipher_decrypt(context, part, opart, ex);
+ if (valid == NULL) {
+ em_format_format_error(emf, stream, ex->desc?_("Could not parse PGP/MIME message"):_("Could not parse PGP/MIME message: Unknown error"));
+ if (ex->desc)
+ em_format_format_error(emf, stream, "%s", ex->desc);
+ em_format_part_as(emf, stream, part, "multipart/mixed");
+ } else {
+ if (emfc == NULL)
+ emfc = emf_insert_cache(emf, emf->part_id->str);
+
+ emfc->valid = camel_cipher_validity_clone(valid);
+ camel_object_ref((emfc->secured = opart));
+
+ em_format_format_secure(emf, stream, opart, valid);
+ }
+
+ /* TODO: Make sure when we finalize this part, it is zero'd out */
+ camel_object_unref(opart);
+ camel_object_unref(context);
+ camel_exception_free(ex);
+}
+
+static void
+emf_write_related(EMFormat *emf, CamelStream *stream, EMFormatPURI *puri)
+{
+ em_format_format_content(emf, stream, puri->part);
+ camel_stream_close(stream);
+}
+
+/* RFC 2387 */
+static void
+emf_multipart_related(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part);
+ CamelMimePart *body_part, *display_part = NULL;
+ CamelContentType *content_type;
+ const gchar *start;
+ gint i, nparts, partidlen, displayid = 0;
+ gchar *oldpartid;
+ struct _EMFormatPURITree *ptree;
+ EMFormatPURI *puri, *purin;
+
+ if (!CAMEL_IS_MULTIPART(mp)) {
+ em_format_format_source(emf, stream, part);
+ return;
+ }
+
+ /* FIXME: put this stuff in a shared function */
+ nparts = camel_multipart_get_number(mp);
+ content_type = camel_mime_part_get_content_type(part);
+ start = camel_content_type_param (content_type, "start");
+ if (start && strlen(start)>2) {
+ gint len;
+ const gchar *cid;
+
+ /* strip <>'s */
+ len = strlen (start) - 2;
+ start++;
+
+ for (i=0; i<nparts; i++) {
+ body_part = camel_multipart_get_part(mp, i);
+ cid = camel_mime_part_get_content_id(body_part);
+
+ if (cid && !strncmp(cid, start, len) && strlen(cid) == len) {
+ display_part = body_part;
+ displayid = i;
+ break;
+ }
+ }
+ } else {
+ display_part = camel_multipart_get_part(mp, 0);
+ }
+
+ if (display_part == NULL) {
+ emf_multipart_mixed(emf, stream, part, info);
+ return;
+ }
+
+ em_format_push_level(emf);
+
+ oldpartid = g_strdup(emf->part_id->str);
+ partidlen = emf->part_id->len;
+
+ /* queue up the parts for possible inclusion */
+ for (i = 0; i < nparts; i++) {
+ body_part = camel_multipart_get_part(mp, i);
+ if (body_part != display_part) {
+ /* set the partid since add_puri uses it */
+ g_string_append_printf(emf->part_id, ".related.%d", i);
+ puri = em_format_add_puri(emf, sizeof(EMFormatPURI), NULL, body_part, emf_write_related);
+ g_string_truncate(emf->part_id, partidlen);
+ d(printf(" part '%s' '%s' added\n", puri->uri?puri->uri:"", puri->cid));
+ }
+ }
+
+ g_string_append_printf(emf->part_id, ".related.%d", displayid);
+ em_format_part(emf, stream, display_part);
+ g_string_truncate(emf->part_id, partidlen);
+ camel_stream_flush(stream);
+
+ ptree = emf->pending_uri_level;
+ puri = (EMFormatPURI *)ptree->uri_list.head;
+ purin = puri->next;
+ while (purin) {
+ if (puri->use_count == 0) {
+ d(printf("part '%s' '%s' used '%d'\n", puri->uri?puri->uri:"", puri->cid, puri->use_count));
+ if (puri->func == emf_write_related) {
+ g_string_printf(emf->part_id, "%s", puri->part_id);
+ em_format_part(emf, stream, puri->part);
+ } else {
+ d(printf("unreferenced uri generated by format code: %s\n", puri->uri?puri->uri:puri->cid));
+ }
+ }
+ puri = purin;
+ purin = purin->next;
+ }
+
+ g_string_printf(emf->part_id, "%s", oldpartid);
+ g_free(oldpartid);
+
+ em_format_pull_level(emf);
+}
+
+static void
+emf_multipart_signed(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ CamelMimePart *cpart;
+ CamelMultipartSigned *mps;
+ CamelCipherContext *cipher = NULL;
+ struct _EMFormatCache *emfc;
+
+ /* should this perhaps run off a key of ".secured" ? */
+ emfc = g_hash_table_lookup(emf->inline_table, emf->part_id->str);
+ if (emfc && emfc->valid) {
+ em_format_format_secure(emf, stream, emfc->secured, camel_cipher_validity_clone(emfc->valid));
+ return;
+ }
+
+ mps = (CamelMultipartSigned *)camel_medium_get_content_object((CamelMedium *)part);
+ if (!CAMEL_IS_MULTIPART_SIGNED(mps)
+ || (cpart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_CONTENT)) == NULL) {
+ em_format_format_error(emf, stream, _("Could not parse MIME message. Displaying as source."));
+ em_format_format_source(emf, stream, part);
+ return;
+ }
+
+ /* FIXME: Should be done via a plugin interface */
+ /* FIXME: duplicated in em-format-html-display.c */
+ if (mps->protocol) {
+#ifdef ENABLE_SMIME
+ if (g_ascii_strcasecmp("application/x-pkcs7-signature", mps->protocol) == 0
+ || g_ascii_strcasecmp("application/pkcs7-signature", mps->protocol) == 0)
+ cipher = camel_smime_context_new(emf->session);
+ else
+#endif
+ if (g_ascii_strcasecmp("application/pgp-signature", mps->protocol) == 0)
+ cipher = camel_gpg_context_new(emf->session);
+ }
+
+ if (cipher == NULL) {
+ em_format_format_error(emf, stream, _("Unsupported signature format"));
+ em_format_part_as(emf, stream, part, "multipart/mixed");
+ } else {
+ CamelException *ex = camel_exception_new();
+ CamelCipherValidity *valid;
+
+ valid = camel_cipher_verify(cipher, part, ex);
+ if (valid == NULL) {
+ em_format_format_error(emf, stream, ex->desc?_("Error verifying signature"):_("Unknown error verifying signature"));
+ if (ex->desc)
+ em_format_format_error(emf, stream, "%s", ex->desc);
+ em_format_part_as(emf, stream, part, "multipart/mixed");
+ } else {
+ if (emfc == NULL)
+ emfc = emf_insert_cache(emf, emf->part_id->str);
+
+ emfc->valid = camel_cipher_validity_clone(valid);
+ camel_object_ref((emfc->secured = cpart));
+
+ em_format_format_secure(emf, stream, cpart, valid);
+ }
+
+ camel_exception_free(ex);
+ camel_object_unref(cipher);
+ }
+}
+
+/* RFC 4155 */
+static void
+emf_application_mbox (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *mime_part,
+ const EMFormatHandler *info)
+{
+ const EMFormatHandler *handle;
+ CamelMimeParser *parser;
+ CamelStream *mem_stream;
+ camel_mime_parser_state_t state;
+
+ /* Extract messages from the application/mbox part and
+ * render them as a flat list of messages. */
+
+ /* XXX If the mbox has multiple messages, maybe render them
+ * as a multipart/digest so each message can be expanded
+ * or collapsed individually.
+ *
+ * See attachment_handler_mail_x_uid_list() for example. */
+
+ /* XXX This is based on em_utils_read_messages_from_stream().
+ * Perhaps refactor that function to return an array of
+ * messages instead of assuming we want to append them
+ * to a folder? */
+
+ handle = em_format_find_handler (emf, "x-evolution/message/rfc822");
+ g_return_if_fail (handle != NULL);
+
+ parser = camel_mime_parser_new ();
+ camel_mime_parser_scan_from (parser, TRUE);
+
+ mem_stream = camel_stream_mem_new ();
+ camel_data_wrapper_decode_to_stream (
+ CAMEL_DATA_WRAPPER (mime_part), mem_stream);
+ camel_seekable_stream_seek (
+ CAMEL_SEEKABLE_STREAM (mem_stream), 0, CAMEL_STREAM_SET);
+ camel_mime_parser_init_with_stream (parser, mem_stream);
+ camel_object_unref (mem_stream);
+
+ /* Extract messages from the mbox. */
+ state = camel_mime_parser_step (parser, NULL, NULL);
+ while (state == CAMEL_MIME_PARSER_STATE_FROM) {
+ CamelMimeMessage *message;
+
+ message = camel_mime_message_new ();
+ mime_part = CAMEL_MIME_PART (message);
+
+ if (camel_mime_part_construct_from_parser (mime_part, parser) == -1) {
+ camel_object_unref (message);
+ break;
+ }
+
+ /* Render the message. */
+ handle->handler (emf, stream, mime_part, handle);
+
+ camel_object_unref (message);
+
+ /* Skip past CAMEL_MIME_PARSER_STATE_FROM_END. */
+ state = camel_mime_parser_step (parser, NULL, NULL);
+
+ state = camel_mime_parser_step (parser, NULL, NULL);
+ }
+
+ camel_object_unref (parser);
+}
+
+static void
+emf_message_rfc822(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)part);
+ const EMFormatHandler *handle;
+ gint len;
+
+ if (!CAMEL_IS_MIME_MESSAGE(dw)) {
+ em_format_format_source(emf, stream, part);
+ return;
+ }
+
+ len = emf->part_id->len;
+ g_string_append_printf(emf->part_id, ".rfc822");
+
+ handle = em_format_find_handler(emf, "x-evolution/message/rfc822");
+ if (handle)
+ handle->handler(emf, stream, (CamelMimePart *)dw, handle);
+
+ g_string_truncate(emf->part_id, len);
+}
+
+static void
+emf_message_deliverystatus(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info)
+{
+ em_format_format_text(emf, stream, (CamelDataWrapper *)part);
+}
+
+static void
+emf_inlinepgp_signed(EMFormat *emf, CamelStream *stream, CamelMimePart *ipart, EMFormatHandler *info)
+{
+ CamelStreamFilter *filtered_stream;
+ CamelMimeFilterPgp *pgp_filter;
+ CamelContentType *content_type;
+ CamelCipherContext *cipher;
+ CamelCipherValidity *valid;
+ CamelDataWrapper *dw;
+ CamelMimePart *opart;
+ CamelStream *ostream;
+ CamelException *ex;
+ gchar *type;
+
+ if (!ipart) {
+ em_format_format_error(emf, stream, _("Unknown error verifying signature"));
+ return;
+ }
+
+ ex = camel_exception_new();
+ cipher = camel_gpg_context_new(emf->session);
+ /* Verify the signature of the message */
+ valid = camel_cipher_verify(cipher, ipart, ex);
+ if (!valid) {
+ em_format_format_error(emf, stream, ex->desc?_("Error verifying signature"):_("Unknown error verifying signature"));
+ if (ex->desc)
+ em_format_format_error(emf, stream, "%s", ex->desc);
+ em_format_format_source(emf, stream, ipart);
+ /* I think this will loop: em_format_part_as(emf, stream, part, "text/plain"); */
+ camel_exception_free(ex);
+ camel_object_unref(cipher);
+ return;
+ }
+
+ /* Setup output stream */
+ ostream = camel_stream_mem_new();
+ filtered_stream = camel_stream_filter_new_with_stream(ostream);
+
+ /* Add PGP header / footer filter */
+ pgp_filter = (CamelMimeFilterPgp *)camel_mime_filter_pgp_new();
+ camel_stream_filter_add(filtered_stream, (CamelMimeFilter *)pgp_filter);
+ camel_object_unref(pgp_filter);
+
+ /* Pass through the filters that have been setup */
+ dw = camel_medium_get_content_object((CamelMedium *)ipart);
+ camel_data_wrapper_decode_to_stream(dw, (CamelStream *)filtered_stream);
+ camel_stream_flush((CamelStream *)filtered_stream);
+ camel_object_unref(filtered_stream);
+
+ /* Create a new text/plain MIME part containing the signed content preserving the original part's Content-Type params */
+ content_type = camel_mime_part_get_content_type (ipart);
+ type = camel_content_type_format (content_type);
+ content_type = camel_content_type_decode (type);
+ g_free (type);
+
+ g_free (content_type->type);
+ content_type->type = g_strdup ("text");
+ g_free (content_type->subtype);
+ content_type->subtype = g_strdup ("plain");
+ type = camel_content_type_format (content_type);
+ camel_content_type_unref (content_type);
+
+ dw = camel_data_wrapper_new ();
+ camel_data_wrapper_construct_from_stream (dw, ostream);
+ camel_data_wrapper_set_mime_type (dw, type);
+ g_free (type);
+
+ opart = camel_mime_part_new ();
+ camel_medium_set_content_object ((CamelMedium *) opart, dw);
+ camel_data_wrapper_set_mime_type_field ((CamelDataWrapper *) opart, dw->mime_type);
+
+ /* Pass it off to the real formatter */
+ em_format_format_secure(emf, stream, opart, valid);
+
+ /* Clean Up */
+ camel_object_unref(dw);
+ camel_object_unref(opart);
+ camel_object_unref(ostream);
+ camel_object_unref(cipher);
+ camel_exception_free(ex);
+}
+
+static void
+emf_inlinepgp_encrypted(EMFormat *emf, CamelStream *stream, CamelMimePart *ipart, EMFormatHandler *info)
+{
+ CamelCipherContext *cipher;
+ CamelCipherValidity *valid;
+ CamelException *ex;
+ CamelMimePart *opart;
+ CamelDataWrapper *dw;
+ gchar *mime_type;
+
+ cipher = camel_gpg_context_new(emf->session);
+ ex = camel_exception_new();
+ opart = camel_mime_part_new();
+ /* Decrypt the message */
+ valid = camel_cipher_decrypt (cipher, ipart, opart, ex);
+ if (!valid) {
+ em_format_format_error(emf, stream, ex->desc?_("Could not parse PGP message"):_("Could not parse PGP message: Unknown error"));
+ if (ex->desc)
+ em_format_format_error(emf, stream, "%s", ex->desc);
+ em_format_format_source(emf, stream, ipart);
+ /* I think this will loop: em_format_part_as(emf, stream, part, "text/plain"); */
+ camel_exception_free(ex);
+ camel_object_unref(cipher);
+ camel_object_unref(opart);
+ return;
+ }
+
+ dw = camel_medium_get_content_object ((CamelMedium *)opart);
+ mime_type = camel_data_wrapper_get_mime_type (dw);
+
+ /* this ensures to show the 'opart' as inlined, if possible */
+ if (mime_type && g_ascii_strcasecmp (mime_type, "application/octet-stream") == 0) {
+ const gchar *snoop = em_format_snoop_type (opart);
+
+ if (snoop)
+ camel_data_wrapper_set_mime_type (dw, snoop);
+ }
+
+ g_free (mime_type);
+
+ /* Pass it off to the real formatter */
+ em_format_format_secure(emf, stream, opart, valid);
+
+ /* Clean Up */
+ camel_object_unref(opart);
+ camel_object_unref (cipher);
+ camel_exception_free (ex);
+}
+
+static EMFormatHandler type_builtin_table[] = {
+#ifdef ENABLE_SMIME
+ { (gchar *) "application/x-pkcs7-mime", (EMFormatFunc)emf_application_xpkcs7mime, EM_FORMAT_HANDLER_INLINE_DISPOSITION },
+#endif
+ { (gchar *) "application/mbox", emf_application_mbox, EM_FORMAT_HANDLER_INLINE },
+ { (gchar *) "multipart/alternative", emf_multipart_alternative },
+ { (gchar *) "multipart/appledouble", emf_multipart_appledouble },
+ { (gchar *) "multipart/encrypted", emf_multipart_encrypted },
+ { (gchar *) "multipart/mixed", emf_multipart_mixed },
+ { (gchar *) "multipart/signed", emf_multipart_signed },
+ { (gchar *) "multipart/related", emf_multipart_related },
+ { (gchar *) "multipart/*", emf_multipart_mixed },
+ { (gchar *) "message/rfc822", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE },
+ { (gchar *) "message/news", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE },
+ { (gchar *) "message/delivery-status", emf_message_deliverystatus },
+ { (gchar *) "message/*", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE },
+
+ /* Insert brokenly-named parts here */
+#ifdef ENABLE_SMIME
+ { (gchar *) "application/pkcs7-mime", (EMFormatFunc)emf_application_xpkcs7mime, EM_FORMAT_HANDLER_INLINE_DISPOSITION },
+#endif
+
+ /* internal types */
+ { (gchar *) "application/x-inlinepgp-signed", (EMFormatFunc)emf_inlinepgp_signed },
+ { (gchar *) "application/x-inlinepgp-encrypted", (EMFormatFunc)emf_inlinepgp_encrypted },
+};
+
+static void
+emf_builtin_init(EMFormatClass *class)
+{
+ gint i;
+
+ for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++)
+ g_hash_table_insert(class->type_handlers, type_builtin_table[i].mime_type, &type_builtin_table[i]);
+}
+
+/**
+ * em_format_snoop_type:
+ * @part:
+ *
+ * Tries to snoop the mime type of a part.
+ *
+ * Return value: NULL if unknown (more likely application/octet-stream).
+ **/
+const gchar *
+em_format_snoop_type (CamelMimePart *part)
+{
+ /* cache is here only to be able still return const gchar * */
+ static GHashTable *types_cache = NULL;
+
+ const gchar *filename;
+ gchar *name_type = NULL, *magic_type = NULL, *res, *tmp;
+ CamelDataWrapper *dw;
+
+ filename = camel_mime_part_get_filename (part);
+ if (filename != NULL)
+ name_type = e_util_guess_mime_type (filename, FALSE);
+
+ dw = camel_medium_get_content_object((CamelMedium *)part);
+ if (!camel_data_wrapper_is_offline(dw)) {
+ CamelStreamMem *mem = (CamelStreamMem *)camel_stream_mem_new();
+
+ if (camel_data_wrapper_decode_to_stream(dw, (CamelStream *)mem) > 0) {
+ gchar *ct = g_content_type_guess (filename, mem->buffer->data, mem->buffer->len, NULL);
+
+ if (ct)
+ magic_type = g_content_type_get_mime_type (ct);
+
+ g_free (ct);
+ }
+ camel_object_unref(mem);
+ }
+
+ d(printf("snooped part, magic_type '%s' name_type '%s'\n", magic_type, name_type));
+
+ /* If gvfs doesn't recognize the data by magic, but it
+ * contains English words, it will call it text/plain. If the
+ * filename-based check came up with something different, use
+ * that instead and if it returns "application/octet-stream"
+ * try to do better with the filename check.
+ */
+
+ if (magic_type) {
+ if (name_type
+ && (!strcmp(magic_type, "text/plain")
+ || !strcmp(magic_type, "application/octet-stream")))
+ res = name_type;
+ else
+ res = magic_type;
+ } else
+ res = name_type;
+
+ if (res != name_type)
+ g_free (name_type);
+
+ if (res != magic_type)
+ g_free (magic_type);
+
+ if (!types_cache)
+ types_cache = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) NULL);
+
+ if (res) {
+ tmp = g_hash_table_lookup (types_cache, res);
+ if (tmp) {
+ g_free (res);
+ res = tmp;
+ } else {
+ g_hash_table_insert (types_cache, res, res);
+ }
+ }
+
+ return res;
+
+ /* We used to load parts to check their type, we dont anymore,
+ see bug #11778 for some discussion */
+}
diff --git a/em-format/em-format.h b/em-format/em-format.h
new file mode 100644
index 0000000000..7e59ecf462
--- /dev/null
+++ b/em-format/em-format.h
@@ -0,0 +1,402 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ Abstract class for formatting mime messages
+*/
+
+#ifndef EM_FORMAT_H
+#define EM_FORMAT_H
+
+#include <glib-object.h>
+#include <camel/camel-url.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-stream.h>
+#include <camel/camel-session.h>
+#include <camel/camel-mime-part.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-cipher-context.h>
+#include <libedataserver/e-msgport.h>
+
+/* Standard GObject macros */
+#define EM_TYPE_FORMAT \
+ (em_format_get_type ())
+#define EM_FORMAT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), EM_TYPE_FORMAT, EMFormat))
+#define EM_FORMAT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), EM_TYPE_FORMAT, EMFormatClass))
+#define EM_IS_FORMAT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), EM_TYPE_FORMAT))
+#define EM_IS_FORMAT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), EM_TYPE_FORMAT))
+#define EM_FORMAT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), EM_TYPE_FORMAT, EMFormatClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMFormat EMFormat;
+typedef struct _EMFormatClass EMFormatClass;
+typedef struct _EMFormatPrivate EMFormatPrivate;
+
+typedef struct _EMFormatHandler EMFormatHandler;
+typedef struct _EMFormatHeader EMFormatHeader;
+
+typedef void (*EMFormatFunc) (EMFormat *md, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info);
+
+typedef enum _em_format_mode_t {
+ EM_FORMAT_NORMAL,
+ EM_FORMAT_ALLHEADERS,
+ EM_FORMAT_SOURCE
+} em_format_mode_t;
+
+/**
+ * struct _EMFormatHandler - MIME type handler.
+ *
+ * @mime_type: Type this handler handles.
+ * @handler: The handler callback.
+ * @flags: Handling flags, see enum _em_format_handler_t.
+ * @old: The last handler set on this type. Allows overrides to
+ * fallback to previous implementation.
+ *
+ **/
+struct _EMFormatHandler {
+ gchar *mime_type;
+ EMFormatFunc handler;
+ guint32 flags;
+
+ struct _EMFormatHandler *old;
+};
+
+/**
+ * enum _em_format_handler_t - Format handler flags.
+ *
+ * @EM_FORMAT_HANDLER_INLINE: This type should be shown expanded
+ * inline by default.
+ * @EM_FORMAT_HANDLER_INLINE_DISPOSITION: This type should always be
+ * shown inline, despite what the Content-Disposition suggests.
+ *
+ **/
+enum _em_format_handler_t {
+ EM_FORMAT_HANDLER_INLINE = 1<<0,
+ EM_FORMAT_HANDLER_INLINE_DISPOSITION = 1<<1
+};
+
+typedef struct _EMFormatPURI EMFormatPURI;
+typedef void (*EMFormatPURIFunc)(EMFormat *md, CamelStream *stream, EMFormatPURI *puri);
+
+/**
+ * struct _EMFormatPURI - Pending URI object.
+ *
+ * @next: Double-linked list header.
+ * @prev: Double-linked list header.
+ * @free: May be set by allocator and will be called when no longer needed.
+ * @format:
+ * @uri: Calculated URI of the part, if the part has one in its
+ * Content-Location field.
+ * @cid: The RFC2046 Content-Id of the part. If none is present, a unique value
+ * is calculated from @part_id.
+ * @part_id: A unique identifier for each part.
+ * @func: Callback for when the URI is requested. The callback writes
+ * its data to the supplied stream.
+ * @part:
+ * @use_count:
+ *
+ * This is used for multipart/related, and other formatters which may
+ * need to include a reference to out-of-band data in the content
+ * stream.
+ *
+ * This object may be subclassed as a struct.
+ **/
+struct _EMFormatPURI {
+ struct _EMFormatPURI *next;
+ struct _EMFormatPURI *prev;
+
+ void (*free)(struct _EMFormatPURI *p); /* optional callback for freeing user-fields */
+ struct _EMFormat *format;
+
+ gchar *uri; /* will be the location of the part, may be empty */
+ gchar *cid; /* will always be set, a fake one created if needed */
+ gchar *part_id; /* will always be set, emf->part_id->str for this part */
+
+ EMFormatPURIFunc func;
+ CamelMimePart *part;
+
+ guint use_count; /* used by multipart/related to see if it was accessed */
+};
+
+/**
+ * struct _EMFormatPURITree - Pending URI visibility tree.
+ *
+ * @next: Double-linked list header.
+ * @prev: Double-linked list header.
+ * @parent: Parent in tree.
+ * @uri_list: List of EMFormatPURI objects at this level.
+ * @children: Child nodes of EMFormatPURITree.
+ *
+ * This structure is used internally to form a visibility tree of
+ * parts in the current formatting stream. This is to implement the
+ * part resolution rules for RFC2387 to implement multipart/related.
+ **/
+struct _EMFormatPURITree {
+ struct _EMFormatPURITree *next;
+ struct _EMFormatPURITree *prev;
+ struct _EMFormatPURITree *parent;
+
+ EDList uri_list;
+ EDList children;
+};
+
+struct _EMFormatHeader {
+ struct _EMFormatHeader *next, *prev;
+
+ guint32 flags; /* E_FORMAT_HEADER_* */
+ gchar name[1];
+};
+
+#define EM_FORMAT_HEADER_BOLD (1<<0)
+#define EM_FORMAT_HEADER_LAST (1<<4) /* reserve 4 slots */
+
+/**
+ * struct _EMFormat - Mail formatter object.
+ *
+ * @parent:
+ * @priv:
+ * @message:
+ * @folder:
+ * @uid:
+ * @part_id:
+ * @header_list:
+ * @session:
+ * @base url:
+ * @snoop_mime_type:
+ * @valid:
+ * @valid_parent:
+ * @inline_table:
+ * @pending_uri_table:
+ * @pending_uri_tree:
+ * @pending_uri_level:
+ * @mode:
+ * @charset:
+ * @default_charset:
+ *
+ * Most fields are private or read-only.
+ *
+ * This is the base MIME formatter class. It provides no formatting
+ * itself, but drives most of the basic types, including multipart / * types.
+ **/
+struct _EMFormat {
+ GObject parent;
+
+ EMFormatPrivate *priv;
+
+ CamelMimeMessage *message; /* the current message */
+
+ CamelFolder *folder;
+ gchar *uid;
+
+ GString *part_id; /* current part id prefix, for identifying parts directly */
+
+ EDList header_list; /* if empty, then all */
+
+ CamelSession *session; /* session, used for authentication when required */
+ CamelURL *base; /* content-base header or absolute content-location, for any part */
+
+ const gchar *snoop_mime_type; /* if we snooped an application/octet-stream type, what we snooped */
+
+ /* for validity enveloping */
+ CamelCipherValidity *valid;
+ CamelCipherValidity *valid_parent;
+
+ /* for forcing inlining */
+ GHashTable *inline_table;
+
+ /* global lookup table for message */
+ GHashTable *pending_uri_table;
+
+ /* visibility tree, also stores every puri permanently */
+ struct _EMFormatPURITree *pending_uri_tree;
+ /* current level to search from */
+ struct _EMFormatPURITree *pending_uri_level;
+
+ em_format_mode_t mode; /* source/headers/etc */
+ gchar *charset; /* charset override */
+ gchar *default_charset; /* charset fallback */
+ gboolean composer; /* Formatting from composer ?*/
+ gboolean print;
+};
+
+struct _EMFormatClass {
+ GObjectClass parent_class;
+
+ GHashTable *type_handlers;
+
+ /* lookup handler, default falls back to hashtable above */
+ const EMFormatHandler *(*find_handler)(EMFormat *, const gchar *mime_type);
+
+ /* start formatting a message */
+ void (*format_clone)(EMFormat *, CamelFolder *, const gchar *uid, CamelMimeMessage *, EMFormat *);
+
+ /* some internel error/inconsistency */
+ void (*format_error)(EMFormat *, CamelStream *, const gchar *msg);
+
+ /* use for external structured parts */
+ void (*format_attachment)(EMFormat *, CamelStream *, CamelMimePart *, const gchar *mime_type, const struct _EMFormatHandler *info);
+
+ /* use for unparsable content */
+ void (*format_source)(EMFormat *, CamelStream *, CamelMimePart *);
+ /* for outputing secure(d) content */
+ void (*format_secure)(EMFormat *, CamelStream *, CamelMimePart *, CamelCipherValidity *);
+
+ /* returns true if the formatter is still busy with pending stuff */
+ gboolean (*busy)(EMFormat *);
+
+ /* Shows optional way to open messages */
+ void (*format_optional)(EMFormat *, CamelStream *, CamelMimePart *, CamelStream* );
+
+ /* signals */
+ /* complete, alternative to polling busy, for asynchronous work */
+ void (*complete)(EMFormat *);
+};
+
+void em_format_set_mode (EMFormat *emf,
+ em_format_mode_t type);
+void em_format_set_charset (EMFormat *emf,
+ const gchar *charset);
+void em_format_set_default_charset (EMFormat *emf,
+ const gchar *charset);
+
+/* also indicates to show all headers */
+void em_format_clear_headers (EMFormat *emf);
+
+void em_format_default_headers (EMFormat *emf);
+void em_format_add_header (EMFormat *emf,
+ const gchar *name,
+ guint32 flags);
+
+/* FIXME: Need a 'clone' api to copy details about the current view (inlines etc)
+ Or maybe it should live with sub-classes? */
+
+gint em_format_is_attachment (EMFormat *emf,
+ CamelMimePart *part);
+
+gint em_format_is_inline (EMFormat *emf,
+ const gchar *partid,
+ CamelMimePart *part,
+ const EMFormatHandler *handle);
+void em_format_set_inline (EMFormat *emf,
+ const gchar *partid,
+ gint state);
+
+gchar * em_format_describe_part (CamelMimePart *part,
+ const gchar *mime_type);
+
+/* for implementers */
+GType em_format_get_type (void);
+
+void em_format_class_add_handler (EMFormatClass *emfc,
+ EMFormatHandler *info);
+void em_format_class_remove_handler (EMFormatClass *emfc,
+ EMFormatHandler *info);
+const EMFormatHandler *
+ em_format_find_handler (EMFormat *emf,
+ const gchar *mime_type);
+const EMFormatHandler *
+ em_format_fallback_handler (EMFormat *emf,
+ const gchar *mime_type);
+
+/* puri is short for pending uri ... really */
+EMFormatPURI * em_format_add_puri (EMFormat *emf,
+ gsize size,
+ const gchar *uri,
+ CamelMimePart *part,
+ EMFormatPURIFunc func);
+EMFormatPURI * em_format_find_visible_puri (EMFormat *emf,
+ const gchar *uri);
+EMFormatPURI * em_format_find_puri (EMFormat *emf,
+ const gchar *uri);
+void em_format_clear_puri_tree (EMFormat *emf);
+void em_format_push_level (EMFormat *emf);
+void em_format_pull_level (EMFormat *emf);
+
+/* clones inline state/view and format, or use to redraw */
+void em_format_format_clone (EMFormat *emf,
+ CamelFolder *folder,
+ const gchar *uid,
+ CamelMimeMessage *message,
+ EMFormat *source);
+
+/* formats a new message */
+void em_format_format (EMFormat *emf,
+ CamelFolder *folder,
+ const gchar *uid,
+ CamelMimeMessage *message);
+void em_format_redraw (EMFormat *emf);
+void em_format_format_attachment (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *mime_part,
+ const gchar *mime_type,
+ const struct _EMFormatHandler *info);
+void em_format_format_error (EMFormat *emf,
+ CamelStream *stream,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (3, 4);
+void em_format_format_secure (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *mime_part,
+ CamelCipherValidity *valid);
+void em_format_format_source (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *mime_part);
+
+gboolean em_format_busy (EMFormat *emf);
+
+/* raw content only */
+void em_format_format_content (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *part);
+
+/* raw content text parts - should this just be checked/done by above? */
+void em_format_format_text (EMFormat *emf,
+ CamelStream *stream,
+ CamelDataWrapper *part);
+
+void em_format_part_as (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *part,
+ const gchar *mime_type);
+void em_format_part (EMFormat *emf,
+ CamelStream *stream,
+ CamelMimePart *part);
+void em_format_merge_handler (EMFormat *new,
+ EMFormat *old);
+
+const gchar * em_format_snoop_type (CamelMimePart *part);
+
+G_END_DECLS
+
+#endif /* EM_FORMAT_H */
diff --git a/em-format/em-stripsig-filter.c b/em-format/em-stripsig-filter.c
new file mode 100644
index 0000000000..42d824778b
--- /dev/null
+++ b/em-format/em-stripsig-filter.c
@@ -0,0 +1,164 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "em-stripsig-filter.h"
+
+static void em_stripsig_filter_class_init (EMStripSigFilterClass *klass);
+static void em_stripsig_filter_init (EMStripSigFilter *filter, EMStripSigFilterClass *klass);
+
+static void filter_filter (CamelMimeFilter *filter, const gchar *in, gsize len, gsize prespace,
+ gchar **out, gsize *outlen, gsize *outprespace);
+static void filter_complete (CamelMimeFilter *filter, const gchar *in, gsize len, gsize prespace,
+ gchar **out, gsize *outlen, gsize *outprespace);
+static void filter_reset (CamelMimeFilter *filter);
+
+static CamelMimeFilterClass *parent_class = NULL;
+
+CamelType
+em_stripsig_filter_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_mime_filter_get_type (),
+ "EMStripSigFilter",
+ sizeof (EMStripSigFilter),
+ sizeof (EMStripSigFilterClass),
+ (CamelObjectClassInitFunc) em_stripsig_filter_class_init,
+ NULL,
+ (CamelObjectInitFunc) em_stripsig_filter_init,
+ NULL);
+ }
+
+ return type;
+}
+
+static void
+em_stripsig_filter_class_init (EMStripSigFilterClass *klass)
+{
+ CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass;
+
+ parent_class = CAMEL_MIME_FILTER_CLASS (camel_type_get_global_classfuncs (camel_mime_filter_get_type ()));
+
+ filter_class->reset = filter_reset;
+ filter_class->filter = filter_filter;
+ filter_class->complete = filter_complete;
+}
+
+static void
+em_stripsig_filter_init (EMStripSigFilter *filter, EMStripSigFilterClass *klass)
+{
+ filter->midline = FALSE;
+}
+
+static void
+strip_signature (CamelMimeFilter *filter, const gchar *in, gsize len, gsize prespace,
+ gchar **out, gsize *outlen, gsize *outprespace, gint flush)
+{
+ EMStripSigFilter *stripsig = (EMStripSigFilter *) filter;
+ register const gchar *inptr = in;
+ const gchar *inend = in + len;
+ const gchar *start = NULL;
+
+ if (stripsig->midline) {
+ while (inptr < inend && *inptr != '\n')
+ inptr++;
+
+ if (inptr < inend) {
+ stripsig->midline = FALSE;
+ inptr++;
+ }
+ }
+
+ while (inptr < inend) {
+ if ((inend - inptr) >= 4 && !strncmp (inptr, "-- \n", 4)) {
+ start = inptr;
+ inptr += 4;
+ } else {
+ while (inptr < inend && *inptr != '\n')
+ inptr++;
+
+ if (inptr == inend) {
+ stripsig->midline = TRUE;
+ break;
+ }
+
+ inptr++;
+ }
+ }
+
+ if (start != NULL)
+ inptr = start;
+
+ if (!flush && inend > inptr)
+ camel_mime_filter_backup (filter, inptr, inend - inptr);
+ else if (!start)
+ inptr = inend;
+
+ *out = (gchar *)in;
+ *outlen = inptr - in;
+ *outprespace = prespace;
+}
+
+static void
+filter_filter (CamelMimeFilter *filter, const gchar *in, gsize len, gsize prespace,
+ gchar **out, gsize *outlen, gsize *outprespace)
+{
+ strip_signature (filter, in, len, prespace, out, outlen, outprespace, FALSE);
+}
+
+static void
+filter_complete (CamelMimeFilter *filter, const gchar *in, gsize len, gsize prespace,
+ gchar **out, gsize *outlen, gsize *outprespace)
+{
+ strip_signature (filter, in, len, prespace, out, outlen, outprespace, TRUE);
+}
+
+/* should this 'flush' outstanding state/data bytes? */
+static void
+filter_reset (CamelMimeFilter *filter)
+{
+ EMStripSigFilter *stripsig = (EMStripSigFilter *) filter;
+
+ stripsig->midline = FALSE;
+}
+
+/**
+ * em_stripsig_filter_new:
+ *
+ * Creates a new stripsig filter.
+ *
+ * Returns a new stripsig filter.
+ **/
+CamelMimeFilter *
+em_stripsig_filter_new (void)
+{
+ return (CamelMimeFilter *) camel_object_new (EM_TYPE_STRIPSIG_FILTER);
+}
diff --git a/em-format/em-stripsig-filter.h b/em-format/em-stripsig-filter.h
new file mode 100644
index 0000000000..39493e5e88
--- /dev/null
+++ b/em-format/em-stripsig-filter.h
@@ -0,0 +1,57 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef __EM_STRIPSIG_FILTER_H__
+#define __EM_STRIPSIG_FILTER_H__
+
+#include <camel/camel-mime-filter.h>
+
+G_BEGIN_DECLS
+
+#define EM_TYPE_STRIPSIG_FILTER (em_stripsig_filter_get_type ())
+#define EM_STRIPSIG_FILTER(obj) (CAMEL_CHECK_CAST ((obj), EM_TYPE_STRIPSIG_FILTER, EMStripSigFilter))
+#define EM_STRIPSIG_FILTER_CLASS(klass) (CAMEL_CHECK_CLASS_CAST ((klass), EM_TYPE_STRIPSIG_FILTER, EMStripSigFilterClass))
+#define EM_IS_STRIPSIG_FILTER(obj) (CAMEL_CHECK_TYPE ((obj), EM_TYPE_STRIPSIG_FILTER))
+#define EM_IS_STRIPSIG_FILTER_CLASS(klass) (CAMEL_CHECK_CLASS_TYPE ((klass), EM_TYPE_STRIPSIG_FILTER))
+#define EM_STRIPSIG_FILTER_GET_CLASS(obj) (CAMEL_CHECK_GET_CLASS ((obj), EM_TYPE_STRIPSIG_FILTER, EMStripSigFilterClass))
+
+typedef struct _EMStripSigFilter EMStripSigFilter;
+typedef struct _EMStripSigFilterClass EMStripSigFilterClass;
+
+struct _EMStripSigFilter {
+ CamelMimeFilter parent_object;
+
+ guint32 midline:1;
+};
+
+struct _EMStripSigFilterClass {
+ CamelMimeFilterClass parent_class;
+
+};
+
+CamelType em_stripsig_filter_get_type (void);
+
+CamelMimeFilter *em_stripsig_filter_new (void);
+
+G_END_DECLS
+
+#endif /* __EM_STRIPSIG_FILTER_H__ */