/*
* 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 <gio/gio.h>
#include <glib/gi18n-lib.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)
typedef struct _EMFormatCache EMFormatCache;
struct _EMFormatPrivate {
guint redraw_idle_id;
};
/* 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 *);
enum {
EMF_COMPLETE,
EMF_LAST_SIGNAL
};
static gpointer parent_class;
static guint signals[EMF_LAST_SIGNAL];
static void
emf_free_cache (EMFormatCache *efc)
{
if (efc->valid)
camel_cipher_validity_free (efc->valid);
if (efc->secured)
g_object_unref (efc->secured);
g_free (efc);
}
static EMFormatCache *
emf_insert_cache (EMFormat *emf,
const gchar *partid)
{
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_clone_inlines (gpointer key,
gpointer val,
gpointer data)
{
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)
g_object_ref ((new->secured = emfc->secured));
}
static gboolean
emf_clear_puri_node (GNode *node)
{
GQueue *queue = node->data;
EMFormatPURI *pn;
while ((pn = g_queue_pop_head (queue)) != NULL) {
if (pn->free != NULL)
pn->free (pn);
g_free (pn->uri);
g_free (pn->cid);
g_free (pn->part_id);
if (pn->part != NULL)
g_object_unref (pn->part);
g_free (pn);
}
g_queue_free (queue);
return FALSE;
}
static void
emf_finalize (GObject *object)
{
EMFormat *emf = EM_FORMAT (object);
if (emf->priv->redraw_idle_id > 0)
g_source_remove (emf->priv->redraw_idle_id);
if (emf->session)
g_object_unref (emf->session);
if (emf->message)
g_object_unref (emf->message);
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);
g_free (emf->current_message_part_id);
g_free (emf->uid);
if (emf->pending_uri_table != NULL)
g_hash_table_destroy (emf->pending_uri_table);
if (emf->pending_uri_tree != NULL) {
g_node_traverse (
emf->pending_uri_tree,
G_IN_ORDER, G_TRAVERSE_ALL, -1,
(GNodeTraverseFunc) emf_clear_puri_node, NULL);
g_node_destroy (emf->pending_uri_tree);
}
/* FIXME: check pending jobs */
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (parent_class)->finalize (object);
}
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);
}
static void
emf_format_clone (EMFormat *emf,
CamelFolder *folder,
const gchar *uid,
CamelMimeMessage *msg,
EMFormat *emfsource,
GCancellable *cancellable)
{
/* Cancel any pending redraws. */
if (emf->priv->redraw_idle_id > 0) {
g_source_remove (emf->priv->redraw_idle_id);
emf->priv->redraw_idle_id = 0;
}
em_format_clear_puri_tree (emf);
if (emf != emfsource) {
g_hash_table_remove_all (emf->inline_table);
if (emfsource) {
GList *link;
/* 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);
link = g_queue_peek_head_link (&emfsource->header_list);
while (link != NULL) {
struct _EMFormatHeader *h = link->data;
em_format_add_header (emf, h->name, h->flags);
link = g_list_next (link);
}
}
}
/* what a mess */
if (folder != emf->folder) {
if (emf->folder)
g_object_unref (emf->folder);
if (folder)
g_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)
g_object_unref (emf->message);
if (msg)
g_object_ref (msg);
emf->message = msg;
}
g_free (emf->current_message_part_id);
emf->current_message_part_id = g_strdup ("root-message");
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,
GCancellable *cancellable)
{
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, cancellable);
g_string_truncate (emf->part_id, len);
emf->valid_parent = save;
}
static gboolean
emf_busy (EMFormat *emf)
{
return FALSE;
}
static gboolean
emf_is_inline (EMFormat *emf,
const gchar *part_id,
CamelMimePart *mime_part,
const EMFormatHandler *handle)
{
EMFormatCache *emfc;
const gchar *disposition;
if (handle == NULL)
return FALSE;
emfc = g_hash_table_lookup (emf->inline_table, part_id);
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;
disposition = camel_mime_part_get_disposition (mime_part);
if (disposition != NULL)
return g_ascii_strcasecmp (disposition, "inline") == 0;
/* Otherwise, use the default for this handler type. */
return (handle->flags & EM_FORMAT_HANDLER_INLINE) != 0;
}
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);
g_type_class_add_private (class, sizeof (EMFormatPrivate));
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;
class->is_inline = emf_is_inline;
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);
}
static void
emf_init (EMFormat *emf)
{
EShell *shell;
EShellSettings *shell_settings;
emf->priv = G_TYPE_INSTANCE_GET_PRIVATE (
emf, EM_TYPE_FORMAT, EMFormatPrivate);
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;
g_queue_init (&emf->header_list);
em_format_default_headers (emf);
emf->part_id = g_string_new("");
emf->current_message_part_id = NULL;
emf->validity_found = 0;
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);
g_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)
{
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;
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;
}
}
/**
* 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 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_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 = g_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) {
g_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);
g_queue_push_tail (emf->pending_uri_level->data, 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)
{
GNode *node;
g_return_if_fail (EM_IS_FORMAT (emf));
node = g_node_new (g_queue_new ());
if (emf->pending_uri_tree == NULL)
emf->pending_uri_tree = node;
else
g_node_append (emf->pending_uri_tree, node);
emf->pending_uri_level = node;
}
/**
* 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)
{
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (emf->pending_uri_level != NULL);
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)
{
GNode *node;
g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
g_return_val_if_fail (uri != NULL, NULL);
node = emf->pending_uri_level;
while (node != NULL) {
GQueue *queue = node->data;
GList *link;
link = g_queue_peek_head_link (queue);
while (link != NULL) {
EMFormatPURI *pw = link->data;
if (g_strcmp0 (pw->uri, uri) == 0)
return pw;
if (g_strcmp0 (pw->cid, uri) == 0)
return pw;
link = g_list_next (link);
}
node = node->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)
{
g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
g_return_val_if_fail (uri != NULL, NULL);
g_return_val_if_fail (emf->pending_uri_table != NULL, NULL);
return g_hash_table_lookup (emf->pending_uri_table, uri);
}
/**
* 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)
{
if (emf->pending_uri_table == NULL)
emf->pending_uri_table =
g_hash_table_new (g_str_hash, g_str_equal);
else {
g_hash_table_remove_all (emf->pending_uri_table);
g_node_traverse (
emf->pending_uri_tree,
G_IN_ORDER, G_TRAVERSE_ALL, -1,
(GNodeTraverseFunc) emf_clear_puri_node, NULL);
g_node_destroy (emf->pending_uri_tree);
emf->pending_uri_tree = NULL;
emf->pending_uri_level = NULL;
}
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,
GCancellable *cancellable)
{
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) {
gboolean is_fallback = FALSE;
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);
is_fallback = TRUE;
}
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,
cancellable, is_fallback);
goto finish;
}
d(printf("this type is an attachment? '%s'\n", mime_type));
} else {
mime_type = "application/octet-stream";
}
EM_FORMAT_GET_CLASS (emf)->format_attachment (
emf, stream, part, mime_type, handle, cancellable);
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 *mime_part,
GCancellable *cancellable)
{
gchar *mime_type;
CamelDataWrapper *dw;
dw = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
mime_type = camel_data_wrapper_get_mime_type (dw);
if (mime_type != NULL) {
camel_strdown (mime_type);
em_format_part_as (
emf, stream, mime_part, mime_type, cancellable);
g_free (mime_type);
} else
em_format_part_as (
emf, stream, mime_part, "text/plain", cancellable);
}
/**
* 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.
* @cancellable: a #GCancellable, or %NULL
*
* 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,
GCancellable *cancellable)
{
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, cancellable);
}
void
em_format_format (EMFormat *emf,
CamelFolder *folder,
const gchar *uid,
CamelMimeMessage *message,
GCancellable *cancellable)
{
/* em_format_format_clone() will check the arguments. */
em_format_format_clone (emf, folder, uid, message, NULL, cancellable);
}
static gboolean
format_redraw_idle_cb (EMFormat *emf)
{
emf->priv->redraw_idle_id = 0;
/* FIXME Not passing a GCancellable here. */
em_format_format_clone (
emf, emf->folder, emf->uid, emf->message, emf, NULL);
return FALSE;
}
void
em_format_queue_redraw (EMFormat *emf)
{
g_return_if_fail (EM_IS_FORMAT (emf));
if (emf->priv->redraw_idle_id == 0)
emf->priv->redraw_idle_id = g_idle_add (
(GSourceFunc) format_redraw_idle_cb, emf);
}
/**
* em_format_set_mode:
* @emf:
* @type:
*
* Set display mode, EM_FORMAT_MODE_SOURCE, EM_FORMAT_MODE_ALLHEADERS,
* or EM_FORMAT_MODE_NORMAL.
**/
void
em_format_set_mode (EMFormat *emf,
EMFormatMode mode)
{
g_return_if_fail (EM_IS_FORMAT (emf));
if (emf->mode == mode)
return;
emf->mode = mode;
/* force redraw if type changed afterwards */
if (emf->message != NULL)
em_format_queue_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_queue_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_queue_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 = g_queue_pop_head (&emf->header_list)) != NULL)
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 ii;
em_format_clear_headers (emf);
for (ii = 0; ii < G_N_ELEMENTS (default_headers); ii++)
em_format_add_header (
emf, default_headers[ii].name,
default_headers[ii].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);
g_queue_push_tail (&emf->header_list, 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 ((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", "calendar")
|| camel_content_type_is (dw->mime_type, "text", "x-calendar")
|| (camel_content_type_is (dw->mime_type, "text", "*")
&& camel_mime_part_get_filename (part) == NULL));
}
/**
* em_format_is_inline:
* @emf:
* @part:
* @part_id: 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:
**/
gboolean
em_format_is_inline (EMFormat *emf,
const gchar *part_id,
CamelMimePart *mime_part,
const EMFormatHandler *handle)
{
EMFormatClass *class;
g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE);
g_return_val_if_fail (part_id != NULL, FALSE);
g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), FALSE);
class = EM_FORMAT_GET_CLASS (emf);
g_return_val_if_fail (class->is_inline != NULL, FALSE);
return class->is_inline (emf, part_id, mime_part, handle);
}
/**
* em_format_set_inline:
* @emf:
* @part_id: 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 *part_id,
gint state)
{
EMFormatCache *emfc;
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (part_id != NULL);
emfc = g_hash_table_lookup (emf->inline_table, part_id);
if (emfc == NULL) {
emfc = emf_insert_cache (emf, part_id);
} else if (emfc->state != INLINE_UNSET && (emfc->state & 1) == state)
return;
emfc->state = state ? INLINE_ON : INLINE_OFF;
if (emf->message)
em_format_queue_redraw (emf);
}
void
em_format_format_attachment (EMFormat *emf,
CamelStream *stream,
CamelMimePart *mime_part,
const gchar *mime_type,
const EMFormatHandler *info,
GCancellable *cancellable)
{
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, cancellable);
}
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,
GCancellable *cancellable)
{
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, cancellable);
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,
GCancellable *cancellable)
{
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, cancellable);
}
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,
GCancellable *cancellable)
{
CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part);
if (camel_content_type_is (dw->mime_type, "text", "*"))
em_format_format_text (
emf, stream, (CamelDataWrapper *) part, cancellable);
else
camel_data_wrapper_decode_to_stream_sync (
dw, stream, cancellable, NULL);
}
/**
* em_format_format_content:
* @emf:
* @stream: Where to write the converted text
* @part: Part whose container is to be formatted
* @cancellable: optional #GCancellable object, or %NULL
*
* Decode/output a part's content to @stream.
**/
void
em_format_format_text (EMFormat *emf,
CamelStream *stream,
CamelDataWrapper *dw,
GCancellable *cancellable)
{
CamelStream *filter_stream;
CamelMimeFilter *filter;
const gchar *charset = NULL;
CamelMimeFilterWindows *windows = NULL;
CamelStream *mem_stream = NULL;
const gchar *key;
gsize size;
gsize max;
GSettings *settings;
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 (null);
g_object_unref (null);
windows = (CamelMimeFilterWindows *) camel_mime_filter_windows_new (charset);
camel_stream_filter_add (
CAMEL_STREAM_FILTER (filter_stream),
CAMEL_MIME_FILTER (windows));
camel_data_wrapper_decode_to_stream_sync (
dw, (CamelStream *) filter_stream, cancellable, NULL);
camel_stream_flush ((CamelStream *) filter_stream, cancellable, NULL);
g_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 (mem_stream);
if ((filter = camel_mime_filter_charset_new (charset, "UTF-8"))) {
camel_stream_filter_add (
CAMEL_STREAM_FILTER (filter_stream),
CAMEL_MIME_FILTER (filter));
g_object_unref (filter);
}
max = -1;
settings = g_settings_new ("org.gnome.evolution.mail");
if (g_settings_get_boolean (settings, "force-message-limit")) {
key = "/apps/evolution/mail/display/message_text_part_limit";
max = g_settings_get_int (settings, "message-text-part-limit");
if (max == 0)
max = -1;
}
g_object_unref (settings);
size = camel_data_wrapper_decode_to_stream_sync (
emf->mode == EM_FORMAT_MODE_SOURCE ?
(CamelDataWrapper *) dw :
camel_medium_get_content ((CamelMedium *) dw),
(CamelStream *) filter_stream, cancellable, NULL);
camel_stream_flush ((CamelStream *) filter_stream, cancellable, NULL);
g_object_unref (filter_stream);
g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL);
if (max == -1 || size == -1 || size < (max * 1024) || emf->composer) {
camel_stream_write_to_stream (
mem_stream, (CamelStream *) stream, cancellable, NULL);
camel_stream_flush ((CamelStream *) stream, cancellable, NULL);
} else {
EM_FORMAT_GET_CLASS (emf)->format_optional (
emf, stream, (CamelMimePart *) dw,
mem_stream, cancellable);
}
if (windows)
g_object_unref (windows);
g_object_unref (mem_stream);
}
/**
* 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);
filename = camel_mime_part_get_filename (part);
description = camel_mime_part_get_description (part);
if (!filename || !*filename) {
CamelDataWrapper *content = camel_medium_get_content (CAMEL_MEDIUM (part));
if (content && CAMEL_IS_MIME_MESSAGE (content))
filename = camel_mime_message_get_subject (CAMEL_MIME_MESSAGE (content));
}
if (filename != NULL && *filename != '\0') {
gchar *basename = g_path_get_basename (filename);
g_string_append_printf (stext, " (%s)", basename);
g_free (basename);
}
if (description != NULL && *description != '\0' &&
g_strcmp0 (filename, description) != 0)
g_string_append_printf (stext, ", \"%s\"", description);
return g_string_free (stext, FALSE);
}
static void
add_validity_found (EMFormat *emf,
CamelCipherValidity *valid)
{
g_return_if_fail (emf != NULL);
if (!valid)
return;
if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE)
emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SIGNED;
if (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE)
emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_ENCRYPTED;
}
/* ********************************************************************** */
static void
preserve_charset_in_content_type (CamelMimePart *ipart,
CamelMimePart *opart)
{
CamelDataWrapper *data_wrapper;
CamelContentType *content_type;
const gchar *charset;
g_return_if_fail (ipart != NULL);
g_return_if_fail (opart != NULL);
data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (ipart));
content_type = camel_data_wrapper_get_mime_type_field (data_wrapper);
if (content_type == NULL)
return;
charset = camel_content_type_param (content_type, "charset");
if (charset == NULL || *charset == '\0')
return;
data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (opart));
content_type = camel_data_wrapper_get_mime_type_field (data_wrapper);
camel_content_type_set_param (content_type, "charset", charset);
}
#ifdef ENABLE_SMIME
static void
emf_application_xpkcs7mime (EMFormat *emf,
CamelStream *stream,
CamelMimePart *part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelCipherContext *context;
CamelMimePart *opart;
CamelCipherValidity *valid;
EMFormatCache *emfc;
GError *local_error = NULL;
/* 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),
cancellable);
return;
}
context = camel_smime_context_new (emf->session);
emf->validity_found |=
EM_FORMAT_VALIDITY_FOUND_ENCRYPTED |
EM_FORMAT_VALIDITY_FOUND_SMIME;
opart = camel_mime_part_new ();
valid = camel_cipher_context_decrypt_sync (
context, part, opart, cancellable, &local_error);
preserve_charset_in_content_type (part, opart);
if (valid == NULL) {
em_format_format_error (
emf, stream, "%s",
local_error->message ? local_error->message :
_("Could not parse S/MIME message: Unknown error"));
g_clear_error (&local_error);
em_format_part_as (emf, stream, part, NULL, cancellable);
} else {
if (emfc == NULL)
emfc = emf_insert_cache (emf, emf->part_id->str);
emfc->valid = camel_cipher_validity_clone (valid);
g_object_ref ((emfc->secured = opart));
add_validity_found (emf, valid);
em_format_format_secure (
emf, stream, opart, valid, cancellable);
}
g_object_unref (opart);
g_object_unref (context);
}
#endif
/* RFC 1740 */
static void
emf_multipart_appledouble (EMFormat *emf,
CamelStream *stream,
CamelMimePart *part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelMultipart *mp;
CamelMimePart *mime_part;
gint len;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
em_format_format_source (emf, stream, part, cancellable);
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, cancellable);
g_string_truncate (emf->part_id, len);
} else
em_format_format_source (emf, stream, part, cancellable);
}
/* RFC ??? */
static void
emf_multipart_mixed (EMFormat *emf,
CamelStream *stream,
CamelMimePart *part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelMultipart *mp;
gint i, nparts, len;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
em_format_format_source (emf, stream, part, cancellable);
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, cancellable);
g_string_truncate (emf->part_id, len);
}
}
static gboolean related_display_part_is_attachment
(EMFormat *emf,
CamelMimePart *part);
/* RFC 1740 */
static void
emf_multipart_alternative (EMFormat *emf,
CamelStream *stream,
CamelMimePart *part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelMultipart *mp;
gint i, nparts, bestid = 0;
CamelMimePart *best = NULL;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
em_format_format_source (emf, stream, part, cancellable);
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++) {
CamelDataWrapper *data_wrapper;
CamelContentType *type;
CamelStream *null_stream;
gchar *mime_type;
gsize content_size;
/* is it correct to use the passed in *part here? */
part = camel_multipart_get_part (mp, i);
if (part == NULL)
continue;
/* This may block even though the stream does not.
* XXX Pretty inefficient way to test if the MIME part
* is empty. Surely there's a quicker way? */
null_stream = camel_stream_null_new ();
data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (part));
camel_data_wrapper_decode_to_stream_sync (
data_wrapper, null_stream, cancellable, NULL);
content_size = CAMEL_STREAM_NULL (null_stream)->written;
g_object_unref (null_stream);
if (content_size == 0)
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_is_attachment (emf, part) &&
(!camel_content_type_is (type, "multipart", "related") ||
!related_display_part_is_attachment (emf, part)) &&
(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, cancellable);
g_string_truncate (emf->part_id, len);
} else
emf_multipart_mixed (
emf, stream, part, info, cancellable, is_fallback);
}
static void
emf_multipart_encrypted (EMFormat *emf,
CamelStream *stream,
CamelMimePart *part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelCipherContext *context;
const gchar *protocol;
CamelMimePart *opart;
CamelCipherValidity *valid;
CamelMultipartEncrypted *mpe;
EMFormatCache *emfc;
GError *local_error = NULL;
/* 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),
cancellable);
return;
}
mpe = (CamelMultipartEncrypted *) camel_medium_get_content ((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, cancellable);
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", cancellable);
return;
}
emf->validity_found |=
EM_FORMAT_VALIDITY_FOUND_ENCRYPTED |
EM_FORMAT_VALIDITY_FOUND_PGP;
context = camel_gpg_context_new (emf->session);
opart = camel_mime_part_new ();
valid = camel_cipher_context_decrypt_sync (
context, part, opart, cancellable, &local_error);
preserve_charset_in_content_type (part, opart);
if (valid == NULL) {
em_format_format_error (
emf, stream, local_error->message ?
_("Could not parse PGP/MIME message") :
_("Could not parse PGP/MIME message: Unknown error"));
if (local_error->message != NULL)
em_format_format_error (
emf, stream, "%s", local_error->message);
g_clear_error (&local_error);
em_format_part_as (
emf, stream, part,
"multipart/mixed", cancellable);
} else {
if (emfc == NULL)
emfc = emf_insert_cache (emf, emf->part_id->str);
emfc->valid = camel_cipher_validity_clone (valid);
g_object_ref ((emfc->secured = opart));
add_validity_found (emf, valid);
em_format_format_secure (
emf, stream, opart, valid, cancellable);
}
/* TODO: Make sure when we finalize this part, it is zero'd out */
g_object_unref (opart);
g_object_unref (context);
}
static CamelMimePart *
get_related_display_part (CamelMimePart *part,
gint *out_displayid)
{
CamelMultipart *mp;
CamelMimePart *body_part, *display_part = NULL;
CamelContentType *content_type;
const gchar *start;
gint i, nparts, displayid = 0;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp))
return NULL;
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 (out_displayid)
*out_displayid = displayid;
return display_part;
}
static gboolean
related_display_part_is_attachment (EMFormat *emf,
CamelMimePart *part)
{
CamelMimePart *display_part;
display_part = get_related_display_part (part, NULL);
return display_part && em_format_is_attachment (emf, display_part);
}
static void
emf_write_related (EMFormat *emf,
CamelStream *stream,
EMFormatPURI *puri,
GCancellable *cancellable)
{
em_format_format_content (emf, stream, puri->part, cancellable);
camel_stream_close (stream, cancellable, NULL);
}
/* RFC 2387 */
static void
emf_multipart_related (EMFormat *emf,
CamelStream *stream,
CamelMimePart *part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelMultipart *mp;
CamelMimePart *body_part, *display_part = NULL;
gint i, nparts, partidlen, displayid = 0;
gchar *oldpartid;
GList *link;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
em_format_format_source (emf, stream, part, cancellable);
return;
}
display_part = get_related_display_part (part, &displayid);
if (display_part == NULL) {
emf_multipart_mixed (
emf, stream, part, info, cancellable, is_fallback);
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 */
nparts = camel_multipart_get_number (mp);
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);
em_format_add_puri (
emf, sizeof (EMFormatPURI), NULL,
body_part, emf_write_related);
g_string_truncate (emf->part_id, partidlen);
}
}
g_string_append_printf(emf->part_id, ".related.%d", displayid);
em_format_part (emf, stream, display_part, cancellable);
g_string_truncate (emf->part_id, partidlen);
camel_stream_flush (stream, NULL, NULL);
link = g_queue_peek_head_link (emf->pending_uri_level->data);
while (link && link->next != NULL) {
EMFormatPURI *puri = link->data;
if (puri->use_count == 0) {
if (puri->func == emf_write_related) {
g_string_printf(emf->part_id, "%s", puri->part_id);
em_format_part (
emf, stream, puri->part, cancellable);
}
}
link = g_list_next (link);
}
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,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelMimePart *cpart;
CamelMultipartSigned *mps;
CamelCipherContext *cipher = NULL;
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),
cancellable);
return;
}
mps = (CamelMultipartSigned *) camel_medium_get_content ((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, cancellable);
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);
emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SMIME;
} else
#endif
if (g_ascii_strcasecmp("application/pgp-signature", mps->protocol) == 0) {
cipher = camel_gpg_context_new (emf->session);
emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_PGP;
}
}
emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SIGNED;
if (cipher == NULL) {
em_format_format_error(emf, stream, _("Unsupported signature format"));
em_format_part_as (
emf, stream, part,
"multipart/mixed", cancellable);
} else {
CamelCipherValidity *valid;
GError *local_error = NULL;
valid = camel_cipher_context_verify_sync (
cipher, part, cancellable, &local_error);
if (valid == NULL) {
em_format_format_error (
emf, stream, local_error->message ?
_("Error verifying signature") :
_("Unknown error verifying signature"));
if (local_error->message != NULL)
em_format_format_error (
emf, stream, "%s",
local_error->message);
g_clear_error (&local_error);
em_format_part_as (
emf, stream, part,
"multipart/mixed", cancellable);
} else {
if (emfc == NULL)
emfc = emf_insert_cache (emf, emf->part_id->str);
emfc->valid = camel_cipher_validity_clone (valid);
g_object_ref ((emfc->secured = cpart));
add_validity_found (emf, valid);
em_format_format_secure (
emf, stream, cpart, valid, cancellable);
}
g_object_unref (cipher);
}
}
/* RFC 4155 */
static void
emf_application_mbox (EMFormat *emf,
CamelStream *stream,
CamelMimePart *mime_part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
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_sync (
camel_medium_get_content (CAMEL_MEDIUM (mime_part)),
mem_stream, NULL, NULL);
g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL);
camel_mime_parser_init_with_stream (parser, mem_stream, NULL);
g_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_sync (
mime_part, parser, NULL, NULL)) {
g_object_unref (message);
break;
}
/* Render the message. */
handle->handler (
emf, stream, mime_part,
handle, cancellable, FALSE);
g_object_unref (message);
/* Skip past CAMEL_MIME_PARSER_STATE_FROM_END. */
camel_mime_parser_step (parser, NULL, NULL);
state = camel_mime_parser_step (parser, NULL, NULL);
}
g_object_unref (parser);
}
static void
emf_message_rfc822 (EMFormat *emf,
CamelStream *stream,
CamelMimePart *part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part);
const EMFormatHandler *handle;
gint len;
gchar *parent_message_part_id;
if (!CAMEL_IS_MIME_MESSAGE (dw)) {
em_format_format_source (emf, stream, part, cancellable);
return;
}
parent_message_part_id = emf->current_message_part_id;
emf->current_message_part_id = g_strdup (emf->part_id->str);
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, CAMEL_MIME_PART (dw),
handle, cancellable, FALSE);
g_string_truncate (emf->part_id, len);
g_free (emf->current_message_part_id);
emf->current_message_part_id = parent_message_part_id;
}
static void
emf_message_deliverystatus (EMFormat *emf,
CamelStream *stream,
CamelMimePart *part,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
em_format_format_text (
emf, stream, (CamelDataWrapper *) part, cancellable);
}
static void
emf_inlinepgp_signed (EMFormat *emf,
CamelStream *stream,
CamelMimePart *ipart,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelStream *filtered_stream;
CamelMimeFilterPgp *pgp_filter;
CamelContentType *content_type;
CamelCipherContext *cipher;
CamelCipherValidity *valid;
CamelDataWrapper *dw;
CamelMimePart *opart;
CamelStream *ostream;
gchar *type;
GError *local_error = NULL;
if (!ipart) {
em_format_format_error(emf, stream, _("Unknown error verifying signature"));
return;
}
emf->validity_found |=
EM_FORMAT_VALIDITY_FOUND_SIGNED |
EM_FORMAT_VALIDITY_FOUND_PGP;
cipher = camel_gpg_context_new (emf->session);
/* Verify the signature of the message */
valid = camel_cipher_context_verify_sync (
cipher, ipart, cancellable, &local_error);
if (!valid) {
em_format_format_error (
emf, stream, local_error->message ?
_("Error verifying signature") :
_("Unknown error verifying signature"));
if (local_error->message)
em_format_format_error (
emf, stream, "%s", local_error->message);
em_format_format_source (emf, stream, ipart, cancellable);
/* XXX I think this will loop:
* em_format_part_as(emf, stream, part, "text/plain"); */
g_clear_error (&local_error);
g_object_unref (cipher);
return;
}
/* Setup output stream */
ostream = camel_stream_mem_new ();
filtered_stream = camel_stream_filter_new (ostream);
/* Add PGP header / footer filter */
pgp_filter = (CamelMimeFilterPgp *) camel_mime_filter_pgp_new ();
camel_stream_filter_add (
CAMEL_STREAM_FILTER (filtered_stream),
CAMEL_MIME_FILTER (pgp_filter));
g_object_unref (pgp_filter);
/* Pass through the filters that have been setup */
dw = camel_medium_get_content ((CamelMedium *) ipart);
camel_data_wrapper_decode_to_stream_sync (
dw, (CamelStream *) filtered_stream, NULL, NULL);
camel_stream_flush ((CamelStream *) filtered_stream, NULL, NULL);
g_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_sync (dw, ostream, NULL, NULL);
camel_data_wrapper_set_mime_type (dw, type);
g_free (type);
opart = camel_mime_part_new ();
camel_medium_set_content ((CamelMedium *) opart, dw);
camel_data_wrapper_set_mime_type_field ((CamelDataWrapper *) opart, dw->mime_type);
add_validity_found (emf, valid);
/* Pass it off to the real formatter */
em_format_format_secure (emf, stream, opart, valid, cancellable);
/* Clean Up */
g_object_unref (dw);
g_object_unref (opart);
g_object_unref (ostream);
g_object_unref (cipher);
}
static void
emf_inlinepgp_encrypted (EMFormat *emf,
CamelStream *stream,
CamelMimePart *ipart,
const EMFormatHandler *info,
GCancellable *cancellable,
gboolean is_fallback)
{
CamelCipherContext *cipher;
CamelCipherValidity *valid;
CamelMimePart *opart;
CamelDataWrapper *dw;
gchar *mime_type;
GError *local_error = NULL;
emf->validity_found |=
EM_FORMAT_VALIDITY_FOUND_ENCRYPTED |
EM_FORMAT_VALIDITY_FOUND_PGP;
cipher = camel_gpg_context_new (emf->session);
opart = camel_mime_part_new ();
/* Decrypt the message */
valid = camel_cipher_context_decrypt_sync (
cipher, ipart, opart, cancellable, &local_error);
if (!valid) {
em_format_format_error (
emf, stream, _("Could not parse PGP message: "));
if (local_error->message != NULL)
em_format_format_error (
emf, stream, "%s", local_error->message);
else
em_format_format_error (
emf, stream, _("Unknown error"));
em_format_format_source (emf, stream, ipart, cancellable);
/* XXX I think this will loop:
* em_format_part_as(emf, stream, part, "text/plain"); */
g_clear_error (&local_error);
g_object_unref (cipher);
g_object_unref (opart);
return;
}
dw = camel_medium_get_content ((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);
}
preserve_charset_in_content_type (ipart, opart);
g_free (mime_type);
add_validity_found (emf, valid);
/* Pass it off to the real formatter */
em_format_format_secure (emf, stream, opart, valid, cancellable);
/* Clean Up */
g_object_unref (opart);
g_object_unref (cipher);
}
static EMFormatHandler type_builtin_table[] = {
#ifdef ENABLE_SMIME
{ (gchar *) "application/x-pkcs7-mime",
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",
emf_application_xpkcs7mime,
EM_FORMAT_HANDLER_INLINE_DISPOSITION },
#endif
/* internal types */
{ (gchar *) "application/x-inlinepgp-signed", emf_inlinepgp_signed },
{ (gchar *) "application/x-inlinepgp-encrypted", emf_inlinepgp_encrypted },
};
static void
emf_builtin_init (EMFormatClass *class)
{
gint ii;
for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++)
g_hash_table_insert (
class->type_handlers,
type_builtin_table[ii].mime_type,
&type_builtin_table[ii]);
}
/**
* 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 ((CamelMedium *) part);
if (!camel_data_wrapper_is_offline (dw)) {
GByteArray *byte_array;
CamelStream *stream;
byte_array = g_byte_array_new ();
stream = camel_stream_mem_new_with_byte_array (byte_array);
if (camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL) > 0) {
gchar *content_type;
content_type = g_content_type_guess (
filename, byte_array->data,
byte_array->len, NULL);
if (content_type != NULL)
magic_type = g_content_type_get_mime_type (content_type);
g_free (content_type);
}
g_object_unref (stream);
}
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 */
}