/*
* 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
*
*
* Authors:
* Michael Zucchi
* Jeffrey Stedfast
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include "em-format.h"
#include "e-util/e-util.h"
#include "shell/e-shell.h"
#include "shell/e-shell-settings.h"
#define d(x)
#define EM_FORMAT_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), EM_TYPE_FORMAT, EMFormatPrivate))
struct _EMFormatPrivate {
GNode *current_node;
CamelSession *session;
CamelURL *base_url;
gchar *charset;
gchar *default_charset;
gboolean composer;
gint last_error;
};
enum {
PROP_0,
PROP_BASE_URL,
PROP_CHARSET,
PROP_COMPOSER,
PROP_DEFAULT_CHARSET,
PROP_SESSION
};
enum {
REDRAW_REQUESTED,
LAST_SIGNAL
};
gint signals[LAST_SIGNAL];
static gpointer parent_class;
/* PARSERS */
static void emf_parse_application_xpkcs7mime (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_application_mbox (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_multipart_alternative (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_multipart_appledouble (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_multipart_encrypted (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_multipart_mixed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_multipart_signed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_multipart_related (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_multipart_digest (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_message_deliverystatus (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_inlinepgp_signed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_inlinepgp_encrypted (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_message (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_headers (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_post_headers (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void emf_parse_source (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
/* WRITERS */
static void emf_write_text (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void emf_write_source (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void emf_write_error (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
/**************************************************************************/
static gboolean
is_secured (CamelMimePart *part)
{
CamelContentType *ct = camel_mime_part_get_content_type (part);
return (camel_content_type_is (ct, "multipart", "signed") ||
camel_content_type_is (ct, "multipart", "encrypted") ||
camel_content_type_is (ct, "application", "x-inlinepgp-signed") ||
camel_content_type_is (ct, "application", "x-inlinepgp-encrypted") ||
camel_content_type_is (ct, "application", "x-pkcs7-mime") ||
camel_content_type_is (ct, "application", "pkcs7-mime"));
}
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);
if (content_type)
camel_content_type_set_param (content_type, "charset", charset);
/* update charset also on the part itself */
data_wrapper = CAMEL_DATA_WRAPPER (opart);
content_type = camel_data_wrapper_get_mime_type_field (data_wrapper);
if (content_type)
camel_content_type_set_param (content_type, "charset", charset);
}
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 from CID */
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);
}
/**************************************************************************/
void
em_format_empty_parser (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
/* DO NOTHING */
}
#ifdef ENABLE_SMIME
static void
emf_parse_application_xpkcs7mime (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelCipherContext *context;
CamelMimePart *opart;
CamelCipherValidity *valid;
GError *local_error = NULL;
if (g_cancellable_is_cancelled (cancellable))
return;
context = camel_smime_context_new (emf->priv->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, "%s",
local_error->message ? local_error->message :
_("Could not parse S/MIME message: Unknown error"));
g_clear_error (&local_error);
} else {
EMFormatParserInfo encinfo = {
info->handler,
info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_SMIME,
valid
};
gint len = part_id->len;
g_string_append (part_id, ".encrypted");
em_format_parse_part (emf, opart, part_id, &encinfo, cancellable);
g_string_truncate (part_id, len);
/* Add a widget with details about the encryption, but only when
* the encrypted isn't itself secured, in that case it has created
* the button itself */
if (!is_secured (opart)) {
g_string_append (part_id, ".encrypted.button");
em_format_parse_part_as (emf, part, part_id, &encinfo,
"x-evolution/message/x-secure-button", cancellable);
g_string_truncate (part_id, len);
}
camel_cipher_validity_free (valid);
}
g_object_unref (opart);
g_object_unref (context);
}
#endif
/* RFC 4155 */
static void
emf_parse_application_mbox (EMFormat *emf,
CamelMimePart *mime_part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelMimeParser *parser;
CamelStream *mem_stream;
camel_mime_parser_state_t state;
gint old_len;
gint messages;
if (g_cancellable_is_cancelled (cancellable))
return;
/* 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? */
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);
old_len = part_id->len;
/* Extract messages from the mbox. */
messages = 0;
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;
}
g_string_append_printf (part_id, ".mbox.%d", messages);
em_format_parse_part_as (emf, CAMEL_MIME_PART (message),
part_id, info, "message/rfc822", cancellable);
g_string_truncate (part_id, old_len);
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);
messages++;
}
g_object_unref (parser);
}
/* RFC 1740 */
static void
emf_parse_multipart_alternative (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelMultipart *mp;
gint i, nparts, bestid = 0;
CamelMimePart *best = NULL;
if (g_cancellable_is_cancelled (cancellable))
return;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
emf_parse_source (emf, part, part_id, info, 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++) {
CamelMimePart *mpart;
CamelDataWrapper *data_wrapper;
CamelContentType *type;
CamelStream *null_stream;
gchar *mime_type;
gsize content_size;
if (g_cancellable_is_cancelled (cancellable))
return;
/* is it correct to use the passed in *part here? */
mpart = camel_multipart_get_part (mp, i);
if (mpart == 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 (mpart));
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 (mpart);
mime_type = camel_content_type_simple (type);
camel_strdown (mime_type);
if (!em_format_is_attachment (emf, mpart) &&
((camel_content_type_is (type, "multipart", "related") == 0) ||
!related_display_part_is_attachment (emf, mpart)) &&
(em_format_find_handler (emf, mime_type)
|| (best == NULL && em_format_fallback_handler (emf, mime_type)))) {
best = mpart;
bestid = i;
}
g_free (mime_type);
}
if (best) {
gint len = part_id->len;
g_string_append_printf(part_id, ".alternative.%d", bestid);
em_format_parse_part (emf, best, part_id, info, cancellable);
g_string_truncate (part_id, len);
} else
emf_parse_multipart_mixed (emf, part, part_id, info, cancellable);
}
/* RFC 1740 */
static void
emf_parse_multipart_appledouble (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelMultipart *mp;
CamelMimePart *mime_part;
if (g_cancellable_is_cancelled (cancellable))
return;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
emf_parse_source (emf, part, part_id, info, cancellable);
return;
}
mime_part = camel_multipart_get_part (mp, 1);
if (mime_part) {
gint len;
/* try the data fork for something useful, doubtful but who knows */
len = part_id->len;
g_string_append_printf(part_id, ".appledouble.1");
em_format_parse_part (emf, mime_part, part_id, info, cancellable);
g_string_truncate (part_id, len);
} else {
emf_parse_source (emf, part, part_id, info, cancellable);
}
}
static void
emf_parse_multipart_encrypted (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelCipherContext *context;
const gchar *protocol;
CamelMimePart *opart;
CamelCipherValidity *valid;
CamelMultipartEncrypted *mpe;
GError *local_error = NULL;
if (g_cancellable_is_cancelled (cancellable))
return;
mpe = (CamelMultipartEncrypted *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART_ENCRYPTED (mpe)) {
em_format_format_error (
emf, _("Could not parse MIME message. "
"Displaying as source."));
emf_parse_source (emf, part, part_id, info, 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, _("Unsupported encryption type for multipart/encrypted"));
emf_parse_multipart_mixed (emf, part, part_id, info, cancellable);
return;
}
context = camel_gpg_context_new (emf->priv->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, 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, "%s", local_error->message);
g_clear_error (&local_error);
emf_parse_multipart_mixed (emf, part, part_id, info, cancellable);
} else {
gint len = part_id->len;
EMFormatParserInfo encinfo = {
info->handler,
info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_PGP,
};
if (info->validity)
camel_cipher_validity_envelope (valid, info->validity);
encinfo.validity = valid;
g_string_append (part_id, ".encrypted");
em_format_parse_part (emf, opart, part_id, &encinfo, cancellable);
g_string_truncate (part_id, len);
/* Add a widget with details about the encryption, but only when
* the encrypted isn't itself secured, in that case it has created
* the button itself */
if (!is_secured (opart)) {
g_string_append (part_id, ".encrypted.button");
em_format_parse_part_as (emf, part, part_id, &encinfo,
"x-evolution/message/x-secure-button", cancellable);
g_string_truncate (part_id, len);
}
camel_cipher_validity_free (valid);
}
/* TODO: Make sure when we finalize this part, it is zero'd out */
g_object_unref (opart);
g_object_unref (context);
}
/* RFC 2046 */
static void
emf_parse_multipart_mixed (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelMultipart *mp;
gint i, nparts, len;
if (g_cancellable_is_cancelled (cancellable))
return;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
emf_parse_source (emf, part, part_id, info, cancellable);
return;
}
len = part_id->len;
nparts = camel_multipart_get_number (mp);
for (i = 0; i < nparts; i++) {
CamelMimePart *subpart;
subpart = camel_multipart_get_part (mp, i);
g_string_append_printf(part_id, ".mixed.%d", i);
em_format_parse_part (emf, subpart, part_id, info, cancellable);
g_string_truncate (part_id, len);
}
}
static void
emf_parse_multipart_signed (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelMimePart *cpart;
CamelMultipartSigned *mps;
CamelCipherContext *cipher = NULL;
guint32 validity_type;
if (g_cancellable_is_cancelled (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, _("Could not parse MIME message. "
"Displaying as source."));
emf_parse_source (emf, part, part_id, info, 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->priv->session);
validity_type = EM_FORMAT_VALIDITY_FOUND_SMIME;
} else
#endif
if (g_ascii_strcasecmp("application/pgp-signature", mps->protocol) == 0) {
cipher = camel_gpg_context_new (emf->priv->session);
validity_type = EM_FORMAT_VALIDITY_FOUND_PGP;
}
}
if (cipher == NULL) {
em_format_format_error(emf, _("Unsupported signature format"));
emf_parse_multipart_mixed (emf, part, part_id, info, 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, local_error->message ?
_("Error verifying signature") :
_("Unknown error verifying signature"));
if (local_error->message != NULL)
em_format_format_error (
emf, "%s",
local_error->message);
g_clear_error (&local_error);
emf_parse_multipart_mixed (emf, part, part_id,info, cancellable);
} else {
gint i, nparts, len = part_id->len;
gboolean secured;
EMFormatParserInfo signinfo = {
info->handler,
info->validity_type | validity_type | EM_FORMAT_VALIDITY_FOUND_SIGNED,
};
if (info->validity)
camel_cipher_validity_envelope (valid, info->validity);
signinfo.validity = valid;
nparts = camel_multipart_get_number (CAMEL_MULTIPART (mps));
secured = FALSE;
for (i = 0; i < nparts; i++) {
CamelMimePart *subpart;
subpart = camel_multipart_get_part (CAMEL_MULTIPART (mps), i);
g_string_append_printf(part_id, ".signed.%d", i);
em_format_parse_part (emf, subpart, part_id, &signinfo, cancellable);
g_string_truncate (part_id, len);
if (!secured)
secured = is_secured (subpart);
}
/* Add a widget with details about the encryption, but only when
* the encrypted isn't itself secured, in that case it has created
* the button itself */
if (!secured) {
g_string_append (part_id, ".signed.button");
em_format_parse_part_as (emf, part, part_id, &signinfo,
"x-evolution/message/x-secure-button", cancellable);
g_string_truncate (part_id, len);
}
camel_cipher_validity_free (valid);
}
}
g_object_unref (cipher);
}
/* RFC 2046 */
static void
emf_parse_multipart_digest (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelMultipart *mp;
gint i, nparts, len;
if (g_cancellable_is_cancelled (cancellable))
return;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
emf_parse_source (emf, part, part_id, info, cancellable);
return;
}
len = part_id->len;
nparts = camel_multipart_get_number (mp);
for (i = 0; i < nparts; i++) {
CamelMimePart *subpart;
CamelContentType *ct;
gchar *cts;
const EMFormatHandler *handler;
subpart = camel_multipart_get_part (mp, i);
if (!subpart)
continue;
g_string_append_printf(part_id, ".digest.%d", i);
ct = camel_mime_part_get_content_type (subpart);
/* According to RFC this shouldn't happen, but who knows... */
if (ct && !camel_content_type_is (ct, "message", "rfc822")) {
cts = camel_content_type_simple (ct);
em_format_parse_part_as (emf, part, part_id, info, cts, cancellable);
g_free (cts);
g_string_truncate (part_id, len);
continue;
}
handler = em_format_find_handler (emf, "message/rfc822");
if (handler && handler->parse_func)
handler->parse_func (emf, subpart, part_id, info, cancellable);
g_string_truncate (part_id, len);
}
}
/* RFC 2387 */
static void
emf_parse_multipart_related (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelMultipart *mp;
CamelMimePart *body_part, *display_part = NULL;
gint i, nparts, partidlen, displayid = 0;
if (g_cancellable_is_cancelled (cancellable))
return;
mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
if (!CAMEL_IS_MULTIPART (mp)) {
emf_parse_source (emf, part, part_id, info, cancellable);
return;
}
display_part = get_related_display_part (part, &displayid);
if (display_part == NULL) {
emf_parse_multipart_mixed (
emf, part, part_id, info, cancellable);
return;
}
/* The to-be-displayed part goes first */
partidlen = part_id->len;
g_string_append_printf(part_id, ".related.%d", displayid);
em_format_parse_part (emf, display_part, part_id, info, cancellable);
g_string_truncate (part_id, partidlen);
/* Process the related parts */
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) {
g_string_append_printf(part_id, ".related.%d", i);
em_format_parse_part (emf, body_part, part_id, info, cancellable);
g_string_truncate (part_id, partidlen);
}
}
}
static void
emf_parse_message_deliverystatus (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
EMFormatPURI *puri;
gint len;
if (g_cancellable_is_cancelled (cancellable))
return;
len = part_id->len;
g_string_append (part_id, ".deliverystatus");
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
puri->write_func = emf_write_text;
puri->mime_type = g_strdup ("text/html");
puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
puri->validity_type = info->validity_type;
g_string_truncate (part_id, len);
em_format_add_puri (emf, puri);
}
static void
emf_parse_inlinepgp_signed (EMFormat *emf,
CamelMimePart *ipart,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelStream *filtered_stream;
CamelMimeFilterPgp *pgp_filter;
CamelContentType *content_type;
CamelCipherContext *cipher;
CamelCipherValidity *valid;
CamelDataWrapper *dw;
CamelMimePart *opart;
CamelStream *ostream;
gchar *type;
gint len;
GError *local_error = NULL;
EMFormatParserInfo signinfo;
GByteArray *ba;
if (g_cancellable_is_cancelled (cancellable))
return;
if (!ipart) {
em_format_format_error(emf, _("Unknown error verifying signature"));
return;
}
cipher = camel_gpg_context_new (emf->priv->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, local_error->message ?
_("Error verifying signature") :
_("Unknown error verifying signature"));
if (local_error->message)
em_format_format_error (
emf, "%s", local_error->message);
emf_parse_source (emf, ipart, part_id, info, 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, cancellable, NULL);
camel_stream_flush ((CamelStream *) filtered_stream, cancellable, 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);
ba = camel_stream_mem_get_byte_array ((CamelStreamMem *) ostream);
opart = camel_mime_part_new ();
camel_mime_part_set_content (opart, (gchar *) ba->data, ba->len, type);
g_free (type);
if (info->validity)
camel_cipher_validity_envelope (valid, info->validity);
/* Pass it off to the real formatter */
len = part_id->len;
g_string_append (part_id, ".inlinepgp_signed");
signinfo.handler = info->handler;
signinfo.validity_type = info->validity_type | EM_FORMAT_VALIDITY_FOUND_SIGNED | EM_FORMAT_VALIDITY_FOUND_PGP;
signinfo.validity = valid;
em_format_parse_part (emf, opart, part_id, &signinfo, cancellable);
g_string_truncate (part_id, len);
/* Add a widget with details about the encryption, but only when
* the encrypted isn't itself secured, in that case it has created
* the button itself */
if (!is_secured (opart)) {
g_string_append (part_id, ".inlinepgp_signed.button");
em_format_parse_part_as (emf, opart, part_id, &signinfo,
"x-evolution/message/x-secure-button", cancellable);
g_string_truncate (part_id, len);
}
/* Clean Up */
camel_cipher_validity_free (valid);
g_object_unref (dw);
g_object_unref (opart);
g_object_unref (ostream);
g_object_unref (cipher);
}
static void
emf_parse_inlinepgp_encrypted (EMFormat *emf,
CamelMimePart *ipart,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelCipherContext *cipher;
CamelCipherValidity *valid;
CamelMimePart *opart;
CamelDataWrapper *dw;
gchar *mime_type;
gint len;
GError *local_error = NULL;
EMFormatParserInfo encinfo;
if (g_cancellable_is_cancelled (cancellable))
return;
cipher = camel_gpg_context_new (emf->priv->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, _("Could not parse PGP message: "));
if (local_error->message != NULL)
em_format_format_error (
emf, "%s", local_error->message);
else
em_format_format_error (
emf, _("Unknown error"));
emf_parse_source (emf, ipart, part_id, info, 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);
if (info->validity)
camel_cipher_validity_envelope (valid, info->validity);
/* Pass it off to the real formatter */
len = part_id->len;
g_string_append (part_id, ".inlinepgp_encrypted");
encinfo.handler = info->handler;
encinfo.validity_type = info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_PGP;
encinfo.validity = valid;
em_format_parse_part (emf, opart, part_id, &encinfo, cancellable);
g_string_truncate (part_id, len);
/* Add a widget with details about the encryption, but only when
* the encrypted isn't itself secured, in that case it has created
* the button itself */
if (!is_secured (opart)) {
g_string_append (part_id, ".inlinepgp_encrypted.button");
em_format_parse_part_as (emf, opart, part_id, &encinfo,
"x-evolution/message/x-secure-button", cancellable);
g_string_truncate (part_id, len);
}
/* Clean Up */
camel_cipher_validity_free (valid);
g_object_unref (opart);
g_object_unref (cipher);
}
static void
emf_parse_message (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
/* Headers */
info->force_handler = TRUE;
em_format_parse_part_as (emf, part, part_id, info,
"x-evolution/message/headers", cancellable);
/* Anything that comes between headers and message body */
info->force_handler = TRUE;
em_format_parse_part_as (emf, part, part_id, info,
"x-evolution/message/post-headers", cancellable);
/* Begin parsing the message */
info->force_handler = FALSE;
em_format_parse_part (emf, part, part_id, info, cancellable);
}
static void
emf_parse_headers (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
EMFormatPURI *puri;
gint len;
len = part_id->len;
g_string_append (part_id, ".headers");
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
puri->write_func = info->handler->write_func;
puri->mime_type = g_strdup ("text/html");
em_format_add_puri (emf, puri);
g_string_truncate (part_id, len);
}
static void
emf_parse_post_headers (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
/* Add attachment bar */
info->force_handler = TRUE;
em_format_parse_part_as (emf, part, part_id, info,
"x-evolution/message/attachment-bar", cancellable);
}
static void
emf_parse_source (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
EMFormatPURI *puri;
gint len;
if (g_cancellable_is_cancelled (cancellable))
return;
len = part_id->len;
g_string_append (part_id, ".source");
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
puri->write_func = info->handler->write_func;
puri->mime_type = g_strdup ("text/html");
g_string_truncate (part_id, len);
em_format_add_puri (emf, puri);
}
/**************************************************************************/
void
em_format_empty_writer (EMFormat *emf,
EMFormatPURI *puri,
CamelStream *stream,
EMFormatWriterInfo *info,
GCancellable *cancellable)
{
/* DO NOTHING */
}
static void
emf_write_error (EMFormat *emf,
EMFormatPURI *puri,
CamelStream *stream,
EMFormatWriterInfo *info,
GCancellable *cancellable)
{
camel_data_wrapper_decode_to_stream_sync ((CamelDataWrapper *) puri->part,
stream, cancellable, NULL);
}
static void
emf_write_text (EMFormat *emf,
EMFormatPURI *puri,
CamelStream *stream,
EMFormatWriterInfo *info,
GCancellable *cancellable)
{
CamelContentType *ct;
ct = camel_mime_part_get_content_type (puri->part);
if (!camel_content_type_is (ct, "text", "plain")) {
camel_stream_write_string (stream, _("Cannot proccess non-text mime/part"),
cancellable, NULL);
return;
}
camel_data_wrapper_decode_to_stream_sync ((CamelDataWrapper *) puri->part,
stream, cancellable, NULL);
}
static void
emf_write_source (EMFormat *emf,
EMFormatPURI *puri,
CamelStream *stream,
EMFormatWriterInfo *info,
GCancellable *cancellable)
{
GByteArray *ba;
gchar *data;
g_return_if_fail (EM_IS_FORMAT (emf));
ba = camel_data_wrapper_get_byte_array ((CamelDataWrapper *) puri->part);
data = g_strndup ((gchar *) ba->data, ba->len);
camel_stream_write_string (stream, data, cancellable, NULL);
g_free (data);
}
/**************************************************************************/
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;
/* 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 EMFormatHandler type_handlers[] = {
#ifdef ENABLE_SMIME
{ (gchar *) "application/x-pkcs7-mime", emf_parse_application_xpkcs7mime, 0, EM_FORMAT_HANDLER_INLINE_DISPOSITION },
#endif
{ (gchar *) "application/mbox", emf_parse_application_mbox, 0, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE },
{ (gchar *) "multipart/alternative", emf_parse_multipart_alternative, },
{ (gchar *) "multipart/appledouble", emf_parse_multipart_appledouble, },
{ (gchar *) "multipart/encrypted", emf_parse_multipart_encrypted, },
{ (gchar *) "multipart/mixed", emf_parse_multipart_mixed, },
{ (gchar *) "multipart/signed", emf_parse_multipart_signed, },
{ (gchar *) "multipart/related", emf_parse_multipart_related, },
{ (gchar *) "multipart/digest", emf_parse_multipart_digest, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE },
{ (gchar *) "multipart/*", emf_parse_multipart_mixed, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE },
{ (gchar *) "message/deliverystatus", emf_parse_message_deliverystatus, 0, },
/* Ignore PGP signature part */
{ (gchar *) "application/pgp-signature", em_format_empty_parser, },
/* Insert brokenly-named parts here */
#ifdef ENABLE_SMIME
{ (gchar *) "application/pkcs7-mime", emf_parse_application_xpkcs7mime, 0, EM_FORMAT_HANDLER_INLINE_DISPOSITION },
#endif
/* internal types */
{ (gchar *) "application/x-inlinepgp-signed", emf_parse_inlinepgp_signed, },
{ (gchar *) "application/x-inlinepgp-encrypted", emf_parse_inlinepgp_encrypted, },
{ (gchar *) "x-evolution/message", emf_parse_message, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE },
{ (gchar *) "x-evolution/message/headers", emf_parse_headers, },
{ (gchar *) "x-evolution/message/post-headers", emf_parse_post_headers, },
{ (gchar *) "x-evolution/message/source", emf_parse_source, emf_write_source },
};
/* 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 },
};
static void
em_format_set_session (EMFormat *emf,
CamelSession *session)
{
g_return_if_fail (CAMEL_IS_SESSION (session));
g_return_if_fail (emf->priv->session == NULL);
emf->priv->session = g_object_ref (session);
}
static void
em_format_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_BASE_URL:
em_format_set_base_url (
EM_FORMAT (object),
g_value_get_object (value));
return;
case PROP_CHARSET:
em_format_set_charset (
EM_FORMAT (object),
g_value_get_string (value));
return;
case PROP_COMPOSER:
em_format_set_composer (
EM_FORMAT (object),
g_value_get_boolean (value));
return;
case PROP_DEFAULT_CHARSET:
em_format_set_default_charset (
EM_FORMAT (object),
g_value_get_string (value));
return;
case PROP_SESSION:
em_format_set_session (
EM_FORMAT (object),
g_value_get_object (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
em_format_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_BASE_URL:
g_value_set_object (
value, em_format_get_base_url (
EM_FORMAT (object)));
return;
case PROP_CHARSET:
g_value_set_string (
value, em_format_get_charset (
EM_FORMAT (object)));
return;
case PROP_COMPOSER:
g_value_set_boolean (
value, em_format_get_composer (
EM_FORMAT (object)));
return;
case PROP_DEFAULT_CHARSET:
g_value_set_string (
value, em_format_get_default_charset (
EM_FORMAT (object)));
return;
case PROP_SESSION:
g_value_set_object (
value, em_format_get_session (
EM_FORMAT (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
em_format_finalize (GObject *object)
{
EMFormat *emf = EM_FORMAT (object);
if (emf->message_uid) {
g_free (emf->message_uid);
emf->message_uid = NULL;
}
if (emf->uri_base) {
g_free (emf->uri_base);
emf->uri_base = NULL;
}
if (emf->message) {
g_object_unref (emf->message);
emf->message = NULL;
}
if (emf->folder) {
g_object_unref (emf->folder);
emf->folder = NULL;
}
if (emf->mail_part_table) {
/* This will destroy all the EMFormatPURI objects stored
* inside!!!! */
g_hash_table_destroy (emf->mail_part_table);
emf->mail_part_table = NULL;
}
if (emf->mail_part_list) {
g_list_free (emf->mail_part_list);
emf->mail_part_list = NULL;
}
if (emf->priv->base_url) {
camel_url_free (emf->priv->base_url);
emf->priv->base_url = NULL;
}
if (emf->priv->session) {
g_object_unref (emf->priv->session);
emf->priv->session = NULL;
}
if (emf->priv->charset) {
g_free (emf->priv->charset);
emf->priv->charset = NULL;
}
em_format_clear_headers (emf);
/* Chain up to parent's finalize() method */
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
em_format_base_init (EMFormatClass *class)
{
gint i;
class->type_handlers = g_hash_table_new (g_str_hash, g_str_equal);
for (i = 0; i < G_N_ELEMENTS (type_handlers); i++) {
g_hash_table_insert (class->type_handlers,
type_handlers[i].mime_type,
&type_handlers[i]);
}
}
static void
em_format_class_init (EMFormatClass *class)
{
GObjectClass *object_class;
parent_class = g_type_class_peek_parent (class);
g_type_class_add_private (class, sizeof (EMFormatPrivate));
class->is_inline = emf_is_inline;
object_class = G_OBJECT_CLASS (class);
object_class->set_property = em_format_set_property;
object_class->get_property = em_format_get_property;
object_class->finalize = em_format_finalize;
g_object_class_install_property (
object_class,
PROP_BASE_URL,
g_param_spec_pointer (
"base-url",
NULL,
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_CHARSET,
g_param_spec_string (
"charset",
NULL,
NULL,
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_COMPOSER,
g_param_spec_boolean (
"composer",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_DEFAULT_CHARSET,
g_param_spec_string (
"default-charset",
NULL,
NULL,
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_SESSION,
g_param_spec_object (
"session",
"Session",
"A CamelSession",
CAMEL_TYPE_SESSION,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
signals[REDRAW_REQUESTED] = g_signal_new (
"redraw-requested",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EMFormatClass, redraw_requested),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,0);
}
static void
mail_part_table_item_free (gpointer data)
{
GList *iter = data;
EMFormatPURI *puri = iter->data;
em_format_puri_free (puri);
}
static void
em_format_init (EMFormat *emf)
{
emf->priv = EM_FORMAT_GET_PRIVATE (emf);
emf->message = NULL;
emf->folder = NULL;
emf->mail_part_list = NULL;
emf->mail_part_table = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL, (GDestroyNotify) mail_part_table_item_free);
/* No need to free the key, because it's owned and free'd by the PURI */
emf->priv->last_error = 0;
em_format_default_headers (emf);
}
EMFormat *
em_format_new (void)
{
return g_object_new (EM_TYPE_FORMAT, NULL);
}
GType
em_format_get_type (void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0)) {
static const GTypeInfo type_info = {
sizeof (EMFormatClass),
(GBaseInitFunc) em_format_base_init,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) em_format_class_init,
(GClassFinalizeFunc) NULL,
NULL, /* class_data */
sizeof (EMFormat),
0, /* n_preallocs */
(GInstanceInitFunc) em_format_init,
NULL /* value_table */
};
type = g_type_register_static (
G_TYPE_OBJECT, "EMFormat", &type_info, 0);
}
return type;
}
void
em_format_set_charset (EMFormat *emf,
const gchar *charset)
{
g_return_if_fail (EM_IS_FORMAT (emf));
if (emf->priv->charset)
g_free (emf->priv->charset);
emf->priv->charset = g_strdup (charset);
g_object_notify (G_OBJECT (emf), "charset");
}
const gchar *
em_format_get_charset (EMFormat *emf)
{
g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
return emf->priv->charset;
}
void
em_format_set_default_charset (EMFormat *emf,
const gchar *charset)
{
g_return_if_fail (EM_IS_FORMAT (emf));
if (emf->priv->default_charset)
g_free (emf->priv->default_charset);
emf->priv->default_charset = g_strdup (charset);
g_object_notify (G_OBJECT (emf), "default-charset");
}
const gchar *
em_format_get_default_charset (EMFormat *emf)
{
g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
return emf->priv->default_charset;
}
void
em_format_set_composer (EMFormat *emf,
gboolean composer)
{
g_return_if_fail (EM_IS_FORMAT (emf));
if (emf->priv->composer && composer)
return;
emf->priv->composer = composer;
g_object_notify (G_OBJECT (emf), "composer");
}
gboolean
em_format_get_composer (EMFormat *emf)
{
g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE);
return emf->priv->composer;
}
CamelSession *
em_format_get_session (EMFormat *emf)
{
g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
return emf->priv->session;
}
void
em_format_set_base_url (EMFormat *emf,
CamelURL *url)
{
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (url);
if (emf->priv->base_url)
camel_url_free (emf->priv->base_url);
emf->priv->base_url = camel_url_copy (url);
g_object_notify (G_OBJECT (emf), "base-url");
}
void
em_format_set_base_url_string (EMFormat *emf,
const gchar *url_string)
{
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (url_string && *url_string);
if (emf->priv->base_url)
camel_url_free (emf->priv->base_url);
emf->priv->base_url = camel_url_new (url_string, NULL);
g_object_notify (G_OBJECT (emf), "base-url");
}
CamelURL *
em_format_get_base_url (EMFormat *emf)
{
g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
return emf->priv->base_url;
}
/**
* 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;
g_return_if_fail (EM_IS_FORMAT (emf));
while ((eh = g_queue_pop_head (&emf->header_list)) != NULL) {
em_format_header_free (eh);
}
}
void
em_format_default_headers (EMFormat *emf)
{
gint ii;
g_return_if_fail (EM_IS_FORMAT (emf));
/* Set the default headers */
em_format_clear_headers (emf);
for (ii = 0; ii < G_N_ELEMENTS (default_headers); ii++)
em_format_add_header (
emf, default_headers[ii].name, NULL,
default_headers[ii].flags);
}
/**
* em_format_add_header:
* @emf:
* @name: The name of the header, as it will appear during output.
* @value: Value of the header. Can be NULL.
* @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,
const gchar *value,
guint32 flags)
{
EMFormatHeader *h;
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (name && *name);
h = em_format_header_new (name, value);
h->flags = flags;
g_queue_push_tail (&emf->header_list, h);
}
void
em_format_add_header_struct (EMFormat *emf,
EMFormatHeader *header)
{
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (header && header->name);
em_format_add_header (emf, header->name, header->value, header->flags);
}
void
em_format_remove_header (EMFormat *emf,
const gchar *name,
const gchar *value)
{
GList *iter = NULL;
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (name && *name);
iter = g_queue_peek_head_link (&emf->header_list);
while (iter) {
EMFormatHeader *header = iter->data;
if (!header->value || !*header->value) {
GList *next = iter->next;
if (g_strcmp0 (name, header->name) == 0)
g_queue_delete_link (&emf->header_list, iter);
iter = next;
continue;
}
if (value && *value) {
if ((g_strcmp0 (name, header->name) == 0) &&
(g_strcmp0 (value, header->value) == 0))
break;
} else {
if (g_strcmp0 (name, header->name) == 0)
break;
}
iter = iter->next;
}
if (iter) {
em_format_header_free (iter->data);
g_queue_delete_link (&emf->header_list, iter);
}
}
void
em_format_remove_header_struct (EMFormat *emf,
const EMFormatHeader *header)
{
g_return_if_fail (header);
em_format_remove_header (emf, header->name, header->value);
}
void
em_format_add_puri (EMFormat *emf,
EMFormatPURI *puri)
{
GList *item;
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (puri != NULL);
emf->mail_part_list = g_list_append (emf->mail_part_list, puri);
item = g_list_last (emf->mail_part_list);
g_hash_table_insert (emf->mail_part_table,
puri->uri, item);
d(printf("Added PURI %s\n", puri->uri));
}
EMFormatPURI *
em_format_find_puri (EMFormat *emf,
const gchar *id)
{
GList *list_iter;
/* First handle CIDs... */
if (g_str_has_prefix (id, "CID:") || g_str_has_prefix (id, "cid:")) {
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, emf->mail_part_table);
while (g_hash_table_iter_next (&iter, &key, &value)) {
EMFormatPURI *puri = ((GList *) value)->data;
if (g_strcmp0 (puri->cid, id) == 0)
return puri;
}
return NULL;
}
list_iter = g_hash_table_lookup (emf->mail_part_table, id);
if (list_iter)
return list_iter->data;
return NULL;
}
void
em_format_class_add_handler (EMFormatClass *emfc,
EMFormatHandler *handler)
{
EMFormatHandler *old_handler;
g_return_if_fail (EM_IS_FORMAT_CLASS (emfc));
g_return_if_fail (handler);
old_handler = g_hash_table_lookup (
emfc->type_handlers, handler->mime_type);
handler->old = old_handler;
/* If parse_func or write_func of the new handler is not set,
* use function from the old handler (if it exists).
* This way we can assign a new write_func for to an existing
* parse_func */
if (old_handler && handler->parse_func == NULL) {
handler->parse_func = old_handler->parse_func;
}
if (old_handler && handler->write_func == NULL) {
handler->write_func = old_handler->write_func;
}
g_hash_table_insert (emfc->type_handlers,
handler->mime_type, handler);
}
void
em_format_class_remove_handler (EMFormatClass *emfc,
EMFormatHandler *handler)
{
g_return_if_fail (EM_IS_FORMAT_CLASS (emfc));
g_return_if_fail (handler);
g_hash_table_remove (emfc->type_handlers, handler->mime_type);
}
const EMFormatHandler *
em_format_find_handler (EMFormat *emf,
const gchar *mime_type)
{
EMFormatClass *emfc;
gchar *s;
const EMFormatHandler *handler;
g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
g_return_val_if_fail (mime_type && *mime_type, NULL);
emfc = (EMFormatClass *) G_OBJECT_GET_CLASS (emf);
s = g_ascii_strdown (mime_type, -1);
handler = g_hash_table_lookup (
emfc->type_handlers, s);
g_free (s);
return handler;
}
/**
* 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);
}
void
em_format_parse (EMFormat *emf,
CamelMimeMessage *message,
CamelFolder *folder,
GCancellable *cancellable)
{
GString *part_id;
EMFormatPURI *puri;
EMFormatParserInfo info = { 0 };
g_return_if_fail (EM_IS_FORMAT (emf));
if (g_cancellable_is_cancelled (cancellable))
return;
if (message) {
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
if (emf->message)
g_object_unref (emf->message);
emf->message = g_object_ref (message);
}
if (folder) {
g_return_if_fail (CAMEL_IS_FOLDER (folder));
if (emf->folder)
g_object_unref (emf->folder);
emf->folder = g_object_ref (folder);
}
/* Before the actual parsing starts,
* let child classes prepare themselves. */
if (EM_FORMAT_GET_CLASS (emf)->preparse)
EM_FORMAT_GET_CLASS (emf)->preparse (emf);
part_id = g_string_new (".message");
/* Create a special PURI with entire message */
puri = em_format_puri_new (emf, sizeof (EMFormatPURI),
(CamelMimePart *) emf->message, part_id->str);
puri->mime_type = g_strdup ("text/html");
em_format_add_puri (emf, puri);
info.force_handler = TRUE;
em_format_parse_part_as (emf, CAMEL_MIME_PART (emf->message), part_id, &info,
"x-evolution/message", cancellable);
g_string_free (part_id, TRUE);
}
void
em_format_write (EMFormat *emf,
CamelStream *stream,
EMFormatWriterInfo *info,
GCancellable *cancellable)
{
EMFormatClass *emf_class;
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (CAMEL_IS_STREAM (stream));
emf_class = EM_FORMAT_GET_CLASS (emf);
if (emf_class->write)
emf_class->write (emf, stream, info, cancellable);
}
static void
emf_start_async_parser (GSimpleAsyncResult *result,
GObject *object,
GCancellable *cancellable)
{
em_format_parse (EM_FORMAT (object), NULL, NULL, cancellable);
}
void
em_format_parse_async (EMFormat *emf,
CamelMimeMessage *message,
CamelFolder *folder,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *simple;
g_return_if_fail (EM_IS_FORMAT (emf));
if (g_cancellable_is_cancelled (cancellable))
return;
if (message) {
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
if (emf->message)
g_object_unref (emf->message);
emf->message = g_object_ref (message);
}
if (folder) {
g_return_if_fail (CAMEL_IS_FOLDER (folder));
if (emf->folder)
g_object_unref (emf->folder);
emf->folder = g_object_ref (folder);
}
simple = g_simple_async_result_new (
G_OBJECT (emf), callback,
user_data, em_format_parse_async);
g_simple_async_result_set_check_cancellable (simple, cancellable);
g_simple_async_result_run_in_thread (
simple, emf_start_async_parser,
G_PRIORITY_DEFAULT, cancellable);
g_object_unref (simple);
}
void
em_format_parse_part_as (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
const gchar *mime_type,
GCancellable *cancellable)
{
const EMFormatHandler *handler;
const CamelContentDisposition *disposition;
EMFormatParserInfo ninfo = {
.handler = 0,
.validity_type = info ? info->validity_type : 0,
.validity = info ? info->validity : 0,
.force_handler = 0
};
/* Let everything that claims to be an attachment or inlined
* part to be parsed as an attachment. The parser will decide
* how to display it. */
disposition = camel_mime_part_get_content_disposition (part);
if (!info->force_handler && disposition &&
(g_strcmp0 (disposition->disposition, "attachment") == 0)) {
ninfo.is_attachment = TRUE;
handler = em_format_find_handler (emf, "x-evolution/message/attachment");
ninfo.handler = handler;
if (handler && handler->parse_func)
handler->parse_func (emf, part, part_id, &ninfo, cancellable);
return;
}
handler = em_format_find_handler (emf, mime_type);
if (handler && handler->parse_func) {
ninfo.handler = handler;
handler->parse_func (emf, part, part_id, &ninfo, cancellable);
} else {
handler = em_format_find_handler (emf, "x-evolution/message/attachment");
ninfo.handler = handler;
/* When this fails, something is probably very wrong...*/
if (handler && handler->parse_func)
handler->parse_func (emf, part, part_id, &ninfo, cancellable);
}
}
void
em_format_parse_part (EMFormat *emf,
CamelMimePart *part,
GString *part_id,
EMFormatParserInfo *info,
GCancellable *cancellable)
{
CamelContentType *ct;
gchar *mime_type;
ct = camel_mime_part_get_content_type (part);
if (ct) {
mime_type = camel_content_type_simple (ct);
} else {
mime_type = (gchar *) "text/plain";
}
em_format_parse_part_as (emf, part, part_id, info, mime_type, cancellable);
if (ct)
g_free (mime_type);
}
gboolean
em_format_is_inline (EMFormat *emf,
const gchar *part_id,
CamelMimePart *part,
const EMFormatHandler *handler)
{
EMFormatClass *class;
g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE);
g_return_val_if_fail (part_id && *part_id, FALSE);
g_return_val_if_fail (CAMEL_IS_MIME_PART (part), FALSE);
g_return_val_if_fail (handler, FALSE);
class = EM_FORMAT_GET_CLASS (emf);
g_return_val_if_fail (class->is_inline != NULL, FALSE);
return class->is_inline (emf, part_id, part, handler);
}
void
em_format_format_error (EMFormat *emf,
const gchar *format,
...)
{
EMFormatPURI *puri;
CamelMimePart *part;
const EMFormatHandler *handler;
gchar *errmsg;
gchar *uri;
va_list ap;
g_return_if_fail (EM_IS_FORMAT (emf));
g_return_if_fail (format != NULL);
va_start (ap, format);
errmsg = g_strdup_vprintf (format, ap);
part = camel_mime_part_new ();
camel_mime_part_set_content (part, errmsg, strlen (errmsg), "text/plain");
g_free (errmsg);
va_end (ap);
handler = em_format_find_handler (emf, "x-evolution/error");
emf->priv->last_error++;
uri = g_strdup_printf (".error.%d", emf->priv->last_error);
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, uri);
puri->mime_type = g_strdup ("text/html");
if (handler && handler->write_func)
puri->write_func = handler->write_func;
else
puri->write_func = emf_write_error;
em_format_add_puri (emf, puri);
g_free (uri);
g_object_unref (part);
}
/**
* em_format_format_text:
* @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;
if (g_cancellable_is_cancelled (cancellable))
return;
if (emf->priv->charset) {
charset = emf->priv->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->priv->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);
}
camel_data_wrapper_decode_to_stream_sync (
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);
camel_stream_write_to_stream (
mem_stream, (CamelStream *) stream, cancellable, NULL);
camel_stream_flush ((CamelStream *) mem_stream, cancellable, NULL);
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 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 != NULL ? 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;
content = camel_medium_get_content (CAMEL_MEDIUM (part));
if (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);
}
/**
* 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;
d(printf("checking is attachment %s/%s\n", dw->mime_type->type, dw->mime_type->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_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);
}
/* 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 don't anymore,
* see bug #211778 for some discussion */
}
/**
* Construct a URI for message.
*
* The URI can contain multiple query parameters. The list of parameters must be
* NULL-terminated. Each query must contain name, GType of value and value.
*
* @param folder Folder wit the message
* @param message_uid ID of message within the \p folder
* @param first_param_name Name of first query parameter followed by GType of it's value and value.
*/
gchar *
em_format_build_mail_uri (CamelFolder *folder,
const gchar *message_uid,
const gchar *first_param_name,
...)
{
CamelStore *store;
gchar *uri, *tmp;
va_list ap;
const gchar *name;
const gchar *service_uid, *folder_name;
gchar separator;
g_return_val_if_fail (message_uid && *message_uid, NULL);
if (!folder) {
folder_name = "generic";
service_uid = "generic";
} else {
tmp = (gchar *) camel_folder_get_full_name (folder);
folder_name = (const gchar *) soup_uri_encode (tmp, NULL);
store = camel_folder_get_parent_store (folder);
if (store)
service_uid = camel_service_get_uid (CAMEL_SERVICE (store));
else
service_uid = "generic";
}
tmp = g_strdup_printf ("mail://%s/%s/%s",
service_uid,
folder_name,
message_uid);
if (folder) {
g_free ((gchar *) folder_name);
}
va_start (ap, first_param_name);
name = first_param_name;
separator = '?';
while (name) {
gchar *tmp2;
gint type = va_arg (ap, gint);
switch (type) {
case G_TYPE_INT:
case G_TYPE_BOOLEAN: {
gint val = va_arg (ap, gint);
tmp2 = g_strdup_printf ("%s%c%s=%d", tmp,
separator, name, val);
break;
}
case G_TYPE_FLOAT:
case G_TYPE_DOUBLE: {
gdouble val = va_arg (ap, double);
tmp2 = g_strdup_printf ("%s%c%s=%f", tmp,
separator, name, val);
break;
}
case G_TYPE_STRING: {
gchar *val = va_arg (ap, gchar *);
gchar *escaped = soup_uri_encode (val, NULL);
tmp2 = g_strdup_printf ("%s%c%s=%s", tmp,
separator, name, escaped);
g_free (escaped);
break;
}
default:
g_warning ("Invalid param type %s", g_type_name (type));
return NULL;
}
g_free (tmp);
tmp = tmp2;
if (separator == '?')
separator = '&';
name = va_arg (ap, gchar *);
}
va_end (ap);
uri = tmp;
if (uri == NULL)
return NULL;
/* For some reason, webkit won't accept URL with username, but
* without password (mail://store@host/folder/mail), so we
* will replace the '@' symbol by '/' to get URL like
* mail://store/host/folder/mail which is OK
*/
while ((tmp = strchr (uri, '@')) != NULL) {
tmp[0] = '/';
}
return uri;
}
void
em_format_redraw (EMFormat *emf)
{
g_return_if_fail (EM_IS_FORMAT (emf));
g_signal_emit (emf, signals[REDRAW_REQUESTED], 0);
}
/**************************************************************************/
EMFormatPURI *
em_format_puri_new (EMFormat *emf,
gsize puri_size,
CamelMimePart *part,
const gchar *uri)
{
EMFormatPURI *puri;
g_return_val_if_fail (EM_IS_FORMAT (emf), NULL);
g_return_val_if_fail (puri_size >= sizeof (EMFormatPURI), NULL);
puri = (EMFormatPURI *) g_malloc0 (puri_size);
puri->emf = emf;
if (part)
puri->part = g_object_ref (part);
if (uri)
puri->uri = g_strdup (uri);
return puri;
}
void
em_format_puri_free (EMFormatPURI *puri)
{
g_return_if_fail (puri);
if (puri->part)
g_object_unref (puri->part);
if (puri->uri)
g_free (puri->uri);
if (puri->cid)
g_free (puri->cid);
if (puri->mime_type)
g_free (puri->mime_type);
if (puri->validity)
camel_cipher_validity_free (puri->validity);
if (puri->validity_parent)
camel_cipher_validity_free (puri->validity_parent);
if (puri->free)
puri->free (puri);
g_free (puri);
}
void
em_format_puri_write (EMFormatPURI *puri,
CamelStream *stream,
EMFormatWriterInfo *info,
GCancellable *cancellable)
{
g_return_if_fail (puri);
g_return_if_fail (CAMEL_IS_STREAM (stream));
if (info->mode == EM_FORMAT_WRITE_MODE_SOURCE) {
const EMFormatHandler *handler;
handler = em_format_find_handler (puri->emf, "x-evolution/message/source");
handler->write_func (puri->emf, puri, stream, info, cancellable);
return;
}
if (puri->write_func) {
puri->write_func (puri->emf, puri, stream, info, cancellable);
} else {
const EMFormatHandler *handler;
const gchar *mime_type;
if (puri->mime_type) {
mime_type = puri->mime_type;
} else {
mime_type = (gchar *) "plain/text";
}
handler = em_format_find_handler (puri->emf, mime_type);
if (handler && handler->write_func) {
handler->write_func (puri->emf,
puri, stream, info, cancellable);
}
}
}
EMFormatHeader *
em_format_header_new (const gchar *name,
const gchar *value)
{
EMFormatHeader *header;
g_return_val_if_fail (name && *name, NULL);
header = g_new0 (EMFormatHeader, 1);
header->name = g_strdup (name);
if (value && *value)
header->value = g_strdup (value);
return header;
}
void
em_format_header_free (EMFormatHeader *header)
{
g_return_if_fail (header != NULL);
if (header->name) {
g_free (header->name);
header->name = NULL;
}
if (header->value) {
g_free (header->value);
header->value = NULL;
}
g_free (header);
}