/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
/* camel-mime-message.c : class for a mime_message
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library 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.
*
* This library 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 this library. If not, see .
*
* Authors: Bertrand Guiheneuf
* Michael Zucchi
* Jeffrey Stedfast
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include "camel-iconv.h"
#include "camel-mime-filter-bestenc.h"
#include "camel-mime-filter-charset.h"
#include "camel-mime-message.h"
#include "camel-multipart.h"
#include "camel-stream-filter.h"
#include "camel-stream-mem.h"
#include "camel-stream-null.h"
#include "camel-string-utils.h"
#include "camel-url.h"
#ifdef G_OS_WIN32
#ifdef gmtime_r
#undef gmtime_r
#endif
/* The gmtime() in Microsoft's C library is MT-safe */
#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
#endif
#define d(x)
extern gint camel_verbose_debug;
/* these 2 below should be kept in sync */
typedef enum {
HEADER_UNKNOWN,
HEADER_FROM,
HEADER_REPLY_TO,
HEADER_SUBJECT,
HEADER_TO,
HEADER_RESENT_TO,
HEADER_CC,
HEADER_RESENT_CC,
HEADER_BCC,
HEADER_RESENT_BCC,
HEADER_DATE,
HEADER_MESSAGE_ID
} CamelHeaderType;
static const gchar *header_names[] = {
/* dont include HEADER_UNKNOWN string */
"From", "Reply-To", "Subject", "To", "Resent-To", "Cc", "Resent-Cc",
"Bcc", "Resent-Bcc", "Date", "Message-ID", NULL
};
static const gchar *recipient_names[] = {
"To", "Cc", "Bcc", "Resent-To", "Resent-Cc", "Resent-Bcc", NULL
};
static GHashTable *header_name_table;
G_DEFINE_TYPE (CamelMimeMessage, camel_mime_message, CAMEL_TYPE_MIME_PART)
/* FIXME: check format of fields. */
static gboolean
process_header (CamelMedium *medium,
const gchar *name,
const gchar *value)
{
CamelHeaderType header_type;
CamelMimeMessage *message = CAMEL_MIME_MESSAGE (medium);
CamelInternetAddress *addr;
const gchar *charset;
gchar *unfolded;
header_type = (CamelHeaderType) g_hash_table_lookup (header_name_table, name);
switch (header_type) {
case HEADER_FROM:
addr = camel_internet_address_new ();
unfolded = camel_header_unfold (value);
if (camel_address_decode ((CamelAddress *) addr, unfolded) <= 0) {
g_object_unref (addr);
} else {
if (message->from)
g_object_unref (message->from);
message->from = addr;
}
g_free (unfolded);
break;
case HEADER_REPLY_TO:
addr = camel_internet_address_new ();
unfolded = camel_header_unfold (value);
if (camel_address_decode ((CamelAddress *) addr, unfolded) <= 0) {
g_object_unref (addr);
} else {
if (message->reply_to)
g_object_unref (message->reply_to);
message->reply_to = addr;
}
g_free (unfolded);
break;
case HEADER_SUBJECT:
g_free (message->subject);
if (((CamelDataWrapper *) message)->mime_type) {
charset = camel_content_type_param (((CamelDataWrapper *) message)->mime_type, "charset");
charset = camel_iconv_charset_name (charset);
} else
charset = NULL;
unfolded = camel_header_unfold (value);
message->subject = g_strstrip (camel_header_decode_string (unfolded, charset));
g_free (unfolded);
break;
case HEADER_TO:
case HEADER_CC:
case HEADER_BCC:
case HEADER_RESENT_TO:
case HEADER_RESENT_CC:
case HEADER_RESENT_BCC:
addr = g_hash_table_lookup (message->recipients, name);
if (value) {
unfolded = camel_header_unfold (value);
camel_address_decode (CAMEL_ADDRESS (addr), unfolded);
g_free (unfolded);
} else {
camel_address_remove (CAMEL_ADDRESS (addr), -1);
}
return FALSE;
case HEADER_DATE:
if (value) {
message->date = camel_header_decode_date (value, &message->date_offset);
} else {
message->date = CAMEL_MESSAGE_DATE_CURRENT;
message->date_offset = 0;
}
break;
case HEADER_MESSAGE_ID:
g_free (message->message_id);
if (value)
message->message_id = camel_header_msgid_decode (value);
else
message->message_id = NULL;
break;
default:
return FALSE;
}
return TRUE;
}
static void
unref_recipient (gpointer key,
gpointer value,
gpointer user_data)
{
g_object_unref (value);
}
static void
mime_message_ensure_required_headers (CamelMimeMessage *message)
{
CamelMedium *medium = CAMEL_MEDIUM (message);
if (message->from == NULL) {
camel_medium_set_header (medium, "From", "");
}
if (!camel_medium_get_header (medium, "Date"))
camel_mime_message_set_date (
message, CAMEL_MESSAGE_DATE_CURRENT, 0);
if (message->subject == NULL)
camel_mime_message_set_subject (message, "No Subject");
if (message->message_id == NULL)
camel_mime_message_set_message_id (message, NULL);
/* FIXME: "To" header needs to be set explicitly as well ... */
if (!camel_medium_get_header (medium, "Mime-Version"))
camel_medium_set_header (medium, "Mime-Version", "1.0");
}
static void
mime_message_dispose (GObject *object)
{
CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
if (message->reply_to != NULL) {
g_object_unref (message->reply_to);
message->reply_to = NULL;
}
if (message->from != NULL) {
g_object_unref (message->from);
message->from = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (camel_mime_message_parent_class)->dispose (object);
}
static void
mime_message_finalize (GObject *object)
{
CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
g_free (message->subject);
g_free (message->message_id);
g_hash_table_foreach (message->recipients, unref_recipient, NULL);
g_hash_table_destroy (message->recipients);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_mime_message_parent_class)->finalize (object);
}
static gssize
mime_message_write_to_stream_sync (CamelDataWrapper *data_wrapper,
CamelStream *stream,
GCancellable *cancellable,
GError **error)
{
CamelMimeMessage *message;
message = CAMEL_MIME_MESSAGE (data_wrapper);
mime_message_ensure_required_headers (message);
/* Chain up to parent's write_to_stream_sync() method. */
return CAMEL_DATA_WRAPPER_CLASS (camel_mime_message_parent_class)->
write_to_stream_sync (
data_wrapper, stream, cancellable, error);
}
static gssize
mime_message_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
GOutputStream *output_stream,
GCancellable *cancellable,
GError **error)
{
CamelMimeMessage *message;
message = CAMEL_MIME_MESSAGE (data_wrapper);
mime_message_ensure_required_headers (message);
/* Chain up to parent's write_to_output_stream_sync() method. */
return CAMEL_DATA_WRAPPER_CLASS (camel_mime_message_parent_class)->
write_to_output_stream_sync (
data_wrapper, output_stream, cancellable, error);
}
static void
mime_message_add_header (CamelMedium *medium,
const gchar *name,
gconstpointer value)
{
CamelMediumClass *medium_class;
medium_class = CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class);
/* if we process it, then it must be forced unique as well ... */
if (process_header (medium, name, value))
medium_class->set_header (medium, name, value);
else
medium_class->add_header (medium, name, value);
}
static void
mime_message_set_header (CamelMedium *medium,
const gchar *name,
gconstpointer value)
{
process_header (medium, name, value);
/* Chain up to parent's set_header() method. */
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (medium, name, value);
}
static void
mime_message_remove_header (CamelMedium *medium,
const gchar *name)
{
process_header (medium, name, NULL);
/* Chain up to parent's remove_header() method. */
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (medium, name);
}
static gboolean
mime_message_construct_from_parser_sync (CamelMimePart *dw,
CamelMimeParser *mp,
GCancellable *cancellable,
GError **error)
{
CamelMimePartClass *mime_part_class;
gchar *buf;
gsize len;
gint state;
gint err;
gboolean success;
/* let the mime-part construct the guts ... */
mime_part_class = CAMEL_MIME_PART_CLASS (camel_mime_message_parent_class);
success = mime_part_class->construct_from_parser_sync (
dw, mp, cancellable, error);
if (!success)
return FALSE;
/* ... then clean up the follow-on state */
state = camel_mime_parser_step (mp, &buf, &len);
switch (state) {
case CAMEL_MIME_PARSER_STATE_EOF:
case CAMEL_MIME_PARSER_STATE_FROM_END:
/* these doesn't belong to us */
camel_mime_parser_unstep (mp);
case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
break;
default:
g_error ("Bad parser state: Expecing MESSAGE_END or EOF or EOM, got: %u", camel_mime_parser_state (mp));
camel_mime_parser_unstep (mp);
return FALSE;
}
err = camel_mime_parser_errno (mp);
if (err != 0) {
errno = err;
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
"%s", g_strerror (errno));
success = FALSE;
}
return success;
}
static void
camel_mime_message_class_init (CamelMimeMessageClass *class)
{
GObjectClass *object_class;
CamelDataWrapperClass *data_wrapper_class;
CamelMimePartClass *mime_part_class;
CamelMediumClass *medium_class;
gint ii;
object_class = G_OBJECT_CLASS (class);
object_class->dispose = mime_message_dispose;
object_class->finalize = mime_message_finalize;
data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
data_wrapper_class->write_to_stream_sync = mime_message_write_to_stream_sync;
data_wrapper_class->decode_to_stream_sync = mime_message_write_to_stream_sync;
data_wrapper_class->write_to_output_stream_sync = mime_message_write_to_output_stream_sync;
data_wrapper_class->decode_to_output_stream_sync = mime_message_write_to_output_stream_sync;
medium_class = CAMEL_MEDIUM_CLASS (class);
medium_class->add_header = mime_message_add_header;
medium_class->set_header = mime_message_set_header;
medium_class->remove_header = mime_message_remove_header;
mime_part_class = CAMEL_MIME_PART_CLASS (class);
mime_part_class->construct_from_parser_sync = mime_message_construct_from_parser_sync;
header_name_table = g_hash_table_new (
camel_strcase_hash, camel_strcase_equal);
for (ii = 0; header_names[ii] != NULL; ii++)
g_hash_table_insert (
header_name_table,
(gpointer) header_names[ii],
GINT_TO_POINTER (ii + 1));
}
static void
camel_mime_message_init (CamelMimeMessage *mime_message)
{
gint ii;
mime_message->recipients = g_hash_table_new (
camel_strcase_hash, camel_strcase_equal);
for (ii = 0; recipient_names[ii] != NULL; ii++) {
g_hash_table_insert (
mime_message->recipients,
(gpointer) recipient_names[ii],
camel_internet_address_new ());
}
mime_message->subject = NULL;
mime_message->reply_to = NULL;
mime_message->from = NULL;
mime_message->date = CAMEL_MESSAGE_DATE_CURRENT;
mime_message->date_offset = 0;
mime_message->date_received = CAMEL_MESSAGE_DATE_CURRENT;
mime_message->date_received_offset = 0;
mime_message->message_id = NULL;
}
/**
* camel_mime_message_new:
*
* Create a new #CamelMimeMessage object.
*
* Returns: a new #CamelMimeMessage object
**/
CamelMimeMessage *
camel_mime_message_new (void)
{
return g_object_new (CAMEL_TYPE_MIME_MESSAGE, NULL);
}
/* **** Date: */
/**
* camel_mime_message_set_date:
* @message: a #CamelMimeMessage object
* @date: a time_t date
* @offset: an offset from GMT
*
* Set the date on a message.
**/
void
camel_mime_message_set_date (CamelMimeMessage *message,
time_t date,
gint offset)
{
gchar *datestr;
g_return_if_fail (message);
if (date == CAMEL_MESSAGE_DATE_CURRENT) {
struct tm local;
gint tz;
date = time (NULL);
camel_localtime_with_offset (date, &local, &tz);
offset = (((tz / 60 / 60) * 100) + (tz / 60 % 60));
}
message->date = date;
message->date_offset = offset;
datestr = camel_header_format_date (date, offset);
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header ((CamelMedium *) message, "Date", datestr);
g_free (datestr);
}
/**
* camel_mime_message_get_date:
* @message: a #CamelMimeMessage object
* @offset: output for the GMT offset
*
* Get the date and GMT offset of a message.
*
* Returns: the date of the message
**/
time_t
camel_mime_message_get_date (CamelMimeMessage *msg,
gint *offset)
{
if (offset)
*offset = msg->date_offset;
return msg->date;
}
/**
* camel_mime_message_get_date_received:
* @message: a #CamelMimeMessage object
* @offset: output for the GMT offset
*
* Get the received date and GMT offset of a message.
*
* Returns: the received date of the message
**/
time_t
camel_mime_message_get_date_received (CamelMimeMessage *msg,
gint *offset)
{
if (msg->date_received == CAMEL_MESSAGE_DATE_CURRENT) {
const gchar *received;
received = camel_medium_get_header ((CamelMedium *) msg, "received");
if (received)
received = strrchr (received, ';');
if (received)
msg->date_received = camel_header_decode_date (received + 1, &msg->date_received_offset);
}
if (offset)
*offset = msg->date_received_offset;
return msg->date_received;
}
/* **** Message-ID: */
/**
* camel_mime_message_set_message_id:
* @message: a #CamelMimeMessage object
* @message_id: id of the message
*
* Set the message-id on a message.
**/
void
camel_mime_message_set_message_id (CamelMimeMessage *mime_message,
const gchar *message_id)
{
gchar *id;
g_return_if_fail (mime_message);
g_free (mime_message->message_id);
if (message_id) {
id = g_strstrip (g_strdup (message_id));
} else {
CamelInternetAddress *from;
const gchar *domain = NULL;
from = camel_mime_message_get_from (mime_message);
if (from && camel_internet_address_get (from, 0, NULL, &domain) && domain) {
const gchar *at = strchr (domain, '@');
if (at)
domain = at + 1;
else
domain = NULL;
}
id = camel_header_msgid_generate (domain);
}
mime_message->message_id = id;
id = g_strdup_printf ("<%s>", mime_message->message_id);
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (mime_message), "Message-ID", id);
g_free (id);
}
/**
* camel_mime_message_get_message_id:
* @message: a #CamelMimeMessage object
*
* Get the message-id of a message.
*
* Returns: the message-id of a message
**/
const gchar *
camel_mime_message_get_message_id (CamelMimeMessage *mime_message)
{
g_return_val_if_fail (mime_message, NULL);
return mime_message->message_id;
}
/* **** Reply-To: */
/**
* camel_mime_message_set_reply_to:
* @message: a #CamelMimeMessage object
* @reply_to: a #CamelInternetAddress object
*
* Set the Reply-To of a message.
**/
void
camel_mime_message_set_reply_to (CamelMimeMessage *msg,
CamelInternetAddress *reply_to)
{
gchar *addr;
g_return_if_fail (msg);
if (msg->reply_to) {
g_object_unref (msg->reply_to);
msg->reply_to = NULL;
}
if (reply_to == NULL) {
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (msg), "Reply-To");
return;
}
msg->reply_to = (CamelInternetAddress *) camel_address_new_clone ((CamelAddress *) reply_to);
addr = camel_address_encode ((CamelAddress *) msg->reply_to);
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (msg), "Reply-To", addr);
g_free (addr);
}
/**
* camel_mime_message_get_reply_to:
* @message: a #CamelMimeMessage object
*
* Get the Reply-To of a message.
*
* Returns: the Reply-Toa ddress of the message
**/
CamelInternetAddress *
camel_mime_message_get_reply_to (CamelMimeMessage *mime_message)
{
g_return_val_if_fail (mime_message, NULL);
/* TODO: ref for threading? */
return mime_message->reply_to;
}
/* **** Subject: */
/**
* camel_mime_message_set_subject:
* @message: a #CamelMimeMessage object
* @subject: UTF-8 message subject
*
* Set the subject text of a message.
**/
void
camel_mime_message_set_subject (CamelMimeMessage *message,
const gchar *subject)
{
gchar *text;
g_return_if_fail (message);
g_free (message->subject);
if (subject) {
message->subject = g_strstrip (g_strdup (subject));
text = camel_header_encode_string ((guchar *) message->subject);
} else {
message->subject = NULL;
text = NULL;
}
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (message), "Subject", text);
g_free (text);
}
/**
* camel_mime_message_get_subject:
* @message: a #CamelMimeMessage object
*
* Get the UTF-8 subject text of a message.
*
* Returns: the message subject
**/
const gchar *
camel_mime_message_get_subject (CamelMimeMessage *mime_message)
{
g_return_val_if_fail (mime_message, NULL);
return mime_message->subject;
}
/* *** From: */
/* Thought: Since get_from/set_from are so rarely called, it is probably not useful
* to cache the from (and reply_to) addresses as InternetAddresses internally, we
* could just get it from the headers and reprocess every time. */
/**
* camel_mime_message_set_from:
* @message: a #CamelMimeMessage object
* @from: a #CamelInternetAddress object
*
* Set the from address of a message.
**/
void
camel_mime_message_set_from (CamelMimeMessage *msg,
CamelInternetAddress *from)
{
gchar *addr;
g_return_if_fail (msg);
if (msg->from) {
g_object_unref (msg->from);
msg->from = NULL;
}
if (from == NULL || camel_address_length ((CamelAddress *) from) == 0) {
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (msg), "From");
return;
}
msg->from = (CamelInternetAddress *) camel_address_new_clone ((CamelAddress *) from);
addr = camel_address_encode ((CamelAddress *) msg->from);
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (msg), "From", addr);
g_free (addr);
}
/**
* camel_mime_message_get_from:
* @message: a #CamelMimeMessage object
*
* Get the from address of a message.
*
* Returns: the from address of the message
**/
CamelInternetAddress *
camel_mime_message_get_from (CamelMimeMessage *mime_message)
{
g_return_val_if_fail (mime_message, NULL);
/* TODO: we should really ref this for multi-threading to work */
return mime_message->from;
}
/* **** To: Cc: Bcc: */
/**
* camel_mime_message_set_recipients:
* @message: a #CamelMimeMessage object
* @type: recipient type (one of #CAMEL_RECIPIENT_TYPE_TO, #CAMEL_RECIPIENT_TYPE_CC, or #CAMEL_RECIPIENT_TYPE_BCC)
* @recipients: a #CamelInternetAddress with the recipient addresses set
*
* Set the recipients of a message.
**/
void
camel_mime_message_set_recipients (CamelMimeMessage *mime_message,
const gchar *type,
CamelInternetAddress *r)
{
gchar *text;
CamelInternetAddress *addr;
g_return_if_fail (mime_message);
addr = g_hash_table_lookup (mime_message->recipients, type);
if (addr == NULL) {
g_warning ("trying to set a non-valid receipient type: %s", type);
return;
}
if (r == NULL || camel_address_length ((CamelAddress *) r) == 0) {
camel_address_remove ((CamelAddress *) addr, -1);
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (mime_message), type);
return;
}
/* note this does copy, and not append (cat) */
camel_address_copy ((CamelAddress *) addr, (CamelAddress *) r);
/* and sync our headers */
text = camel_address_encode (CAMEL_ADDRESS (addr));
CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (mime_message), type, text);
g_free (text);
}
/**
* camel_mime_message_get_recipients:
* @message: a #CamelMimeMessage object
* @type: recipient type
*
* Get the message recipients of a specified type.
*
* Returns: the requested recipients
**/
CamelInternetAddress *
camel_mime_message_get_recipients (CamelMimeMessage *mime_message,
const gchar *type)
{
g_return_val_if_fail (mime_message, NULL);
return g_hash_table_lookup (mime_message->recipients, type);
}
void
camel_mime_message_set_source (CamelMimeMessage *mime_message,
const gchar *source_uid)
{
CamelMedium *medium;
const gchar *name;
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (mime_message));
/* FIXME The header name is Evolution-specific.
* "X" header prefix should be configurable
* somehow, perhaps through CamelSession. */
name = "X-Evolution-Source";
medium = CAMEL_MEDIUM (mime_message);
camel_medium_remove_header (medium, name);
if (source_uid != NULL)
camel_medium_add_header (medium, name, source_uid);
}
const gchar *
camel_mime_message_get_source (CamelMimeMessage *mime_message)
{
CamelMedium *medium;
const gchar *name;
const gchar *src;
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (mime_message), NULL);
/* FIXME The header name is Evolution-specific.
* "X" header prefix should be configurable
* somehow, perhaps through CamelSession. */
name = "X-Evolution-Source";
medium = CAMEL_MEDIUM (mime_message);
src = camel_medium_get_header (medium, name);
if (src != NULL) {
while (*src && isspace ((unsigned) *src))
++src;
}
return src;
}
typedef gboolean (*CamelPartFunc)(CamelMimeMessage *, CamelMimePart *, gpointer data);
static gboolean
message_foreach_part_rec (CamelMimeMessage *msg,
CamelMimePart *part,
CamelPartFunc callback,
gpointer data)
{
CamelDataWrapper *containee;
gint parts, i;
gint go = TRUE;
if (callback (msg, part, data) == FALSE)
return FALSE;
containee = camel_medium_get_content (CAMEL_MEDIUM (part));
if (containee == NULL)
return go;
/* using the object types is more accurate than using the mime/types */
if (CAMEL_IS_MULTIPART (containee)) {
parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
for (i = 0; go && i < parts; i++) {
CamelMimePart *mpart = camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
go = message_foreach_part_rec (msg, mpart, callback, data);
}
} else if (CAMEL_IS_MIME_MESSAGE (containee)) {
go = message_foreach_part_rec (msg, (CamelMimePart *) containee, callback, data);
}
return go;
}
/* dont make this public yet, it might need some more thinking ... */
/* MPZ */
static void
camel_mime_message_foreach_part (CamelMimeMessage *msg,
CamelPartFunc callback,
gpointer data)
{
message_foreach_part_rec (msg, (CamelMimePart *) msg, callback, data);
}
static gboolean
check_8bit (CamelMimeMessage *msg,
CamelMimePart *part,
gpointer data)
{
CamelTransferEncoding encoding;
gint *has8bit = data;
/* check this part, and stop as soon as we are done */
encoding = camel_mime_part_get_encoding (part);
*has8bit = encoding == CAMEL_TRANSFER_ENCODING_8BIT || encoding == CAMEL_TRANSFER_ENCODING_BINARY;
return !(*has8bit);
}
/**
* camel_mime_message_has_8bit_parts:
* @message: a #CamelMimeMessage object
*
* Find out if a message contains 8bit or binary encoded parts.
*
* Returns: %TRUE if the message contains 8bit parts or %FALSE otherwise
**/
gboolean
camel_mime_message_has_8bit_parts (CamelMimeMessage *msg)
{
gint has8bit = FALSE;
camel_mime_message_foreach_part (msg, check_8bit, &has8bit);
return has8bit;
}
static gboolean
mime_part_is_attachment (CamelMimePart *mp)
{
const CamelContentDisposition *content_disposition;
content_disposition = camel_mime_part_get_content_disposition (mp);
return content_disposition &&
content_disposition->disposition &&
g_ascii_strcasecmp (content_disposition->disposition, "attachment") == 0;
}
/* finds the best charset and transfer encoding for a given part */
static CamelTransferEncoding
find_best_encoding (CamelMimePart *part,
CamelBestencRequired required,
CamelBestencEncoding enctype,
gchar **charsetp)
{
CamelMimeFilter *charenc = NULL;
CamelTransferEncoding encoding;
CamelMimeFilter *bestenc;
guint flags, callerflags;
CamelDataWrapper *content;
CamelStream *filter;
const gchar *charsetin = NULL;
gchar *charset = NULL;
CamelStream *null;
gint idb, idc = -1;
gboolean istext;
/* we use all these weird stream things so we can do it with streams, and
* not have to read the whole lot into memory - although i have a feeling
* it would make things a fair bit simpler to do so ... */
d (printf ("starting to check part\n"));
content = camel_medium_get_content ((CamelMedium *) part);
if (content == NULL) {
/* charset might not be right here, but it'll get the right stuff
* if it is ever set */
*charsetp = NULL;
return CAMEL_TRANSFER_ENCODING_DEFAULT;
}
istext = camel_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*");
if (istext) {
flags = CAMEL_BESTENC_GET_CHARSET | CAMEL_BESTENC_GET_ENCODING;
enctype |= CAMEL_BESTENC_TEXT;
} else {
flags = CAMEL_BESTENC_GET_ENCODING;
}
/* when building the message, any encoded parts are translated already */
flags |= CAMEL_BESTENC_LF_IS_CRLF;
/* and get any flags the caller passed in */
callerflags = (required & CAMEL_BESTENC_NO_FROM);
flags |= callerflags;
/* first a null stream, so any filtering is thrown away; we only want the sideeffects */
null = (CamelStream *) camel_stream_null_new ();
filter = camel_stream_filter_new (null);
/* if we're looking for the best charset, then we need to convert to UTF-8 */
if (istext && (required & CAMEL_BESTENC_GET_CHARSET) != 0
&& (charsetin = camel_content_type_param (content->mime_type, "charset"))) {
charenc = camel_mime_filter_charset_new (charsetin, "UTF-8");
if (charenc != NULL)
idc = camel_stream_filter_add (
CAMEL_STREAM_FILTER (filter), charenc);
charsetin = NULL;
}
bestenc = camel_mime_filter_bestenc_new (flags);
idb = camel_stream_filter_add (
CAMEL_STREAM_FILTER (filter), bestenc);
d (printf ("writing to checking stream\n"));
camel_data_wrapper_decode_to_stream_sync (content, filter, NULL, NULL);
camel_stream_filter_remove (CAMEL_STREAM_FILTER (filter), idb);
if (idc != -1) {
camel_stream_filter_remove (CAMEL_STREAM_FILTER (filter), idc);
g_object_unref (charenc);
charenc = NULL;
}
if (istext && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
charsetin = camel_mime_filter_bestenc_get_best_charset (
CAMEL_MIME_FILTER_BESTENC (bestenc));
d (printf ("best charset = %s\n", charsetin ? charsetin : "(null)"));
charset = g_strdup (charsetin);
charsetin = camel_content_type_param (content->mime_type, "charset");
} else {
charset = NULL;
}
/* if we have US-ASCII, or we're not doing text, we dont need to bother with the rest */
if (istext && charsetin && charset && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
d (printf ("have charset, trying conversion/etc\n"));
/* now that 'bestenc' has told us what the best encoding is, we can use that to create
* a charset conversion filter as well, and then re-add the bestenc to filter the
* result to find the best encoding to use as well */
charenc = camel_mime_filter_charset_new (charsetin, charset);
if (charenc != NULL) {
/* otherwise, try another pass, converting to the real charset */
camel_mime_filter_reset (bestenc);
camel_mime_filter_bestenc_set_flags (
CAMEL_MIME_FILTER_BESTENC (bestenc),
CAMEL_BESTENC_GET_ENCODING |
CAMEL_BESTENC_LF_IS_CRLF | callerflags);
camel_stream_filter_add (
CAMEL_STREAM_FILTER (filter), charenc);
camel_stream_filter_add (
CAMEL_STREAM_FILTER (filter), bestenc);
/* and write it to the new stream */
camel_data_wrapper_write_to_stream_sync (
content, filter, NULL, NULL);
g_object_unref (charenc);
}
}
encoding = camel_mime_filter_bestenc_get_best_encoding (
CAMEL_MIME_FILTER_BESTENC (bestenc), enctype);
g_object_unref (filter);
g_object_unref (bestenc);
g_object_unref (null);
d (printf ("done, best encoding = %d\n", encoding));
if (charsetp)
*charsetp = charset;
else
g_free (charset);
return encoding;
}
struct _enc_data {
CamelBestencRequired required;
CamelBestencEncoding enctype;
};
static gboolean
best_encoding (CamelMimeMessage *msg,
CamelMimePart *part,
gpointer datap)
{
struct _enc_data *data = datap;
CamelTransferEncoding encoding;
CamelDataWrapper *wrapper;
gchar *charset;
/* Keep attachments untouched. */
if (mime_part_is_attachment (part))
return TRUE;
wrapper = camel_medium_get_content (CAMEL_MEDIUM (part));
if (!wrapper)
return FALSE;
/* we only care about actual content objects */
if (!CAMEL_IS_MULTIPART (wrapper) && !CAMEL_IS_MIME_MESSAGE (wrapper)) {
encoding = find_best_encoding (part, data->required, data->enctype, &charset);
/* we always set the encoding, if we got this far. GET_CHARSET implies
* also GET_ENCODING */
camel_mime_part_set_encoding (part, encoding);
if ((data->required & CAMEL_BESTENC_GET_CHARSET) != 0) {
if (camel_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*")) {
gchar *newct;
/* FIXME: ick, the part content_type interface needs fixing bigtime */
camel_content_type_set_param (
((CamelDataWrapper *) part)->mime_type, "charset",
charset ? charset : "us-ascii");
newct = camel_content_type_format (((CamelDataWrapper *) part)->mime_type);
if (newct) {
d (printf ("Setting content-type to %s\n", newct));
camel_mime_part_set_content_type (part, newct);
g_free (newct);
}
}
}
g_free (charset);
}
return TRUE;
}
/**
* camel_mime_message_set_best_encoding:
* @message: a #CamelMimeMessage object
* @required: a bitwise ORing of #CAMEL_BESTENC_GET_ENCODING and #CAMEL_BESTENC_GET_CHARSET
* @enctype: an encoding to enforce
*
* Re-encode all message parts to conform with the required encoding rules.
*
* If @enctype is #CAMEL_BESTENC_7BIT, then all parts will be re-encoded into
* one of the 7bit transfer encodings. If @enctype is #CAMEL_BESTENC_8bit, all
* parts will be re-encoded to either a 7bit encoding or, if the part is 8bit
* text, allowed to stay 8bit. If @enctype is #CAMEL_BESTENC_BINARY, then binary
* parts will be encoded as binary and 8bit textual parts will be encoded as 8bit.
**/
void
camel_mime_message_set_best_encoding (CamelMimeMessage *msg,
CamelBestencRequired required,
CamelBestencEncoding enctype)
{
struct _enc_data data;
if ((required & (CAMEL_BESTENC_GET_ENCODING | CAMEL_BESTENC_GET_CHARSET)) == 0)
return;
data.required = required;
data.enctype = enctype;
camel_mime_message_foreach_part (msg, best_encoding, &data);
}
/**
* camel_mime_message_encode_8bit_parts:
* @message: a #CamelMimeMessage object
*
* Encode all message parts to a suitable transfer encoding for transport (7bit clean).
**/
void
camel_mime_message_encode_8bit_parts (CamelMimeMessage *mime_message)
{
camel_mime_message_set_best_encoding (mime_message, CAMEL_BESTENC_GET_ENCODING, CAMEL_BESTENC_7BIT);
}
struct _check_content_id {
CamelMimePart *part;
const gchar *content_id;
};
static gboolean
check_content_id (CamelMimeMessage *message,
CamelMimePart *part,
gpointer data)
{
struct _check_content_id *check = (struct _check_content_id *) data;
const gchar *content_id;
gboolean found;
content_id = camel_mime_part_get_content_id (part);
found = content_id && !strcmp (content_id, check->content_id) ? TRUE : FALSE;
if (found)
check->part = g_object_ref (part);
return !found;
}
/**
* camel_mime_message_get_part_by_content_id:
* @message: a #CamelMimeMessage object
* @content_id: content-id to search for
*
* Get a MIME part by id from a message.
*
* Returns: the MIME part with the requested id or %NULL if not found
**/
CamelMimePart *
camel_mime_message_get_part_by_content_id (CamelMimeMessage *message,
const gchar *id)
{
struct _check_content_id check;
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
if (id == NULL)
return NULL;
check.content_id = id;
check.part = NULL;
camel_mime_message_foreach_part (message, check_content_id, &check);
return check.part;
}
static const gchar tz_months[][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const gchar tz_days[][4] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
/**
* camel_mime_message_build_mbox_from:
* @message: a #CamelMimeMessage object
*
* Build an MBox from-line from @message.
*
* Returns: an MBox from-line suitable for use in an mbox file
**/
gchar *
camel_mime_message_build_mbox_from (CamelMimeMessage *message)
{
struct _camel_header_raw *header = ((CamelMimePart *) message)->headers;
GString *out = g_string_new ("From ");
gchar *ret;
const gchar *tmp;
time_t thetime;
gint offset;
struct tm tm;
tmp = camel_header_raw_find (&header, "Sender", NULL);
if (tmp == NULL)
tmp = camel_header_raw_find (&header, "From", NULL);
if (tmp != NULL) {
struct _camel_header_address *addr = camel_header_address_decode (tmp, NULL);
tmp = NULL;
if (addr) {
if (addr->type == CAMEL_HEADER_ADDRESS_NAME) {
g_string_append (out, addr->v.addr);
tmp = "";
}
camel_header_address_unref (addr);
}
}
if (tmp == NULL)
g_string_append (out, "unknown@nodomain.now.au");
/* try use the received header to get the date */
tmp = camel_header_raw_find (&header, "Received", NULL);
if (tmp) {
tmp = strrchr (tmp, ';');
if (tmp)
tmp++;
}
/* if there isn't one, try the Date field */
if (tmp == NULL)
tmp = camel_header_raw_find (&header, "Date", NULL);
thetime = camel_header_decode_date (tmp, &offset);
thetime += ((offset / 100) * (60 * 60)) + (offset % 100) * 60;
gmtime_r (&thetime, &tm);
g_string_append_printf (
out, " %s %s %2d %02d:%02d:%02d %4d\n",
tz_days[tm.tm_wday],
tz_months[tm.tm_mon],
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
tm.tm_year + 1900);
ret = out->str;
g_string_free (out, FALSE);
return ret;
}
static gboolean
find_attachment (CamelMimeMessage *msg,
CamelMimePart *part,
gpointer data)
{
const CamelContentDisposition *cd;
CamelContentType *ct;
gboolean *found = (gboolean *) data;
g_return_val_if_fail (part != NULL, FALSE);
ct = camel_mime_part_get_content_type (part);
if (ct && (
camel_content_type_is (ct, "application", "xpkcs7mime") ||
camel_content_type_is (ct, "application", "x-pkcs7-mime") ||
camel_content_type_is (ct, "application", "pkcs7-mime") ||
camel_content_type_is (ct, "application", "pkcs7-signature") ||
camel_content_type_is (ct, "application", "xpkcs7-signature") ||
camel_content_type_is (ct, "application", "x-pkcs7-signature") ||
camel_content_type_is (ct, "application", "pkcs7-signature") ||
camel_content_type_is (ct, "application", "pgp-signature") ||
camel_content_type_is (ct, "application", "pgp-encrypted")))
return !(*found);
cd = camel_mime_part_get_content_disposition (part);
if (cd) {
const struct _camel_header_param *param;
*found = (cd->disposition && g_ascii_strcasecmp (cd->disposition, "attachment") == 0);
if (!*found && (!cd->disposition || g_ascii_strcasecmp (cd->disposition, "inline") != 0)) {
for (param = cd->params; param && !(*found); param = param->next) {
if (param->name && param->value && *param->value && g_ascii_strcasecmp (param->name, "filename") == 0)
*found = TRUE;
}
}
}
return !(*found);
}
/**
* camel_mime_message_has_attachment:
* @message: a #CamelMimeMessage object
*
* Returns whether message contains at least one attachment part.
*
* Since: 2.28
**/
gboolean
camel_mime_message_has_attachment (CamelMimeMessage *message)
{
gboolean found = FALSE;
g_return_val_if_fail (message != NULL, FALSE);
camel_mime_message_foreach_part (message, find_attachment, &found);
return found;
}
static void
dumpline (const gchar *indent,
guint8 *data,
gsize data_len)
{
gint j;
gchar *gutter;
guint gutter_size;
g_return_if_fail (data_len <= 16);
gutter_size = ((16 - data_len) * 3) + 4;
gutter = alloca (gutter_size + 1);
memset (gutter, ' ', gutter_size);
gutter[gutter_size] = 0;
printf ("%s ", indent);
/* Hex dump */
for (j = 0; j < data_len; j++)
printf ("%s%02x", j > 0 ? " " : "", data[j]);
/* ASCII dump */
printf ("%s", gutter);
for (j = 0; j < data_len; j++) {
printf ("%c", isprint (data[j]) ? data[j] : '.');
}
printf ("\n");
}
static void
cmm_dump_rec (CamelMimeMessage *msg,
CamelMimePart *part,
gint body,
gint depth)
{
CamelDataWrapper *containee;
gint parts, i;
gint go = TRUE;
gchar *s;
const GByteArray *data;
s = alloca (depth + 1);
memset (s, ' ', depth);
s[depth] = 0;
/* yes this leaks, so what its only debug stuff */
printf ("%sclass: %s\n", s, G_OBJECT_TYPE_NAME (part));
printf ("%smime-type: %s\n", s, camel_content_type_format (((CamelDataWrapper *) part)->mime_type));
containee = camel_medium_get_content ((CamelMedium *) part);
if (containee == NULL)
return;
printf ("%scontent class: %s\n", s, G_OBJECT_TYPE_NAME (containee));
printf ("%scontent mime-type: %s\n", s, camel_content_type_format (((CamelDataWrapper *) containee)->mime_type));
data = camel_data_wrapper_get_byte_array (containee);
if (body && data) {
guint t = 0;
printf ("%scontent len %d\n", s, data->len);
for (t = 0; t < data->len / 16; t++)
dumpline (s, &data->data[t * 16], 16);
if (data->len % 16)
dumpline (s, &data->data[t * 16], data->len % 16);
}
/* using the object types is more accurate than using the mime/types */
if (CAMEL_IS_MULTIPART (containee)) {
parts = camel_multipart_get_number ((CamelMultipart *) containee);
for (i = 0; go && i < parts; i++) {
CamelMimePart *mpart = camel_multipart_get_part ((CamelMultipart *) containee, i);
cmm_dump_rec (msg, mpart, body, depth + 2);
}
} else if (CAMEL_IS_MIME_MESSAGE (containee)) {
cmm_dump_rec (msg, (CamelMimePart *) containee, body, depth + 2);
}
}
/**
* camel_mime_message_dump:
* @message:
* @body:
*
* Dump information about the mime message to stdout.
*
* If body is TRUE, then dump body content of the message as well.
**/
void
camel_mime_message_dump (CamelMimeMessage *message,
gint body)
{
cmm_dump_rec (message, (CamelMimePart *) message, body, 0);
}