diff options
Diffstat (limited to 'mail/em-format.c')
-rw-r--r-- | mail/em-format.c | 1228 |
1 files changed, 1228 insertions, 0 deletions
diff --git a/mail/em-format.c b/mail/em-format.c new file mode 100644 index 0000000000..d239266806 --- /dev/null +++ b/mail/em-format.c @@ -0,0 +1,1228 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libgnomevfs/gnome-vfs-mime.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> + +#include <gconf/gconf-client.h> + +#include <e-util/e-msgport.h> +#include <camel/camel-url.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-string-utils.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-stream-null.h> +#include <camel/camel-mime-filter-charset.h> +#include <camel/camel-mime-filter-windows.h> + +#include "em-format.h" + +#define d(x) + +static void emf_builtin_init(EMFormatClass *); +static const char *emf_snoop_part(CamelMimePart *part); + +static void emf_format_clone(EMFormat *emf, CamelMedium *msg, EMFormat *emfsource); +static gboolean emf_busy(EMFormat *emf); + +enum { + EMF_COMPLETE, + EMF_LAST_SIGNAL, +}; + +static guint emf_signals[EMF_LAST_SIGNAL]; +static GObjectClass *emf_parent; + +static void +emf_init(GObject *o) +{ + EMFormat *emf = (EMFormat *)o; + + emf->inline_table = g_hash_table_new(NULL, NULL); + e_dlist_init(&emf->header_list); + em_format_default_headers(emf); +} + +static void +emf_finalise(GObject *o) +{ + EMFormat *emf = (EMFormat *)o; + + if (emf->session) + camel_object_unref(emf->session); + + if (emf->inline_table) + g_hash_table_destroy(emf->inline_table); + + em_format_clear_headers(emf); + g_free(emf->charset); + + /* FIXME: check pending jobs */ + + ((GObjectClass *)emf_parent)->finalize(o); +} + +static void +emf_base_init(EMFormatClass *emfklass) +{ + emfklass->type_handlers = g_hash_table_new(g_str_hash, g_str_equal); + emf_builtin_init(emfklass); +} + +static void +emf_class_init(GObjectClass *klass) +{ + ((EMFormatClass *)klass)->type_handlers = g_hash_table_new(g_str_hash, g_str_equal); + emf_builtin_init((EMFormatClass *)klass); + + klass->finalize = emf_finalise; + ((EMFormatClass *)klass)->format_clone = emf_format_clone; + ((EMFormatClass *)klass)->busy = emf_busy; + + emf_signals[EMF_COMPLETE] = + g_signal_new("complete", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EMFormatClass, complete), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +GType +em_format_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMFormatClass), + (GBaseInitFunc)emf_base_init, NULL, + (GClassInitFunc)emf_class_init, + NULL, NULL, + sizeof(EMFormat), 0, + (GInstanceInitFunc)emf_init + }; + emf_parent = g_type_class_ref(G_TYPE_OBJECT); + type = g_type_register_static(G_TYPE_OBJECT, "EMFormat", &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. + * + * 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) +{ + g_hash_table_insert(emfc->type_handlers, info->mime_type, info); + /* FIXME: do we care? This is really gui stuff */ + /* + if (info->applications == NULL) + info->applications = gnome_vfs_mime_get_short_list_applications(info->mime_type);*/ +} + + +/** + * em_format_class_remove_handler: + * @emfc: EMFormatClass + * @mime_type: mime-type of handler to remove + * + * Remove a mime type handler from this class. This is only used by + * implementing classes. + **/ +void +em_format_class_remove_handler (EMFormatClass *emfc, const char *mime_type) +{ + g_hash_table_remove (emfc->type_handlers, mime_type); +} + + +/** + * em_format_find_handler: + * @emf: + * @mime_type: + * + * Find a format handler by @mime_type. + * + * Return value: NULL if no handler is available. + **/ +const EMFormatHandler * +em_format_find_handler(EMFormat *emf, const char *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 char *mime_type) +{ + char *mime, *s; + + s = strchr(mime_type, '/'); + if (s == NULL) + mime = (char *)mime_type; + else { + size_t 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, size_t size, const char *cid, CamelMimePart *part, EMFormatPURIFunc func) +{ + EMFormatPURI *puri; + const char *tmp; + static unsigned int uriid; + + g_assert(size >= sizeof(*puri)); + puri = g_malloc0(size); + + puri->format = emf; + puri->func = func; + puri->use_count = 0; + puri->cid = g_strdup(cid); + + 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-%u", uriid++); + + 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) { + if (emf->base) + puri->uri = camel_url_to_string(emf->base, 0); + } 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_assert(puri->cid != NULL); + g_assert(emf->pending_uri_level != NULL); + g_assert(emf->pending_uri_table != 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 char *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 char *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) { + g_free(pw->uri); + g_free(pw->cid); + 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 char *mime_type) +{ + const EMFormatHandler *handle = NULL; + + if (mime_type != NULL) { + if (g_ascii_strcasecmp(mime_type, "application/octet-stream") == 0) + mime_type = emf_snoop_part(part); + + 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); + return; + } + 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); +} + +void +em_format_part(EMFormat *emf, CamelStream *stream, CamelMimePart *part) +{ + char *mime_type; + CamelDataWrapper *dw; + + dw = camel_medium_get_content_object((CamelMedium *)part); + mime_type = camel_data_wrapper_get_mime_type(dw); + camel_strdown(mime_type); + em_format_part_as(emf, stream, part, mime_type); + g_free(mime_type); +} + +static void +emf_clone_inlines(void *key, void *val, void *data) +{ + g_hash_table_insert(((EMFormat *)data)->inline_table, key, val); +} + +static void +emf_format_clone(EMFormat *emf, CamelMedium *msg, EMFormat *emfsource) +{ + em_format_clear_puri_tree(emf); + + if (emf != emfsource) { + g_hash_table_destroy(emf->inline_table); + emf->inline_table = g_hash_table_new(NULL, NULL); + if (emfsource) { + /* 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); + /* FIXME: clone headers shown */ + } + } + + if (msg != emf->message) { + if (emf->message) + camel_object_unref(emf->message); + if (msg) + camel_object_ref(msg); + emf->message = msg; + } +} + +static gboolean +emf_busy(EMFormat *emf) +{ + return FALSE; +} + +/** + * em_format_format_clone: + * @emf: Mail formatter. + * @msg: Mail message. + * @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. + **/ +/* e_format_format_clone is a macro */ + +/** + * em_format_set_session: + * @emf: + * @s: + * + * Set the CamelSession to be used for signature verification and decryption + * purposes. If this is not set, then signatures cannot be verified or + * encrypted messages viewed. + **/ +void +em_format_set_session(EMFormat *emf, struct _CamelSession *s) +{ + if (s) + camel_object_ref(s); + if (emf->session) + camel_object_unref(emf->session); + emf->session = s; +} + +/** + * 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_format_clone(emf, emf->message, 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 char *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_format_clone(emf, emf->message, 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); +} + +static const struct { + const char *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 }, + { "x-evolution-mailer", 0 }, /* DO NOT translate */ +}; + +/** + * 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) +{ + int 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 char *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 + **/ +int 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); + + /*printf("checking is attachment %s/%s\n", ct->type, ct->subtype);*/ + return !(/*header_content_type_is(ct, "message", "*") + ||*/ header_content_type_is(dw->mime_type, "multipart", "*") + || (header_content_type_is(dw->mime_type, "text", "*") + && camel_mime_part_get_filename(part) == NULL)); + +} + +/** + * em_format_is_inline: + * @emf: + * @part: + * + * Returns true if the part should be displayed inline. Any part with + * a Content-Disposition of inline, or any message type is displayed + * inline. + * + * ::set_inline() called on the same part will override any calculated + * value. + * + * Return value: + **/ +int em_format_is_inline(EMFormat *emf, CamelMimePart *part) +{ + void *dummy, *override; + const char *tmp; + CamelContentType *ct; + + if (g_hash_table_lookup_extended(emf->inline_table, part, &dummy, &override)) + return GPOINTER_TO_INT(override); + + tmp = camel_mime_part_get_disposition(part); + if (tmp) + return g_ascii_strcasecmp(tmp, "inline") == 0; + + /* messages are always inline? */ + ct = camel_mime_part_get_content_type(part); + + return header_content_type_is(ct, "message", "*"); +} + +/** + * em_format_set_inline: + * @emf: + * @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, CamelMimePart *part, int state) +{ + g_hash_table_insert(emf->inline_table, part, GINT_TO_POINTER(state)); +} + +/* 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 (header_content_type_is(dw->mime_type, "text", "*")) + em_format_format_text(emf, stream, dw); + 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 char *charset; + char *fallback_charset = NULL; + + if (emf->charset) { + charset = emf->charset; + } else if (dw->mime_type + && (charset = header_content_type_param(dw->mime_type, "charset")) + && g_ascii_strncasecmp(charset, "iso-8859-", 9) == 0) { + CamelMimeFilterWindows *windows; + 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 = fallback_charset = g_strdup(camel_mime_filter_windows_real_charset(windows)); + camel_object_unref(windows); + } else if (charset == NULL) { + /* FIXME: remove gconf query every time */ + GConfClient *gconf = gconf_client_get_default(); + + charset = fallback_charset = gconf_client_get_string(gconf, "/apps/evolution/mail/format/charset", NULL); + g_object_unref(gconf); + } + + filter_stream = camel_stream_filter_new_with_stream(stream); + + if ((filter = camel_mime_filter_charset_new_convert(charset, "UTF-8"))) { + camel_stream_filter_add(filter_stream, (CamelMimeFilter *) filter); + camel_object_unref(filter); + } + + g_free(fallback_charset); + + camel_data_wrapper_decode_to_stream(dw, (CamelStream *)filter_stream); + camel_stream_flush((CamelStream *)filter_stream); + camel_object_unref(filter_stream); +} + +/** + * em_format_describe_part: + * @part: + * @mimetype: + * + * Generate a simple textual description of a part, @mime_type represents the + * the content. + * + * Return value: + **/ +char * +em_format_describe_part(CamelMimePart *part, const char *mime_type) +{ + GString *stext; + const char *text; + char *out; + + stext = g_string_new(""); + text = gnome_vfs_mime_get_description(mime_type); + g_string_append_printf(stext, _("%s attachment"), text?text:mime_type); + if ((text = camel_mime_part_get_filename (part))) + g_string_append_printf(stext, " (%s)", text); + if ((text = camel_mime_part_get_description(part))) + g_string_append_printf(stext, ", \"%s\"", text); + + out = stext->str; + g_string_free(stext, FALSE); + + return out; +} + +/* ********************************************************************** */ + +/* originally from mail-identify.c */ +static const char * +emf_snoop_part(CamelMimePart *part) +{ + const char *filename, *name_type = NULL, *magic_type = NULL; + CamelDataWrapper *dw; + + filename = camel_mime_part_get_filename (part); + if (filename) { + /* GNOME-VFS will misidentify TNEF attachments as MPEG */ + if (!strcmp (filename, "winmail.dat")) + return "application/vnd.ms-tnef"; + + name_type = gnome_vfs_mime_type_from_name(filename); + } + + 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) + magic_type = gnome_vfs_get_mime_type_for_data(mem->buffer->data, mem->buffer->len); + camel_object_unref(mem); + } + + d(printf("snooped part, magic_type '%s' name_type '%s'\n", magic_type, name_type)); + + /* If GNOME-VFS 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"))) + return name_type; + else + return magic_type; + } else + return name_type; + + /* We used to load parts to check their type, we dont anymore, + see bug #11778 for some discussion */ +} + +/* 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); + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + /* try the data fork for something useful, doubtful but who knows */ + em_format_part(emf, stream, camel_multipart_get_part(mp, 1)); +} + +/* 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); + int i, nparts; + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + nparts = camel_multipart_get_number(mp); + for (i = 0; i < nparts; i++) { + /* FIXME: separate part markers ... + if (i != 0) + camel_stream_printf(stream, "<hr>\n");*/ + + part = camel_multipart_get_part(mp, i); + em_format_part(emf, stream, part); + } +} + +/* 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); + int i, nparts; + CamelMimePart *best = NULL; + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + /* as pre rfc, find the last part we know how to display */ + nparts = camel_multipart_get_number(mp); + for (i = 0; i < nparts; i++) { + CamelMimePart *part = camel_multipart_get_part(mp, i); + CamelContentType *type = camel_mime_part_get_content_type (part); + char *mime_type = header_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; + + g_free(mime_type); + } + + if (best) + em_format_part(emf, stream, best); + else + emf_multipart_mixed(emf, stream, part, info); +} + +static void +emf_multipart_encrypted(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMultipartEncrypted *mpe; + CamelMimePart *mime_part; + CamelCipherContext *cipher; + CamelException ex; + const char *protocol; + + /* Currently we only handle RFC2015-style PGP encryption. */ + protocol = header_content_type_param (((CamelDataWrapper *) part)->mime_type, "protocol"); + if (!protocol || strcmp (protocol, "application/pgp-encrypted") != 0) + return emf_multipart_mixed(emf, stream, part, info); + + mpe = (CamelMultipartEncrypted *)camel_medium_get_content_object((CamelMedium *)part); + + if (!CAMEL_IS_MULTIPART_ENCRYPTED(mpe)) { + em_format_format_source(emf, stream, part); + return; + } + + camel_exception_init (&ex); + cipher = camel_gpg_context_new(emf->session); + mime_part = camel_multipart_encrypted_decrypt(mpe, cipher, &ex); + camel_object_unref(cipher); + + if (camel_exception_is_set(&ex)) { + /* FIXME: error handler */ + em_format_format_error(emf, stream, camel_exception_get_description(&ex)); + camel_exception_clear(&ex); + return; + } + + em_format_part(emf, stream, mime_part); + camel_object_unref(mime_part); +} + +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 char *location, *start; + int i, nparts; + CamelURL *base_save = NULL; + 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 = header_content_type_param(content_type, "start"); + if (start && strlen(start)>2) { + int len; + const char *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; + break; + } + } + } else { + display_part = camel_multipart_get_part(mp, 0); + } + + if (display_part == NULL) { + emf_multipart_mixed(emf, stream, part, info); + return; + } + + /* stack of present location and pending uri's */ + location = camel_mime_part_get_content_location(part); + if (location) { + d(printf("setting content location %s\n", location)); + base_save = emf->base; + emf->base = camel_url_new(location, NULL); + } + em_format_push_level(emf); + + /* 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) { + puri = em_format_add_puri(emf, sizeof(EMFormatPURI), NULL, body_part, emf_write_related); + d(printf(" part '%s' '%s' added\n", puri->uri?puri->uri:"", puri->cid)); + } + } + + em_format_part(emf, stream, display_part); + camel_stream_flush(stream); + + ptree = emf->pending_uri_level; + puri = (EMFormatPURI *)ptree->uri_list.head; + purin = puri->next; + while (purin) { + if (purin->use_count == 0) { + d(printf("part '%s' '%s' used '%d'\n", purin->uri?purin->uri:"", purin->cid, purin->use_count)); + if (purin->func == emf_write_related) + em_format_part(emf, stream, puri->part); + else + printf("unreferenced uri generated by format code: %s\n", purin->uri?purin->uri:purin->cid); + } + puri = purin; + purin = purin->next; + } + em_format_pull_level(emf); + + if (location) { + camel_url_free(emf->base); + emf->base = base_save; + } +} + +/* this is only a fallback implementation, implementations should override */ +static void +emf_multipart_signed(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMimePart *cpart, *spart; + CamelMultipartSigned *mps; + CamelCipherValidity *valid = NULL; + CamelException ex; + const char *message = NULL; + gboolean good = FALSE; + CamelCipherContext *cipher; + + 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_source(emf, stream, part); + return; + } + + em_format_part(emf, stream, cpart); + + spart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_SIGNATURE); + camel_exception_init(&ex); + if (spart == NULL) { + message = _("No signature present"); + } else if (emf->session == NULL) { + message = _("Session not initialised"); + } else if ((cipher = camel_gpg_context_new(emf->session)) == NULL) { + message = _("Could not create signature verfication context"); + } else { + valid = camel_multipart_signed_verify(mps, cipher, &ex); + camel_object_unref(cipher); + if (valid) { + good = camel_cipher_validity_get_valid(valid); + message = camel_cipher_validity_get_description(valid); + } else { + message = camel_exception_get_description(&ex); + } + } + + if (good) + em_format_format_error(emf, stream, _("This message is digitally signed and has been found to be authentic.")); + else + em_format_format_error(emf, stream, _("This message is digitally signed but can not be proven to be authentic.")); + + if (message) + em_format_format_error(emf, stream, message); + + camel_exception_clear(&ex); + camel_cipher_validity_free(valid); +} + +/* this is only a fallback, any implementer should implement */ +static void +emf_message_rfc822(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)part); + + if (!CAMEL_IS_MIME_MESSAGE(dw)) { + em_format_format_source(emf, stream, part); + return; + } + + em_format_format_message(emf, stream, (CamelMedium *)dw); +} + +static EMFormatHandler type_builtin_table[] = { + { "multipart/alternative", emf_multipart_alternative }, + { "multipart/appledouble", emf_multipart_appledouble }, + { "multipart/encrypted", emf_multipart_encrypted }, + { "multipart/mixed", emf_multipart_mixed }, + { "multipart/signed", emf_multipart_signed }, + { "multipart/related", emf_multipart_related }, + { "multipart/*", emf_multipart_mixed }, + { "message/rfc822", emf_message_rfc822 }, + { "message/news", emf_message_rfc822 }, + { "message/*", emf_message_rfc822 }, +}; + +static void +emf_builtin_init(EMFormatClass *klass) +{ + int i; + + for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++) + g_hash_table_insert(klass->type_handlers, type_builtin_table[i].mime_type, &type_builtin_table[i]); +} |