diff options
Diffstat (limited to 'em-format')
-rw-r--r-- | em-format/Makefile.am | 28 | ||||
-rw-r--r-- | em-format/em-format-quote.c | 585 | ||||
-rw-r--r-- | em-format/em-format-quote.h | 81 | ||||
-rw-r--r-- | em-format/em-format.c | 2016 | ||||
-rw-r--r-- | em-format/em-format.h | 402 | ||||
-rw-r--r-- | em-format/em-stripsig-filter.c | 164 | ||||
-rw-r--r-- | em-format/em-stripsig-filter.h | 57 |
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 <", 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, ">"); + 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__ */ |