From 3e5f29f65034bd84afa560c58000ac0ba1695536 Mon Sep 17 00:00:00 2001 From: Matthew Loper Date: Tue, 8 Feb 2000 23:18:29 +0000 Subject: New member to 'CamelFormatterPrivate', 'attachments', will be used to let * camel/camel-formatter.c: New member to 'CamelFormatterPrivate', 'attachments', will be used to let the caller know which items should be treated as attachments (as opposed to objects which are inline to the body). (text_to_html): name changed from 'encode_entities'. Also now converts newlines to
tags. (camel_formatter_mime_message_to_html): Now takes two output streams -- one for the header, and one for the body. (get_bonobo_tag_for_object): New function; tries to make a tag out of (1) the leaf of a mime message and (2) a bonobo object that can handle its mime type, but can return NULL if it fails to find the mime type. (handle_vcard): New function; will write out a vcard as html. svn path=/trunk/; revision=1696 --- ChangeLog | 16 ++ camel/camel-formatter.c | 490 +++++++++++++++++++++++++++++++++--------------- camel/camel-formatter.h | 7 +- 3 files changed, 362 insertions(+), 151 deletions(-) diff --git a/ChangeLog b/ChangeLog index bb0c41bcb7..a652ba148b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2000-02-08 Matt Loper + + * camel/camel-formatter.c: New member to 'CamelFormatterPrivate', + 'attachments', will be used to let the caller know which items + should be treated as attachments (as opposed to objects which are + inline to the body). + (text_to_html): name changed from 'encode_entities'. Also now + converts newlines to
tags. + (camel_formatter_mime_message_to_html): Now takes two output + streams -- one for the header, and one for the body. + (get_bonobo_tag_for_object): New function; tries to make a tag out + of (1) the leaf of a mime message and (2) a bonobo object that can + handle its mime type, but can return NULL if it fails to find the + mime type. + (handle_vcard): New function; will write out a vcard as html. + 2000-02-08 Iain Holmes * shell/Makefile.am: Changed the order of the compilation, so the CORBA diff --git a/camel/camel-formatter.c b/camel/camel-formatter.c index f2e35aac2f..49033f05a7 100644 --- a/camel/camel-formatter.c +++ b/camel/camel-formatter.c @@ -28,6 +28,7 @@ #include "camel-log.h" #include #include // for isprint +#include // for strstr /* * The CamelFormatter takes a mime message, and produces html from it, @@ -54,9 +55,11 @@ static void handle_text_html (CamelFormatter *formatter, CamelDataWrapper *wrapper); static void handle_image (CamelFormatter *formatter, CamelDataWrapper *wrapper); +static void handle_vcard (CamelFormatter *formatter, + CamelDataWrapper *wrapper); static void handle_mime_message (CamelFormatter *formatter, CamelDataWrapper *wrapper); -static void handle_multipart_mixed (CamelFormatter *formatter, +static void handle_multipart_mixed (CamelFormatter *formatter, CamelDataWrapper *wrapper); static void handle_multipart_related (CamelFormatter *formatter, CamelDataWrapper *wrapper); @@ -65,25 +68,27 @@ static void handle_multipart_alternate(CamelFormatter *formatter, static void handle_unknown_type (CamelFormatter *formatter, CamelDataWrapper *wrapper); - - /* encodes some characters into their 'escaped' version; * so '<' turns into '<', and '"' turns into '"' */ -static gchar* encode_entities (const guchar *input, +static gchar* text_to_html (const guchar *input, guint len, guint *encoded_len_return); +/* compares strings case-insensitively */ +static gint strcase_equal (gconstpointer v, gconstpointer v2); -static GtkObjectClass *parent_class = NULL; +/* writes the header info for a mime message into a stream */ +static void write_header_info_to_stream (CamelMimeMessage* mime_message, + CamelStream* stream); -static void _finalize (GtkObject *object); +static GtkObjectClass *parent_class = NULL; struct _CamelFormatterPrivate { CamelMimeMessage *current_root; CamelStream *stream; + GHashTable *attachments; }; -static GHashTable *mime_function_table = NULL; static void debug (const gchar *format, ...) @@ -116,18 +121,31 @@ debug (const gchar *format, ...) void camel_formatter_mime_message_to_html (CamelFormatter* formatter, CamelMimeMessage* mime_message, - CamelStream* stream) + CamelStream* header_stream, + CamelStream* body_stream) { - camel_stream_write_string (stream, "\n"); - - formatter->priv->current_root = mime_message; - formatter->priv->stream = stream; + CamelFormatterPrivate* fmt = formatter->priv; + + /* initialize members of our formatter */ + fmt->current_root = mime_message; + fmt->stream = body_stream; + if (fmt->attachments) + g_hash_table_destroy (fmt->attachments); + fmt->attachments = g_hash_table_new (g_str_hash, strcase_equal); + /* write the subj:, to:, from: etc. fields out as html to the + header stream */ + write_header_info_to_stream (mime_message, + header_stream); + + /* write everything to the stream */ + camel_stream_write_string (fmt->stream, "\n"); + handle_mime_message ( formatter, CAMEL_DATA_WRAPPER (mime_message)); - camel_stream_write_string (formatter->priv->stream, + camel_stream_write_string (fmt->stream, "\n\n"); } @@ -139,51 +157,100 @@ typedef void (*mime_handler_fn) (CamelFormatter *formatter, static gchar* lookup_unique_id (CamelMimeMessage* root, CamelDataWrapper* child) { + /* TODO: assert our return value != NULL */ + return "NYI"; } +static GHashTable* mime_function_table; + +static gchar* +get_bonobo_tag_for_object (CamelFormatter* formatter, + CamelDataWrapper* wrapper, + gchar* mimetype) +{ + + CamelMimeMessage* root = formatter->priv->current_root; + char* uid = lookup_unique_id (root, wrapper); + const char* goad_id = gnome_mime_get_value ( + mimetype, "bonobo-goad_id"); + + g_assert (root); + + if (goad_id) { + char* tag = g_strdup_printf ( + "", + goad_id, uid); + return tag; + } + else + return NULL; +} + -/* takes a mimetype, calls a function to handle it */ +/* + * 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 (CamelFormatter* formatter, CamelDataWrapper* wrapper, - gchar* mimetype) + gchar* mimetype_whole, /* ex. "image/jpeg" */ + gchar* mimetype_main) /* ex. "image" */ { - mime_handler_fn handler_function; - - /* try to find a handler function in our own lookup table */ - handler_function = g_hash_table_lookup ( - mime_function_table, mimetype); - - /* If there's no such handler function, try to find bonobo - * object to show the object */ - if (!handler_function) - { - CamelMimeMessage* root = formatter->priv->current_root; - char* uid = lookup_unique_id (root, wrapper); - const char* goad_id = gnome_mime_get_value ( - mimetype, "bonobo-goad_id"); - - g_assert (root && uid); - - if (goad_id) { - char* tag = g_strdup_printf ( - "", - goad_id, uid); - - camel_stream_write_string (formatter->priv->stream, - tag); - } - /* we don't know how to show something of this - mimetype; punt */ - else { - debug ("no function or bonobo object found for mimetype \"%s\"\n", - mimetype); - handler_function = handle_unknown_type; + mime_handler_fn handler_function = NULL; + + g_assert (mimetype_whole || mimetype_main); +/* + * Try to find a handler function in our own lookup table + */ + if (mimetype_whole) + handler_function = g_hash_table_lookup ( + mime_function_table, mimetype_whole); + + if (mimetype_main && !handler_function) + 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 ( + formatter, wrapper, mimetype_whole); + + if (mimetype_main && !bonobo_tag) + bonobo_tag = get_bonobo_tag_for_object ( + formatter, wrapper, mimetype_main); + + if (bonobo_tag) { + + /* we can print a tag, and return! */ + camel_stream_write_string (formatter->priv->stream, bonobo_tag); + g_free (bonobo_tag); + + return; } } - else { +/* + * Use either a handler function we've found, or a default handler + */ + if (handler_function) (*handler_function)(formatter, wrapper); + else { + handle_unknown_type (formatter, wrapper); + debug ("no function or bonobo object found for mimetype \"%s\"\n", + mimetype_whole || mimetype_main); } } @@ -195,81 +262,92 @@ call_handler_function (CamelFormatter* formatter, /* This routine was originally written by Daniel Velliard, (C) 1998 World Wide Web Consortium. - It will (for example) turn the input 'ab ' into 'ab <c>' */ + It will (for example) turn the input 'ab ' into 'ab <c>' + + It has also been altered to turn '\n' into
. +*/ static gchar * -encode_entities (const guchar *input, +text_to_html (const guchar *input, guint len, guint *encoded_len_return) { - const guchar *cur = input; - guchar *buffer = NULL; - guchar *out = NULL; - gint buffer_size = 0; - guint count; - - /* Allocate a translation buffer. */ - buffer_size = 1000; - buffer = g_malloc (buffer_size); - - out = buffer; - count = 0; - - while (count < len) { - if (out - buffer > buffer_size - 100) { - gint index = out - buffer; - - buffer_size *= 2; - buffer = g_realloc (buffer, buffer_size); - out = &buffer[index]; - } + const guchar *cur = input; + guchar *buffer = NULL; + guchar *out = NULL; + gint buffer_size = 0; + guint count; + + /* Allocate a translation buffer. */ + buffer_size = 1000; + buffer = g_malloc (buffer_size); + + out = buffer; + count = 0; + + while (count < len) { + if (out - buffer > buffer_size - 100) { + gint index = out - buffer; + + buffer_size *= 2; + buffer = g_realloc (buffer, buffer_size); + out = &buffer[index]; + } - /* By default one has to encode at least '<', '>', '"' and '&'. */ - - if (*cur == '<') { - *out++ = '&'; - *out++ = 'l'; - *out++ = 't'; - *out++ = ';'; - } else if (*cur == '>') { - *out++ = '&'; - *out++ = 'g'; - *out++ = 't'; - *out++ = ';'; - } else if (*cur == '&') { - *out++ = '&'; - *out++ = 'a'; - *out++ = 'm'; - *out++ = 'p'; - *out++ = ';'; - } else if (*cur == '"') { - *out++ = '&'; - *out++ = 'q'; - *out++ = 'u'; - *out++ = 'o'; - *out++ = 't'; - *out++ = ';'; - } else if (((*cur >= 0x20) && (*cur < 0x80)) - || (*cur == '\n') || (*cur == '\r') || (*cur == '\t')) { - /* Default case, just copy. */ - *out++ = *cur; - } else { - char buf[10], *ptr; - - g_snprintf(buf, 9, "&#%d;", *cur); - - ptr = buf; - while (*ptr != 0) - *out++ = *ptr++; - } + /* By default one has to encode at least '<', '>', '"' and '&'. */ + if (*cur == '<') { + *out++ = '&'; + *out++ = 'l'; + *out++ = 't'; + *out++ = ';'; + } else if (*cur == '>') { + *out++ = '&'; + *out++ = 'g'; + *out++ = 't'; + *out++ = ';'; + } else if (*cur == '&') { + *out++ = '&'; + *out++ = 'a'; + *out++ = 'm'; + *out++ = 'p'; + *out++ = ';'; + } else if (*cur == '"') { + *out++ = '&'; + *out++ = 'q'; + *out++ = 'u'; + *out++ = 'o'; + *out++ = 't'; + *out++ = ';'; + } else if (((*cur >= 0x20) && (*cur < 0x80)) + || (*cur == '\n') || (*cur == '\r') || (*cur == '\t')) { + /* Default case, just copy. */ + *out++ = *cur; + } else { + char buf[10], *ptr; + + g_snprintf(buf, 9, "&#%d;", *cur); + + ptr = buf; + while (*ptr != 0) + *out++ = *ptr++; + } - cur++; - count++; - } + /* turn newlines into
*/ + if (*cur == '\n') { + *out++ = '<'; + *out++ = 'b'; + *out++ = 'r'; + *out++ = '>'; + } + - *out = 0; - *encoded_len_return = out - buffer; + cur++; + count++; + } - return buffer; + *out = 0; + *encoded_len_return = out - buffer; + + return buffer; } @@ -279,7 +357,7 @@ write_field_to_stream (const gchar* description, const gchar* value, { gchar *s; guint ev_length; - gchar* encoded_value = value?encode_entities ( + gchar* encoded_value = value?text_to_html ( value, strlen(value), &ev_length):""; int i; for (i = 0; i < strlen (value); i++) @@ -405,21 +483,21 @@ handle_text_plain (CamelFormatter *formatter, CamelDataWrapper *wrapper) /* If there's any text, write it to the stream */ if (simple_data_wrapper->byte_array->len != 0) { + int returned_strlen; - debug ("yay, simple_data_wrapper->byte_array->len != 0\n"); + g_assert (simple_data_wrapper->byte_array->data); /* replace '<' with '<', etc. */ - text = encode_entities (simple_data_wrapper->byte_array->data, - simple_data_wrapper->byte_array->len, - &returned_strlen); + text = text_to_html (simple_data_wrapper->byte_array->data, + simple_data_wrapper->byte_array->len, + &returned_strlen); camel_stream_write_string (formatter->priv->stream, text); g_free (text); } - else { - debug ("boo, simple_data_wrapper->byte_array->len == 0\n"); - } + else + debug ("Warning: handle_text_plain: length of byte array is zero!\n"); debug ("handle_text_plain: exiting\n"); } @@ -427,10 +505,32 @@ handle_text_plain (CamelFormatter *formatter, CamelDataWrapper *wrapper) static void handle_text_html (CamelFormatter *formatter, CamelDataWrapper *wrapper) { + CamelSimpleDataWrapper* simple_data_wrapper; + gchar* text; + debug ("handle_text_html: entered\n"); - /* TODO: replace 'body' tag with 'table' tag; delete - everything prior to the 'body' tag */ + /* text is embodied in a CamelSimpleDataWrapper */ + g_assert (CAMEL_IS_SIMPLE_DATA_WRAPPER (wrapper)); + simple_data_wrapper = CAMEL_SIMPLE_DATA_WRAPPER (wrapper); + + /* If there's any text, write it to the stream */ + if (simple_data_wrapper->byte_array->len != 0) { + + int returned_strlen; + + g_assert (simple_data_wrapper->byte_array->data); + + /* replace '<' with '<', etc. */ + text = text_to_html (simple_data_wrapper->byte_array->data, + simple_data_wrapper->byte_array->len, + &returned_strlen); + + camel_stream_write_string (formatter->priv->stream, text); + g_free (text); + } + else + debug ("Warning: handle_text_html: length of byte array is zero!\n"); debug ("handle_text_html: exiting\n"); } @@ -444,7 +544,6 @@ handle_image (CamelFormatter *formatter, CamelDataWrapper *wrapper) debug ("handle_image: entered\n"); uuid = lookup_unique_id (formatter->priv->current_root, wrapper); - g_assert (uuid); tag = g_strdup_printf ("\n", uuid); camel_stream_write_string (formatter->priv->stream, tag); @@ -455,6 +554,18 @@ handle_image (CamelFormatter *formatter, CamelDataWrapper *wrapper) debug ("handle_image: exiting\n"); } +static void +handle_vcard (CamelFormatter *formatter, CamelDataWrapper *wrapper) +{ + gchar* vcard; + debug ("handle_vcard: entered\n"); + +// camel_stream_write_string (formatter->priv->stream, vcard); +// g_free (vcard); + + debug ("handle_vcard: exiting\n"); +} + static void handle_mime_message (CamelFormatter *formatter, CamelDataWrapper *wrapper) @@ -469,13 +580,10 @@ handle_mime_message (CamelFormatter *formatter, camel_stream_write_string (formatter->priv->stream, "
\n\n"); - /* write the subj:, to:, from: etc. fields out as html */ - write_header_info_to_stream (mime_message, - formatter->priv->stream); - /* dispatch the correct handler function for the mime type */ call_handler_function (formatter, message_contents, - MIME_TYPE_WHOLE (mime_message)); + MIME_TYPE_WHOLE (mime_message), + MIME_TYPE_MAIN (mime_message)); /* close up the table we opened */ camel_stream_write_string (formatter->priv->stream, @@ -504,6 +612,10 @@ find_preferred_displayable_body_part_in_multipart_alternative ( for (i = 0; i < max_multiparts; i++) { CamelMimeBodyPart* body_part = camel_multipart_get_part (multipart, i); + + if (!strcase_equal (MIME_TYPE_MAIN (body_part), "text")) + continue; + if (strcase_equal (MIME_TYPE_SUB (body_part), "plain")) { plain_part = CAMEL_MIME_PART (body_part); } @@ -519,16 +631,70 @@ find_preferred_displayable_body_part_in_multipart_alternative ( return NULL; } +/* we're maintaining a hashtable of mimetypes -> functions; + * those functions have the following signature...*/ +typedef void (*multipart_foreach_fn) (CamelMimeBodyPart* mime_part, + CamelFormatter* stream, + gpointer user_data); + +static void +multipart_foreach (CamelMultipart *multipart, + multipart_foreach_fn* fn, + CamelFormatter* formatter, + gpointer user_data) +{ + int i, max_multiparts; + + max_multiparts = camel_multipart_get_number (multipart); + for (i = 0; i < max_multiparts; i++) { + CamelMimeBodyPart* body_part = + camel_multipart_get_part (multipart, i); + + (*fn)(body_part, formatter, user_data); + } +} + + +/* called for each body part in a multipart/mixed, through + 'multipart_foreach' */ +static void +print_camel_body_part (CamelMimeBodyPart* body_part, + CamelFormatter* formatter, + gboolean* text_printed_yet) +{ + CamelDataWrapper* contents = + camel_medium_get_content_object (CAMEL_MEDIUM (body_part)); + gboolean is_text = + strcase_equal (MIME_TYPE_MAIN (body_part), "text"); + + if (is_text && *text_printed_yet) + return; + + call_handler_function (formatter, contents, MIME_TYPE_WHOLE (body_part), + MIME_TYPE_MAIN (body_part)); + camel_stream_write_string (formatter->priv->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 (CamelFormatter *formatter, CamelDataWrapper *wrapper) { CamelMultipart* mp = CAMEL_MULTIPART (wrapper); + gboolean text_printed_yet = FALSE; + debug ("handle_multipart_mixed: entered\n"); - /* TODO: serialize items in CamelMultipart */ - + multipart_foreach ( + mp, (multipart_foreach_fn*)print_camel_body_part, + formatter, + &text_printed_yet); + debug ("handle_multipart_mixed: exiting\n"); } @@ -539,22 +705,45 @@ handle_multipart_related (CamelFormatter *formatter, CamelMultipart* mp = CAMEL_MULTIPART (wrapper); debug ("handle_multipart_related: entered\n"); + debug ("handle_multipart_related: NYI!!\n"); + /* TODO: read RFC, in terms of how a one message may refer to another object */ debug ("handle_multipart_related: exiting\n"); } +/* + The current policy for multipart/alternate 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_alternate (CamelFormatter *formatter, CamelDataWrapper *wrapper) { - CamelMultipart* mp = CAMEL_MULTIPART (wrapper); + CamelMultipart* multipart = CAMEL_MULTIPART (wrapper); + CamelMimePart* mime_part; + debug ("handle_multipart_alternate: entered\n"); - /* TODO: - find_preferred_displayable_body_part_in_multipart_alternate */ + mime_part = find_preferred_displayable_body_part_in_multipart_alternative( + multipart); + if (mime_part) { + + CamelDataWrapper* contents = + camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); + call_handler_function (formatter, contents, + MIME_TYPE_WHOLE (mime_part), + MIME_TYPE_MAIN (mime_part)); + } + debug ("handle_multipart_alternate: exiting\n"); } @@ -567,7 +756,6 @@ handle_unknown_type (CamelFormatter *formatter, char* uid = lookup_unique_id (root, wrapper); debug ("handle_unknown_type: entered\n"); - g_assert (uid); tag = g_strdup_printf ("click-me-to-save\n", uid); @@ -588,6 +776,18 @@ camel_formatter_new () } +static void +_finalize (GtkObject* object) +{ + CamelFormatter *formatter = CAMEL_FORMATTER (object); + + if (formatter->priv->attachments) + g_hash_table_destroy (formatter->priv->attachments); + + g_free (formatter->priv); + + GTK_OBJECT_CLASS (parent_class)->finalize (object); +} static void camel_formatter_class_init (CamelFormatterClass *camel_formatter_class) @@ -597,7 +797,8 @@ camel_formatter_class_init (CamelFormatterClass *camel_formatter_class) parent_class = gtk_type_class (gtk_object_get_type ()); - mime_function_table = g_hash_table_new (g_str_hash, strcase_equal); + mime_function_table = + g_hash_table_new (g_str_hash, strcase_equal); #define ADD_HANDLER(a,b) g_hash_table_insert (mime_function_table, a, b) @@ -606,14 +807,15 @@ camel_formatter_class_init (CamelFormatterClass *camel_formatter_class) ADD_HANDLER ("text/html", handle_text_html); ADD_HANDLER ("multipart/alternate", handle_multipart_alternate); ADD_HANDLER ("multipart/related", handle_multipart_related); - ADD_HANDLER ("multipart/related", handle_multipart_mixed); + ADD_HANDLER ("multipart/mixed", handle_multipart_mixed); ADD_HANDLER ("message/rfc822", handle_mime_message); - ADD_HANDLER ("image/", handle_image); + ADD_HANDLER ("image/", handle_image); + ADD_HANDLER ("vcard/", handle_vcard); /* virtual method overload */ gtk_object_class->finalize = _finalize; } - + static void camel_formatter_init (gpointer object, gpointer klass) @@ -650,15 +852,7 @@ camel_formatter_get_type (void) } -static void -_finalize (GtkObject* object) -{ - CamelFormatter *formatter = CAMEL_FORMATTER (object); - g_free (formatter->priv); - - GTK_OBJECT_CLASS (parent_class)->finalize (object); -} diff --git a/camel/camel-formatter.h b/camel/camel-formatter.h index 30dbc2008e..186ce6b1e2 100644 --- a/camel/camel-formatter.h +++ b/camel/camel-formatter.h @@ -34,8 +34,8 @@ extern "C" { #include "camel.h" #define CAMEL_FORMATTER_TYPE (camel_formatter_get_type ()) -#define CAMEL_FORMATTER(obj) (GTK_CHECK_CAST((obj), CAMEL_FORMATTER_TYPE, CamelDataWrapper)) -#define CAMEL_FORMATTER_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), CAMEL_FORMATTER_TYPE, CamelDataWrapperClass)) +#define CAMEL_FORMATTER(obj) (GTK_CHECK_CAST((obj), CAMEL_FORMATTER_TYPE, CamelFormatter)) +#define CAMEL_FORMATTER_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), CAMEL_FORMATTER_TYPE, CamelFormatterClass)) #define CAMEL_IS_CAMEL_FORMATTER(o) (GTK_CHECK_TYPE((o), CAMEL_FORMATTER_TYPE)) typedef struct _CamelFormatterPrivate CamelFormatterPrivate; @@ -63,7 +63,8 @@ CamelFormatter* camel_formatter_new (void); produce html from it. */ void camel_formatter_mime_message_to_html (CamelFormatter* formatter, CamelMimeMessage* mime_message, - CamelStream* stream); + CamelStream* header_stream, + CamelStream* body_stream); #ifdef __cplusplus -- cgit v1.2.3