I#353 - [Prefer Plain] Generate plain text from text/html, if not available

Closes https://gitlab.gnome.org/GNOME/evolution/-/issues/353
This commit is contained in:
Milan Crha
2020-09-11 08:38:44 +02:00
parent f895ea667f
commit bf38f23d01
3 changed files with 197 additions and 22 deletions

View File

@ -188,6 +188,7 @@ e_http_request_process_sync (EContentRequest *request,
gchar *mail_uri = NULL;
GInputStream *stream;
gboolean force_load_images = FALSE;
gboolean disable_remote_content = FALSE;
EImageLoadingPolicy image_policy;
gchar *uri_md5;
EShell *shell;
@ -341,9 +342,17 @@ e_http_request_process_sync (EContentRequest *request,
if (!e_shell_get_online (shell))
goto cleanup;
settings = e_util_ref_settings ("org.gnome.evolution.mail");
image_policy = g_settings_get_enum (settings, "image-loading-policy");
g_object_unref (settings);
if (WEBKIT_IS_WEB_VIEW (requester))
disable_remote_content = g_strcmp0 (webkit_web_view_get_uri (WEBKIT_WEB_VIEW (requester)), "evo://disable-remote-content") == 0;
if (disable_remote_content) {
force_load_images = FALSE;
image_policy = E_IMAGE_LOADING_POLICY_NEVER;
} else {
settings = e_util_ref_settings ("org.gnome.evolution.mail");
image_policy = g_settings_get_enum (settings, "image-loading-policy");
g_object_unref (settings);
}
/* Item not found in cache, but image loading policy allows us to fetch
* it from the interwebs */

View File

@ -18,8 +18,6 @@
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include "e-mail-parser-prefer-plain.h"
#include <em-format/e-mail-extension-registry.h>
#include <em-format/e-mail-parser-extension.h>
#include <em-format/e-mail-part.h>
@ -28,14 +26,11 @@
#include <libebackend/libebackend.h>
#include <e-util/e-util.h>
#define d(x)
#include "e-mail-parser-prefer-plain.h"
typedef struct _EMailParserPreferPlain EMailParserPreferPlain;
typedef struct _EMailParserPreferPlainClass EMailParserPreferPlainClass;
typedef EExtension EMailParserPreferPlainLoader;
typedef EExtensionClass EMailParserPreferPlainLoaderClass;
struct _EMailParserPreferPlain {
EMailParserExtension parent;
@ -57,10 +52,7 @@ enum {
ONLY_PLAIN
};
G_DEFINE_DYNAMIC_TYPE (
EMailParserPreferPlain,
e_mail_parser_prefer_plain,
E_TYPE_MAIL_PARSER_EXTENSION)
G_DEFINE_DYNAMIC_TYPE (EMailParserPreferPlain, e_mail_parser_prefer_plain, E_TYPE_MAIL_PARSER_EXTENSION)
static const gchar *parser_mime_types[] = {
"multipart/alternative",
@ -196,6 +188,150 @@ hide_parts (GQueue *work_queue)
}
}
static gchar *
mail_parser_prefer_plain_dup_part_text (CamelMimePart *mime_part,
GCancellable *cancellable)
{
CamelDataWrapper *data_wrapper;
CamelMedium *medium;
CamelStream *stream;
GByteArray *array;
gchar *content;
if (!mime_part)
return NULL;
array = g_byte_array_new ();
medium = CAMEL_MEDIUM (mime_part);
/* Stream takes ownership of the byte array. */
stream = camel_stream_mem_new_with_byte_array (array);
data_wrapper = camel_medium_get_content (medium);
camel_data_wrapper_decode_to_stream_sync (
data_wrapper, stream, NULL, NULL);
content = g_strndup ((gchar *) array->data, array->len);
g_object_unref (stream);
return content;
}
typedef struct _AsyncContext {
gchar *text_input;
gchar *text_output;
GCancellable *cancellable;
EFlag *flag;
WebKitWebView *web_view;
} AsyncContext;
static void
mail_parser_prefer_plain_convert_jsc_call_done_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
WebKitJavascriptResult *js_result;
AsyncContext *async_context = user_data;
GError *error = NULL;
g_return_if_fail (async_context != NULL);
js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (source), result, &error);
if (error) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
(!g_error_matches (error, WEBKIT_JAVASCRIPT_ERROR, WEBKIT_JAVASCRIPT_ERROR_SCRIPT_FAILED) ||
/* WebKit can return empty error message, thus ignore those. */
(error->message && *(error->message))))
g_warning ("%s: JSC call failed: %s:%d: %s", G_STRFUNC, g_quark_to_string (error->domain), error->code, error->message);
g_clear_error (&error);
}
if (js_result) {
JSCException *exception;
JSCValue *value;
value = webkit_javascript_result_get_js_value (js_result);
exception = jsc_context_get_exception (jsc_value_get_context (value));
if (exception) {
g_warning ("%s: JSC call failed: %s", G_STRFUNC, jsc_exception_get_message (exception));
jsc_context_clear_exception (jsc_value_get_context (value));
} else if (jsc_value_is_string (value)) {
async_context->text_output = jsc_value_to_string (value);
}
webkit_javascript_result_unref (js_result);
}
g_clear_object (&async_context->web_view);
e_flag_set (async_context->flag);
}
static gboolean
mail_parser_prefer_plain_convert_text (gpointer user_data)
{
AsyncContext *async_context = user_data;
gchar *script;
g_return_val_if_fail (async_context != NULL, FALSE);
async_context->web_view = g_object_ref_sink (e_web_view_new ());
e_web_view_load_uri (E_WEB_VIEW (async_context->web_view), "evo://disable-remote-content");
script = e_web_view_jsc_printf_script (
"var elem;\n"
"elem = document.createElement('X-EVO-CONVERT');\n"
"elem.innerHTML = %s;\n"
"EvoConvert.ToPlainText(elem, -1);",
async_context->text_input);
webkit_web_view_run_javascript (async_context->web_view, script, async_context->cancellable,
mail_parser_prefer_plain_convert_jsc_call_done_cb, async_context);
g_free (script);
return FALSE;
}
static gchar *
mail_parser_prefer_plain_convert_content_sync (CamelMimePart *mime_part,
GCancellable *cancellable)
{
AsyncContext async_context;
gchar *res = NULL;
memset (&async_context, 0, sizeof (AsyncContext));
async_context.text_input = mail_parser_prefer_plain_dup_part_text (mime_part, cancellable);
if (!async_context.text_input || g_cancellable_is_cancelled (cancellable)) {
g_free (async_context.text_input);
return NULL;
}
async_context.flag = e_flag_new ();
async_context.cancellable = cancellable;
/* Run it in the main/GUI thread */
g_timeout_add (1, mail_parser_prefer_plain_convert_text, &async_context);
e_flag_wait (async_context.flag);
e_flag_free (async_context.flag);
if (async_context.text_output) {
res = async_context.text_output;
async_context.text_output = NULL;
}
g_free (async_context.text_input);
g_free (async_context.text_output);
return res;
}
static gboolean
empe_prefer_plain_parse (EMailParserExtension *extension,
EMailParser *parser,
@ -250,12 +386,40 @@ empe_prefer_plain_parse (EMailParserExtension *extension,
if (emp_pp->mode != ONLY_PLAIN)
return FALSE;
/* Enforcing text/plain but got only HTML part, so add it
* as attachment to not show empty message preview, which
* is confusing. */
make_part_attachment (
parser, part, part_id, TRUE,
cancellable, out_mail_parts);
if (!e_mail_part_is_attachment (part)) {
gchar *content;
partidlen = part_id->len;
g_string_truncate (part_id, partidlen);
g_string_append_printf (part_id, ".alternative-prefer-plain.%d.converted", -1);
content = mail_parser_prefer_plain_convert_content_sync (part, cancellable);
if (content) {
EMailPart *mail_part;
CamelMimePart *text_part;
text_part = camel_mime_part_new ();
camel_mime_part_set_content (text_part, content, strlen (content), "application/vnd.evolution.plaintext");
mail_part = e_mail_part_new (text_part, part_id->str);
e_mail_part_set_mime_type (mail_part, "application/vnd.evolution.plaintext");
g_free (content);
g_queue_push_tail (out_mail_parts, mail_part);
}
g_string_truncate (part_id, partidlen);
}
if (emp_pp->show_suppressed || e_mail_part_is_attachment (part)) {
/* Enforcing text/plain but got only HTML part, so add it
* as attachment to not show empty message preview, which
* is confusing. */
make_part_attachment (
parser, part, part_id, TRUE,
cancellable, out_mail_parts);
}
return TRUE;
}
@ -565,4 +729,3 @@ e_mail_parser_prefer_plain_type_register (GTypeModule *type_module)
{
e_mail_parser_prefer_plain_register_type (type_module);
}

View File

@ -104,11 +104,14 @@ web_page_send_request_cb (WebKitWebPage *web_page,
request_uri = webkit_uri_request_get_uri (request);
page_uri = webkit_web_page_get_uri (web_page);
if (!request_uri)
return FALSE;
/* Always load the main resource. */
if (g_strcmp0 (request_uri, page_uri) == 0 ||
/* Do not influence real pages, like those with eds OAuth sign-in */
g_str_has_prefix (page_uri, "http:") ||
g_str_has_prefix (page_uri, "https:"))
(page_uri && (g_str_has_prefix (page_uri, "http:") ||
g_str_has_prefix (page_uri, "https:"))))
return FALSE;
if (g_str_has_prefix (request_uri, "http:") ||