/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Author :
* Matt Loper <matt@helixcode.com>
*
* Copyright 2000, Helix Code, Inc. (http://www.helixcode.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
*
*/
#include <config.h>
#include "mail-format.h"
#include "camel/hash-table-utils.h"
#include <libgnome/libgnome.h>
#include <ctype.h> /* for isprint */
#include <string.h> /* for strstr */
static void handle_text_plain (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static void handle_text_html (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static void handle_image (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static void handle_vcard (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static void handle_mime_part (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static void handle_multipart_mixed (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static void handle_multipart_related (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static void handle_multipart_alternative(CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static void handle_unknown_type (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
/* encodes some characters into their 'escaped' version;
* so '<' turns into '<', and '"' turns into '"' */
static gchar *text_to_html (const guchar *input,
guint len,
guint *encoded_len_return,
gboolean add_pre,
gboolean convert_newlines_to_br);
/* writes the header info for a mime message into an html stream */
static void write_header_info_to_stream (CamelMimeMessage* mime_message,
GtkHTMLStreamHandle *stream);
/* dispatch html printing via mimetype */
static void call_handler_function (CamelDataWrapper *wrapper,
gchar *mimetype_whole,
gchar *mimetype_main,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
#if 0
/**
* camel_formatter_wrapper_to_html:
* @formatter: the camel formatter object
* @data_wrapper: the data wrapper
* @stream: byte stream where data will be written
*
* Writes a CamelDataWrapper out, as html, into a stream passed in as
* a parameter.
**/
void camel_formatter_wrapper_to_html (CamelFormatter* formatter,
CamelDataWrapper* data_wrapper,
CamelStream* stream_out)
{
CamelFormatterPrivate* fmt = formatter->priv;
gchar *mimetype_whole =
g_strdup_printf ("%s/%s",
data_wrapper->mime_type->type,
data_wrapper->mime_type->subtype);
debug ("camel_formatter_wrapper_to_html: entered\n");
g_assert (formatter && data_wrapper && stream_out);
/* give the root CamelDataWrapper and the stream to the formatter */
initialize_camel_formatter (formatter, data_wrapper, stream_out);
if (stream_out) {
/* write everything to the stream */
camel_stream_write_string (
fmt->stream, "<html><body bgcolor=\"white\">\n");
call_handler_function (
formatter,
data_wrapper,
mimetype_whole,
data_wrapper->mime_type->type);
camel_stream_write_string (fmt->stream, "\n</body></html>\n");
}
g_free (mimetype_whole);
}
#endif
/**
* mail_format_mime_message:
* @mime_message: the input mime message
* @header_stream: HTML stream to write headers to
* @body_stream: HTML stream to write data to
*
* Writes a CamelMimeMessage out, as html, into streams passed in as
* a parameter. Either stream may be #NULL.
**/
void
mail_format_mime_message (CamelMimeMessage *mime_message,
GtkHTMLStreamHandle *header_stream,
GtkHTMLStreamHandle *body_stream)
{
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (mime_message));
/* Write the headers fields out as HTML to the header stream. */
if (header_stream)
write_header_info_to_stream (mime_message, header_stream);
/* Write the contents of the MIME message to the body stream. */
if (body_stream) {
mail_write_html (body_stream, "<html><body>\n");
call_handler_function (CAMEL_DATA_WRAPPER (mime_message),
"message/rfc822",
"message",
body_stream,
CAMEL_DATA_WRAPPER (mime_message));
mail_write_html (body_stream, "\n</body></html>\n");
}
}
/* We're maintaining a hashtable of mimetypes -> functions;
* Those functions have the following signature...
*/
typedef void (*mime_handler_fn) (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root);
static gchar*
lookup_unique_id (CamelDataWrapper *root, CamelDataWrapper *child)
{
/* ** FIXME : replace this with a string representing
the location of the objetc in the tree */
/* TODO: assert our return value != NULL */
gchar *temp_hack_uid;
temp_hack_uid = g_strdup_printf ("%p", camel_data_wrapper_get_output_stream (child));
return temp_hack_uid;
}
static GHashTable *mime_function_table;
/* This tries to create a tag, given a mimetype and the child of a
* mime message. It can return NULL if it can't match the mimetype to
* a bonobo object.
*/
static gchar *
get_bonobo_tag_for_object (CamelDataWrapper *wrapper,
gchar *mimetype, CamelDataWrapper *root)
{
char *uid = lookup_unique_id (root, wrapper);
const char *goad_id = gnome_mime_get_value (mimetype,
"bonobo-goad-id");
if (goad_id) {
return g_strdup_printf ("<object classid=\"%s\"> "
"<param name=\"uid\" "
"value=\"camel://%s\"> </object>",
goad_id, uid);
} else
return NULL;
}
/*
* This takes a mimetype, and tries to map that mimetype to a function
* or a bonobo object.
*
* - If it's mapped to a bonobo object, this function prints a tag
* into the stream, designating the bonobo object and a place that
* the bonobo object can find data to hydrate from
*
* - otherwise, the mimetype is mapped to another function, which can
* print into the stream
*/
static void
call_handler_function (CamelDataWrapper *wrapper,
gchar *mimetype_whole_in, /* ex. "image/jpeg" */
gchar *mimetype_main_in, /* ex. "image" */
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
mime_handler_fn handler_function = NULL;
gchar *mimetype_whole = NULL;
gchar *mimetype_main = NULL;
g_return_if_fail (mimetype_whole_in || mimetype_main_in);
g_return_if_fail (CAMEL_IS_DATA_WRAPPER (wrapper));
g_return_if_fail (CAMEL_IS_DATA_WRAPPER (root));
if (mime_function_table == NULL) {
mime_function_table = g_hash_table_new (g_strcase_hash,
g_strcase_equal);
/* hook up mime types to functions that handle them */
g_hash_table_insert (mime_function_table, "text/plain",
handle_text_plain);
g_hash_table_insert (mime_function_table, "text/richtext",
handle_text_plain);
g_hash_table_insert (mime_function_table, "text/html",
handle_text_html);
g_hash_table_insert (mime_function_table, "multipart/alternative",
handle_multipart_alternative);
g_hash_table_insert (mime_function_table, "multipart/related",
handle_multipart_related);
g_hash_table_insert (mime_function_table, "multipart/mixed",
handle_multipart_mixed);
g_hash_table_insert (mime_function_table, "message/rfc822",
handle_mime_part);
g_hash_table_insert (mime_function_table, "image",
handle_image);
g_hash_table_insert (mime_function_table, "vcard",
handle_vcard);
/* RFC 2046 says unrecognized multipart subtypes should
* be treated like multipart/mixed.
*/
g_hash_table_insert (mime_function_table, "multipart",
handle_multipart_mixed);
/* Body parts don't have mime parts per se, so Camel
* sticks on the following one.
*/
g_hash_table_insert (mime_function_table, "mime/body-part",
handle_mime_part);
}
/* Try to find a handler function in our own lookup table */
if (mimetype_whole_in) {
mimetype_whole = g_strdup (mimetype_whole_in);
g_strdown (mimetype_whole);
handler_function = g_hash_table_lookup (mime_function_table,
mimetype_whole);
}
if (mimetype_main_in && !handler_function) {
mimetype_main = g_strdup (mimetype_main_in);
g_strdown (mimetype_main);
handler_function = g_hash_table_lookup (mime_function_table,
mimetype_main);
}
/* Upon failure, try to find a bonobo object to show the object */
if (!handler_function) {
gchar *bonobo_tag = NULL;
if (mimetype_whole)
bonobo_tag = get_bonobo_tag_for_object (
wrapper, mimetype_whole, root);
if (mimetype_main && !bonobo_tag)
bonobo_tag = get_bonobo_tag_for_object (
wrapper, mimetype_main, root);
if (bonobo_tag) {
/* We can print a tag, and return! */
mail_write_html (stream, bonobo_tag);
g_free (bonobo_tag);
if (mimetype_whole)
g_free (mimetype_whole);
if (mimetype_main)
g_free (mimetype_main);
return;
}
}
/* Use either a handler function we've found, or a default handler. */
if (handler_function)
(*handler_function) (wrapper, stream, root);
else
handle_unknown_type (wrapper, stream, root);
if (mimetype_whole)
g_free (mimetype_whole);
if (mimetype_main)
g_free (mimetype_main);
}
/* Convert plain text in equivalent-looking valid HTML. */
static gchar *
text_to_html (const guchar *input, guint len,
guint *encoded_len_return, gboolean add_pre,
gboolean convert_newlines_to_br)
{
const guchar *cur = input;
guchar *buffer = NULL;
guchar *out = NULL;
gint buffer_size = 0;
/* Allocate a translation buffer. */
buffer_size = len * 2 + 5;
buffer = g_malloc (buffer_size);
out = buffer;
if (add_pre)
out += sprintf (out, "<PRE>\n");
while (len--) {
if (out - buffer > buffer_size - 100) {
gint index = out - buffer;
buffer_size *= 2;
buffer = g_realloc (buffer, buffer_size);
out = buffer + index;
}
switch (*cur) {
case '<':
strcpy (out, "<");
out += 4;
break;
case '>':
strcpy (out, ">");
out += 4;
break;
case '&':
strcpy (out, "&");
out += 5;
break;
case '"':
strcpy (out, """);
out += 6;
break;
case '\n':
*out++ = *cur;
if (convert_newlines_to_br) {
strcpy (out, "<br>");
out += 4;
}
break;
default:
if ((*cur >= 0x20 && *cur < 0x80) ||
(*cur == '\r' || *cur == '\t')) {
/* Default case, just copy. */
*out++ = *cur;
} else
out += g_snprintf(out, 9, "&#%d;", *cur);
break;
}
cur++;
}
if (add_pre)
strcpy (out, "</PRE>");
else
*out = '\0';
if (encoded_len_return)
*encoded_len_return = out - buffer;
return buffer;
}
static void
write_field_to_stream (const gchar *description, const gchar *value,
gboolean bold, GtkHTMLStreamHandle *stream)
{
gchar *s;
gchar *encoded_value;
if (value) {
unsigned char *p;
encoded_value = text_to_html (value, strlen(value),
NULL, FALSE, TRUE);
for (p = (unsigned char *)encoded_value; *p; p++) {
if (!isprint (*p))
*p = '?';
}
} else
encoded_value = g_strdup ("");
s = g_strdup_printf ("<tr valign=top><%s align=right>%s</%s>"
"<td>%s</td></tr>", bold ? "th" : "td",
description, bold ? "th" : "td",
encoded_value);
mail_write_html (stream, s);
g_free (encoded_value);
g_free (s);
}
static void
write_recipients_to_stream (const gchar *recipient_type,
const GList *recipients, gboolean bold,
GtkHTMLStreamHandle *stream)
{
gchar *recipients_string = NULL;
while (recipients) {
gchar *old_string = recipients_string;
recipients_string =
g_strdup_printf ("%s%s%s",
old_string ? old_string : "",
old_string ? "; " : "",
(gchar *)recipients->data);
g_free (old_string);
recipients = recipients->next;
}
write_field_to_stream (recipient_type, recipients_string,
bold, stream);
g_free (recipients_string);
}
static void
write_header_info_to_stream (CamelMimeMessage *mime_message,
GtkHTMLStreamHandle *stream)
{
const GList *recipients;
mail_write_html (stream, "<table>");
/* A few fields will probably be available from the mime_message;
* for each one that's available, write it to the output stream
* with a helper function, 'write_field_to_stream'.
*/
write_field_to_stream ("From:",
camel_mime_message_get_from (mime_message),
TRUE, stream);
if (camel_mime_message_get_reply_to (mime_message)) {
write_field_to_stream ("Reply-To:",
camel_mime_message_get_reply_to (mime_message),
FALSE, stream);
}
write_recipients_to_stream ("To:",
camel_mime_message_get_recipients (mime_message, CAMEL_RECIPIENT_TYPE_TO),
TRUE, stream);
recipients = camel_mime_message_get_recipients (mime_message, CAMEL_RECIPIENT_TYPE_CC);
if (recipients)
write_recipients_to_stream ("Cc:", recipients, TRUE, stream);
write_field_to_stream ("Subject:",
camel_mime_message_get_subject (mime_message),
TRUE, stream);
mail_write_html (stream, "</table>");
}
#define MIME_TYPE_WHOLE(a) (gmime_content_field_get_mime_type ( \
camel_mime_part_get_content_type (CAMEL_MIME_PART (a))))
#define MIME_TYPE_MAIN(a) ((camel_mime_part_get_content_type (CAMEL_MIME_PART (a)))->type)
#define MIME_TYPE_SUB(a) ((camel_mime_part_get_content_type (CAMEL_MIME_PART (a)))->subtype)
/*----------------------------------------------------------------------*
* Mime handling functions
*----------------------------------------------------------------------*/
static void
handle_text_plain (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
gchar *text;
CamelStream *wrapper_output_stream;
gchar tmp_buffer[4096];
gint nb_bytes_read;
gboolean empty_text = TRUE;
mail_write_html (stream, "\n<!-- text/plain below -->\n");
mail_write_html (stream, "<pre>\n");
/* FIXME: text/richtext is not difficult to translate into HTML */
if (strcmp (wrapper->mime_type->subtype, "richtext") == 0) {
mail_write_html (stream, "<center><b>"
"<table bgcolor=\"b0b0ff\" cellpadding=3>"
"<tr><td>Warning: the following "
"richtext may not be formatted correctly. "
"</b></td></tr></table></center><br>");
}
/* Get the output stream of the data wrapper. */
wrapper_output_stream = camel_data_wrapper_get_output_stream (wrapper);
camel_stream_reset (wrapper_output_stream);
do {
/* Read next chunk of text. */
nb_bytes_read = camel_stream_read (wrapper_output_stream,
tmp_buffer, 4096);
/* If there's any text, write it to the stream. */
if (nb_bytes_read > 0) {
int returned_strlen;
empty_text = FALSE;
/* replace '<' with '<', etc. */
text = text_to_html (tmp_buffer,
nb_bytes_read,
&returned_strlen,
FALSE, FALSE);
mail_write_html (stream, text);
g_free (text);
}
} while (!camel_stream_eos (wrapper_output_stream));
if (empty_text)
mail_write_html (stream, "<b>(empty)</b>");
mail_write_html (stream, "</pre>\n");
}
static void
handle_text_html (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
CamelStream *wrapper_output_stream;
gchar tmp_buffer[4096];
gint nb_bytes_read;
gboolean empty_text = TRUE;
/* Get the output stream of the data wrapper. */
wrapper_output_stream = camel_data_wrapper_get_output_stream (wrapper);
camel_stream_reset (wrapper_output_stream);
/* Write the header. */
mail_write_html (stream, "\n<!-- text/html below -->\n");
do {
/* Read next chunk of text. */
nb_bytes_read = camel_stream_read (wrapper_output_stream,
tmp_buffer, 4096);
/* If there's any text, write it to the stream */
if (nb_bytes_read > 0) {
empty_text = FALSE;
/* Write the buffer to the html stream */
gtk_html_stream_write (stream, tmp_buffer,
nb_bytes_read);
}
} while (!camel_stream_eos (wrapper_output_stream));
if (empty_text)
mail_write_html (stream, "<b>(empty)</b>");
}
static void
handle_image (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
gchar *uuid;
gchar *tag;
uuid = lookup_unique_id (root, wrapper);
mail_write_html (stream, "\n<!-- image below -->\n");
tag = g_strdup_printf ("<img src=\"camel://%s\">\n", uuid);
mail_write_html (stream, tag);
g_free (uuid);
g_free (tag);
}
static void
handle_vcard (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
mail_write_html (stream, "\n<!-- vcard below -->\n");
/* FIXME: do something here. */
}
static void
handle_mime_part (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
CamelMimePart *mime_part;
CamelDataWrapper *message_contents;
gchar *whole_mime_type;
g_return_if_fail (CAMEL_IS_MIME_PART (wrapper));
mime_part = CAMEL_MIME_PART (wrapper);
message_contents =
camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
g_assert (message_contents);
mail_write_html (stream, "\n<!-- mime message below -->\n");
// mail_write_html (stream,
// "<table width=95% border=1><tr><td>\n\n");
/* dispatch the correct handler function for the mime type */
whole_mime_type = MIME_TYPE_WHOLE (mime_part);
call_handler_function (message_contents,
whole_mime_type,
MIME_TYPE_MAIN (mime_part),
stream, root);
g_free (whole_mime_type);
/* close up the table we opened */
// mail_write_html (stream,
// "\n\n</td></tr></table>\n\n");
}
/* called for each body part in a multipart/mixed */
static void
display_camel_body_part (CamelMimeBodyPart *body_part,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
gchar *whole_mime_type;
CamelDataWrapper* contents =
camel_medium_get_content_object (CAMEL_MEDIUM (body_part));
whole_mime_type = MIME_TYPE_WHOLE (body_part);
call_handler_function (contents, whole_mime_type,
MIME_TYPE_MAIN (body_part),
stream, root);
g_free (whole_mime_type);
mail_write_html (stream, "\n<hr>\n");
}
/* Our policy here is this:
(1) print text/(plain|html) parts found
(2) print vcards and images inline
(3) treat all other parts as attachments */
static void
handle_multipart_mixed (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream, CamelDataWrapper *root)
{
CamelMultipart *mp;
int i, nparts;
g_return_if_fail (CAMEL_IS_MULTIPART (wrapper));
mp = CAMEL_MULTIPART (wrapper);
nparts = camel_multipart_get_number (mp);
for (i = 0; i < nparts; i++) {
CamelMimeBodyPart *body_part =
camel_multipart_get_part (mp, i);
display_camel_body_part (body_part, stream, root);
}
}
/* As seen in RFC 2387! */
/* FIXME: camel doesn't currently set CamelMultipart::parent, so we
* can use it here.
*/
static void
handle_multipart_related (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
CamelMultipart *mp;
CamelMimeBodyPart *body_part;
#if 0
GMimeContentField *parent_content_type;
const char *start, *cid;
int i, nparts;
#endif
g_return_if_fail (CAMEL_IS_MULTIPART (wrapper));
mp = CAMEL_MULTIPART (wrapper);
#if 0
parent_content_type =
camel_mime_part_get_content_type (
camel_multipart_get_parent (mp));
start = gmime_content_field_get_parameter (parent_content_type,
"start");
if (start) {
nparts = camel_multipart_get_number (mp);
for (i = 0; i < nparts; i++) {
CamelMimeBodyPart *body_part =
camel_multipart_get_part (mp, i);
cid = camel_mime_part_get_content_id (
CAMEL_MIME_PART (body_part));
if (!strcmp (cid, start)) {
display_camel_body_part (body_part, stream,
root);
return;
}
}
/* Oops. Hrmph. */
handle_multipart_mixed (wrapper, stream, root);
}
#endif
/* No start parameter, so it defaults to the first part. */
body_part = camel_multipart_get_part (mp, 0);
display_camel_body_part (body_part, stream, root);
}
/* multipart/alternative helper function --
* Returns NULL if no displayable msg is found
*/
static CamelMimePart *
find_preferred_alternative (CamelMultipart *multipart)
{
int i, nparts;
CamelMimePart* html_part = NULL;
CamelMimePart* plain_part = NULL;
/* Find out out many parts are in it. */
nparts = camel_multipart_get_number (multipart);
/* FIXME: DO LEAF-LOOKUP HERE FOR OTHER MIME-TYPES!!! */
for (i = 0; i < nparts; i++) {
CamelMimeBodyPart *body_part =
camel_multipart_get_part (multipart, i);
if (strcasecmp (MIME_TYPE_MAIN (body_part), "text") != 0)
continue;
if (strcasecmp (MIME_TYPE_SUB (body_part), "plain") == 0)
plain_part = CAMEL_MIME_PART (body_part);
else if (strcasecmp (MIME_TYPE_SUB (body_part), "html") == 0)
html_part = CAMEL_MIME_PART (body_part);
}
if (html_part)
return html_part;
if (plain_part)
return plain_part;
return NULL;
}
/* The current policy for multipart/alternative is this:
*
* if (we find a text/html body part)
* we print it
* else if (we find a text/plain body part)
* we print it
* else
* we print nothing
*/
static void
handle_multipart_alternative (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
CamelMultipart *multipart = CAMEL_MULTIPART (wrapper);
CamelMimePart *mime_part;
gchar *whole_mime_type;
mime_part = find_preferred_alternative (multipart);
if (mime_part) {
CamelDataWrapper *contents =
camel_medium_get_content_object (
CAMEL_MEDIUM (mime_part));
whole_mime_type = MIME_TYPE_WHOLE (mime_part);
call_handler_function (contents, whole_mime_type,
MIME_TYPE_MAIN (mime_part),
stream, root);
g_free (whole_mime_type);
}
}
static void
handle_unknown_type (CamelDataWrapper *wrapper,
GtkHTMLStreamHandle *stream,
CamelDataWrapper *root)
{
gchar *tag;
char *uid = lookup_unique_id (root, wrapper);
tag = g_strdup_printf ("<a href=\"camel://%s\">click-me-to-save</a>\n",
uid);
mail_write_html (stream, tag);
}
void
mail_write_html (GtkHTMLStreamHandle *stream, const char *data)
{
gtk_html_stream_write (stream, data, strlen (data));
}
static char *
get_data_wrapper_text (CamelDataWrapper *data)
{
CamelStream *memstream;
GByteArray *ba;
char *text;
ba = g_byte_array_new ();
memstream = camel_stream_mem_new_with_byte_array (ba, CAMEL_STREAM_MEM_WRITE);
camel_data_wrapper_write_to_stream (data, memstream);
text = g_malloc (ba->len + 1);
memcpy (text, ba->data, ba->len);
text[ba->len] = '\0';
camel_stream_close (memstream);
return text;
}
static char *
reply_body (CamelDataWrapper *data, gboolean *html)
{
CamelMultipart *mp;
CamelMimePart *subpart;
int i, nparts;
char *subtext, *old;
const char *boundary, *disp;
char *text = NULL;
GMimeContentField *mime_type;
/* We only include text, message, and multipart bodies. */
mime_type = camel_data_wrapper_get_mime_type_field (data);
if (strcasecmp (mime_type->type, "message") == 0)
return get_data_wrapper_text (data);
if (strcasecmp (mime_type->type, "text") == 0) {
*html = !strcasecmp (mime_type->subtype, "html");
return get_data_wrapper_text (data);
}
/* If it's not message and it's not text, and it's not
* multipart, we don't want to deal with it.
*/
if (strcasecmp (mime_type->type, "multipart") != 0)
return NULL;
mp = CAMEL_MULTIPART (data);
if (strcasecmp (mime_type->subtype, "alternative") == 0) {
/* Pick our favorite alternative and reply to it. */
subpart = find_preferred_alternative (mp);
if (!subpart)
return NULL;
return reply_body (camel_medium_get_content_object (CAMEL_MEDIUM (subpart)), html);
}
nparts = camel_multipart_get_number (mp);
/* If any subpart is HTML, pull it out and reply to it by itself.
* (If we supported any other non-plain text types, we'd do the
* same for them here.)
*/
for (i = 0; i < nparts; i++) {
subpart = CAMEL_MIME_PART (camel_multipart_get_part (mp, i));
if (strcasecmp (MIME_TYPE_SUB (subpart), "html") == 0)
return reply_body (camel_medium_get_content_object (CAMEL_MEDIUM (subpart)), html);
}
/* Otherwise, concatenate all the parts that:
* - are text/plain or message
* - are not explicitly tagged with non-inline disposition
*/
boundary = camel_multipart_get_boundary (mp);
for (i = 0; i < nparts; i++) {
subpart = CAMEL_MIME_PART (camel_multipart_get_part (mp, i));
disp = camel_mime_part_get_disposition (subpart);
if (disp && strcasecmp (disp, "inline") != 0)
continue;
subtext = get_data_wrapper_text (data);
if (text) {
old = text;
text = g_strdup_printf ("%s\n--%s\n%s", text,
boundary, subtext);
g_free (subtext);
g_free (old);
} else
text = subtext;
}
if (!text)
return NULL;
old = text;
text = g_strdup_printf ("%s\n--%s--\n", text, boundary);
g_free (old);
return text;
}
EMsgComposer *
mail_generate_reply (CamelMimeMessage *message, gboolean to_all)
{
CamelDataWrapper *contents;
char *text, *subject;
EMsgComposer *composer;
gboolean html;
const char *repl_to, *message_id, *references;
GList *to, *cc;
contents = camel_medium_get_content_object (CAMEL_MEDIUM (message));
text = reply_body (contents, &html);
composer = E_MSG_COMPOSER (e_msg_composer_new ());
/* Set the quoted reply text. */
if (text) {
char *repl_text;
if (html) {
repl_text = g_strdup_printf ("<blockquote><i>\n%s\n"
"</i></blockquote>\n",
text);
} else {
char *s, *d, *quoted_text;
int lines, len;
/* Count the number of lines in the body. If
* the text ends with a \n, this will be one
* too high, but that's ok. Allocate enough
* space for the text and the "> "s.
*/
for (s = text, lines = 0; s; s = strchr (s + 1, '\n'))
lines++;
quoted_text = g_malloc (strlen (text) + lines * 2);
s = text;
d = quoted_text;
/* Copy text to quoted_text line by line,
* prepending "> ".
*/
while (1) {
len = strcspn (s, "\n");
if (len == 0 && !*s)
break;
sprintf (d, "> %.*s\n", len, s);
s += len;
if (!*s++)
break;
d += len + 3;
}
/* Now convert that to HTML. */
repl_text = text_to_html (quoted_text,
strlen (quoted_text),
&len, TRUE, FALSE);
g_free (quoted_text);
}
e_msg_composer_set_body_text (composer, repl_text);
g_free (repl_text);
g_free (text);
}
/* Set the recipients */
repl_to = camel_mime_message_get_reply_to (message);
if (!repl_to)
repl_to = camel_mime_message_get_from (message);
to = g_list_append (NULL, repl_to);
if (to_all) {
const GList *recip;
recip = camel_mime_message_get_recipients (message,
CAMEL_RECIPIENT_TYPE_TO);
cc = g_list_copy (recip);
recip = camel_mime_message_get_recipients (message,
CAMEL_RECIPIENT_TYPE_CC);
while (recip) {
cc = g_list_append (cc, recip->data);
recip = recip->next;
}
} else
cc = NULL;
/* Set the subject of the new message. */
subject = camel_mime_message_get_subject (message);
if (!subject)
subject = g_strdup ("");
else if (!strncasecmp (subject, "Re: ", 4))
subject = g_strdup (subject);
else
subject = g_strdup_printf ("Re: %s", subject);
e_msg_composer_set_headers (composer, to, cc, NULL, subject);
g_list_free (to);
g_list_free (cc);
g_free (subject);
/* Add In-Reply-To and References. */
message_id = camel_medium_get_header (CAMEL_MEDIUM (message),
"Message-Id");
references = camel_medium_get_header (CAMEL_MEDIUM (message),
"References");
if (message_id) {
e_msg_composer_add_header (composer, "In-Reply-To",
message_id);
if (references) {
char *reply_refs;
reply_refs = g_strdup_printf ("%s %s", references,
message_id);
e_msg_composer_add_header (composer, "References",
reply_refs);
g_free (reply_refs);
}
} else if (references)
e_msg_composer_add_header (composer, "References", references);
return composer;
}
/* This is part of the temporary kludge below. */
#ifndef HAVE_MKSTEMP
#include <fcntl.h>
#include <sys/stat.h>
#endif
EMsgComposer *
mail_generate_forward (CamelMimeMessage *mime_message,
gboolean forward_as_attachment,
gboolean keep_attachments)
{
EMsgComposer *composer;
char *tmpfile;
int fd;
CamelStream *stream;
if (!forward_as_attachment)
g_warning ("Forward as non-attachment not implemented.");
if (!keep_attachments)
g_warning ("Forwarding without attachments not implemented.");
/* For now, we kludge by writing out a temp file. Later,
* EMsgComposer will support attaching CamelMimeParts directly,
* or something. FIXME.
*/
tmpfile = g_strdup ("/tmp/evolution-kludge-XXXX");
#ifdef HAVE_MKSTEMP
fd = mkstemp (tmpfile);
#else
if (mktemp (tmpfile)) {
fd = open (tmpfile, O_RDWR | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR);
} else
fd = -1;
#endif
if (fd == -1) {
g_warning ("Couldn't create temp file for forwarding");
g_free (tmpfile);
return NULL;
}
stream = camel_stream_fs_new_with_fd (fd);
camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (mime_message),
stream);
camel_stream_close (stream);
composer = E_MSG_COMPOSER (e_msg_composer_new ());
e_msg_composer_attachment_bar_attach (composer->attachment_bar,
tmpfile);
g_free (tmpfile);
/* FIXME: should we default a subject? */
return composer;
}