2000-02-17 bertrand <Bertrand.Guiheneuf@aful.org> * camel/camel-formatter.c (handle_text_plain): (handle_text_html): use camel_stream_reset instead of seek. The formatter should be able to work with all streams, not only seekable streams. In the case where some provider implementation would not be able to provide a reset method to their stream, implementors would have to find a workaround. * camel/camel-session.c (camel_session_new): use (void) instean of () in function decl. * camel/camel-folder.c: ifdef async operation related code. * camel/camel-seekable-stream.c (_seek): added a warning. (_reset): default implementation of reset for seekable stream. * camel/camel-mime-message.h: set_received_date declaration fix. cosmetic changes. * camel/providers/mbox/camel-mbox-provider.c (camel_provider_module_init): use (void) instead of (). * camel/camel-stream.c (camel_stream_reset): new method for CamelStream. svn path=/trunk/; revision=1835
1030 lines
27 KiB
C
1030 lines
27 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
|
|
/*--------------------------------*-C-*---------------------------------*
|
|
*
|
|
* 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 "camel-formatter.h"
|
|
|
|
#include "camel-log.h"
|
|
#include <libgnome/libgnome.h>
|
|
#include <ctype.h> /* for isprint */
|
|
#include <string.h> /* for strstr */
|
|
|
|
/*
|
|
* The CamelFormatter takes a mime message, and produces html from it,
|
|
* through the single function camel_formatter_mime_message_to_html().
|
|
* The flow of execution goes something like this:
|
|
*
|
|
* camel_formatter_mime_message_to_html()
|
|
* |
|
|
* V
|
|
* call_handler_function()
|
|
*
|
|
* Then, 'call_handler_function' acts as a dispatcher, using a
|
|
* hashtable to match a mime type to one of the following functions;
|
|
* note that the below functions sometimes then use
|
|
* 'call_handler_function()' to continue the process recursively.
|
|
*/
|
|
|
|
static void handle_text_plain (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper);
|
|
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_part (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper);
|
|
static void handle_multipart_mixed (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper);
|
|
static void handle_multipart_related (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper);
|
|
static void handle_multipart_alternative(CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper);
|
|
static void handle_unknown_type (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper);
|
|
|
|
/* 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);
|
|
|
|
/* compares strings case-insensitively */
|
|
static gint strcase_equal (gconstpointer v, gconstpointer v2);
|
|
static gchar* str_tolower (gchar* str);
|
|
|
|
/* writes the header info for a mime message into a stream */
|
|
static void write_header_info_to_stream (CamelMimeMessage* mime_message,
|
|
CamelStream* stream);
|
|
|
|
/* dispatch html printing via mimetype */
|
|
static void call_handler_function (CamelFormatter* formatter,
|
|
CamelDataWrapper* wrapper,
|
|
gchar* mimetype_whole,
|
|
gchar* mimetype_main);
|
|
|
|
static GtkObjectClass *parent_class = NULL;
|
|
|
|
struct _CamelFormatterPrivate {
|
|
CamelDataWrapper *current_root;
|
|
CamelStream *stream;
|
|
GHashTable *attachments;
|
|
};
|
|
|
|
|
|
static void
|
|
debug (const gchar *format, ...)
|
|
{
|
|
va_list args;
|
|
gchar *string;
|
|
|
|
g_return_if_fail (format != NULL);
|
|
|
|
va_start (args, format);
|
|
string = g_strdup_vprintf (format, args);
|
|
va_end (args);
|
|
|
|
fputs (string, stdout);
|
|
fflush (stdout);
|
|
|
|
g_free (string);
|
|
}
|
|
|
|
static void
|
|
initialize_camel_formatter (CamelFormatter* formatter,
|
|
CamelDataWrapper* data_wrapper,
|
|
CamelStream* stream)
|
|
{
|
|
CamelFormatterPrivate* fmt = formatter->priv;
|
|
|
|
/* initialize members of our formatter */
|
|
fmt->current_root = data_wrapper;
|
|
fmt->stream = stream;
|
|
if (fmt->attachments)
|
|
g_hash_table_destroy (fmt->attachments);
|
|
fmt->attachments = g_hash_table_new (g_str_hash, strcase_equal);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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);
|
|
|
|
g_print ("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);
|
|
}
|
|
|
|
|
|
/**
|
|
* camel_formatter_mime_message_to_html:
|
|
* @formatter: the camel formatter object
|
|
* @mime_message: the input mime message
|
|
* @header_stream: byte stream where data will be written (can be
|
|
* NULL)
|
|
* @body_stream: byte stream where data will be written (required)
|
|
*
|
|
* Writes a CamelMimeMessage out, as html, into a stream passed in as
|
|
* a parameter.
|
|
**/
|
|
void
|
|
camel_formatter_mime_message_to_html (CamelFormatter* formatter,
|
|
CamelMimeMessage* mime_message,
|
|
CamelStream* header_stream,
|
|
CamelStream* body_stream)
|
|
{
|
|
g_print ("camel_formatter_mime_message_to_html: entered\n");
|
|
|
|
g_assert (formatter != NULL);
|
|
g_assert (CAMEL_IS_FORMATTER (formatter));
|
|
g_assert (mime_message != NULL);
|
|
g_assert (CAMEL_IS_MIME_MESSAGE (mime_message));
|
|
|
|
g_assert (header_stream || body_stream);
|
|
|
|
/* give the root CamelDataWrapper and the stream to the
|
|
formatter */
|
|
initialize_camel_formatter (formatter,
|
|
CAMEL_DATA_WRAPPER (mime_message),
|
|
body_stream);
|
|
|
|
if (body_stream) {
|
|
/* Write the contents of the mime message to the stream */
|
|
camel_stream_write_string (body_stream, "<html><body>\n");
|
|
call_handler_function (
|
|
formatter,
|
|
CAMEL_DATA_WRAPPER (mime_message),
|
|
"message/rfc822",
|
|
"message");
|
|
camel_stream_write_string (body_stream, "\n</body></html>\n");
|
|
}
|
|
|
|
/* write the subj:, to:, from: etc. fields out as html to the
|
|
header stream */
|
|
if (header_stream)
|
|
write_header_info_to_stream (mime_message,
|
|
header_stream);
|
|
}
|
|
|
|
/* we're maintaining a hashtable of mimetypes -> functions;
|
|
* those functions have the following signature...*/
|
|
typedef void (*mime_handler_fn) (CamelFormatter *formatter,
|
|
CamelDataWrapper *data_wrapper);
|
|
|
|
static gchar*
|
|
lookup_unique_id (CamelDataWrapper* root, CamelDataWrapper* child)
|
|
{
|
|
/* TODO: assert our return value != NULL */
|
|
|
|
return "NYI";
|
|
}
|
|
|
|
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 (CamelFormatter* formatter,
|
|
CamelDataWrapper* wrapper,
|
|
gchar* mimetype)
|
|
{
|
|
|
|
CamelDataWrapper* 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 (
|
|
"<object classid=\"%s\" uid=\"camel://%s\">",
|
|
goad_id, uid);
|
|
return tag;
|
|
}
|
|
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 (CamelFormatter* formatter,
|
|
CamelDataWrapper* wrapper,
|
|
gchar* mimetype_whole_in, /* ex. "image/jpeg" */
|
|
gchar* mimetype_main_in) /* ex. "image" */
|
|
{
|
|
mime_handler_fn handler_function = NULL;
|
|
gchar* mimetype_whole = NULL;
|
|
gchar* mimetype_main = NULL;
|
|
|
|
g_assert (formatter);
|
|
g_assert (mimetype_whole_in || mimetype_main_in);
|
|
g_assert (wrapper);
|
|
|
|
/*
|
|
* Try to find a handler function in our own lookup table
|
|
*/
|
|
if (mimetype_whole_in) {
|
|
mimetype_whole = str_tolower (mimetype_whole_in);
|
|
|
|
handler_function = g_hash_table_lookup (
|
|
mime_function_table, mimetype_whole);
|
|
}
|
|
|
|
if (mimetype_main_in)
|
|
mimetype_main = str_tolower (mimetype_main_in);
|
|
|
|
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);
|
|
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)(formatter, wrapper);
|
|
else {
|
|
handle_unknown_type (formatter, wrapper);
|
|
debug ("no function or bonobo object found for mimetype \"%s\"\n",
|
|
mimetype_whole?mimetype_whole:mimetype_main);
|
|
}
|
|
if (mimetype_whole) g_free (mimetype_whole);
|
|
if (mimetype_main) g_free (mimetype_main);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*
|
|
* Header (ex. "subj:", "from:") helper functions for mime msgs
|
|
*----------------------------------------------------------------------*/
|
|
|
|
/* This routine was originally written by Daniel Velliard, (C) 1998
|
|
* World Wide Web Consortium.
|
|
* - It will (for example) turn the input 'ab <c>' into 'ab <c>'
|
|
* - It has also been altered to turn '\n' into <br>. */
|
|
static gchar *
|
|
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];
|
|
}
|
|
|
|
/* 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++;
|
|
}
|
|
|
|
/* turn newlines into <br> */
|
|
if (*cur == '\n') {
|
|
*out++ = '<';
|
|
*out++ = 'b';
|
|
*out++ = 'r';
|
|
*out++ = '>';
|
|
}
|
|
|
|
|
|
cur++;
|
|
count++;
|
|
}
|
|
|
|
*out = 0;
|
|
*encoded_len_return = out - buffer;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
|
|
static void
|
|
write_field_to_stream (const gchar* description, const gchar* value,
|
|
CamelStream *stream, gboolean as_table_row)
|
|
{
|
|
gchar *s;
|
|
guint ev_length;
|
|
gchar* encoded_value = value?text_to_html (
|
|
value, strlen(value), &ev_length):"";
|
|
int i;
|
|
for (i = 0; i < strlen (value); i++)
|
|
if (!isprint(encoded_value[i]))
|
|
encoded_value[i] = 'Z';
|
|
|
|
g_assert (description && value);
|
|
|
|
s = g_strdup_printf ("%s<b>%s</b>%s%s%s\n",
|
|
as_table_row?"<tr><td>":"",
|
|
description,
|
|
as_table_row?"</td><td>":" ",
|
|
encoded_value,
|
|
as_table_row?"</td></tr>":"<br>");
|
|
|
|
camel_stream_write_string (stream, s);
|
|
g_free (encoded_value);
|
|
g_free (s);
|
|
}
|
|
|
|
|
|
static void
|
|
write_recipients_to_stream (const gchar* recipient_type,
|
|
const GList* recipients,
|
|
CamelStream* stream,
|
|
gboolean as_table_row)
|
|
{
|
|
/* list of recipients, like "elvis@graceland; bart@springfield" */
|
|
gchar *recipients_string = NULL;
|
|
g_assert (recipient_type && stream);
|
|
|
|
/* Write out each recipient of 'recipient_type' to the stream */
|
|
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,
|
|
as_table_row);
|
|
|
|
g_free (recipients_string);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
write_header_info_to_stream (CamelMimeMessage* mime_message,
|
|
CamelStream* stream)
|
|
{
|
|
gchar *s = NULL;
|
|
const GList *recipients = NULL;
|
|
|
|
g_assert (mime_message && stream);
|
|
|
|
camel_stream_write_string (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'. */
|
|
if ((s = (gchar*)camel_mime_message_get_subject (mime_message))) {
|
|
write_field_to_stream ("Subject: ", s, stream, TRUE);
|
|
}
|
|
|
|
if ((s = (gchar*)camel_mime_message_get_from (mime_message))) {
|
|
write_field_to_stream ("From: ", s, stream, TRUE);
|
|
}
|
|
|
|
if ((s = (gchar*)camel_mime_message_get_received_date (mime_message))) {
|
|
write_field_to_stream ("Received Date: ", s, stream, TRUE);
|
|
}
|
|
|
|
if ((s = (gchar*)camel_mime_message_get_sent_date (mime_message))) {
|
|
write_field_to_stream ("Sent Date: ", s, stream, TRUE);
|
|
}
|
|
|
|
/* Fill out the "To:" recipients line */
|
|
recipients = camel_mime_message_get_recipients (
|
|
mime_message, CAMEL_RECIPIENT_TYPE_TO);
|
|
|
|
if (recipients)
|
|
write_recipients_to_stream ("To:", recipients, stream, TRUE);
|
|
|
|
/* Fill out the "CC:" recipients line */
|
|
recipients = camel_mime_message_get_recipients (
|
|
mime_message, CAMEL_RECIPIENT_TYPE_CC);
|
|
if (recipients)
|
|
write_recipients_to_stream ("CC:", recipients, stream, TRUE);
|
|
|
|
/* Fill out the "BCC:" recipients line */
|
|
recipients = camel_mime_message_get_recipients (
|
|
mime_message, CAMEL_RECIPIENT_TYPE_BCC);
|
|
if (recipients)
|
|
write_recipients_to_stream ("BCC:", recipients, stream, TRUE);
|
|
|
|
camel_stream_write_string (stream, "</table>");
|
|
}
|
|
|
|
/* case-insensitive string comparison */
|
|
static gint
|
|
strcase_equal (gconstpointer v, gconstpointer v2)
|
|
{
|
|
return g_strcasecmp ((const gchar*) v, (const gchar*)v2) == 0;
|
|
}
|
|
|
|
static gchar*
|
|
str_tolower (gchar* str)
|
|
{
|
|
int i;
|
|
int len = strlen (str);
|
|
gchar* new_str = g_strdup (str);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
new_str[i] = tolower (str[i]);
|
|
}
|
|
return new_str;
|
|
}
|
|
|
|
|
|
#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 (CamelFormatter *formatter, CamelDataWrapper *wrapper)
|
|
{
|
|
gchar* text;
|
|
CamelStream *wrapper_output_stream;
|
|
gchar tmp_buffer[4096];
|
|
gint nb_bytes_read;
|
|
gboolean empty_text = TRUE;
|
|
|
|
debug ("handle_text_plain: entered\n");
|
|
|
|
camel_stream_write_string (formatter->priv->stream,
|
|
"\n<!-- text/plain below -->\n");
|
|
|
|
if (strcmp (wrapper->mime_type->subtype, "richtext") == 0) {
|
|
|
|
camel_stream_write_string (
|
|
formatter->priv->stream,
|
|
"<center><b><table bgcolor=\"b0b0ff\" cellpadding=3><tr><td>Warning: the following richtext may not");
|
|
camel_stream_write_string (
|
|
formatter->priv->stream,
|
|
" 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);
|
|
|
|
camel_stream_write_string (formatter->priv->stream, text);
|
|
g_free (text);
|
|
}
|
|
|
|
|
|
} while (!camel_stream_eos (wrapper_output_stream));
|
|
|
|
|
|
if (empty_text) {
|
|
debug ("Warning: handle_text_plain: text part is empty!\n");
|
|
camel_stream_write_string (formatter->priv->stream,
|
|
"<b>(empty)</b>");
|
|
}
|
|
|
|
debug ("handle_text_plain: exiting\n");
|
|
}
|
|
|
|
static void
|
|
handle_text_html (CamelFormatter *formatter, CamelDataWrapper *wrapper)
|
|
{
|
|
CamelStream *wrapper_output_stream;
|
|
gchar tmp_buffer[4096];
|
|
gint nb_bytes_read;
|
|
gboolean empty_text = TRUE;
|
|
|
|
|
|
debug ("handle_text_html: entered\n");
|
|
|
|
/* 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 */
|
|
camel_stream_write_string (formatter->priv->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 formater output stream */
|
|
camel_stream_write (formatter->priv->stream, tmp_buffer, nb_bytes_read);
|
|
}
|
|
|
|
|
|
} while (!camel_stream_eos (wrapper_output_stream));
|
|
|
|
|
|
if (empty_text) {
|
|
debug ("Warning: handle_text_html: html part is empty!\n");
|
|
camel_stream_write_string (formatter->priv->stream,
|
|
"<b>(empty)</b>");
|
|
}
|
|
|
|
debug ("handle_text_html: exiting\n");
|
|
}
|
|
|
|
static void
|
|
handle_image (CamelFormatter *formatter, CamelDataWrapper *wrapper)
|
|
{
|
|
gchar* uuid;
|
|
gchar* tag;
|
|
|
|
debug ("handle_image: entered\n");
|
|
|
|
uuid = lookup_unique_id (formatter->priv->current_root, wrapper);
|
|
|
|
tag = g_strdup_printf ("<img src=\"%s\">\n", uuid);
|
|
camel_stream_write_string (formatter->priv->stream, tag);
|
|
|
|
g_free (uuid);
|
|
g_free (tag);
|
|
|
|
debug ("handle_image: exiting\n");
|
|
}
|
|
|
|
static void
|
|
handle_vcard (CamelFormatter *formatter, CamelDataWrapper *wrapper)
|
|
{
|
|
gchar* vcard = NULL;
|
|
debug ("handle_vcard: entered\n");
|
|
|
|
camel_stream_write_string (formatter->priv->stream,
|
|
"\n<!-- image below -->\n");
|
|
// camel_stream_write_string (formatter->priv->stream, vcard);
|
|
// g_free (vcard);
|
|
|
|
debug ("handle_vcard: exiting\n");
|
|
}
|
|
|
|
static void
|
|
handle_mime_part (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper)
|
|
{
|
|
CamelMimePart* mime_part;
|
|
CamelDataWrapper* message_contents;
|
|
|
|
g_assert (formatter);
|
|
g_assert (wrapper);
|
|
g_assert (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);
|
|
|
|
debug ("handle_mime_part: entered\n");
|
|
camel_stream_write_string (formatter->priv->stream,
|
|
"\n<!-- mime message below -->\n");
|
|
|
|
// camel_stream_write_string (formatter->priv->stream,
|
|
// "<table width=95% border=1><tr><td>\n\n");
|
|
|
|
/* dispatch the correct handler function for the mime type */
|
|
call_handler_function (formatter, message_contents,
|
|
MIME_TYPE_WHOLE (mime_part),
|
|
MIME_TYPE_MAIN (mime_part));
|
|
|
|
/* close up the table we opened */
|
|
// camel_stream_write_string (formatter->priv->stream,
|
|
// "\n\n</td></tr></table>\n\n");
|
|
|
|
debug ("handle_mime_part: exiting\n");
|
|
}
|
|
|
|
|
|
/*
|
|
* multipart-alternative helper function --
|
|
* returns NULL if no text/html or text/plan msg is found
|
|
*/
|
|
static CamelMimePart*
|
|
find_preferred_displayable_body_part_in_multipart_alternative (
|
|
CamelMultipart* multipart)
|
|
{
|
|
int i, max_multiparts;
|
|
CamelMimePart* html_part = NULL;
|
|
CamelMimePart* plain_part = NULL;
|
|
|
|
/* find out out many parts are in it...*/
|
|
max_multiparts = camel_multipart_get_number (multipart);
|
|
|
|
/* TODO: DO LEAF-LOOKUP HERE FOR OTHER MIME-TYPES!!! */
|
|
|
|
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);
|
|
}
|
|
else if (strcase_equal (MIME_TYPE_SUB (body_part), "html")) {
|
|
html_part = CAMEL_MIME_PART (body_part);
|
|
}
|
|
}
|
|
|
|
if (html_part)
|
|
return html_part;
|
|
if (plain_part)
|
|
return plain_part;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* called for each body part in a multipart/mixed */
|
|
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<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 (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper)
|
|
{
|
|
CamelMultipart* mp;
|
|
gboolean text_printed_yet = FALSE;
|
|
|
|
g_assert (formatter);
|
|
g_assert (wrapper);
|
|
g_assert (CAMEL_IS_MULTIPART (wrapper));
|
|
|
|
mp = CAMEL_MULTIPART (wrapper);
|
|
g_assert (mp);
|
|
|
|
// debug ("handle_multipart_mixed: entered\n");
|
|
|
|
|
|
{
|
|
int i, max_multiparts;
|
|
|
|
max_multiparts = camel_multipart_get_number (mp);
|
|
for (i = 0; i < max_multiparts; i++) {
|
|
CamelMimeBodyPart* body_part =
|
|
camel_multipart_get_part (mp, i);
|
|
|
|
print_camel_body_part (body_part, formatter, &text_printed_yet);
|
|
}
|
|
}
|
|
|
|
|
|
// debug ("handle_multipart_mixed: exiting\n");
|
|
}
|
|
|
|
static void
|
|
handle_multipart_related (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper)
|
|
{
|
|
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/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 (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper)
|
|
{
|
|
CamelMultipart* multipart = CAMEL_MULTIPART (wrapper);
|
|
CamelMimePart* mime_part;
|
|
|
|
debug ("handle_multipart_alternative: entered\n");
|
|
|
|
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_alternative: exiting\n");
|
|
}
|
|
|
|
static void
|
|
handle_unknown_type (CamelFormatter *formatter,
|
|
CamelDataWrapper *wrapper)
|
|
{
|
|
gchar* tag;
|
|
CamelDataWrapper* root = formatter->priv->current_root;
|
|
char* uid = lookup_unique_id (root, wrapper);
|
|
|
|
debug ("handle_unknown_type: entered\n");
|
|
|
|
tag = g_strdup_printf ("<a href=\"camel://%s\">click-me-to-save</a>\n",
|
|
uid);
|
|
|
|
camel_stream_write_string (formatter->priv->stream, tag);
|
|
|
|
debug ("handle_unknown_type: exiting\n");
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*
|
|
* Standard Gtk+ class functions
|
|
*----------------------------------------------------------------------*/
|
|
|
|
CamelFormatter*
|
|
camel_formatter_new ()
|
|
{
|
|
return (gtk_type_new (CAMEL_FORMATTER_TYPE));
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
GtkObjectClass *gtk_object_class =
|
|
GTK_OBJECT_CLASS (camel_formatter_class);
|
|
|
|
parent_class = gtk_type_class (gtk_object_get_type ());
|
|
|
|
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)
|
|
|
|
/* hook up mime types to functions that handle them */
|
|
ADD_HANDLER ("text/plain", handle_text_plain);
|
|
ADD_HANDLER ("text/richtext", handle_text_plain);
|
|
ADD_HANDLER ("text/html", handle_text_html);
|
|
ADD_HANDLER ("multipart/alternative", handle_multipart_alternative);
|
|
ADD_HANDLER ("multipart/related", handle_multipart_related);
|
|
ADD_HANDLER ("multipart/mixed", handle_multipart_mixed);
|
|
ADD_HANDLER ("message/rfc822", handle_mime_part);
|
|
ADD_HANDLER ("image/", handle_image);
|
|
ADD_HANDLER ("vcard/", handle_vcard);
|
|
|
|
/* body parts don't have mime parts per se, so camel
|
|
sticks on the following one */
|
|
ADD_HANDLER ("mime/body-part", handle_mime_part);
|
|
|
|
/* virtual method overload */
|
|
gtk_object_class->finalize = _finalize;
|
|
}
|
|
|
|
|
|
static void
|
|
camel_formatter_init (gpointer object, gpointer klass)
|
|
{
|
|
CamelFormatter* cmf = CAMEL_FORMATTER (object);
|
|
cmf->priv = g_new (CamelFormatterPrivate, 1);
|
|
cmf->priv->attachments = NULL;
|
|
}
|
|
|
|
|
|
GtkType
|
|
camel_formatter_get_type (void)
|
|
{
|
|
static GtkType camel_formatter_type = 0;
|
|
|
|
if (!camel_formatter_type) {
|
|
GtkTypeInfo camel_formatter_info =
|
|
{
|
|
"CamelFormatter",
|
|
sizeof (CamelFormatter),
|
|
sizeof (CamelFormatterClass),
|
|
(GtkClassInitFunc) camel_formatter_class_init,
|
|
(GtkObjectInitFunc) camel_formatter_init,
|
|
/* reserved_1 */ NULL,
|
|
/* reserved_2 */ NULL,
|
|
(GtkClassInitFunc) NULL,
|
|
};
|
|
|
|
camel_formatter_type = gtk_type_unique (
|
|
gtk_object_get_type (),
|
|
&camel_formatter_info);
|
|
}
|
|
|
|
return camel_formatter_type;
|
|
}
|