1 /*
2 * text-highlight.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 #include "evolution-config.h"
19
20 #include "e-mail-formatter-text-highlight.h"
21 #include "languages.h"
22
23 #include <em-format/e-mail-formatter-extension.h>
24 #include <em-format/e-mail-formatter.h>
25 #include <em-format/e-mail-part-utils.h>
26 #include <e-util/e-util.h>
27
28 #include <libebackend/libebackend.h>
29 #include <libedataserver/libedataserver.h>
30
31 #include <glib/gi18n-lib.h>
32 #include <camel/camel.h>
33
34 typedef EMailFormatterExtension EMailFormatterTextHighlight;
35 typedef EMailFormatterExtensionClass EMailFormatterTextHighlightClass;
36
37 typedef EExtension EMailFormatterTextHighlightLoader;
38 typedef EExtensionClass EMailFormatterTextHighlightLoaderClass;
39
40 typedef struct _TextHighlightClosure TextHighlightClosure;
41
42 struct _TextHighlightClosure {
43 gboolean wrote_anything;
44 CamelStream *read_stream;
45 GOutputStream *output_stream;
46 GCancellable *cancellable;
47 GError *error;
48 };
49
50 GType e_mail_formatter_text_highlight_get_type (void);
51
G_DEFINE_DYNAMIC_TYPE(EMailFormatterTextHighlight,e_mail_formatter_text_highlight,E_TYPE_MAIL_FORMATTER_EXTENSION)52 G_DEFINE_DYNAMIC_TYPE (
53 EMailFormatterTextHighlight,
54 e_mail_formatter_text_highlight,
55 E_TYPE_MAIL_FORMATTER_EXTENSION)
56
57 static gboolean
58 emfe_text_highlight_formatter_is_enabled (void)
59 {
60 GSettings *settings;
61 gboolean enabled;
62
63 settings = e_util_ref_settings ("org.gnome.evolution.text-highlight");
64 enabled = g_settings_get_boolean (settings, "enabled");
65 g_object_unref (settings);
66
67 return enabled;
68 }
69
70 static gchar *
get_syntax(EMailPart * part,const gchar * uri)71 get_syntax (EMailPart *part,
72 const gchar *uri)
73 {
74 gchar *syntax = NULL;
75 CamelContentType *ct = NULL;
76 CamelMimePart *mime_part;
77
78 mime_part = e_mail_part_ref_mime_part (part);
79
80 if (uri) {
81 SoupURI *soup_uri = soup_uri_new (uri);
82 GHashTable *query = soup_form_decode (soup_uri->query);
83
84 syntax = g_hash_table_lookup (query, "__formatas");
85 if (syntax) {
86 syntax = g_strdup (syntax);
87 }
88 g_hash_table_destroy (query);
89 soup_uri_free (soup_uri);
90 }
91
92 /* Try to detect syntax by content-type first */
93 if (syntax == NULL) {
94 ct = camel_mime_part_get_content_type (mime_part);
95 if (ct) {
96 gchar *mime_type = camel_content_type_simple (ct);
97
98 syntax = (gchar *) get_syntax_for_mime_type (mime_type);
99 syntax = syntax ? g_strdup (syntax) : NULL;
100 g_free (mime_type);
101 }
102 }
103
104 /* If it fails or the content type too generic, try to detect it by
105 * filename extension */
106 if (syntax == NULL ||
107 (ct != NULL &&
108 (camel_content_type_is (ct, "application", "octet-stream") ||
109 (camel_content_type_is (ct, "text", "plain"))))) {
110 const gchar *filename;
111
112 filename = camel_mime_part_get_filename (mime_part);
113 if (filename != NULL) {
114 gchar *ext = g_strrstr (filename, ".");
115 if (ext != NULL) {
116 g_free (syntax);
117 syntax = (gchar *) get_syntax_for_ext (ext + 1);
118 syntax = syntax ? g_strdup (syntax) : NULL;
119 }
120 }
121 }
122
123 /* Out of ideas - use plain text */
124 if (syntax == NULL) {
125 syntax = g_strdup ("txt");
126 }
127
128 g_object_unref (mime_part);
129
130 return syntax;
131 }
132
133 static gpointer
text_hightlight_read_data_thread(gpointer user_data)134 text_hightlight_read_data_thread (gpointer user_data)
135 {
136 TextHighlightClosure *closure = user_data;
137 gint nbuffer = 10240;
138 gchar *buffer;
139
140 g_return_val_if_fail (closure != NULL, NULL);
141
142 buffer = g_new (gchar, nbuffer);
143
144 while (!camel_stream_eos (closure->read_stream) &&
145 !g_cancellable_set_error_if_cancelled (closure->cancellable, &closure->error)) {
146 gssize read;
147 gsize wrote = 0;
148
149 read = camel_stream_read (closure->read_stream, buffer, nbuffer, closure->cancellable, &closure->error);
150 if (read < 0 || closure->error)
151 break;
152
153 closure->wrote_anything = closure->wrote_anything || read > 0;
154
155 if (!g_output_stream_write_all (closure->output_stream, buffer, read, &wrote, closure->cancellable, &closure->error) ||
156 (gssize) wrote != read || closure->error)
157 break;
158 }
159
160 g_free (buffer);
161
162 return NULL;
163 }
164
165 static gboolean
text_highlight_feed_data(GOutputStream * output_stream,CamelDataWrapper * data_wrapper,gint pipe_stdin,gint pipe_stdout,GCancellable * cancellable,GError ** error)166 text_highlight_feed_data (GOutputStream *output_stream,
167 CamelDataWrapper *data_wrapper,
168 gint pipe_stdin,
169 gint pipe_stdout,
170 GCancellable *cancellable,
171 GError **error)
172 {
173 TextHighlightClosure closure;
174 CamelContentType *content_type;
175 CamelStream *write_stream;
176 gboolean success = TRUE;
177 GThread *thread;
178
179 closure.wrote_anything = FALSE;
180 closure.read_stream = camel_stream_fs_new_with_fd (pipe_stdout);
181 closure.output_stream = output_stream;
182 closure.cancellable = cancellable;
183 closure.error = NULL;
184
185 write_stream = camel_stream_fs_new_with_fd (pipe_stdin);
186
187 thread = g_thread_new (NULL, text_hightlight_read_data_thread, &closure);
188
189 content_type = camel_data_wrapper_get_mime_type_field (data_wrapper);
190 if (content_type) {
191 const gchar *charset = camel_content_type_param (content_type, "charset");
192
193 /* Convert to UTF-8 charset, if needed, which the 'highlight' expects;
194 it can cope with non-UTF-8 letters, thus no need for a content UTF-8-validation */
195 if (charset && g_ascii_strcasecmp (charset, "utf-8") != 0) {
196 CamelMimeFilter *filter;
197
198 filter = camel_mime_filter_charset_new (charset, "UTF-8");
199 if (filter != NULL) {
200 CamelStream *filtered = camel_stream_filter_new (write_stream);
201
202 if (filtered) {
203 camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered), filter);
204 g_object_unref (write_stream);
205 write_stream = filtered;
206 }
207
208 g_object_unref (filter);
209 }
210 }
211 }
212
213 if (camel_data_wrapper_decode_to_stream_sync (data_wrapper, write_stream, cancellable, error) < 0) {
214 g_cancellable_cancel (cancellable);
215 success = FALSE;
216 } else {
217 /* Close the stream, thus the highlight knows no more data will come */
218 g_clear_object (&write_stream);
219 }
220
221 g_thread_join (thread);
222
223 g_clear_object (&closure.read_stream);
224 g_clear_object (&write_stream);
225
226 if (closure.error) {
227 if (error && !*error)
228 g_propagate_error (error, closure.error);
229 else
230 g_clear_error (&closure.error);
231
232 return FALSE;
233 }
234
235 return success && closure.wrote_anything;
236 }
237
238 static gboolean
emfe_text_highlight_format(EMailFormatterExtension * extension,EMailFormatter * formatter,EMailFormatterContext * context,EMailPart * part,GOutputStream * stream,GCancellable * cancellable)239 emfe_text_highlight_format (EMailFormatterExtension *extension,
240 EMailFormatter *formatter,
241 EMailFormatterContext *context,
242 EMailPart *part,
243 GOutputStream *stream,
244 GCancellable *cancellable)
245 {
246 CamelMimePart *mime_part;
247 CamelContentType *ct;
248 gboolean success = FALSE;
249
250 mime_part = e_mail_part_ref_mime_part (part);
251 ct = camel_mime_part_get_content_type (mime_part);
252
253 /* Don't format text/html unless it's an attachment */
254 if (ct && camel_content_type_is (ct, "text", "html")) {
255 const CamelContentDisposition *disp;
256
257 disp = camel_mime_part_get_content_disposition (mime_part);
258
259 if (disp == NULL)
260 goto exit;
261
262 if (g_strcmp0 (disp->disposition, "attachment") != 0)
263 goto exit;
264 }
265
266 if (context->mode == E_MAIL_FORMATTER_MODE_PRINTING) {
267 /* Don't interfere with printing. */
268 goto exit;
269
270 } else if (context->mode == E_MAIL_FORMATTER_MODE_RAW) {
271 gint pipe_stdin, pipe_stdout;
272 GPid pid;
273 CamelDataWrapper *dw;
274 gchar *font_family, *font_size, *syntax, *theme;
275 PangoFontDescription *fd;
276 GSettings *settings;
277 gchar *font = NULL;
278
279 const gchar *argv[] = {
280 HIGHLIGHT_COMMAND,
281 NULL, /* --font= */
282 NULL, /* --font-size= */
283 NULL, /* --syntax= */
284 NULL, /* --style= */
285 "--out-format=html",
286 "--include-style",
287 "--inline-css",
288 "--encoding=none",
289 "--failsafe",
290 NULL };
291
292 if (!emfe_text_highlight_formatter_is_enabled ()) {
293 gboolean can_process = FALSE;
294
295 if (context->uri) {
296 SoupURI *soup_uri;
297
298 soup_uri = soup_uri_new (context->uri);
299 if (soup_uri) {
300 GHashTable *query;
301
302 query = soup_form_decode (soup_uri->query);
303 can_process = query && g_strcmp0 (g_hash_table_lookup (query, "__force_highlight"), "true") == 0;
304 if (query)
305 g_hash_table_destroy (query);
306 soup_uri_free (soup_uri);
307 }
308 }
309
310 if (!can_process) {
311 success = e_mail_formatter_format_as (
312 formatter, context, part, stream,
313 "application/vnd.evolution.plaintext",
314 cancellable);
315 goto exit;
316 }
317 }
318
319 dw = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
320 if (dw == NULL)
321 goto exit;
322
323 syntax = get_syntax (part, context->uri);
324
325 /* Use the traditional text/plain formatter for plain-text */
326 if (g_strcmp0 (syntax, "txt") == 0) {
327 g_free (syntax);
328 goto exit;
329 }
330
331 settings = e_util_ref_settings ("org.gnome.evolution.mail");
332 if (g_settings_get_boolean (settings, "use-custom-font"))
333 font = g_settings_get_string (
334 settings, "monospace-font");
335 g_object_unref (settings);
336
337 if (font == NULL) {
338 settings = e_util_ref_settings ("org.gnome.desktop.interface");
339 font = g_settings_get_string (
340 settings, "monospace-font-name");
341 g_object_unref (settings);
342 }
343
344 if (font == NULL)
345 font = g_strdup ("monospace 10");
346
347 fd = pango_font_description_from_string (font);
348
349 g_free (font);
350
351 font_family = g_strdup_printf (
352 "--font='%s'",
353 pango_font_description_get_family (fd));
354 font_size = g_strdup_printf (
355 "--font-size=%d",
356 pango_font_description_get_size (fd) / PANGO_SCALE);
357
358 settings = e_util_ref_settings ("org.gnome.evolution.text-highlight");
359 theme = g_settings_get_string (settings, "theme");
360 g_object_unref (settings);
361
362 if (!theme || !*theme) {
363 g_free (theme);
364 theme = g_strdup ("bclear");
365 }
366
367 argv[1] = font_family;
368 argv[2] = font_size;
369 argv[3] = g_strdup_printf ("--syntax=%s", syntax);
370 argv[4] = g_strdup_printf ("--style=%s", theme);
371 g_free (syntax);
372 g_free (theme);
373
374 success = g_spawn_async_with_pipes (
375 NULL, (gchar **) argv, NULL, 0, NULL, NULL,
376 &pid, &pipe_stdin, &pipe_stdout, NULL, NULL);
377
378 if (success) {
379 GError *local_error = NULL;
380
381 success = text_highlight_feed_data (
382 stream, dw,
383 pipe_stdin, pipe_stdout,
384 cancellable, &local_error);
385
386 if (g_error_matches (
387 local_error, G_IO_ERROR,
388 G_IO_ERROR_CANCELLED)) {
389 /* Do nothing. */
390
391 } else if (local_error != NULL) {
392 g_warning (
393 "%s: %s", G_STRFUNC,
394 local_error->message);
395 }
396
397 g_clear_error (&local_error);
398
399 g_spawn_close_pid (pid);
400 }
401
402 if (!success) {
403 /* We can't call e_mail_formatter_format_as on text/plain,
404 * because text-highlight is registered as a handler for
405 * text/plain, so we would end up in an endless recursion.
406 *
407 * Just return FALSE here and EMailFormatter will automatically
408 * fall back to the default text/plain formatter */
409 if (!camel_content_type_is (ct, "text", "plain")) {
410 /* In case of any other content, force use of
411 * text/plain formatter, because returning FALSE
412 * for text/x-patch or application/php would show
413 * an error, as there is no other handler registered
414 * for these */
415 success = e_mail_formatter_format_as (
416 formatter, context, part, stream,
417 "application/vnd.evolution.plaintext",
418 cancellable);
419 }
420 }
421
422 g_free (font_family);
423 g_free (font_size);
424 g_free ((gchar *) argv[3]);
425 g_free ((gchar *) argv[4]);
426 pango_font_description_free (fd);
427
428 if (!success)
429 goto exit;
430 } else {
431 CamelFolder *folder;
432 const gchar *message_uid;
433 const gchar *default_charset, *charset;
434 gchar *uri, *str;
435 gchar *syntax;
436
437 folder = e_mail_part_list_get_folder (context->part_list);
438 message_uid = e_mail_part_list_get_message_uid (context->part_list);
439 default_charset = e_mail_formatter_get_default_charset (formatter);
440 charset = e_mail_formatter_get_charset (formatter);
441
442 if (!default_charset)
443 default_charset = "";
444 if (!charset)
445 charset = "";
446
447 syntax = get_syntax (part, NULL);
448
449 uri = e_mail_part_build_uri (
450 folder, message_uid,
451 "part_id", G_TYPE_STRING, e_mail_part_get_id (part),
452 "mode", G_TYPE_INT, E_MAIL_FORMATTER_MODE_RAW,
453 "__formatas", G_TYPE_STRING, syntax,
454 "formatter_default_charset", G_TYPE_STRING, default_charset,
455 "formatter_charset", G_TYPE_STRING, charset,
456 NULL);
457
458 g_free (syntax);
459
460 str = g_strdup_printf (
461 "<div class=\"part-container-nostyle\" >"
462 "<iframe width=\"100%%\" height=\"10\""
463 " id=\"%s\" name=\"%s\" "
464 " class=\"-e-mail-formatter-frame-color %s -e-web-view-background-color\" "
465 " frameborder=\"0\" src=\"%s\" "
466 " >"
467 "</iframe>"
468 "</div>",
469 e_mail_part_get_id (part),
470 e_mail_part_get_id (part),
471 e_mail_part_get_frame_security_style (part),
472 uri);
473
474 g_output_stream_write_all (
475 stream, str, strlen (str),
476 NULL, cancellable, NULL);
477
478 g_free (str);
479 g_free (uri);
480 }
481
482 success = TRUE;
483
484 exit:
485 g_object_unref (mime_part);
486
487 return success;
488 }
489
490 static void
e_mail_formatter_text_highlight_class_init(EMailFormatterExtensionClass * class)491 e_mail_formatter_text_highlight_class_init (EMailFormatterExtensionClass *class)
492 {
493 class->display_name = _("Text Highlight");
494 class->description = _("Syntax highlighting of mail parts");
495 class->mime_types = get_mime_types ();
496 class->format = emfe_text_highlight_format;
497 }
498
499 static void
e_mail_formatter_text_highlight_class_finalize(EMailFormatterExtensionClass * class)500 e_mail_formatter_text_highlight_class_finalize (EMailFormatterExtensionClass *class)
501 {
502 }
503
504 static void
e_mail_formatter_text_highlight_init(EMailFormatterExtension * extension)505 e_mail_formatter_text_highlight_init (EMailFormatterExtension *extension)
506 {
507 }
508
509 void
e_mail_formatter_text_highlight_type_register(GTypeModule * type_module)510 e_mail_formatter_text_highlight_type_register (GTypeModule *type_module)
511 {
512 e_mail_formatter_text_highlight_register_type (type_module);
513 }
514
515