diff options
Diffstat (limited to 'mail/mail-format.c')
-rw-r--r-- | mail/mail-format.c | 793 |
1 files changed, 793 insertions, 0 deletions
diff --git a/mail/mail-format.c b/mail/mail-format.c new file mode 100644 index 0000000000..08c6e81073 --- /dev/null +++ b/mail/mail-format.c @@ -0,0 +1,793 @@ +/* -*- 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 */ + +/* We shouldn't be doing this, but I don't feel like fixing it right + * now. (It's for gtk_html_stream_write.) When gtkhtml has nicer + * interfaces, we can fix it. + */ +#include <gtkhtml/gtkhtml-private.h> + +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 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 convert_newlines_to_br) +{ + const guchar *cur = input; + guchar *buffer = NULL; + guchar *out = NULL; + gint buffer_size = 0; + guint count; + + /* Allocate a translation buffer. */ + buffer_size = len * 2; + buffer = g_malloc (buffer_size); + + out = buffer; + count = 0; + + 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++; + } + + *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, + GtkHTMLStreamHandle *stream) +{ + gchar *s; + gchar *encoded_value; + + if (value) { + unsigned char *p; + + encoded_value = text_to_html (value, strlen(value), + NULL, 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><th align=right>%s</th>" + "<td>%s</td></tr>", description, 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, + 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, stream); + g_free (recipients_string); +} + + + +static void +write_header_info_to_stream (CamelMimeMessage *mime_message, + GtkHTMLStreamHandle *stream) +{ + 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), + stream); + + write_recipients_to_stream ("To:", + camel_mime_message_get_recipients (mime_message, CAMEL_RECIPIENT_TYPE_TO), + stream); + + recipients = camel_mime_message_get_recipients (mime_message, CAMEL_RECIPIENT_TYPE_CC); + if (recipients) + write_recipients_to_stream ("Cc:", recipients, stream); + write_field_to_stream ("Subject:", + camel_mime_message_get_subject (mime_message), + stream); + + mail_write_html (stream, "</table>"); +} + +/* case-insensitive string comparison */ +static gint +strcase_equal (gconstpointer v, gconstpointer v2) +{ + return g_strcasecmp ((const gchar*) v, (const gchar*)v2) == 0; +} + +#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); + 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); + } +} + +static void +handle_multipart_related (CamelDataWrapper *wrapper, + GtkHTMLStreamHandle *stream, + CamelDataWrapper *root) +{ +// CamelMultipart* mp = CAMEL_MULTIPART (wrapper); + + /* FIXME: read RFC, in terms of how a one message + may refer to another object */ +} + +/* 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)); +} |