/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Author : * Matt Loper * * 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 #include "mail-format.h" #include "camel/hash-table-utils.h" #include #include /* for isprint */ #include /* 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 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, "\n"); call_handler_function ( formatter, data_wrapper, mimetype_whole, data_wrapper->mime_type->type); camel_stream_write_string (fmt->stream, "\n\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, "\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\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 (" " " ", 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, "
"); 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, gboolean bold, 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 ("<%s align=right>%s" "%s", 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, ""); /* 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, "
"); } #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\n"); mail_write_html (stream, "
\n");

	/* FIXME: text/richtext is not difficult to translate into HTML */
	if (strcmp (wrapper->mime_type->subtype, "richtext") == 0) {
		mail_write_html (stream, "
" "" "
Warning: the following " "richtext may not be formatted correctly. " "

"); } /* 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, "(empty)"); mail_write_html (stream, "
\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\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, "(empty)"); } 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\n"); tag = g_strdup_printf ("\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\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\n"); // mail_write_html (stream, // "
\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
\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
\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 ("click-me-to-save\n", uid); mail_write_html (stream, tag); } void mail_write_html (GtkHTMLStreamHandle *stream, const char *data) { gtk_html_stream_write (stream, data, strlen (data)); }