/*
* e-mail-formatter-text-html.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/>
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#include <string.h>
#include <glib/gi18n-lib.h>
#include <e-util/e-util.h>
#include "e-mail-formatter-extension.h"
#include "e-mail-inline-filter.h"
#include "e-mail-part-utils.h"
typedef EMailFormatterExtension EMailFormatterTextHTML;
typedef EMailFormatterExtensionClass EMailFormatterTextHTMLClass;
GType e_mail_formatter_text_html_get_type (void);
G_DEFINE_TYPE (
EMailFormatterTextHTML,
e_mail_formatter_text_html,
E_TYPE_MAIL_FORMATTER_EXTENSION)
static const gchar *formatter_mime_types[] = {
"text/html",
NULL
};
static gchar *
get_tag (const gchar *utf8_string,
const gchar *tag_name,
gchar *opening,
gchar *closing)
{
gchar *t;
gunichar c;
gboolean has_end;
c = '\0';
t = g_utf8_find_prev_char (utf8_string, closing);
while (t != opening) {
c = g_utf8_get_char (t);
if (!g_unichar_isspace (c))
break;
}
/* Not a pair tag */
if (c == '/')
return g_strndup (opening, closing - opening + 1);
t = closing;
while (t) {
c = g_utf8_get_char (t);
if (c == '<') {
if (t[1] == '!' && t[2] == '-' && t[3] == '-') {
/* it's a comment start, read until the end of "-->" */
gchar *end = strstr (t + 4, "-->");
if (end) {
t = end + 2;
} else
break;
} else
break;
}
t = g_utf8_find_next_char (t, NULL);
}
has_end = FALSE;
do {
c = g_utf8_get_char (t);
if (c == '/') {
has_end = TRUE;
break;
}
if (c == '>') {
has_end = FALSE;
break;
}
t = g_utf8_find_next_char (t, NULL);
} while (t);
/* Broken HTML? */
if (!has_end)
return NULL;
do {
c = g_utf8_get_char (t);
if ((c != ' ') && (c != '/'))
break;
t = g_utf8_find_next_char (t, NULL);
} while (t);
/* tag_name is always ASCII */
if (g_ascii_strncasecmp (t, tag_name, strlen (tag_name)) == 0) {
closing = g_utf8_strchr (t, -1, '>');
return g_strndup (opening, closing - opening + 1);
}
/* Broken HTML? */
return NULL;
}
static gboolean
emfe_text_html_format (EMailFormatterExtension *extension,
EMailFormatter *formatter,
EMailFormatterContext *context,
EMailPart *part,
CamelStream *stream,
GCancellable *cancellable)
{
if (g_cancellable_is_cancelled (cancellable))
return FALSE;
if (context->mode == E_MAIL_FORMATTER_MODE_RAW) {
e_mail_formatter_format_text (formatter, part, stream, cancellable);
} else if (context->mode == E_MAIL_FORMATTER_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 ();
/* FORMATTER FIXME: See above */
e_mail_formatter_format_text (formatter, part, decoded_stream, 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);
if (!g_utf8_validate (string->str, -1, NULL)) {
gchar *valid_utf8;
valid_utf8 = e_util_utf8_make_valid (string->str);
g_string_free (string, TRUE);
string = g_string_new (valid_utf8);
g_free (valid_utf8);
}
tags = NULL;
pos = string->str;
valid = FALSE;
do {
gchar *tmp;
gchar *closing;
gchar *opening;
tmp = g_utf8_find_next_char (pos, NULL);
pos = g_utf8_strchr (tmp, -1, '<');
if (!pos)
break;
opening = pos;
closing = g_utf8_strchr (pos, -1, '>');
/* Find where the actual tag name begins */
tag = g_utf8_find_next_char (pos, NULL);
while ((tag = g_utf8_find_next_char (pos, NULL)) != NULL) {
gunichar c = g_utf8_get_char (tag);
if (!g_unichar_isspace (c))
break;
}
if (g_ascii_strncasecmp (tag, "style", 5) == 0) {
tags = g_list_append (
tags,
get_tag (string->str, "style", opening, closing));
} else if (g_ascii_strncasecmp (tag, "script", 6) == 0) {
tags = g_list_append (
tags,
get_tag (string->str, "script", opening, closing));
} else if (g_ascii_strncasecmp (tag, "link", 4) == 0) {
tags = g_list_append (
tags,
get_tag (string->str, "link", opening, closing));
} else if (g_ascii_strncasecmp (tag, "body", 4) == 0) {
valid = TRUE;
break;
}
} while (pos);
/* Something's wrong, let's write the entire HTML and hope
* that WebKit can handle it */
if (!valid) {
EMailFormatterContext c = {
.part_list = context->part_list,
.flags = context->flags,
.mode = E_MAIL_FORMATTER_MODE_RAW,
};
emfe_text_html_format (
extension, formatter, &c, part, stream, cancellable);
return FALSE;
}
/* 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) {
if (iter->data)
g_string_prepend (string, iter->data);
}
g_list_free_full (tags, g_free);
document_end = NULL;
/* We can probably use ASCII functions here */
if (g_strrstr (string->str, "</body>")) {
document_end = ">ydob/<";
}
if (g_strrstr (string->str, "</html>")) {
if (document_end) {
document_end = ">lmth/<>ydob/<";
} else {
document_end = ">lmth/<";
}
}
if (document_end) {
length = strlen (document_end);
tag = string->str + string->len - 1;
i = 0;
valid = FALSE;
while (i < length - 1) {
gunichar c;
c = g_utf8_get_char (tag);
if (g_unichar_isspace (c)) {
tag = g_utf8_find_prev_char (string->str, tag);
continue;
}
c = g_unichar_tolower (c);
if (c == document_end[i]) {
tag = g_utf8_find_prev_char (string->str, tag);
i++;
valid = TRUE;
continue;
}
tag = g_utf8_find_prev_char (string->str, tag);
valid = FALSE;
}
} else {
/* do not cut, if there is no end tag */
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 {
CamelFolder *folder;
const gchar *message_uid;
const gchar *default_charset, *charset;
gchar *uri, *str;
folder = e_mail_part_list_get_folder (context->part_list);
message_uid = e_mail_part_list_get_message_uid (context->part_list);
default_charset = e_mail_formatter_get_default_charset (formatter);
charset = e_mail_formatter_get_charset (formatter);
if (!default_charset)
default_charset = "";
if (!charset)
charset = "";
uri = e_mail_part_build_uri (
folder, message_uid,
"part_id", G_TYPE_STRING, e_mail_part_get_id (part),
"mode", G_TYPE_INT, E_MAIL_FORMATTER_MODE_RAW,
"formatter_default_charset", G_TYPE_STRING, default_charset,
"formatter_charset", G_TYPE_STRING, charset,
NULL);
/* HTML messages expect white background and black color for text.
* If Evolution uses a dark theme, then the dark background with
* a black text is hard to read, thus force white background color.
* The HTML content can still overwrite both colors.
*/
str = g_strdup_printf (
"<div class=\"part-container-nostyle\">"
"<iframe width=\"100%%\" height=\"10\" "
" frameborder=\"0\" src=\"%s\" "
" id=\"%s.iframe\" name=\"%s\" "
" style=\"border: 1px solid #%06x; background-color: #ffffff;\">"
"</iframe>"
"</div>",
uri,
e_mail_part_get_id (part),
e_mail_part_get_id (part),
e_rgba_to_value (
e_mail_formatter_get_color (
formatter, E_MAIL_FORMATTER_COLOR_FRAME)));
camel_stream_write_string (stream, str, cancellable, NULL);
g_free (str);
g_free (uri);
}
return TRUE;
}
static void
e_mail_formatter_text_html_class_init (EMailFormatterExtensionClass *class)
{
class->display_name = _("HTML");
class->description = _("Format part as HTML");
class->mime_types = formatter_mime_types;
class->priority = G_PRIORITY_LOW;
class->format = emfe_text_html_format;
}
static void
e_mail_formatter_text_html_init (EMailFormatterExtension *extension)
{
}