3014 lines
84 KiB
C
3014 lines
84 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) version 3.
|
|
*
|
|
* 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with the program; if not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*
|
|
* Authors:
|
|
* Michael Zucchi <notzed@ximian.com>
|
|
*
|
|
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#define _GNU_SOURCE /* Enable strcasestr in string.h */
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#ifdef G_OS_WIN32
|
|
/* Work around 'DATADIR' and 'interface' lossage in <windows.h> */
|
|
#define DATADIR crap_DATADIR
|
|
#include <windows.h>
|
|
#undef DATADIR
|
|
#undef interface
|
|
#endif
|
|
|
|
#include <libebackend/e-extensible.h>
|
|
#include <libedataserver/e-time-utils.h>
|
|
#include <libedataserver/e-data-server-util.h> /* for e_utf8_strftime, what about e_time_format_time? */
|
|
|
|
#include "e-util/e-datetime-format.h"
|
|
#include "e-util/e-icon-factory.h"
|
|
#include "e-util/e-util-private.h"
|
|
#include "e-util/e-util.h"
|
|
#include "misc/e-web-view.h"
|
|
|
|
#include <shell/e-shell.h>
|
|
|
|
#include <glib/gi18n.h>
|
|
|
|
#include <JavaScriptCore/JavaScript.h>
|
|
#include <webkit/webkit.h>
|
|
|
|
#include <libemail-utils/mail-mt.h>
|
|
#include <libemail-engine/e-mail-enumtypes.h>
|
|
#include <libemail-engine/e-mail-utils.h>
|
|
#include <libemail-engine/mail-config.h>
|
|
|
|
#include "em-format-html.h"
|
|
#include "em-utils.h"
|
|
#include "e-mail-display.h"
|
|
#include <em-format/em-inline-filter.h>
|
|
|
|
#define EM_FORMAT_HTML_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE \
|
|
((obj), EM_TYPE_FORMAT_HTML, EMFormatHTMLPrivate))
|
|
|
|
#define d(x)
|
|
|
|
struct _EMFormatHTMLPrivate {
|
|
GdkColor colors[EM_FORMAT_HTML_NUM_COLOR_TYPES];
|
|
EMailImageLoadingPolicy image_loading_policy;
|
|
|
|
guint can_load_images : 1;
|
|
guint only_local_photos : 1;
|
|
guint show_sender_photo : 1;
|
|
guint show_real_date : 1;
|
|
guint animate_images : 1;
|
|
};
|
|
|
|
static gpointer parent_class;
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_BODY_COLOR,
|
|
PROP_CITATION_COLOR,
|
|
PROP_CONTENT_COLOR,
|
|
PROP_FRAME_COLOR,
|
|
PROP_HEADER_COLOR,
|
|
PROP_IMAGE_LOADING_POLICY,
|
|
PROP_MARK_CITATIONS,
|
|
PROP_ONLY_LOCAL_PHOTOS,
|
|
PROP_SHOW_SENDER_PHOTO,
|
|
PROP_SHOW_REAL_DATE,
|
|
PROP_TEXT_COLOR,
|
|
PROP_ANIMATE_IMAGES
|
|
};
|
|
|
|
#define EFM_MESSAGE_START_ANAME "evolution_message_start"
|
|
#define EFH_MESSAGE_START "<A name=\"" EFM_MESSAGE_START_ANAME "\"></A>"
|
|
|
|
static void efh_parse_image (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
|
|
static void efh_parse_text_enriched (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
|
|
static void efh_parse_text_plain (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
|
|
static void efh_parse_text_html (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
|
|
static void efh_parse_message_external (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
|
|
static void efh_parse_message_deliverystatus (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
|
|
static void efh_parse_message_rfc822 (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
|
|
|
|
static void efh_write_image (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
static void efh_write_text_enriched (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
static void efh_write_text_plain (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
static void efh_write_text_html (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
static void efh_write_source (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
static void efh_write_headers (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
static void efh_write_attachment (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
static void efh_write_error (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
static void efh_write_message_rfc822 (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
|
|
static void efh_format_full_headers (EMFormatHTML *efh, GString *buffer, CamelMedium *part, gboolean all_headers, gboolean visible, GCancellable *cancellable);
|
|
static void efh_format_short_headers (EMFormatHTML *efh, GString *buffer, CamelMedium *part, gboolean visible, GCancellable *cancellable);
|
|
|
|
static void efh_write_message (EMFormat *emf, GList *puris, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
|
|
|
|
/*****************************************************************************/
|
|
static void
|
|
efh_parse_image (EMFormat *emf,
|
|
CamelMimePart *part,
|
|
GString *part_id,
|
|
EMFormatParserInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatPURI *puri;
|
|
const gchar *tmp;
|
|
gchar *cid;
|
|
gint len;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
tmp = camel_mime_part_get_content_id (part);
|
|
if (!tmp) {
|
|
em_format_parse_part_as (emf, part, part_id, info,
|
|
"x-evolution/message/attachment", cancellable);
|
|
return;
|
|
}
|
|
|
|
cid = g_strdup_printf ("cid:%s", tmp);
|
|
len = part_id->len;
|
|
g_string_append (part_id, ".image");
|
|
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
|
|
puri->cid = cid;
|
|
puri->write_func = efh_write_image;
|
|
puri->mime_type = g_strdup (info->handler->mime_type);
|
|
puri->is_attachment = TRUE;
|
|
puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
|
|
puri->validity_type = info->validity_type;
|
|
|
|
em_format_add_puri (emf, puri);
|
|
g_string_truncate (part_id, len);
|
|
}
|
|
|
|
static void
|
|
efh_parse_text_enriched (EMFormat *emf,
|
|
CamelMimePart *part,
|
|
GString *part_id,
|
|
EMFormatParserInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatPURI *puri;
|
|
const gchar *tmp;
|
|
gchar *cid;
|
|
gint len;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
tmp = camel_mime_part_get_content_id (part);
|
|
if (!tmp) {
|
|
cid = g_strdup_printf ("em-no-cid:%s", part_id->str);
|
|
} else {
|
|
cid = g_strdup_printf ("cid:%s", tmp);
|
|
}
|
|
|
|
len = part_id->len;
|
|
g_string_append (part_id, ".text_enriched");
|
|
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
|
|
puri->cid = cid;
|
|
puri->mime_type = g_strdup (info->handler->mime_type);
|
|
puri->write_func = efh_write_text_enriched;
|
|
puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
|
|
puri->validity_type = info->validity_type;
|
|
puri->is_attachment = info->is_attachment;
|
|
|
|
em_format_add_puri (emf, puri);
|
|
g_string_truncate (part_id, len);
|
|
}
|
|
|
|
static void
|
|
efh_parse_text_plain (EMFormat *emf,
|
|
CamelMimePart *part,
|
|
GString *part_id,
|
|
EMFormatParserInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatPURI *puri;
|
|
CamelStream *filtered_stream, *null;
|
|
CamelMultipart *mp;
|
|
CamelDataWrapper *dw;
|
|
CamelContentType *type;
|
|
gint i, count, len;
|
|
EMInlineFilter *inline_filter;
|
|
gboolean charset_added = FALSE;
|
|
const gchar *snoop_type = NULL;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
dw = camel_medium_get_content ((CamelMedium *) part);
|
|
if (!dw)
|
|
return;
|
|
|
|
/* This scans the text part for inline-encoded data, creates
|
|
* a multipart of all the parts inside it. */
|
|
|
|
/* FIXME: We should discard this multipart if it only contains
|
|
* the original text, but it makes this hash lookup more complex */
|
|
|
|
/* TODO: We could probably put this in the superclass, since
|
|
* no knowledge of html is required - but this messes with
|
|
* filters a bit. Perhaps the superclass should just deal with
|
|
* html anyway and be done with it ... */
|
|
|
|
if (!dw->mime_type)
|
|
snoop_type = em_format_snoop_type (part);
|
|
|
|
/* if we had to snoop the part type to get here, then
|
|
* use that as the base type, yuck */
|
|
if (snoop_type == NULL
|
|
|| (type = camel_content_type_decode (snoop_type)) == NULL) {
|
|
type = dw->mime_type;
|
|
camel_content_type_ref (type);
|
|
}
|
|
|
|
if (dw->mime_type && type != dw->mime_type && camel_content_type_param (dw->mime_type, "charset")) {
|
|
camel_content_type_set_param (type, "charset", camel_content_type_param (dw->mime_type, "charset"));
|
|
charset_added = TRUE;
|
|
}
|
|
|
|
null = camel_stream_null_new ();
|
|
filtered_stream = camel_stream_filter_new (null);
|
|
g_object_unref (null);
|
|
inline_filter = em_inline_filter_new (camel_mime_part_get_encoding (part), type);
|
|
camel_stream_filter_add (
|
|
CAMEL_STREAM_FILTER (filtered_stream),
|
|
CAMEL_MIME_FILTER (inline_filter));
|
|
camel_data_wrapper_decode_to_stream_sync (
|
|
dw, (CamelStream *) filtered_stream, cancellable, NULL);
|
|
camel_stream_close ((CamelStream *) filtered_stream, cancellable, NULL);
|
|
g_object_unref (filtered_stream);
|
|
|
|
mp = em_inline_filter_get_multipart (inline_filter);
|
|
|
|
if (charset_added) {
|
|
camel_content_type_set_param (type, "charset", NULL);
|
|
}
|
|
|
|
g_object_unref (inline_filter);
|
|
camel_content_type_unref (type);
|
|
|
|
/* We handle our made-up multipart here, so we don't recursively call ourselves */
|
|
len = part_id->len;
|
|
count = camel_multipart_get_number (mp);
|
|
for (i = 0; i < count; i++) {
|
|
CamelMimePart *newpart = camel_multipart_get_part (mp, i);
|
|
|
|
if (!newpart)
|
|
continue;
|
|
|
|
type = camel_mime_part_get_content_type (newpart);
|
|
if (camel_content_type_is (type, "text", "*") && (!camel_content_type_is (type, "text", "calendar"))) {
|
|
gint s_len = part_id->len;
|
|
|
|
g_string_append (part_id, ".plain_text");
|
|
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), newpart, part_id->str);
|
|
puri->write_func = efh_write_text_plain;
|
|
puri->mime_type = g_strdup ("text/html");
|
|
puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
|
|
puri->validity_type = info->validity_type;
|
|
puri->is_attachment = info->is_attachment;
|
|
g_string_truncate (part_id, s_len);
|
|
em_format_add_puri (emf, puri);
|
|
} else {
|
|
g_string_append_printf (part_id, ".inline.%d", i);
|
|
em_format_parse_part (emf, CAMEL_MIME_PART (newpart), part_id, info, cancellable);
|
|
g_string_truncate (part_id, len);
|
|
}
|
|
}
|
|
|
|
g_object_unref (mp);
|
|
}
|
|
|
|
static void
|
|
efh_parse_text_html (EMFormat *emf,
|
|
CamelMimePart *part,
|
|
GString *part_id,
|
|
EMFormatParserInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatPURI *puri;
|
|
const gchar *location;
|
|
gchar *cid = NULL;
|
|
CamelURL *base;
|
|
gint len;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
base = em_format_get_base_url (emf);
|
|
location = camel_mime_part_get_content_location (part);
|
|
if (location == NULL) {
|
|
if (base)
|
|
cid = camel_url_to_string (base, 0);
|
|
else
|
|
cid = g_strdup (part_id->str);
|
|
} else {
|
|
if (strchr (location, ':') == NULL && base != NULL) {
|
|
CamelURL *uri;
|
|
|
|
uri = camel_url_new_with_base (base, location);
|
|
cid = camel_url_to_string (uri, 0);
|
|
camel_url_free (uri);
|
|
} else {
|
|
cid = g_strdup (location);
|
|
}
|
|
}
|
|
|
|
len = part_id->len;
|
|
g_string_append (part_id, ".text_html");
|
|
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
|
|
puri->write_func = efh_write_text_html;
|
|
puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
|
|
puri->validity_type = info->validity_type;
|
|
puri->is_attachment = info->is_attachment;
|
|
|
|
em_format_add_puri (emf, puri);
|
|
g_string_truncate (part_id, len);
|
|
|
|
if (cid)
|
|
g_free (cid);
|
|
}
|
|
|
|
static void
|
|
efh_parse_message_external (EMFormat *emf,
|
|
CamelMimePart *part,
|
|
GString *part_id,
|
|
EMFormatParserInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatPURI *puri;
|
|
CamelMimePart *newpart;
|
|
CamelContentType *type;
|
|
const gchar *access_type;
|
|
gchar *url = NULL, *desc = NULL;
|
|
gchar *content;
|
|
gint len;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
newpart = camel_mime_part_new ();
|
|
|
|
/* needs to be cleaner */
|
|
type = camel_mime_part_get_content_type (part);
|
|
access_type = camel_content_type_param (type, "access-type");
|
|
if (!access_type) {
|
|
const gchar *msg = _("Malformed external-body part");
|
|
camel_mime_part_set_content (newpart, msg, strlen (msg),
|
|
"text/plain");
|
|
goto addPart;
|
|
}
|
|
|
|
if (!g_ascii_strcasecmp(access_type, "ftp") ||
|
|
!g_ascii_strcasecmp(access_type, "anon-ftp")) {
|
|
const gchar *name, *site, *dir, *mode;
|
|
gchar *path;
|
|
gchar ftype[16];
|
|
|
|
name = camel_content_type_param (type, "name");
|
|
site = camel_content_type_param (type, "site");
|
|
dir = camel_content_type_param (type, "directory");
|
|
mode = camel_content_type_param (type, "mode");
|
|
if (name == NULL || site == NULL)
|
|
goto fail;
|
|
|
|
/* Generate the path. */
|
|
if (dir)
|
|
path = g_strdup_printf("/%s/%s", *dir=='/'?dir+1:dir, name);
|
|
else
|
|
path = g_strdup_printf("/%s", *name=='/'?name+1:name);
|
|
|
|
if (mode && *mode)
|
|
sprintf(ftype, ";type=%c", *mode);
|
|
else
|
|
ftype[0] = 0;
|
|
|
|
url = g_strdup_printf ("ftp://%s%s%s", site, path, ftype);
|
|
g_free (path);
|
|
desc = g_strdup_printf (_("Pointer to FTP site (%s)"), url);
|
|
} else if (!g_ascii_strcasecmp (access_type, "local-file")) {
|
|
const gchar *name, *site;
|
|
|
|
name = camel_content_type_param (type, "name");
|
|
site = camel_content_type_param (type, "site");
|
|
if (name == NULL)
|
|
goto fail;
|
|
|
|
url = g_filename_to_uri (name, NULL, NULL);
|
|
if (site)
|
|
desc = g_strdup_printf(_("Pointer to local file (%s) valid at site \"%s\""), name, site);
|
|
else
|
|
desc = g_strdup_printf(_("Pointer to local file (%s)"), name);
|
|
} else if (!g_ascii_strcasecmp (access_type, "URL")) {
|
|
const gchar *urlparam;
|
|
gchar *s, *d;
|
|
|
|
/* RFC 2017 */
|
|
urlparam = camel_content_type_param (type, "url");
|
|
if (urlparam == NULL)
|
|
goto fail;
|
|
|
|
/* For obscure MIMEy reasons, the URL may be split into words */
|
|
url = g_strdup (urlparam);
|
|
s = d = url;
|
|
while (*s) {
|
|
if (!isspace ((guchar) * s))
|
|
*d++ = *s;
|
|
s++;
|
|
}
|
|
*d = 0;
|
|
desc = g_strdup_printf (_("Pointer to remote data (%s)"), url);
|
|
} else
|
|
goto fail;
|
|
|
|
content = g_strdup_printf ("<a href=\"%s\">%s</a>", url, desc);
|
|
camel_mime_part_set_content (newpart, content, strlen (content), "text/html");
|
|
g_free (content);
|
|
|
|
g_free (url);
|
|
g_free (desc);
|
|
|
|
fail:
|
|
content = g_strdup_printf (
|
|
_("Pointer to unknown external data (\"%s\" type)"),
|
|
access_type);
|
|
camel_mime_part_set_content (newpart, content, strlen (content), "text/plain");
|
|
g_free (content);
|
|
|
|
addPart:
|
|
len = part_id->len;
|
|
g_string_append (part_id, ".msg_external");
|
|
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
|
|
puri->write_func = efh_write_text_html;
|
|
puri->mime_type = g_strdup ("text/html");
|
|
|
|
em_format_add_puri (emf, puri);
|
|
g_string_truncate (part_id, len);
|
|
}
|
|
|
|
static void
|
|
efh_parse_message_deliverystatus (EMFormat *emf,
|
|
CamelMimePart *part,
|
|
GString *part_id,
|
|
EMFormatParserInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatPURI *puri;
|
|
gint len;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
len = part_id->len;
|
|
g_string_append (part_id, ".deliverystatus");
|
|
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
|
|
puri->write_func = efh_write_source;
|
|
puri->mime_type = g_strdup ("text/html");
|
|
puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
|
|
puri->validity_type = info->validity_type;
|
|
puri->is_attachment = info->is_attachment;
|
|
|
|
em_format_add_puri (emf, puri);
|
|
g_string_truncate (part_id, len);
|
|
}
|
|
|
|
static void
|
|
efh_parse_message_rfc822 (EMFormat *emf,
|
|
CamelMimePart *part,
|
|
GString *part_id,
|
|
EMFormatParserInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
CamelDataWrapper *dw;
|
|
CamelMimePart *opart;
|
|
CamelStream *stream;
|
|
CamelMimeParser *parser;
|
|
gint len;
|
|
EMFormatParserInfo oinfo = *info;
|
|
EMFormatPURI *puri;
|
|
|
|
len = part_id->len;
|
|
g_string_append (part_id, ".rfc822");
|
|
|
|
/* Create an empty PURI that will represent start of the RFC message */
|
|
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
|
|
puri->write_func = efh_write_message_rfc822;
|
|
puri->mime_type = g_strdup ("text/html");
|
|
puri->is_attachment = info->is_attachment;
|
|
em_format_add_puri (emf, puri);
|
|
|
|
/* Now parse the message, creating multiple sub-PURIs */
|
|
stream = camel_stream_mem_new ();
|
|
dw = camel_medium_get_content ((CamelMedium *) part);
|
|
camel_data_wrapper_write_to_stream_sync (dw, stream, cancellable, NULL);
|
|
g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, cancellable, NULL);
|
|
|
|
parser = camel_mime_parser_new ();
|
|
camel_mime_parser_init_with_stream (parser, stream, NULL);
|
|
|
|
opart = camel_mime_part_new ();
|
|
camel_mime_part_construct_from_parser_sync (opart, parser, cancellable, NULL);
|
|
|
|
em_format_parse_part_as (emf, opart, part_id, &oinfo,
|
|
"x-evolution/message", cancellable);
|
|
|
|
/* Add another generic PURI that represents end of the RFC message.
|
|
* The em_format_write() function will skip all PURIs between the ".rfc822"
|
|
* PURI and ".rfc822.end" PURI as they will be rendered in an <iframe> */
|
|
g_string_append (part_id, ".end");
|
|
puri = em_format_puri_new (emf, sizeof (EMFormatPURI), NULL, part_id->str);
|
|
em_format_add_puri (emf, puri);
|
|
|
|
g_string_truncate (part_id, len);
|
|
|
|
g_object_unref (opart);
|
|
g_object_unref (parser);
|
|
g_object_unref (stream);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
efh_write_image (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
gchar *content;
|
|
EMFormatHTML *efh;
|
|
CamelDataWrapper *dw;
|
|
GByteArray *ba;
|
|
CamelStream *raw_content;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
efh = (EMFormatHTML *) emf;
|
|
|
|
dw = camel_medium_get_content (CAMEL_MEDIUM (puri->part));
|
|
g_return_if_fail (dw);
|
|
|
|
raw_content = camel_stream_mem_new ();
|
|
camel_data_wrapper_decode_to_stream_sync (dw, raw_content, cancellable, NULL);
|
|
ba = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (raw_content));
|
|
|
|
if (info->mode == EM_FORMAT_WRITE_MODE_RAW) {
|
|
|
|
if (!efh->priv->animate_images) {
|
|
|
|
gchar *buff;
|
|
gsize len;
|
|
|
|
em_format_html_animation_extract_frame (ba, &buff, &len);
|
|
|
|
camel_stream_write (stream, buff, len, cancellable, NULL);
|
|
|
|
g_free (buff);
|
|
|
|
} else {
|
|
|
|
camel_stream_write_to_stream (raw_content, stream, cancellable, NULL);
|
|
}
|
|
|
|
} else {
|
|
|
|
gchar *buffer;
|
|
|
|
if (!efh->priv->animate_images) {
|
|
|
|
gchar *buff;
|
|
gsize len;
|
|
|
|
em_format_html_animation_extract_frame (ba, &buff, &len);
|
|
|
|
content = g_base64_encode ((guchar *) buff, len);
|
|
g_free (buff);
|
|
|
|
} else {
|
|
content = g_base64_encode ((guchar *) ba->data, ba->len);
|
|
}
|
|
|
|
/* The image is already base64-encrypted so we can directly
|
|
* paste it to the output */
|
|
buffer = g_strdup_printf (
|
|
"<img src=\"data:%s;base64,%s\" style=\"max-width: 100%%;\" />",
|
|
puri->mime_type ? puri->mime_type : "image/*", content);
|
|
|
|
camel_stream_write_string (stream, buffer, cancellable, NULL);
|
|
g_free (buffer);
|
|
g_free (content);
|
|
}
|
|
|
|
g_object_unref (raw_content);
|
|
}
|
|
|
|
static void
|
|
efh_write_text_enriched (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatHTML *efh = EM_FORMAT_HTML (emf);
|
|
CamelStream *filtered_stream;
|
|
CamelMimeFilter *enriched;
|
|
guint32 flags = 0;
|
|
GString *buffer;
|
|
CamelContentType *ct;
|
|
gchar *mime_type = NULL;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
ct = camel_mime_part_get_content_type (puri->part);
|
|
if (ct) {
|
|
mime_type = camel_content_type_simple (ct);
|
|
}
|
|
|
|
if (!g_strcmp0(mime_type, "text/richtext")) {
|
|
flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT;
|
|
camel_stream_write_string (
|
|
stream, "\n<!-- text/richtext -->\n",
|
|
cancellable, NULL);
|
|
} else {
|
|
camel_stream_write_string (
|
|
stream, "\n<!-- text/enriched -->\n",
|
|
cancellable, NULL);
|
|
}
|
|
|
|
if (mime_type)
|
|
g_free (mime_type);
|
|
|
|
enriched = camel_mime_filter_enriched_new (flags);
|
|
filtered_stream = camel_stream_filter_new (stream);
|
|
camel_stream_filter_add (
|
|
CAMEL_STREAM_FILTER (filtered_stream), enriched);
|
|
g_object_unref (enriched);
|
|
|
|
buffer = g_string_new ("");
|
|
|
|
g_string_append_printf (buffer,
|
|
"<div class=\"part-container\" style=\"border-color: #%06x; "
|
|
"background-color: #%06x; color: #%06x;\">"
|
|
"<div class=\"part-container-inner-margin\">\n",
|
|
e_color_to_value (&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_FRAME]),
|
|
e_color_to_value (&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_CONTENT]),
|
|
e_color_to_value (&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_TEXT]));
|
|
|
|
camel_stream_write_string (stream, buffer->str, cancellable, NULL);
|
|
g_string_free (buffer, TRUE);
|
|
|
|
em_format_format_text (
|
|
emf, (CamelStream *) filtered_stream,
|
|
(CamelDataWrapper *) puri->part, cancellable);
|
|
|
|
g_object_unref (filtered_stream);
|
|
camel_stream_write_string (stream, "</div></div>", cancellable, NULL);
|
|
}
|
|
|
|
static void
|
|
efh_write_text_plain (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
CamelDataWrapper *dw;
|
|
CamelStream *filtered_stream;
|
|
CamelMimeFilter *html_filter;
|
|
EMFormatHTML *efh = (EMFormatHTML *) emf;
|
|
gchar *content;
|
|
const gchar *format;
|
|
guint32 flags;
|
|
guint32 rgb;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
flags = efh->text_html_flags;
|
|
|
|
dw = camel_medium_get_content (CAMEL_MEDIUM (puri->part));
|
|
|
|
/* Check for RFC 2646 flowed text. */
|
|
if (camel_content_type_is(dw->mime_type, "text", "plain")
|
|
&& (format = camel_content_type_param(dw->mime_type, "format"))
|
|
&& !g_ascii_strcasecmp(format, "flowed"))
|
|
flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED;
|
|
|
|
rgb = e_color_to_value (
|
|
&efh->priv->colors[EM_FORMAT_HTML_COLOR_CITATION]);
|
|
filtered_stream = camel_stream_filter_new (stream);
|
|
html_filter = camel_mime_filter_tohtml_new (flags, rgb);
|
|
camel_stream_filter_add (
|
|
CAMEL_STREAM_FILTER (filtered_stream), html_filter);
|
|
g_object_unref (html_filter);
|
|
|
|
content = g_strdup_printf (
|
|
"<div class=\"part-container\" style=\"border-color: #%06x; "
|
|
"background-color: #%06x; color: #%06x;\">"
|
|
"<div class=\"part-container-inner-margin pre\">\n",
|
|
e_color_to_value (&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_FRAME]),
|
|
e_color_to_value (&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_CONTENT]),
|
|
e_color_to_value (&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_TEXT]));
|
|
|
|
camel_stream_write_string (stream, content, cancellable, NULL);
|
|
em_format_format_text (emf, filtered_stream, (CamelDataWrapper *) puri->part, cancellable);
|
|
camel_stream_flush (filtered_stream, cancellable, NULL);
|
|
|
|
g_object_unref (filtered_stream);
|
|
g_free (content);
|
|
|
|
camel_stream_write_string (stream, "</div></div>\n", cancellable, NULL);
|
|
}
|
|
|
|
static gchar *
|
|
get_tag (const gchar *tag_name,
|
|
gchar *opening,
|
|
gchar *closing)
|
|
{
|
|
gchar *t;
|
|
gboolean has_end;
|
|
|
|
for (t = closing - 1; t != opening; t--) {
|
|
if (*t != ' ')
|
|
break;
|
|
}
|
|
|
|
/* Not a pair tag */
|
|
if (*t == '/')
|
|
return g_strndup (opening, closing - opening + 1);
|
|
|
|
for (t = closing; t && *t; t++) {
|
|
if (*t == '<')
|
|
break;
|
|
}
|
|
|
|
do {
|
|
if (*t == '/') {
|
|
has_end = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (*t == '>') {
|
|
has_end = FALSE;
|
|
break;
|
|
}
|
|
|
|
t++;
|
|
|
|
} while (t && *t);
|
|
|
|
/* Broken HTML? */
|
|
if (!has_end)
|
|
return g_strndup (opening, closing - opening + 1);
|
|
|
|
do {
|
|
if ((*t != ' ') && (*t != '/'))
|
|
break;
|
|
|
|
t++;
|
|
} while (t && *t);
|
|
|
|
if (g_strncasecmp (t, tag_name, strlen (tag_name)) == 0) {
|
|
|
|
closing = strstr (t, ">");
|
|
|
|
return g_strndup (opening, closing - opening + strlen (tag_name));
|
|
}
|
|
|
|
/* Broken HTML? */
|
|
return g_strndup (opening, closing - opening + 1);
|
|
}
|
|
|
|
static void
|
|
efh_write_text_html (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatHTML *efh = (EMFormatHTML *) emf;
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
if (info->mode == EM_FORMAT_WRITE_MODE_RAW) {
|
|
em_format_format_text (emf, stream,
|
|
(CamelDataWrapper *) puri->part, cancellable);
|
|
|
|
} else if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) {
|
|
GString *string;
|
|
GByteArray *ba;
|
|
gchar *pos;
|
|
GList *tags, *iter;
|
|
gboolean valid;
|
|
gchar *tag;
|
|
const gchar *document_end;
|
|
gint length;
|
|
gint i;
|
|
CamelStream *decoded_stream;
|
|
|
|
decoded_stream = camel_stream_mem_new ();
|
|
em_format_format_text (emf, decoded_stream,
|
|
(CamelDataWrapper *) puri->part, cancellable);
|
|
g_seekable_seek (G_SEEKABLE (decoded_stream), 0, G_SEEK_SET, cancellable, NULL);
|
|
|
|
ba = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (decoded_stream));
|
|
string = g_string_new_len ((gchar *) ba->data, ba->len);
|
|
|
|
g_object_unref (decoded_stream);
|
|
|
|
tags = NULL;
|
|
pos = string->str;
|
|
valid = FALSE;
|
|
do {
|
|
gchar *closing;
|
|
gchar *opening;
|
|
|
|
pos = strstr (pos + 1, "<");
|
|
if (!pos)
|
|
break;
|
|
|
|
opening = pos;
|
|
closing = strstr (pos, ">");
|
|
|
|
/* Find where the actual tag name begins */
|
|
for (tag = pos + 1; tag && *tag; tag++) {
|
|
if (*tag != ' ')
|
|
break;
|
|
}
|
|
|
|
if (g_ascii_strncasecmp (tag, "style", 5) == 0) {
|
|
tags = g_list_append (
|
|
tags,
|
|
get_tag ("style", opening, closing));
|
|
} else if (g_ascii_strncasecmp (tag, "script", 6) == 0) {
|
|
tags = g_list_append (
|
|
tags,
|
|
get_tag ("script", opening, closing));
|
|
} else if (g_ascii_strncasecmp (tag, "link", 4) == 0) {
|
|
tags = g_list_append (
|
|
tags,
|
|
get_tag ("link", opening, closing));
|
|
} else if (g_ascii_strncasecmp (tag, "body", 4) == 0) {
|
|
valid = TRUE;
|
|
break;
|
|
}
|
|
|
|
} while (TRUE);
|
|
|
|
/* Something's wrong, let's write the entire HTML and hope
|
|
* that WebKit can handle it */
|
|
if (!valid) {
|
|
EMFormatWriterInfo i = *info;
|
|
i.mode = EM_FORMAT_WRITE_MODE_RAW;
|
|
efh_write_text_html (emf, puri, stream, &i, cancellable);
|
|
return;
|
|
}
|
|
|
|
/* include the "body" as well -----v */
|
|
g_string_erase (string, 0, tag - string->str + 4);
|
|
g_string_prepend (string, "<div ");
|
|
|
|
for (iter = tags; iter; iter = iter->next) {
|
|
g_string_prepend (string, iter->data);
|
|
}
|
|
|
|
g_list_free_full (tags, g_free);
|
|
|
|
/* that's reversed </body></html>... */
|
|
document_end = ">lmth/<>ydob/<";
|
|
length = strlen (document_end);
|
|
tag = string->str + string->len - 1;
|
|
i = 0;
|
|
valid = FALSE;
|
|
while (i < length - 1) {
|
|
gchar c;
|
|
|
|
if (g_ascii_isspace (*tag)) {
|
|
tag--;
|
|
continue;
|
|
}
|
|
|
|
if ((*tag >= 'A') && (*tag <= 'Z'))
|
|
c = *tag + 32;
|
|
else
|
|
c = *tag;
|
|
|
|
if (c == document_end[i]) {
|
|
tag--;
|
|
i++;
|
|
valid = TRUE;
|
|
continue;
|
|
}
|
|
|
|
valid = FALSE;
|
|
}
|
|
|
|
if (valid)
|
|
g_string_truncate (string, tag - string->str);
|
|
|
|
camel_stream_write_string (stream, string->str, cancellable, NULL);
|
|
|
|
g_string_free (string, TRUE);
|
|
} else {
|
|
gchar *str;
|
|
gchar *uri;
|
|
|
|
uri = em_format_build_mail_uri (
|
|
emf->folder, emf->message_uid,
|
|
"part_id", G_TYPE_STRING, puri->uri,
|
|
"mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW,
|
|
NULL);
|
|
|
|
str = g_strdup_printf (
|
|
"<div class=\"part-container-nostyle\">"
|
|
"<iframe width=\"100%%\" height=\"auto\""
|
|
" frameborder=\"0\" src=\"%s\" "
|
|
" style=\"border: 1px solid #%06x; background-color: #%06x;\">"
|
|
"</iframe>"
|
|
"</div>",
|
|
uri,
|
|
e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME]),
|
|
e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT]));
|
|
|
|
camel_stream_write_string (stream, str, cancellable, NULL);
|
|
|
|
g_free (str);
|
|
g_free (uri);
|
|
}
|
|
}
|
|
|
|
static void
|
|
efh_write_source (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormatHTML *efh = (EMFormatHTML *) emf;
|
|
GString *buffer;
|
|
CamelStream *filtered_stream;
|
|
CamelMimeFilter *filter;
|
|
CamelDataWrapper *dw = (CamelDataWrapper *) puri->part;
|
|
|
|
filtered_stream = camel_stream_filter_new (stream);
|
|
|
|
filter = camel_mime_filter_tohtml_new (
|
|
CAMEL_MIME_FILTER_TOHTML_CONVERT_NL |
|
|
CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
|
|
CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT, 0);
|
|
camel_stream_filter_add (
|
|
CAMEL_STREAM_FILTER (filtered_stream), filter);
|
|
g_object_unref (filter);
|
|
|
|
buffer = g_string_new ("");
|
|
|
|
g_string_append_printf (
|
|
buffer, "<div class=\"part-container\" style=\"border: 0; background: #%06x; color: #%06x;\" >",
|
|
e_color_to_value (
|
|
&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_BODY]),
|
|
e_color_to_value (
|
|
&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_TEXT]));
|
|
|
|
camel_stream_write_string (
|
|
stream, buffer->str, cancellable, NULL);
|
|
camel_stream_write_string (
|
|
stream, "<code class=\"pre\">", cancellable, NULL);
|
|
camel_data_wrapper_write_to_stream_sync (dw, filtered_stream,
|
|
cancellable, NULL);
|
|
camel_stream_write_string (
|
|
stream, "</code>", cancellable, NULL);
|
|
|
|
g_object_unref (filtered_stream);
|
|
g_string_free (buffer, TRUE);
|
|
}
|
|
|
|
static void
|
|
efh_write_headers (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
GString *buffer;
|
|
EMFormatHTML *efh = (EMFormatHTML *) emf;
|
|
gint bg_color;
|
|
|
|
if (!puri->part)
|
|
return;
|
|
|
|
buffer = g_string_new ("");
|
|
|
|
if (info->mode & EM_FORMAT_WRITE_MODE_PRINTING) {
|
|
GdkColor white = { 0, G_MAXUINT16, G_MAXUINT16, G_MAXUINT16 };
|
|
bg_color = e_color_to_value (&white);
|
|
} else {
|
|
bg_color = e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_BODY]);
|
|
}
|
|
|
|
g_string_append_printf (
|
|
buffer,
|
|
"<div class=\"headers\" style=\"background: #%06x;\">"
|
|
"<table border=\"0\" width=\"100%%\" style=\"color: #%06x;\">\n"
|
|
"<tr><td valign=\"top\" width=\"16\">\n",
|
|
bg_color,
|
|
e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_HEADER]));
|
|
|
|
if (info->headers_collapsable) {
|
|
g_string_append_printf (buffer,
|
|
"<img src=\"evo-file://%s/%s\" class=\"navigable\" "
|
|
"id=\"__evo-collapse-headers-img\" />"
|
|
"</td><td>",
|
|
EVOLUTION_IMAGESDIR,
|
|
(info->headers_collapsed) ? "plus.png" : "minus.png");
|
|
|
|
efh_format_short_headers (efh, buffer, (CamelMedium *) puri->part,
|
|
info->headers_collapsed,
|
|
cancellable);
|
|
}
|
|
|
|
efh_format_full_headers (efh, buffer, (CamelMedium *) puri->part,
|
|
(info->mode == EM_FORMAT_WRITE_MODE_ALL_HEADERS),
|
|
!info->headers_collapsed,
|
|
cancellable);
|
|
|
|
g_string_append (buffer, "</td></tr></table></div>");
|
|
|
|
camel_stream_write_string (stream, buffer->str, cancellable, NULL);
|
|
|
|
g_string_free (buffer, true);
|
|
}
|
|
|
|
static void
|
|
efh_write_error (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
CamelStream *filtered_stream;
|
|
CamelMimeFilter *filter;
|
|
CamelDataWrapper *dw;
|
|
|
|
dw = camel_medium_get_content ((CamelMedium *) puri->part);
|
|
|
|
camel_stream_write_string (stream, "<em><font color=\"red\">", cancellable, NULL);
|
|
|
|
filtered_stream = camel_stream_filter_new (stream);
|
|
filter = camel_mime_filter_tohtml_new (CAMEL_MIME_FILTER_TOHTML_CONVERT_NL |
|
|
CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0);
|
|
camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered_stream), filter);
|
|
g_object_unref (filter);
|
|
|
|
camel_data_wrapper_decode_to_stream_sync (dw, filtered_stream, cancellable, NULL);
|
|
|
|
g_object_unref (filtered_stream);
|
|
|
|
camel_stream_write_string (stream, "</font></em><br>", cancellable, NULL);
|
|
}
|
|
|
|
static void
|
|
efh_write_message_rfc822 (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
if (info->mode == EM_FORMAT_WRITE_MODE_RAW) {
|
|
|
|
GList *puris;
|
|
GList *iter;
|
|
EMFormatWriterInfo msgInfo = *info;
|
|
msgInfo.mode = EM_FORMAT_WRITE_MODE_NORMAL;
|
|
|
|
/* Create a new fake list of PURIs which will contain only
|
|
* PURIs from this message. */
|
|
iter = g_hash_table_lookup (emf->mail_part_table, puri->uri);
|
|
if (!iter || !iter->next)
|
|
return;
|
|
|
|
iter = iter->next;
|
|
puris = NULL;
|
|
while (iter) {
|
|
|
|
EMFormatPURI *p;
|
|
p = iter->data;
|
|
|
|
if (g_str_has_suffix (p->uri, ".rfc822.end"))
|
|
break;
|
|
|
|
puris = g_list_append (puris, p);
|
|
iter = iter->next;
|
|
|
|
};
|
|
|
|
efh_write_message (emf, puris, stream, &msgInfo, cancellable);
|
|
|
|
g_list_free (puris);
|
|
|
|
} else if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) {
|
|
|
|
GList *iter;
|
|
gboolean can_write = FALSE;
|
|
|
|
iter = g_hash_table_lookup (emf->mail_part_table, puri->uri);
|
|
if (!iter || !iter->next)
|
|
return;
|
|
|
|
/* Skip everything before attachment bar, inclusive */\
|
|
iter = iter->next;
|
|
while (iter) {
|
|
|
|
EMFormatPURI *p = iter->data;
|
|
|
|
/* EMFormatHTMLPrint has registered a special writer
|
|
* for headers, try to find it and use it. */
|
|
if (g_str_has_suffix (p->uri, ".headers")) {
|
|
|
|
const EMFormatHandler *handler;
|
|
|
|
handler = em_format_find_handler (
|
|
emf, "x-evolution/message/headers");
|
|
if (handler && handler->write_func)
|
|
handler->write_func (emf, p, stream, info, cancellable);
|
|
|
|
iter = iter->next;
|
|
continue;
|
|
}
|
|
|
|
if (g_str_has_suffix (p->uri, ".rfc822.end"))
|
|
break;
|
|
|
|
if (g_str_has_suffix (p->uri, ".attachment-bar"))
|
|
can_write = TRUE;
|
|
|
|
if (can_write && p->write_func) {
|
|
p->write_func (
|
|
emf, p, stream, info, cancellable);
|
|
}
|
|
|
|
iter = iter->next;
|
|
}
|
|
|
|
} else {
|
|
gchar *str;
|
|
gchar *uri;
|
|
|
|
EMFormatHTML *efh = (EMFormatHTML *) emf;
|
|
EMFormatPURI *p;
|
|
GList *iter;
|
|
|
|
iter = g_hash_table_lookup (emf->mail_part_table, puri->uri);
|
|
if (!iter || !iter->next)
|
|
return;
|
|
|
|
iter = iter->next;
|
|
p = iter->data;
|
|
|
|
uri = em_format_build_mail_uri (emf->folder, emf->message_uid,
|
|
"part_id", G_TYPE_STRING, p->uri,
|
|
"mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW,
|
|
NULL);
|
|
|
|
str = g_strdup_printf (
|
|
"<div class=\"part-container\" style=\"border-color: #%06x; "
|
|
"background-color: #%06x;\">"
|
|
"<div class=\"part-container-inner-margin\">\n"
|
|
"<iframe width=\"100%%\" height=\"auto\""
|
|
" frameborder=\"0\" src=\"%s\" name=\"%s\"></iframe>"
|
|
"</div></div>",
|
|
e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME]),
|
|
e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT]),
|
|
uri, puri->uri);
|
|
|
|
camel_stream_write_string (stream, str, cancellable, NULL);
|
|
|
|
g_free (str);
|
|
g_free (uri);
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* Notes:
|
|
*
|
|
* image/tiff is omitted because it's a multi-page image format, but
|
|
* gdk-pixbuf unconditionally renders the first page only, and doesn't
|
|
* even indicate through meta-data whether multiple pages are present
|
|
* (see bug 335959). Therefore, make no attempt to render TIFF images
|
|
* inline and defer to an application that can handle multi-page TIFF
|
|
* files properly like Evince or Gimp. Once the referenced bug is
|
|
* fixed we can reevaluate this policy.
|
|
*/
|
|
static EMFormatHandler type_builtin_table[] = {
|
|
{ (gchar *) "image/gif", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/jpeg", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/png", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-png", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-bmp", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/bmp", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/svg", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-cmu-raster", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-ico", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-portable-anymap", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-portable-bitmap", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-portable-graymap", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-portable-pixmap", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/x-xpixmap", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "text/enriched", efh_parse_text_enriched, efh_write_text_enriched, },
|
|
{ (gchar *) "text/plain", efh_parse_text_plain, efh_write_text_plain, },
|
|
{ (gchar *) "text/html", efh_parse_text_html, efh_write_text_html, },
|
|
{ (gchar *) "text/richtext", efh_parse_text_enriched, efh_write_text_enriched, },
|
|
{ (gchar *) "text/*", efh_parse_text_plain, efh_write_text_plain, },
|
|
{ (gchar *) "message/rfc822", efh_parse_message_rfc822, efh_write_message_rfc822, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE },
|
|
{ (gchar *) "message/news", efh_parse_message_rfc822, 0, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE },
|
|
{ (gchar *) "message/delivery-status", efh_parse_message_deliverystatus, efh_write_text_plain, },
|
|
{ (gchar *) "message/external-body", efh_parse_message_external, efh_write_text_plain, },
|
|
{ (gchar *) "message/*", efh_parse_message_rfc822, 0, EM_FORMAT_HANDLER_INLINE },
|
|
|
|
/* This is where one adds those busted, non-registered types,
|
|
* that some idiot mailer writers out there decide to pull out
|
|
* of their proverbials at random. */
|
|
{ (gchar *) "image/jpg", efh_parse_image, efh_write_image, },
|
|
{ (gchar *) "image/pjpeg", efh_parse_image, efh_write_image, },
|
|
|
|
/* special internal types */
|
|
{ (gchar *) "x-evolution/message/rfc822", 0, efh_write_text_plain, },
|
|
{ (gchar *) "x-evolution/message/headers", 0, efh_write_headers, },
|
|
{ (gchar *) "x-evolution/message/source", 0, efh_write_source, },
|
|
{ (gchar *) "x-evolution/message/attachment", 0, efh_write_attachment, },
|
|
{ (gchar *) "x-evolution/message/error", 0, efh_write_error, },
|
|
};
|
|
|
|
static void
|
|
efh_builtin_init (EMFormatHTMLClass *efhc)
|
|
{
|
|
EMFormatClass *emfc;
|
|
gint ii;
|
|
|
|
emfc = (EMFormatClass *) efhc;
|
|
|
|
for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++)
|
|
em_format_class_add_handler (
|
|
emfc, &type_builtin_table[ii]);
|
|
}
|
|
|
|
static void
|
|
efh_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id) {
|
|
case PROP_BODY_COLOR:
|
|
em_format_html_set_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_BODY,
|
|
g_value_get_boxed (value));
|
|
return;
|
|
|
|
case PROP_CITATION_COLOR:
|
|
em_format_html_set_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_CITATION,
|
|
g_value_get_boxed (value));
|
|
return;
|
|
|
|
case PROP_CONTENT_COLOR:
|
|
em_format_html_set_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_CONTENT,
|
|
g_value_get_boxed (value));
|
|
return;
|
|
|
|
case PROP_FRAME_COLOR:
|
|
em_format_html_set_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_FRAME,
|
|
g_value_get_boxed (value));
|
|
return;
|
|
|
|
case PROP_HEADER_COLOR:
|
|
em_format_html_set_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_HEADER,
|
|
g_value_get_boxed (value));
|
|
return;
|
|
|
|
case PROP_IMAGE_LOADING_POLICY:
|
|
em_format_html_set_image_loading_policy (
|
|
EM_FORMAT_HTML (object),
|
|
g_value_get_enum (value));
|
|
return;
|
|
|
|
case PROP_MARK_CITATIONS:
|
|
em_format_html_set_mark_citations (
|
|
EM_FORMAT_HTML (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_ONLY_LOCAL_PHOTOS:
|
|
em_format_html_set_only_local_photos (
|
|
EM_FORMAT_HTML (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_SHOW_SENDER_PHOTO:
|
|
em_format_html_set_show_sender_photo (
|
|
EM_FORMAT_HTML (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_SHOW_REAL_DATE:
|
|
em_format_html_set_show_real_date (
|
|
EM_FORMAT_HTML (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_TEXT_COLOR:
|
|
em_format_html_set_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_TEXT,
|
|
g_value_get_boxed (value));
|
|
return;
|
|
|
|
case PROP_ANIMATE_IMAGES:
|
|
em_format_html_set_animate_images (
|
|
EM_FORMAT_HTML (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
static void
|
|
efh_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GdkColor color;
|
|
|
|
switch (property_id) {
|
|
case PROP_BODY_COLOR:
|
|
em_format_html_get_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_BODY,
|
|
&color);
|
|
g_value_set_boxed (value, &color);
|
|
return;
|
|
|
|
case PROP_CITATION_COLOR:
|
|
em_format_html_get_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_CITATION,
|
|
&color);
|
|
g_value_set_boxed (value, &color);
|
|
return;
|
|
|
|
case PROP_CONTENT_COLOR:
|
|
em_format_html_get_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_CONTENT,
|
|
&color);
|
|
g_value_set_boxed (value, &color);
|
|
return;
|
|
|
|
case PROP_FRAME_COLOR:
|
|
em_format_html_get_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_FRAME,
|
|
&color);
|
|
g_value_set_boxed (value, &color);
|
|
return;
|
|
|
|
case PROP_HEADER_COLOR:
|
|
em_format_html_get_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_HEADER,
|
|
&color);
|
|
g_value_set_boxed (value, &color);
|
|
return;
|
|
|
|
case PROP_IMAGE_LOADING_POLICY:
|
|
g_value_set_enum (
|
|
value,
|
|
em_format_html_get_image_loading_policy (
|
|
EM_FORMAT_HTML (object)));
|
|
return;
|
|
|
|
case PROP_MARK_CITATIONS:
|
|
g_value_set_boolean (
|
|
value, em_format_html_get_mark_citations (
|
|
EM_FORMAT_HTML (object)));
|
|
return;
|
|
|
|
case PROP_ONLY_LOCAL_PHOTOS:
|
|
g_value_set_boolean (
|
|
value, em_format_html_get_only_local_photos (
|
|
EM_FORMAT_HTML (object)));
|
|
return;
|
|
|
|
case PROP_SHOW_SENDER_PHOTO:
|
|
g_value_set_boolean (
|
|
value, em_format_html_get_show_sender_photo (
|
|
EM_FORMAT_HTML (object)));
|
|
return;
|
|
|
|
case PROP_SHOW_REAL_DATE:
|
|
g_value_set_boolean (
|
|
value, em_format_html_get_show_real_date (
|
|
EM_FORMAT_HTML (object)));
|
|
return;
|
|
|
|
case PROP_TEXT_COLOR:
|
|
em_format_html_get_color (
|
|
EM_FORMAT_HTML (object),
|
|
EM_FORMAT_HTML_COLOR_TEXT,
|
|
&color);
|
|
g_value_set_boxed (value, &color);
|
|
return;
|
|
case PROP_ANIMATE_IMAGES:
|
|
g_value_set_boolean (
|
|
value, em_format_html_get_animate_images (
|
|
EM_FORMAT_HTML (object)));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
static void
|
|
efh_finalize (GObject *object)
|
|
{
|
|
/* Chain up to parent's finalize() method. */
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
efh_constructed (GObject *object)
|
|
{
|
|
/* Chain up to parent's constructed() method. */
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
|
|
e_extensible_load_extensions (E_EXTENSIBLE (object));
|
|
}
|
|
|
|
static void
|
|
efh_write_attachment (EMFormat *emf,
|
|
EMFormatPURI *puri,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
gchar *text, *html;
|
|
CamelContentType *ct;
|
|
gchar *mime_type;
|
|
const EMFormatHandler *handler;
|
|
|
|
/* we display all inlined attachments only */
|
|
|
|
/* this could probably be cleaned up ... */
|
|
camel_stream_write_string (
|
|
stream,
|
|
"<table border=1 cellspacing=0 cellpadding=0><tr><td>"
|
|
"<table width=10 cellspacing=0 cellpadding=0>"
|
|
"<tr><td></td></tr></table></td>"
|
|
"<td><table width=3 cellspacing=0 cellpadding=0>"
|
|
"<tr><td></td></tr></table></td><td><font size=-1>\n",
|
|
cancellable, NULL);
|
|
|
|
ct = camel_mime_part_get_content_type (puri->part);
|
|
mime_type = camel_content_type_simple (ct);
|
|
|
|
/* output some info about it */
|
|
text = em_format_describe_part (puri->part, mime_type);
|
|
html = camel_text_to_html (
|
|
text, ((EMFormatHTML *) emf)->text_html_flags &
|
|
CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0);
|
|
camel_stream_write_string (stream, html, cancellable, NULL);
|
|
g_free (html);
|
|
g_free (text);
|
|
|
|
camel_stream_write_string (
|
|
stream, "</font></td></tr><tr></table>", cancellable, NULL);
|
|
|
|
handler = em_format_find_handler (emf, mime_type);
|
|
if (handler && handler->write_func && handler->write_func != efh_write_attachment) {
|
|
if (em_format_is_inline (emf, puri->uri, puri->part, handler))
|
|
handler->write_func (emf, puri, stream, info, cancellable);
|
|
}
|
|
|
|
g_free (mime_type);
|
|
}
|
|
|
|
static void
|
|
efh_preparse (EMFormat *emf)
|
|
{
|
|
EMFormatHTML *efh = EM_FORMAT_HTML (emf);
|
|
CamelInternetAddress *addr;
|
|
CamelSession *session;
|
|
ESourceRegistry *registry;
|
|
|
|
if (!emf->message) {
|
|
efh->priv->can_load_images = FALSE;
|
|
return;
|
|
}
|
|
|
|
session = em_format_get_session (emf);
|
|
registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
|
|
|
|
addr = camel_mime_message_get_from (emf->message);
|
|
efh->priv->can_load_images = em_utils_in_addressbook (
|
|
registry, addr, FALSE);
|
|
}
|
|
|
|
static void
|
|
efh_write_message (EMFormat *emf,
|
|
GList *puris,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
GList *iter;
|
|
EMFormatHTML *efh;
|
|
gchar *header;
|
|
|
|
efh = (EMFormatHTML *) emf;
|
|
|
|
header = g_strdup_printf (
|
|
"<!DOCTYPE HTML>\n<html>\n"
|
|
"<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\" />\n"
|
|
"<title>Evolution Mail Display</title>\n"
|
|
"<link type=\"text/css\" rel=\"stylesheet\" href=\"evo-file://" EVOLUTION_PRIVDATADIR "/theme/webview.css\" />\n"
|
|
"<style type=\"text/css\">\n"
|
|
" table th { color: #000; font-weight: bold; }\n"
|
|
"</style>\n"
|
|
"</head><body bgcolor=\"#%06x\">",
|
|
e_color_to_value (&efh->priv->colors[
|
|
EM_FORMAT_HTML_COLOR_BODY]));
|
|
|
|
camel_stream_write_string (stream, header, cancellable, NULL);
|
|
g_free (header);
|
|
|
|
if (info->mode == EM_FORMAT_WRITE_MODE_SOURCE) {
|
|
|
|
efh_write_source (emf, emf->mail_part_list->data,
|
|
stream, info, cancellable);
|
|
|
|
camel_stream_write_string (stream, "</body></html>", cancellable, NULL);
|
|
return;
|
|
}
|
|
|
|
for (iter = puris; iter; iter = iter->next) {
|
|
|
|
EMFormatPURI *puri = iter->data;
|
|
|
|
if (!puri)
|
|
continue;
|
|
|
|
/* If current PURI has suffix .rfc822 then iterate through all
|
|
* subsequent PURIs until PURI with suffix .rfc822.end is found.
|
|
* These skipped PURIs contain entire RFC message which will
|
|
* be written in <iframe> as attachment.
|
|
*/
|
|
if (g_str_has_suffix (puri->uri, ".rfc822")) {
|
|
|
|
/* If the PURI is not an attachment, then we must
|
|
* inline it here otherwise it would not be displayed. */
|
|
if (!puri->is_attachment && puri->write_func) {
|
|
/* efh_write_message_rfc822 starts parsing _after_
|
|
* the passed PURI, so we must give it previous PURI here */
|
|
EMFormatPURI *p;
|
|
if (!iter->prev)
|
|
continue;
|
|
|
|
p = iter->prev->data;
|
|
puri->write_func (emf, p, stream, info, cancellable);
|
|
}
|
|
|
|
while (iter && !g_str_has_suffix (puri->uri, ".rfc822.end")) {
|
|
|
|
iter = iter->next;
|
|
if (iter)
|
|
puri = iter->data;
|
|
|
|
d(printf(".rfc822 - skipping %s\n", puri->uri));
|
|
}
|
|
|
|
/* Skip the .rfc822.end PURI as well. */
|
|
if (!iter)
|
|
break;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (puri->write_func && !puri->is_attachment) {
|
|
puri->write_func (emf, puri, stream, info, cancellable);
|
|
d(printf("Writing PURI %s\n", puri->uri));
|
|
} else {
|
|
d(printf("Skipping PURI %s\n", puri->uri));
|
|
}
|
|
}
|
|
|
|
camel_stream_write_string (stream, "</body></html>", cancellable, NULL);
|
|
}
|
|
|
|
static void
|
|
efh_write (EMFormat *emf,
|
|
CamelStream *stream,
|
|
EMFormatWriterInfo *info,
|
|
GCancellable *cancellable)
|
|
{
|
|
efh_write_message (emf, emf->mail_part_list, stream, info, cancellable);
|
|
}
|
|
|
|
static void
|
|
efh_base_init (EMFormatHTMLClass *klass)
|
|
{
|
|
efh_builtin_init (klass);
|
|
}
|
|
|
|
static void
|
|
efh_class_init (EMFormatHTMLClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
EMFormatClass *emf_class;
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
g_type_class_add_private (klass, sizeof (EMFormatHTMLPrivate));
|
|
|
|
emf_class = EM_FORMAT_CLASS (klass);
|
|
emf_class->preparse = efh_preparse;
|
|
emf_class->write = efh_write;
|
|
|
|
object_class = G_OBJECT_CLASS (klass);
|
|
object_class->constructed = efh_constructed;
|
|
object_class->set_property = efh_set_property;
|
|
object_class->get_property = efh_get_property;
|
|
object_class->finalize = efh_finalize;
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_BODY_COLOR,
|
|
g_param_spec_boxed (
|
|
"body-color",
|
|
"Body Color",
|
|
NULL,
|
|
GDK_TYPE_COLOR,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_CITATION_COLOR,
|
|
g_param_spec_boxed (
|
|
"citation-color",
|
|
"Citation Color",
|
|
NULL,
|
|
GDK_TYPE_COLOR,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_CONTENT_COLOR,
|
|
g_param_spec_boxed (
|
|
"content-color",
|
|
"Content Color",
|
|
NULL,
|
|
GDK_TYPE_COLOR,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_FRAME_COLOR,
|
|
g_param_spec_boxed (
|
|
"frame-color",
|
|
"Frame Color",
|
|
NULL,
|
|
GDK_TYPE_COLOR,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_HEADER_COLOR,
|
|
g_param_spec_boxed (
|
|
"header-color",
|
|
"Header Color",
|
|
NULL,
|
|
GDK_TYPE_COLOR,
|
|
G_PARAM_READWRITE));
|
|
|
|
/* FIXME Make this a proper enum property. */
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_IMAGE_LOADING_POLICY,
|
|
g_param_spec_enum (
|
|
"image-loading-policy",
|
|
"Image Loading Policy",
|
|
NULL,
|
|
E_TYPE_MAIL_IMAGE_LOADING_POLICY,
|
|
E_MAIL_IMAGE_LOADING_POLICY_ALWAYS,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_MARK_CITATIONS,
|
|
g_param_spec_boolean (
|
|
"mark-citations",
|
|
"Mark Citations",
|
|
NULL,
|
|
TRUE,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_ONLY_LOCAL_PHOTOS,
|
|
g_param_spec_boolean (
|
|
"only-local-photos",
|
|
"Only Local Photos",
|
|
NULL,
|
|
TRUE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_SHOW_SENDER_PHOTO,
|
|
g_param_spec_boolean (
|
|
"show-sender-photo",
|
|
"Show Sender Photo",
|
|
NULL,
|
|
FALSE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_SHOW_REAL_DATE,
|
|
g_param_spec_boolean (
|
|
"show-real-date",
|
|
"Show real Date header value",
|
|
NULL,
|
|
TRUE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_TEXT_COLOR,
|
|
g_param_spec_boxed (
|
|
"text-color",
|
|
"Text Color",
|
|
NULL,
|
|
GDK_TYPE_COLOR,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_ANIMATE_IMAGES,
|
|
g_param_spec_boolean (
|
|
"animate-images",
|
|
"Animate images",
|
|
NULL,
|
|
FALSE,
|
|
G_PARAM_READWRITE));
|
|
}
|
|
|
|
static void
|
|
efh_init (EMFormatHTML *efh,
|
|
EMFormatHTMLClass *klass)
|
|
{
|
|
GdkColor *color;
|
|
|
|
efh->priv = EM_FORMAT_HTML_GET_PRIVATE (efh);
|
|
|
|
g_queue_init (&efh->pending_object_list);
|
|
|
|
color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_BODY];
|
|
gdk_color_parse ("#eeeeee", color);
|
|
|
|
color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT];
|
|
gdk_color_parse ("#ffffff", color);
|
|
|
|
color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME];
|
|
gdk_color_parse ("#3f3f3f", color);
|
|
|
|
color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_HEADER];
|
|
gdk_color_parse ("#eeeeee", color);
|
|
|
|
color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_TEXT];
|
|
gdk_color_parse ("#000000", color);
|
|
|
|
efh->text_html_flags =
|
|
CAMEL_MIME_FILTER_TOHTML_CONVERT_NL |
|
|
CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
|
|
CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;
|
|
efh->show_icon = TRUE;
|
|
}
|
|
|
|
GType
|
|
em_format_html_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (G_UNLIKELY (type == 0)) {
|
|
static const GTypeInfo type_info = {
|
|
sizeof (EMFormatHTMLClass),
|
|
(GBaseInitFunc) efh_base_init,
|
|
(GBaseFinalizeFunc) NULL,
|
|
(GClassInitFunc) efh_class_init,
|
|
(GClassFinalizeFunc) NULL,
|
|
NULL, /* class_data */
|
|
sizeof (EMFormatHTML),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) efh_init,
|
|
NULL /* value_table */
|
|
};
|
|
|
|
static const GInterfaceInfo extensible_info = {
|
|
(GInterfaceInitFunc) NULL,
|
|
(GInterfaceFinalizeFunc) NULL,
|
|
NULL /* interface_data */
|
|
};
|
|
|
|
type = g_type_register_static (
|
|
em_format_get_type(), "EMFormatHTML",
|
|
&type_info, G_TYPE_FLAG_ABSTRACT);
|
|
|
|
g_type_add_interface_static (
|
|
type, E_TYPE_EXTENSIBLE, &extensible_info);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
void
|
|
em_format_html_get_color (EMFormatHTML *efh,
|
|
EMFormatHTMLColorType type,
|
|
GdkColor *color)
|
|
{
|
|
GdkColor *format_color;
|
|
|
|
g_return_if_fail (EM_IS_FORMAT_HTML (efh));
|
|
g_return_if_fail (type < EM_FORMAT_HTML_NUM_COLOR_TYPES);
|
|
g_return_if_fail (color != NULL);
|
|
|
|
format_color = &efh->priv->colors[type];
|
|
|
|
color->red = format_color->red;
|
|
color->green = format_color->green;
|
|
color->blue = format_color->blue;
|
|
}
|
|
|
|
void
|
|
em_format_html_set_color (EMFormatHTML *efh,
|
|
EMFormatHTMLColorType type,
|
|
const GdkColor *color)
|
|
{
|
|
GdkColor *format_color;
|
|
const gchar *property_name;
|
|
|
|
g_return_if_fail (EM_IS_FORMAT_HTML (efh));
|
|
g_return_if_fail (type < EM_FORMAT_HTML_NUM_COLOR_TYPES);
|
|
g_return_if_fail (color != NULL);
|
|
|
|
format_color = &efh->priv->colors[type];
|
|
|
|
if (gdk_color_equal (color, format_color))
|
|
return;
|
|
|
|
format_color->red = color->red;
|
|
format_color->green = color->green;
|
|
format_color->blue = color->blue;
|
|
|
|
switch (type) {
|
|
case EM_FORMAT_HTML_COLOR_BODY:
|
|
property_name = "body-color";
|
|
break;
|
|
case EM_FORMAT_HTML_COLOR_CITATION:
|
|
property_name = "citation-color";
|
|
break;
|
|
case EM_FORMAT_HTML_COLOR_CONTENT:
|
|
property_name = "content-color";
|
|
break;
|
|
case EM_FORMAT_HTML_COLOR_FRAME:
|
|
property_name = "frame-color";
|
|
break;
|
|
case EM_FORMAT_HTML_COLOR_HEADER:
|
|
property_name = "header-color";
|
|
break;
|
|
case EM_FORMAT_HTML_COLOR_TEXT:
|
|
property_name = "text-color";
|
|
break;
|
|
default:
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (efh), property_name);
|
|
}
|
|
|
|
EMailImageLoadingPolicy
|
|
em_format_html_get_image_loading_policy (EMFormatHTML *efh)
|
|
{
|
|
g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), 0);
|
|
|
|
return efh->priv->image_loading_policy;
|
|
}
|
|
|
|
void
|
|
em_format_html_set_image_loading_policy (EMFormatHTML *efh,
|
|
EMailImageLoadingPolicy policy)
|
|
{
|
|
g_return_if_fail (EM_IS_FORMAT_HTML (efh));
|
|
|
|
if (policy == efh->priv->image_loading_policy)
|
|
return;
|
|
|
|
efh->priv->image_loading_policy = policy;
|
|
|
|
g_object_notify (G_OBJECT (efh), "image-loading-policy");
|
|
}
|
|
|
|
gboolean
|
|
em_format_html_get_mark_citations (EMFormatHTML *efh)
|
|
{
|
|
guint32 flags;
|
|
|
|
g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);
|
|
|
|
flags = efh->text_html_flags;
|
|
|
|
return ((flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) != 0);
|
|
}
|
|
|
|
void
|
|
em_format_html_set_mark_citations (EMFormatHTML *efh,
|
|
gboolean mark_citations)
|
|
{
|
|
g_return_if_fail (EM_IS_FORMAT_HTML (efh));
|
|
|
|
if (mark_citations)
|
|
efh->text_html_flags |=
|
|
CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;
|
|
else
|
|
efh->text_html_flags &=
|
|
~CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;
|
|
|
|
g_object_notify (G_OBJECT (efh), "mark-citations");
|
|
}
|
|
|
|
gboolean
|
|
em_format_html_get_only_local_photos (EMFormatHTML *efh)
|
|
{
|
|
g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);
|
|
|
|
return efh->priv->only_local_photos;
|
|
}
|
|
|
|
void
|
|
em_format_html_set_only_local_photos (EMFormatHTML *efh,
|
|
gboolean only_local_photos)
|
|
{
|
|
g_return_if_fail (EM_IS_FORMAT_HTML (efh));
|
|
|
|
efh->priv->only_local_photos = only_local_photos;
|
|
|
|
g_object_notify (G_OBJECT (efh), "only-local-photos");
|
|
}
|
|
|
|
gboolean
|
|
em_format_html_get_show_sender_photo (EMFormatHTML *efh)
|
|
{
|
|
g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);
|
|
|
|
return efh->priv->show_sender_photo;
|
|
}
|
|
|
|
void
|
|
em_format_html_set_show_sender_photo (EMFormatHTML *efh,
|
|
gboolean show_sender_photo)
|
|
{
|
|
g_return_if_fail (EM_IS_FORMAT_HTML (efh));
|
|
|
|
efh->priv->show_sender_photo = show_sender_photo;
|
|
|
|
g_object_notify (G_OBJECT (efh), "show-sender-photo");
|
|
}
|
|
|
|
gboolean
|
|
em_format_html_get_show_real_date (EMFormatHTML *efh)
|
|
{
|
|
g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);
|
|
|
|
return efh->priv->show_real_date;
|
|
}
|
|
|
|
void
|
|
em_format_html_set_show_real_date (EMFormatHTML *efh,
|
|
gboolean show_real_date)
|
|
{
|
|
g_return_if_fail (EM_IS_FORMAT_HTML (efh));
|
|
|
|
efh->priv->show_real_date = show_real_date;
|
|
|
|
g_object_notify (G_OBJECT (efh), "show-real-date");
|
|
}
|
|
|
|
gboolean
|
|
em_format_html_get_animate_images (EMFormatHTML *efh)
|
|
{
|
|
g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);
|
|
|
|
return efh->priv->animate_images;
|
|
}
|
|
|
|
void
|
|
em_format_html_set_animate_images (EMFormatHTML *efh,
|
|
gboolean animate_images)
|
|
{
|
|
g_return_if_fail (EM_IS_FORMAT_HTML (efh));
|
|
|
|
efh->priv->animate_images = animate_images;
|
|
|
|
g_object_notify (G_OBJECT (efh), "animate-images");
|
|
}
|
|
|
|
CamelMimePart *
|
|
em_format_html_file_part (EMFormatHTML *efh,
|
|
const gchar *mime_type,
|
|
const gchar *filename,
|
|
GCancellable *cancellable)
|
|
{
|
|
CamelMimePart *part;
|
|
CamelStream *stream;
|
|
CamelDataWrapper *dw;
|
|
gchar *basename;
|
|
|
|
stream = camel_stream_fs_new_with_name (filename, O_RDONLY, 0, NULL);
|
|
if (stream == NULL)
|
|
return NULL;
|
|
|
|
dw = camel_data_wrapper_new ();
|
|
camel_data_wrapper_construct_from_stream_sync (
|
|
dw, stream, cancellable, NULL);
|
|
g_object_unref (stream);
|
|
if (mime_type)
|
|
camel_data_wrapper_set_mime_type (dw, mime_type);
|
|
part = camel_mime_part_new ();
|
|
camel_medium_set_content ((CamelMedium *) part, dw);
|
|
g_object_unref (dw);
|
|
basename = g_path_get_basename (filename);
|
|
camel_mime_part_set_filename (part, basename);
|
|
g_free (basename);
|
|
|
|
return part;
|
|
}
|
|
|
|
void
|
|
em_format_html_format_cert_infos (GQueue *cert_infos,
|
|
GString *output_buffer)
|
|
{
|
|
GQueue valid = G_QUEUE_INIT;
|
|
GList *head, *link;
|
|
|
|
g_return_if_fail (cert_infos != NULL);
|
|
g_return_if_fail (output_buffer != NULL);
|
|
|
|
head = g_queue_peek_head_link (cert_infos);
|
|
|
|
/* Make sure we have a valid CamelCipherCertInfo before
|
|
* appending anything to the output buffer, so we don't
|
|
* end up with "()". */
|
|
for (link = head; link != NULL; link = g_list_next (link)) {
|
|
CamelCipherCertInfo *cinfo = link->data;
|
|
|
|
if ((cinfo->name != NULL && *cinfo->name != '\0') ||
|
|
(cinfo->email != NULL && *cinfo->email != '\0')) {
|
|
g_queue_push_tail (&valid, cinfo);
|
|
}
|
|
}
|
|
|
|
if (g_queue_is_empty (&valid))
|
|
return;
|
|
|
|
g_string_append (output_buffer, " (");
|
|
|
|
while (!g_queue_is_empty (&valid)) {
|
|
CamelCipherCertInfo *cinfo;
|
|
|
|
cinfo = g_queue_pop_head (&valid);
|
|
|
|
if (cinfo->name != NULL && *cinfo->name != '\0') {
|
|
g_string_append (output_buffer, cinfo->name);
|
|
|
|
if (cinfo->email != NULL && *cinfo->email != '\0') {
|
|
g_string_append (output_buffer, " <");
|
|
g_string_append (output_buffer, cinfo->email);
|
|
g_string_append (output_buffer, ">");
|
|
}
|
|
|
|
} else if (cinfo->email != NULL && *cinfo->email != '\0') {
|
|
g_string_append (output_buffer, cinfo->email);
|
|
}
|
|
|
|
if (!g_queue_is_empty (&valid))
|
|
g_string_append (output_buffer, ", ");
|
|
}
|
|
|
|
g_string_append_c (output_buffer, ')');
|
|
}
|
|
|
|
static void
|
|
efh_format_text_header (EMFormatHTML *emfh,
|
|
GString *buffer,
|
|
const gchar *label,
|
|
const gchar *value,
|
|
guint32 flags)
|
|
{
|
|
const gchar *fmt, *html;
|
|
gchar *mhtml = NULL;
|
|
gboolean is_rtl;
|
|
|
|
if (value == NULL)
|
|
return;
|
|
|
|
while (*value == ' ')
|
|
value++;
|
|
|
|
if (!(flags & EM_FORMAT_HTML_HEADER_HTML))
|
|
html = mhtml = camel_text_to_html (value, emfh->text_html_flags, 0);
|
|
else
|
|
html = value;
|
|
|
|
is_rtl = gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL;
|
|
|
|
if (flags & EM_FORMAT_HTML_HEADER_NOCOLUMNS) {
|
|
if (flags & EM_FORMAT_HEADER_BOLD) {
|
|
fmt = "<tr class=\"header-item\" style=\"display: %s\"><td><b>%s:</b> %s</td></tr>";
|
|
} else {
|
|
fmt = "<tr class=\"header-item\" style=\"display: %s\"><td>%s: %s</td></tr>";
|
|
}
|
|
} else if (flags & EM_FORMAT_HTML_HEADER_NODEC) {
|
|
if (is_rtl)
|
|
fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th valign=top align=\"left\" nowrap>%1$s<b> </b></th></tr>";
|
|
else
|
|
fmt = "<tr class=\"header-item\" style=\"display: %s\"><th align=\"right\" valign=\"top\" nowrap>%s<b> </b></th><td valign=top>%s</td></tr>";
|
|
} else {
|
|
if (flags & EM_FORMAT_HEADER_BOLD) {
|
|
if (is_rtl)
|
|
fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th align=\"left\" nowrap>%1$s:<b> </b></th></tr>";
|
|
else
|
|
fmt = "<tr class=\"header-item\" style=\"display: %s\"><th align=\"right\" valign=\"top\" nowrap>%s:<b> </b></th><td>%s</td></tr>";
|
|
} else {
|
|
if (is_rtl)
|
|
fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%\">%2$s</td><td align=\"left\" nowrap>%1$s:<b> </b></td></tr>";
|
|
else
|
|
fmt = "<tr class=\"header-item\" style=\"display: %s\"><td align=\"right\" valign=\"top\" nowrap>%s:<b> </b></td><td>%s</td></tr>";
|
|
}
|
|
}
|
|
|
|
g_string_append_printf (buffer, fmt,
|
|
(flags & EM_FORMAT_HTML_HEADER_HIDDEN ? "none" : "table-row"), label, html);
|
|
|
|
g_free (mhtml);
|
|
}
|
|
|
|
static const gchar *addrspec_hdrs[] = {
|
|
"Sender", "From", "Reply-To", "To", "Cc", "Bcc",
|
|
"Resent-Sender", "Resent-From", "Resent-Reply-To",
|
|
"Resent-To", "Resent-Cc", "Resent-Bcc", NULL
|
|
};
|
|
|
|
static gchar *
|
|
efh_format_address (EMFormatHTML *efh,
|
|
GString *out,
|
|
struct _camel_header_address *a,
|
|
gchar *field,
|
|
gboolean no_links)
|
|
{
|
|
guint32 flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES;
|
|
gchar *name, *mailto, *addr;
|
|
gint i = 0;
|
|
gchar *str = NULL;
|
|
gint limit = mail_config_get_address_count ();
|
|
|
|
while (a) {
|
|
if (a->name)
|
|
name = camel_text_to_html (a->name, flags, 0);
|
|
else
|
|
name = NULL;
|
|
|
|
switch (a->type) {
|
|
case CAMEL_HEADER_ADDRESS_NAME:
|
|
if (name && *name) {
|
|
gchar *real, *mailaddr;
|
|
|
|
if (strchr (a->name, ',') || strchr (a->name, ';'))
|
|
g_string_append_printf (out, ""%s"", name);
|
|
else
|
|
g_string_append (out, name);
|
|
|
|
g_string_append (out, " <");
|
|
|
|
/* rfc2368 for mailto syntax and url encoding extras */
|
|
if ((real = camel_header_encode_phrase ((guchar *) a->name))) {
|
|
mailaddr = g_strdup_printf("%s <%s>", real, a->v.addr);
|
|
g_free (real);
|
|
mailto = camel_url_encode (mailaddr, "?=&()");
|
|
g_free (mailaddr);
|
|
} else {
|
|
mailto = camel_url_encode (a->v.addr, "?=&()");
|
|
}
|
|
} else {
|
|
mailto = camel_url_encode (a->v.addr, "?=&()");
|
|
}
|
|
addr = camel_text_to_html (a->v.addr, flags, 0);
|
|
if (no_links)
|
|
g_string_append_printf (out, "%s", addr);
|
|
else
|
|
g_string_append_printf (out, "<a href=\"mailto:%s\">%s</a>", mailto, addr);
|
|
g_free (mailto);
|
|
g_free (addr);
|
|
|
|
if (name && *name)
|
|
g_string_append (out, ">");
|
|
break;
|
|
case CAMEL_HEADER_ADDRESS_GROUP:
|
|
g_string_append_printf (out, "%s: ", name);
|
|
efh_format_address (efh, out, a->v.members, field, no_links);
|
|
g_string_append_printf (out, ";");
|
|
break;
|
|
default:
|
|
g_warning ("Invalid address type");
|
|
break;
|
|
}
|
|
|
|
g_free (name);
|
|
|
|
i++;
|
|
a = a->next;
|
|
if (a)
|
|
g_string_append (out, ", ");
|
|
|
|
/* Let us add a '...' if we have more addresses */
|
|
if (limit > 0 && (i == limit - 1)) {
|
|
const gchar *id = NULL;
|
|
|
|
if (strcmp (field, _("To")) == 0) {
|
|
id = "to";
|
|
} else if (strcmp (field, _("Cc")) == 0) {
|
|
id = "cc";
|
|
} else if (strcmp (field, _("Bcc")) == 0) {
|
|
id = "bcc";
|
|
}
|
|
|
|
if (id) {
|
|
g_string_append_printf (out,
|
|
"<span id=\"__evo-moreaddr-%s\" "
|
|
"style=\"display: none;\">", id);
|
|
str = g_strdup_printf (
|
|
"<img src=\"evo-file://%s/plus.png\" "
|
|
"id=\"__evo-moreaddr-img-%s\" class=\"navigable\">",
|
|
EVOLUTION_IMAGESDIR, id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (str) {
|
|
const gchar *id = NULL;
|
|
|
|
if (strcmp (field, _("To")) == 0) {
|
|
id = "to";
|
|
} else if (strcmp (field, _("Cc")) == 0) {
|
|
id = "cc";
|
|
} else if (strcmp (field, _("Bcc")) == 0) {
|
|
id = "bcc";
|
|
}
|
|
|
|
if (id) {
|
|
g_string_append_printf (out,
|
|
"</span>"
|
|
"<span class=\"navigable\" "
|
|
"id=\"__evo-moreaddr-ellipsis-%s\" "
|
|
"style=\"display: inline;\">...</span>",
|
|
id);
|
|
}
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
static void
|
|
canon_header_name (gchar *name)
|
|
{
|
|
gchar *inptr = name;
|
|
|
|
/* canonicalise the header name... first letter is
|
|
* capitalised and any letter following a '-' also gets
|
|
* capitalised */
|
|
|
|
if (*inptr >= 'a' && *inptr <= 'z')
|
|
*inptr -= 0x20;
|
|
|
|
inptr++;
|
|
|
|
while (*inptr) {
|
|
if (inptr[-1] == '-' && *inptr >= 'a' && *inptr <= 'z')
|
|
*inptr -= 0x20;
|
|
else if (*inptr >= 'A' && *inptr <= 'Z')
|
|
*inptr += 0x20;
|
|
|
|
inptr++;
|
|
}
|
|
}
|
|
|
|
void
|
|
em_format_html_format_header (EMFormat *emf,
|
|
GString *buffer,
|
|
CamelMedium *part,
|
|
struct _camel_header_raw *header,
|
|
guint32 flags,
|
|
const gchar *charset)
|
|
{
|
|
EMFormatHTML *efh = EM_FORMAT_HTML (emf);
|
|
gchar *name, *buf, *value = NULL;
|
|
const gchar *label, *txt;
|
|
gboolean addrspec = FALSE;
|
|
gchar *str_field = NULL;
|
|
gint i;
|
|
|
|
name = g_alloca (strlen (header->name) + 1);
|
|
strcpy (name, header->name);
|
|
canon_header_name (name);
|
|
|
|
for (i = 0; addrspec_hdrs[i]; i++) {
|
|
if (!strcmp (name, addrspec_hdrs[i])) {
|
|
addrspec = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
label = _(name);
|
|
|
|
if (addrspec) {
|
|
struct _camel_header_address *addrs;
|
|
GString *html;
|
|
gchar *img;
|
|
const gchar *charset = em_format_get_charset (emf) ?
|
|
em_format_get_charset (emf) : em_format_get_default_charset (emf);
|
|
|
|
buf = camel_header_unfold (header->value);
|
|
if (!(addrs = camel_header_address_decode (buf, charset))) {
|
|
g_free (buf);
|
|
return;
|
|
}
|
|
|
|
g_free (buf);
|
|
|
|
html = g_string_new("");
|
|
img = efh_format_address (efh, html, addrs, (gchar *) label,
|
|
(flags & EM_FORMAT_HTML_HEADER_NOLINKS));
|
|
|
|
if (img) {
|
|
str_field = g_strdup_printf ("%s%s:", img, label);
|
|
label = str_field;
|
|
flags |= EM_FORMAT_HTML_HEADER_NODEC;
|
|
g_free (img);
|
|
}
|
|
|
|
camel_header_address_list_clear (&addrs);
|
|
txt = value = html->str;
|
|
g_string_free (html, FALSE);
|
|
|
|
flags |= EM_FORMAT_HEADER_BOLD | EM_FORMAT_HTML_HEADER_HTML;
|
|
} else if (!strcmp (name, "Subject")) {
|
|
buf = camel_header_unfold (header->value);
|
|
txt = value = camel_header_decode_string (buf, charset);
|
|
g_free (buf);
|
|
|
|
flags |= EM_FORMAT_HEADER_BOLD;
|
|
} else if (!strcmp(name, "X-evolution-mailer")) {
|
|
/* pseudo-header */
|
|
label = _("Mailer");
|
|
txt = value = camel_header_format_ctext (header->value, charset);
|
|
flags |= EM_FORMAT_HEADER_BOLD;
|
|
} else if (!strcmp (name, "Date") || !strcmp (name, "Resent-Date")) {
|
|
gint msg_offset, local_tz;
|
|
time_t msg_date;
|
|
struct tm local;
|
|
gchar *html;
|
|
gboolean hide_real_date;
|
|
|
|
hide_real_date = !em_format_html_get_show_real_date (efh);
|
|
|
|
txt = header->value;
|
|
while (*txt == ' ' || *txt == '\t')
|
|
txt++;
|
|
|
|
html = camel_text_to_html (txt, efh->text_html_flags, 0);
|
|
|
|
msg_date = camel_header_decode_date (txt, &msg_offset);
|
|
e_localtime_with_offset (msg_date, &local, &local_tz);
|
|
|
|
/* Convert message offset to minutes (e.g. -0400 --> -240) */
|
|
msg_offset = ((msg_offset / 100) * 60) + (msg_offset % 100);
|
|
/* Turn into offset from localtime, not UTC */
|
|
msg_offset -= local_tz / 60;
|
|
|
|
/* value will be freed at the end */
|
|
if (!hide_real_date && !msg_offset) {
|
|
/* No timezone difference; just show the real Date: header */
|
|
txt = value = html;
|
|
} else {
|
|
gchar *date_str;
|
|
|
|
date_str = e_datetime_format_format ("mail", "header",
|
|
DTFormatKindDateTime, msg_date);
|
|
|
|
if (hide_real_date) {
|
|
/* Show only the local-formatted date, losing all timezone
|
|
* information like Outlook does. Should we attempt to show
|
|
* it somehow? */
|
|
txt = value = date_str;
|
|
} else {
|
|
txt = value = g_strdup_printf ("%s (<I>%s</I>)", html, date_str);
|
|
g_free (date_str);
|
|
}
|
|
g_free (html);
|
|
}
|
|
flags |= EM_FORMAT_HTML_HEADER_HTML | EM_FORMAT_HEADER_BOLD;
|
|
} else if (!strcmp(name, "Newsgroups")) {
|
|
struct _camel_header_newsgroup *ng, *scan;
|
|
GString *html;
|
|
|
|
buf = camel_header_unfold (header->value);
|
|
|
|
if (!(ng = camel_header_newsgroups_decode (buf))) {
|
|
g_free (buf);
|
|
return;
|
|
}
|
|
|
|
g_free (buf);
|
|
|
|
html = g_string_new("");
|
|
scan = ng;
|
|
while (scan) {
|
|
if (flags & EM_FORMAT_HTML_HEADER_NOLINKS)
|
|
g_string_append_printf (html, "%s", scan->newsgroup);
|
|
else
|
|
g_string_append_printf(html, "<a href=\"news:%s\">%s</a>",
|
|
scan->newsgroup, scan->newsgroup);
|
|
scan = scan->next;
|
|
if (scan)
|
|
g_string_append_printf(html, ", ");
|
|
}
|
|
|
|
camel_header_newsgroups_free (ng);
|
|
|
|
txt = html->str;
|
|
g_string_free (html, FALSE);
|
|
flags |= EM_FORMAT_HEADER_BOLD | EM_FORMAT_HTML_HEADER_HTML;
|
|
} else if (!strcmp (name, "Received") || !strncmp (name, "X-", 2)) {
|
|
/* don't unfold Received nor extension headers */
|
|
txt = value = camel_header_decode_string (header->value, charset);
|
|
} else {
|
|
/* don't unfold Received nor extension headers */
|
|
buf = camel_header_unfold (header->value);
|
|
txt = value = camel_header_decode_string (buf, charset);
|
|
g_free (buf);
|
|
}
|
|
|
|
efh_format_text_header (efh, buffer, label, txt, flags);
|
|
|
|
g_free (value);
|
|
g_free (str_field);
|
|
}
|
|
|
|
static void
|
|
efh_format_short_headers (EMFormatHTML *efh,
|
|
GString *buffer,
|
|
CamelMedium *part,
|
|
gboolean visible,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormat *emf = EM_FORMAT (efh);
|
|
const gchar *charset;
|
|
CamelContentType *ct;
|
|
const gchar *hdr_charset;
|
|
gchar *evolution_imagesdir;
|
|
gchar *subject = NULL;
|
|
struct _camel_header_address *addrs = NULL;
|
|
struct _camel_header_raw *header;
|
|
GString *from;
|
|
gboolean is_rtl;
|
|
|
|
if (cancellable && g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
ct = camel_mime_part_get_content_type ((CamelMimePart *) part);
|
|
charset = camel_content_type_param (ct, "charset");
|
|
charset = camel_iconv_charset_name (charset);
|
|
hdr_charset = em_format_get_charset (emf) ?
|
|
em_format_get_charset (emf) : em_format_get_default_charset (emf);
|
|
|
|
evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL);
|
|
from = g_string_new ("");
|
|
|
|
g_string_append_printf (buffer,
|
|
"<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" "
|
|
"id=\"__evo-short-headers\" style=\"display: %s\">",
|
|
visible ? "block" : "none");
|
|
|
|
header = ((CamelMimePart *) part)->headers;
|
|
while (header) {
|
|
if (!g_ascii_strcasecmp (header->name, "From")) {
|
|
GString *tmp;
|
|
if (!(addrs = camel_header_address_decode (header->value, hdr_charset))) {
|
|
header = header->next;
|
|
continue;
|
|
}
|
|
tmp = g_string_new ("");
|
|
efh_format_address (efh, tmp, addrs, header->name, FALSE);
|
|
|
|
if (tmp->len)
|
|
g_string_printf (from, _("From: %s"), tmp->str);
|
|
g_string_free (tmp, TRUE);
|
|
|
|
} else if (!g_ascii_strcasecmp (header->name, "Subject")) {
|
|
gchar *buf = NULL;
|
|
subject = camel_header_unfold (header->value);
|
|
buf = camel_header_decode_string (subject, hdr_charset);
|
|
g_free (subject);
|
|
subject = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT, 0);
|
|
g_free (buf);
|
|
}
|
|
header = header->next;
|
|
}
|
|
|
|
is_rtl = gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL;
|
|
if (is_rtl) {
|
|
g_string_append_printf (
|
|
buffer,
|
|
"<tr><td width=\"100%%\" align=\"right\">%s%s%s <strong>%s</strong></td></tr>",
|
|
from->len ? "(" : "", from->str, from->len ? ")" : "",
|
|
subject ? subject : _("(no subject)"));
|
|
} else {
|
|
g_string_append_printf (
|
|
buffer,
|
|
"<tr><td><strong>%s</strong> %s%s%s</td></tr>",
|
|
subject ? subject : _("(no subject)"),
|
|
from->len ? "(" : "", from->str, from->len ? ")" : "");
|
|
}
|
|
|
|
g_string_append (buffer, "</table>");
|
|
|
|
g_free (subject);
|
|
if (addrs)
|
|
camel_header_address_list_clear (&addrs);
|
|
|
|
g_string_free (from, TRUE);
|
|
g_free (evolution_imagesdir);
|
|
}
|
|
|
|
static void
|
|
write_contact_picture (CamelMimePart *part,
|
|
gint size,
|
|
GString *buffer)
|
|
{
|
|
gchar *b64, *content_type;
|
|
CamelDataWrapper *dw;
|
|
CamelContentType *ct;
|
|
GByteArray *ba;
|
|
|
|
ba = NULL;
|
|
dw = camel_medium_get_content (CAMEL_MEDIUM (part));
|
|
if (dw) {
|
|
ba = camel_data_wrapper_get_byte_array (dw);
|
|
}
|
|
|
|
if (!ba || ba->len == 0) {
|
|
|
|
if (camel_mime_part_get_filename (part)) {
|
|
|
|
if (size >= 0) {
|
|
g_string_append_printf (
|
|
buffer,
|
|
"<img width=\"%d\" src=\"evo-file://%s\" />",
|
|
size, camel_mime_part_get_filename (part));
|
|
} else {
|
|
g_string_append_printf (
|
|
buffer,
|
|
"<img src=\"evo-file://%s\" />",
|
|
camel_mime_part_get_filename (part));
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
b64 = g_base64_encode (ba->data, ba->len);
|
|
ct = camel_mime_part_get_content_type (part);
|
|
content_type = camel_content_type_simple (ct);
|
|
|
|
if (size >= 0) {
|
|
g_string_append_printf (
|
|
buffer,
|
|
"<img width=\"%d\" src=\"data:%s;base64,%s\">",
|
|
size, content_type, b64);
|
|
} else {
|
|
g_string_append_printf (
|
|
buffer,
|
|
"<img src=\"data:%s;base64,%s\">",
|
|
content_type, b64);
|
|
}
|
|
|
|
g_free (b64);
|
|
g_free (content_type);
|
|
}
|
|
|
|
static void
|
|
efh_format_full_headers (EMFormatHTML *efh,
|
|
GString *buffer,
|
|
CamelMedium *part,
|
|
gboolean all_headers,
|
|
gboolean visible,
|
|
GCancellable *cancellable)
|
|
{
|
|
EMFormat *emf = EM_FORMAT (efh);
|
|
const gchar *charset;
|
|
CamelContentType *ct;
|
|
struct _camel_header_raw *header;
|
|
gboolean have_icon = FALSE;
|
|
const gchar *photo_name = NULL;
|
|
CamelInternetAddress *cia = NULL;
|
|
CamelSession *session;
|
|
ESourceRegistry *registry;
|
|
gboolean face_decoded = FALSE, contact_has_photo = FALSE;
|
|
guchar *face_header_value = NULL;
|
|
gsize face_header_len = 0;
|
|
gchar *header_sender = NULL, *header_from = NULL, *name;
|
|
gboolean mail_from_delegate = FALSE;
|
|
const gchar *hdr_charset;
|
|
gchar *evolution_imagesdir;
|
|
|
|
if (cancellable && g_cancellable_is_cancelled (cancellable))
|
|
return;
|
|
|
|
session = em_format_get_session (emf);
|
|
registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
|
|
|
|
ct = camel_mime_part_get_content_type ((CamelMimePart *) part);
|
|
charset = camel_content_type_param (ct, "charset");
|
|
charset = camel_iconv_charset_name (charset);
|
|
hdr_charset = em_format_get_charset (emf) ?
|
|
em_format_get_charset (emf) : em_format_get_default_charset (emf);
|
|
|
|
evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL);
|
|
|
|
g_string_append_printf (buffer,
|
|
"<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" "
|
|
"id=\"__evo-full-headers\" style=\"display: %s\" width=\"100%%\">",
|
|
visible ? "block" : "none");
|
|
|
|
header = ((CamelMimePart *) part)->headers;
|
|
while (header) {
|
|
if (!g_ascii_strcasecmp (header->name, "Sender")) {
|
|
struct _camel_header_address *addrs;
|
|
GString *html;
|
|
|
|
if (!(addrs = camel_header_address_decode (header->value, hdr_charset)))
|
|
break;
|
|
|
|
html = g_string_new("");
|
|
name = efh_format_address (efh, html, addrs, header->name, FALSE);
|
|
|
|
header_sender = html->str;
|
|
camel_header_address_list_clear (&addrs);
|
|
|
|
g_string_free (html, FALSE);
|
|
g_free (name);
|
|
} else if (!g_ascii_strcasecmp (header->name, "From")) {
|
|
struct _camel_header_address *addrs;
|
|
GString *html;
|
|
|
|
if (!(addrs = camel_header_address_decode (header->value, hdr_charset)))
|
|
break;
|
|
|
|
html = g_string_new("");
|
|
name = efh_format_address (efh, html, addrs, header->name, FALSE);
|
|
|
|
header_from = html->str;
|
|
camel_header_address_list_clear (&addrs);
|
|
|
|
g_string_free (html, FALSE);
|
|
g_free (name);
|
|
} else if (!g_ascii_strcasecmp (header->name, "X-Evolution-Mail-From-Delegate")) {
|
|
mail_from_delegate = TRUE;
|
|
}
|
|
|
|
header = header->next;
|
|
}
|
|
|
|
if (header_sender && header_from && mail_from_delegate) {
|
|
gchar *bold_sender, *bold_from;
|
|
|
|
g_string_append (
|
|
buffer,
|
|
"<tr><td><table border=1 width=\"100%%\" "
|
|
"cellspacing=2 cellpadding=2><tr>");
|
|
if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL)
|
|
g_string_append (
|
|
buffer, "<td align=\"right\" width=\"100%%\">");
|
|
else
|
|
g_string_append (
|
|
buffer, "<td align=\"left\" width=\"100%%\">");
|
|
bold_sender = g_strconcat ("<b>", header_sender, "</b>", NULL);
|
|
bold_from = g_strconcat ("<b>", header_from, "</b>", NULL);
|
|
/* Translators: This message suggests to the receipients
|
|
* that the sender of the mail is different from the one
|
|
* listed in From field. */
|
|
g_string_append_printf (
|
|
buffer,
|
|
_("This message was sent by %s on behalf of %s"),
|
|
bold_sender, bold_from);
|
|
g_string_append (buffer, "</td></tr></table></td></tr>");
|
|
g_free (bold_sender);
|
|
g_free (bold_from);
|
|
}
|
|
|
|
g_free (header_sender);
|
|
g_free (header_from);
|
|
|
|
g_string_append (buffer, "<tr><td width=\"100%%\"><table border=0 cellpadding=\"0\">\n");
|
|
|
|
g_free (evolution_imagesdir);
|
|
|
|
/* dump selected headers */
|
|
if (all_headers) {
|
|
header = ((CamelMimePart *) part)->headers;
|
|
while (header) {
|
|
em_format_html_format_header (
|
|
emf, buffer, part, header,
|
|
EM_FORMAT_HTML_HEADER_NOCOLUMNS, charset);
|
|
header = header->next;
|
|
}
|
|
} else {
|
|
GList *link;
|
|
gint mailer_shown = FALSE;
|
|
|
|
link = g_queue_peek_head_link (&emf->header_list);
|
|
|
|
while (link != NULL) {
|
|
EMFormatHeader *h = link->data;
|
|
gint mailer, face;
|
|
|
|
header = ((CamelMimePart *) part)->headers;
|
|
mailer = !g_ascii_strcasecmp (h->name, "X-Evolution-Mailer");
|
|
face = !g_ascii_strcasecmp (h->name, "Face");
|
|
|
|
while (header) {
|
|
if (em_format_html_get_show_sender_photo (efh) &&
|
|
!photo_name && !g_ascii_strcasecmp (header->name, "From"))
|
|
photo_name = header->value;
|
|
|
|
if (!mailer_shown && mailer && (
|
|
!g_ascii_strcasecmp (header->name, "X-Mailer") ||
|
|
!g_ascii_strcasecmp (header->name, "User-Agent") ||
|
|
!g_ascii_strcasecmp (header->name, "X-Newsreader") ||
|
|
!g_ascii_strcasecmp (header->name, "X-MimeOLE"))) {
|
|
struct _camel_header_raw xmailer, *use_header = NULL;
|
|
|
|
if (!g_ascii_strcasecmp (header->name, "X-MimeOLE")) {
|
|
for (use_header = header->next; use_header; use_header = use_header->next) {
|
|
if (!g_ascii_strcasecmp (use_header->name, "X-Mailer") ||
|
|
!g_ascii_strcasecmp (use_header->name, "User-Agent") ||
|
|
!g_ascii_strcasecmp (use_header->name, "X-Newsreader")) {
|
|
/* even we have X-MimeOLE, then use rather the standard one, when available */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!use_header)
|
|
use_header = header;
|
|
|
|
xmailer.name = (gchar *) "X-Evolution-Mailer";
|
|
xmailer.value = use_header->value;
|
|
mailer_shown = TRUE;
|
|
|
|
em_format_html_format_header (
|
|
emf, buffer, part,
|
|
&xmailer, h->flags, charset);
|
|
if (strstr(use_header->value, "Evolution"))
|
|
have_icon = TRUE;
|
|
} else if (!face_decoded && face && !g_ascii_strcasecmp (header->name, "Face")) {
|
|
gchar *cp = header->value;
|
|
|
|
/* Skip over spaces */
|
|
while (*cp == ' ')
|
|
cp++;
|
|
|
|
face_header_value = g_base64_decode (
|
|
cp, &face_header_len);
|
|
face_header_value = g_realloc (
|
|
face_header_value,
|
|
face_header_len + 1);
|
|
face_header_value[face_header_len] = 0;
|
|
face_decoded = TRUE;
|
|
/* Showing an encoded "Face" header makes little sense */
|
|
} else if (!g_ascii_strcasecmp (header->name, h->name) && !face) {
|
|
em_format_html_format_header (
|
|
emf, buffer, part,
|
|
header, h->flags, charset);
|
|
}
|
|
|
|
header = header->next;
|
|
}
|
|
|
|
link = g_list_next (link);
|
|
}
|
|
}
|
|
|
|
g_string_append (buffer, "</table></td>");
|
|
|
|
if (photo_name) {
|
|
CamelMimePart *photopart;
|
|
gboolean only_local_photo;
|
|
|
|
cia = camel_internet_address_new ();
|
|
camel_address_decode ((CamelAddress *) cia, (const gchar *) photo_name);
|
|
only_local_photo =
|
|
em_format_html_get_only_local_photos (efh);
|
|
photopart = em_utils_contact_photo (
|
|
registry, cia, only_local_photo);
|
|
|
|
if (photopart) {
|
|
g_string_append (buffer, "<td align=\"right\" valign=\"top\">");
|
|
write_contact_picture (photopart, -1, buffer);
|
|
g_string_append (buffer, "</td>");
|
|
g_object_unref (photopart);
|
|
}
|
|
g_object_unref (cia);
|
|
}
|
|
|
|
if (!contact_has_photo && face_decoded) {
|
|
CamelMimePart *part;
|
|
|
|
part = camel_mime_part_new ();
|
|
camel_mime_part_set_content (
|
|
(CamelMimePart *) part,
|
|
(const gchar *) face_header_value,
|
|
face_header_len, "image/png");
|
|
|
|
g_string_append (buffer, "<td align=\"right\" valign=\"top\">");
|
|
write_contact_picture (part, 48, buffer);
|
|
g_string_append (buffer, "</td>");
|
|
|
|
g_object_unref (part);
|
|
g_free (face_header_value);
|
|
}
|
|
|
|
if (have_icon && efh->show_icon) {
|
|
GtkIconInfo *icon_info;
|
|
CamelMimePart *iconpart = NULL;
|
|
|
|
icon_info = gtk_icon_theme_lookup_icon (
|
|
gtk_icon_theme_get_default (),
|
|
"evolution", 16, GTK_ICON_LOOKUP_NO_SVG);
|
|
if (icon_info != NULL) {
|
|
iconpart = em_format_html_file_part (
|
|
(EMFormatHTML *) emf, "image/png",
|
|
gtk_icon_info_get_filename (icon_info),
|
|
cancellable);
|
|
gtk_icon_info_free (icon_info);
|
|
}
|
|
if (iconpart) {
|
|
g_string_append (buffer, "<td align=\"right\" valign=\"top\">");
|
|
write_contact_picture (iconpart, 16, buffer);
|
|
g_string_append (buffer, "</td>");
|
|
|
|
g_object_unref (iconpart);
|
|
}
|
|
}
|
|
|
|
g_string_append (buffer, "</tr></table>");
|
|
}
|
|
|
|
gboolean
|
|
em_format_html_can_load_images (EMFormatHTML *efh)
|
|
{
|
|
g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);
|
|
|
|
return ((efh->priv->image_loading_policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS) ||
|
|
((efh->priv->image_loading_policy == E_MAIL_IMAGE_LOADING_POLICY_SOMETIMES) &&
|
|
efh->priv->can_load_images));
|
|
}
|
|
|
|
void
|
|
em_format_html_animation_extract_frame (const GByteArray *anim,
|
|
gchar **frame,
|
|
gsize *len)
|
|
{
|
|
GdkPixbufLoader *loader;
|
|
GdkPixbufAnimation *animation;
|
|
GdkPixbuf *frame_buf;
|
|
|
|
/* GIF89a (GIF image signature) */
|
|
const gchar GIF_HEADER[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };
|
|
const gint GIF_HEADER_LEN = sizeof (GIF_HEADER);
|
|
|
|
/* NETSCAPE2.0 (extension describing animated GIF, starts on 0x310) */
|
|
const gchar GIF_APPEXT[] = { 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41,
|
|
0x50, 0x45, 0x32, 0x2E, 0x30 };
|
|
const gint GIF_APPEXT_LEN = sizeof (GIF_APPEXT);
|
|
|
|
if ((anim == NULL) || (anim->data == NULL)) {
|
|
*frame = NULL;
|
|
*len = 0;
|
|
return;
|
|
}
|
|
|
|
/* Check if the image is an animated GIF. We don't care about any
|
|
* other animated formats (APNG or MNG) as WebKit does not support them
|
|
* and displays only the first frame. */
|
|
if ((anim->len < 0x331)
|
|
|| (memcmp (anim->data, GIF_HEADER, GIF_HEADER_LEN) != 0)
|
|
|| (memcmp (&anim->data[0x310], GIF_APPEXT, GIF_APPEXT_LEN) != 0)) {
|
|
|
|
*frame = g_memdup (anim->data, anim->len);
|
|
*len = anim->len;
|
|
return;
|
|
}
|
|
|
|
loader = gdk_pixbuf_loader_new ();
|
|
gdk_pixbuf_loader_write (loader, (guchar *) anim->data, anim->len, NULL);
|
|
gdk_pixbuf_loader_close (loader, NULL);
|
|
animation = gdk_pixbuf_loader_get_animation (loader);
|
|
if (!animation) {
|
|
|
|
*frame = g_memdup (anim->data, anim->len);
|
|
*len = anim->len;
|
|
g_object_unref (loader);
|
|
return;
|
|
}
|
|
|
|
/* Extract first frame */
|
|
frame_buf = gdk_pixbuf_animation_get_static_image (animation);
|
|
if (!frame_buf) {
|
|
*frame = g_memdup (anim->data, anim->len);
|
|
*len = anim->len;
|
|
g_object_unref (loader);
|
|
g_object_unref (animation);
|
|
return;
|
|
}
|
|
|
|
/* Unforunatelly, GdkPixbuf cannot save to GIF, but WebKit does not
|
|
* have any trouble displaying PNG image despite the part having
|
|
* image/gif mime-type */
|
|
gdk_pixbuf_save_to_buffer (frame_buf, frame, len, "png", NULL, NULL);
|
|
|
|
g_object_unref (loader);
|
|
}
|