/*
* e-mail-display.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
*
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#define LIBSOUP_USE_UNSTABLE_REQUEST_API
#include "e-mail-display.h"
#include
#include
#include "e-util/e-marshal.h"
#include "e-util/e-util.h"
#include "e-util/e-plugin-ui.h"
#include "mail/em-composer-utils.h"
#include "mail/em-utils.h"
#include "mail/e-mail-request.h"
#include "mail/em-format-html-display.h"
#include "mail/e-mail-attachment-bar.h"
#include "widgets/misc/e-attachment-button.h"
#include
#include
#include
#include
#define d(x)
G_DEFINE_TYPE (EMailDisplay, e_mail_display, E_TYPE_WEB_VIEW)
#define E_MAIL_DISPLAY_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_MAIL_DISPLAY, EMailDisplayPrivate))
struct _EMailDisplayPrivate {
EMFormatHTML *formatter;
EMFormatWriteMode mode;
gboolean headers_collapsable;
gboolean headers_collapsed;
GtkActionGroup *mailto_actions;
GtkActionGroup *images_actions;
gint force_image_load: 1;
GSettings *settings;
};
enum {
PROP_0,
PROP_FORMATTER,
PROP_MODE,
PROP_HEADERS_COLLAPSABLE,
PROP_HEADERS_COLLAPSED,
};
static gpointer parent_class;
static CamelDataCache *emd_global_http_cache = 0;
static const gchar *ui =
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
"";
static const gchar *image_ui =
""
" "
" "
" "
" "
" "
"";
static GtkActionEntry mailto_entries[] = {
{ "add-to-address-book",
"contact-new",
N_("_Add to Address Book..."),
NULL,
NULL, /* XXX Add a tooltip! */
NULL /* Handled by EMailReader */ },
{ "search-folder-recipient",
NULL,
N_("_To This Address"),
NULL,
NULL, /* XXX Add a tooltip! */
NULL /* Handled by EMailReader */ },
{ "search-folder-sender",
NULL,
N_("_From This Address"),
NULL,
NULL, /* XXX Add a tooltip! */
NULL /* Handled by EMailReader */ },
{ "send-reply",
NULL,
N_("Send _Reply To..."),
NULL,
N_("Send a reply message to this address"),
NULL /* Handled by EMailReader */ },
/*** Menus ***/
{ "search-folder-menu",
"folder-saved-search",
N_("Create Search _Folder"),
NULL,
NULL,
NULL }
};
static GtkActionEntry image_entries[] = {
{ "image-save",
GTK_STOCK_SAVE,
N_("Save _Image..."),
NULL,
N_("Save the image to a file"),
NULL /* Handled by EMailReader */ },
};
static void
mail_display_webview_update_actions (EWebView *web_view,
gpointer user_data)
{
const gchar *image_src;
gboolean visible;
GtkAction *action;
g_return_if_fail (web_view != NULL);
image_src = e_web_view_get_cursor_image_src (web_view);
visible = image_src && g_str_has_prefix (image_src, "cid:");
if (!visible && image_src) {
CamelStream *image_stream;
image_stream = camel_data_cache_get (emd_global_http_cache, "http", image_src, NULL);
visible = image_stream != NULL;
if (image_stream)
g_object_unref (image_stream);
}
action = e_web_view_get_action (web_view, "image-save");
if (action)
gtk_action_set_visible (action, visible);
}
static void
formatter_image_loading_policy_changed_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
EMailDisplay *display = user_data;
e_mail_display_load_images (display);
}
static void
mail_display_update_formatter_colors (EMailDisplay *display)
{
EMFormatHTMLColorType type;
EMFormatHTML *formatter;
GdkColor *color;
GtkStateType state;
GtkStyle *style;
state = gtk_widget_get_state (GTK_WIDGET (display));
formatter = display->priv->formatter;
if (!display->priv->formatter)
return;
style = gtk_widget_get_style (GTK_WIDGET (display));
if (style == NULL)
return;
g_object_freeze_notify (G_OBJECT (formatter));
color = &style->bg[state];
type = EM_FORMAT_HTML_COLOR_BODY;
em_format_html_set_color (formatter, type, color);
color = &style->base[GTK_STATE_NORMAL];
type = EM_FORMAT_HTML_COLOR_CONTENT;
em_format_html_set_color (formatter, type, color);
color = &style->dark[state];
type = EM_FORMAT_HTML_COLOR_FRAME;
em_format_html_set_color (formatter, type, color);
color = &style->fg[state];
type = EM_FORMAT_HTML_COLOR_HEADER;
em_format_html_set_color (formatter, type, color);
color = &style->text[state];
type = EM_FORMAT_HTML_COLOR_TEXT;
em_format_html_set_color (formatter, type, color);
g_object_thaw_notify (G_OBJECT (formatter));
}
static void
mail_display_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FORMATTER:
e_mail_display_set_formatter (
E_MAIL_DISPLAY (object),
g_value_get_object (value));
return;
case PROP_MODE:
e_mail_display_set_mode (
E_MAIL_DISPLAY (object),
g_value_get_int (value));
return;
case PROP_HEADERS_COLLAPSABLE:
e_mail_display_set_headers_collapsable (
E_MAIL_DISPLAY (object),
g_value_get_boolean (value));
return;
case PROP_HEADERS_COLLAPSED:
e_mail_display_set_headers_collapsed (
E_MAIL_DISPLAY (object),
g_value_get_boolean (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
mail_display_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FORMATTER:
g_value_set_object (
value, e_mail_display_get_formatter (
E_MAIL_DISPLAY (object)));
return;
case PROP_MODE:
g_value_set_int (
value, e_mail_display_get_mode (
E_MAIL_DISPLAY (object)));
return;
case PROP_HEADERS_COLLAPSABLE:
g_value_set_boolean (
value, e_mail_display_get_headers_collapsable (
E_MAIL_DISPLAY (object)));
return;
case PROP_HEADERS_COLLAPSED:
g_value_set_boolean (
value, e_mail_display_get_headers_collapsed (
E_MAIL_DISPLAY (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
mail_display_dispose (GObject *object)
{
EMailDisplayPrivate *priv;
priv = E_MAIL_DISPLAY_GET_PRIVATE (object);
if (priv->formatter) {
g_object_unref (priv->formatter);
priv->formatter = NULL;
}
if (priv->settings) {
g_object_unref (priv->settings);
priv->settings = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
mail_display_realize (GtkWidget *widget)
{
/* Chain up to parent's realize() method. */
GTK_WIDGET_CLASS (parent_class)->realize (widget);
mail_display_update_formatter_colors (E_MAIL_DISPLAY (widget));
}
static void
mail_display_style_set (GtkWidget *widget,
GtkStyle *previous_style)
{
EMailDisplay *display = E_MAIL_DISPLAY (widget);
mail_display_update_formatter_colors (display);
/* Chain up to parent's style_set() method. */
GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
}
static gboolean
mail_display_process_mailto (EWebView *web_view,
const gchar *mailto_uri,
gpointer user_data)
{
g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
g_return_val_if_fail (mailto_uri != NULL, FALSE);
if (g_ascii_strncasecmp (mailto_uri, "mailto:", 7) == 0) {
EMFormat *format;
CamelFolder *folder = NULL;
EShell *shell;
format = (EMFormat *) E_MAIL_DISPLAY (web_view)->priv->formatter;
if (format != NULL && format->folder != NULL)
folder = format->folder;
shell = e_shell_get_default ();
em_utils_compose_new_message_with_mailto (
shell, mailto_uri, folder);
return TRUE;
}
return FALSE;
}
static gboolean
mail_display_link_clicked (WebKitWebView *web_view,
WebKitWebFrame *frame,
WebKitNetworkRequest *request,
WebKitWebNavigationAction *navigation_action,
WebKitWebPolicyDecision *policy_decision,
gpointer user_data)
{
EMailDisplay *display;
const gchar *uri = webkit_network_request_get_uri (request);
display = E_MAIL_DISPLAY (web_view);
if (display->priv->formatter == NULL)
return FALSE;
if (mail_display_process_mailto (E_WEB_VIEW (web_view), uri, NULL)) {
/* do nothing, function handled the "mailto:" uri already */
webkit_web_policy_decision_ignore (policy_decision);
return TRUE;
} else if (g_ascii_strncasecmp (uri, "thismessage:", 12) == 0) {
/* ignore */ ;
webkit_web_policy_decision_ignore (policy_decision);
return TRUE;
} else if (g_ascii_strncasecmp (uri, "cid:", 4) == 0) {
/* ignore */ ;
webkit_web_policy_decision_ignore (policy_decision);
return TRUE;
}
/* Let webkit handle it */
return FALSE;
}
static void
webkit_request_load_from_file (WebKitNetworkRequest *request,
const gchar *path)
{
gchar *data = NULL;
gsize length = 0;
gboolean status;
gchar *b64, *new_uri;
gchar *ct;
status = g_file_get_contents (path, &data, &length, NULL);
if (!status)
return;
b64 = g_base64_encode ((guchar *) data, length);
ct = g_content_type_guess (path, NULL, 0, NULL);
new_uri = g_strdup_printf ("data:%s;base64,%s", ct, b64);
webkit_network_request_set_uri (request, new_uri);
g_free (b64);
g_free (new_uri);
g_free (ct);
g_free (data);
}
static void
mail_display_resource_requested (WebKitWebView *web_view,
WebKitWebFrame *frame,
WebKitWebResource *resource,
WebKitNetworkRequest *request,
WebKitNetworkResponse *response,
gpointer user_data)
{
EMailDisplay *display = E_MAIL_DISPLAY (web_view);
EMFormat *formatter = EM_FORMAT (display->priv->formatter);
const gchar *uri = webkit_network_request_get_uri (request);
if (!formatter) {
webkit_network_request_set_uri (request, "invalid://uri");
return;
}
/* Redirect cid:part_id to mail://mail_id/cid:part_id */
if (g_str_has_prefix (uri, "cid:")) {
/* Always write raw content of CID object */
gchar *new_uri = em_format_build_mail_uri (formatter->folder,
formatter->message_uid,
"part_id", G_TYPE_STRING, uri,
"mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW, NULL);
webkit_network_request_set_uri (request, new_uri);
g_free (new_uri);
/* WebKit won't allow to load a local file when displaing "remote" mail://,
protocol, so we need to handle this manually */
} else if (g_str_has_prefix (uri, "file:")) {
gchar *path;
path = g_filename_from_uri (uri, NULL, NULL);
if (!path)
return;
webkit_request_load_from_file (request, path);
g_free (path);
/* Redirect http(s) request to evo-http(s) protocol. See EMailRequest for
* further details about this. */
} else if (g_str_has_prefix (uri, "http:") || g_str_has_prefix (uri, "https")) {
gchar *new_uri, *mail_uri, *enc;
SoupURI *soup_uri;
GHashTable *query;
gchar *uri_md5;
CamelStream *stream;
/* Open Evolution's cache */
uri_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1);
stream = camel_data_cache_get (
emd_global_http_cache, "http", uri_md5, NULL);
g_free (uri_md5);
/* If the URI is not cached and we are not allowed to load it
* then redirect to invalid URI, so that webkit would display
* a native placeholder for it. */
if (!stream && !display->priv->force_image_load &&
!em_format_html_can_load_images (display->priv->formatter)) {
webkit_network_request_set_uri (request, "invalid://protocol");
return;
}
new_uri = g_strconcat ("evo-", uri, NULL);
mail_uri = em_format_build_mail_uri (formatter->folder,
formatter->message_uid, NULL, NULL);
soup_uri = soup_uri_new (new_uri);
if (soup_uri->query) {
query = soup_form_decode (soup_uri->query);
} else {
query = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
}
enc = soup_uri_encode (mail_uri, NULL);
g_hash_table_insert (query, g_strdup ("__evo-mail"), enc);
if (display->priv->force_image_load) {
g_hash_table_insert (query,
g_strdup ("__evo-load-images"),
g_strdup ("true"));
}
g_free (mail_uri);
soup_uri_set_query_from_form (soup_uri, query);
g_free (new_uri);
new_uri = soup_uri_to_string (soup_uri, FALSE);
webkit_network_request_set_uri (request, new_uri);
g_free (new_uri);
soup_uri_free (soup_uri);
g_hash_table_unref (query);
}
}
static WebKitDOMElement *
find_element_by_id (WebKitDOMDocument *document,
const gchar *id)
{
WebKitDOMNodeList *frames;
WebKitDOMElement *element;
gulong i, length;
/* Try to look up the element in this DOM document */
element = webkit_dom_document_get_element_by_id (document, id);
if (element)
return element;
/* If the element is not here then recursively scan all frames */
frames = webkit_dom_document_get_elements_by_tag_name(document, "iframe");
length = webkit_dom_node_list_get_length (frames);
for (i = 0; i < length; i++) {
WebKitDOMHTMLIFrameElement *iframe =
WEBKIT_DOM_HTML_IFRAME_ELEMENT (
webkit_dom_node_list_item (frames, i));
WebKitDOMDocument *frame_doc =
webkit_dom_html_iframe_element_get_content_document (iframe);
WebKitDOMElement *el =
find_element_by_id (frame_doc, id);
if (el)
return el;
}
return NULL;
}
static void
mail_display_plugin_widget_resize (GObject *object,
gpointer dummy,
EMailDisplay *display)
{
GtkWidget *widget;
WebKitDOMElement *parent_element;
gchar *dim;
gint height;
widget = GTK_WIDGET (object);
gtk_widget_get_preferred_height (widget, &height, NULL);
parent_element = g_object_get_data (object, "parent_element");
if (!parent_element || !WEBKIT_DOM_IS_ELEMENT (parent_element)) {
d(printf("%s: %s does not have (valid) parent element!\n",
G_STRFUNC, (gchar *) g_object_get_data (object, "uri")));
return;
}
/* Int -> Str */
dim = g_strdup_printf ("%d", height);
/* Set height of the containment