/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Michael Zucchi * Jeffrey Stedfast * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "em-format.h" #include "em-utils.h" #include "mail-config.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 { struct _CamelCipherValidity *valid; /* validity copy */ struct _CamelMimePart *secured; /* encrypted subpart */ unsigned int state:2; /* inline state */ char 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 char *mime_type); static void emf_format_clone(EMFormat *emf, CamelFolder *folder, const char *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 guint emf_signals[EMF_LAST_SIGNAL]; static GObjectClass *emf_parent; 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 char *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_init(GObject *o) { EMFormat *emf = (EMFormat *)o; emf->inline_table = g_hash_table_new_full ( g_str_hash, g_str_equal, (GDestroyNotify) NULL, (GDestroyNotify) emf_free_cache); emf->composer = FALSE; emf->show_photo = TRUE; emf->photo_local = TRUE; e_dlist_init(&emf->header_list); em_format_default_headers(emf); emf->part_id = g_string_new(""); } static void emf_finalise(GObject *o) { EMFormat *emf = (EMFormat *)o; 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 */ ((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)->find_handler = emf_find_handler; ((EMFormatClass *)klass)->format_clone = emf_format_clone; ((EMFormatClass *)klass)->format_secure = emf_format_secure; ((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. 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, 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, 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; } } /** * 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 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; 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 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) { 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 char *mime_type) { const EMFormatHandler *handle = NULL; const char *snoop_save = emf->snoop_mime_type, *tmp; CamelURL *base_save = emf->base, *base = NULL; char *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:"")); 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_utils_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) { char *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(void *key, void *val, void *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 char *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", 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; int 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: Mail formatter. * @folder: Camel Folder. * @uid: Uid of message. * @msg: Camel 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_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 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_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 char *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 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 }, { N_("Newsgroups"), EM_FORMAT_HEADER_BOLD }, }; /** * 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; iflags = 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 !(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: **/ int em_format_is_inline(EMFormat *emf, const char *partid, CamelMimePart *part, const EMFormatHandler *handle) { struct _EMFormatCache *emfc; const char *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 char *partid, int 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_error(EMFormat *emf, CamelStream *stream, const char *fmt, ...) { va_list ap; char *txt; va_start(ap, fmt); txt = g_strdup_vprintf(fmt, ap); ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_error((emf), (stream), (txt)); g_free(txt); } void em_format_format_secure(EMFormat *emf, struct _CamelStream *stream, struct _CamelMimePart *part, struct _CamelCipherValidity *valid) { ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_secure(emf, stream, part, valid); if (emf->valid_parent == NULL && emf->valid != NULL) { camel_cipher_validity_free(emf->valid); emf->valid = NULL; } } /* 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 char *charset = NULL; CamelMimeFilterWindows *windows = NULL; CamelStream *mem_stream = NULL; size_t size; size_t max; 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 = mail_config_get_message_limit(); 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 < (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: **/ char * em_format_describe_part(CamelMimePart *part, const char *mime_type) { GString *stext; const char *filename, *description; char *out, *desc; stext = g_string_new(""); /* TODO: mime_type isn't content_type on some systems (Win32), thus this will not work there. */ desc = g_content_type_get_description (mime_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); out = stext->str; g_string_free(stext, FALSE); return out; } /* ********************************************************************** */ #ifdef ENABLE_SMIME static void emf_application_xpkcs7mime(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) { CamelCipherContext *context; CamelException *ex; extern CamelSession *session; 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(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; int 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); int 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); int 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; char *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) { int 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 char *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 finalise 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 char *start; int i, nparts, partidlen, displayid = 0; char *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) { int len; const char *cid; /* strip <>'s */ len = strlen (start) - 2; start++; for (i=0; ipart_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); } } 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; int 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; char *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; 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; } /* 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 { "application/x-pkcs7-mime", (EMFormatFunc)emf_application_xpkcs7mime, EM_FORMAT_HANDLER_INLINE_DISPOSITION }, #endif { "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, EM_FORMAT_HANDLER_INLINE }, { "message/news", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE }, { "message/delivery-status", emf_message_deliverystatus }, { "message/*", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE }, /* Insert brokenly-named parts here */ #ifdef ENABLE_SMIME { "application/pkcs7-mime", (EMFormatFunc)emf_application_xpkcs7mime, EM_FORMAT_HANDLER_INLINE_DISPOSITION }, #endif /* internal types */ { "application/x-inlinepgp-signed", (EMFormatFunc)emf_inlinepgp_signed }, { "application/x-inlinepgp-encrypted", (EMFormatFunc)emf_inlinepgp_encrypted }, }; static void emf_builtin_init(EMFormatClass *klass) { int i; for (i=0;itype_handlers, type_builtin_table[i].mime_type, &type_builtin_table[i]); }