* more security by only displaying images if the setting is on
[modest] / src / modest-tny-msg-view.c
1 /* modest-tny-msg-view.c */
2
3 /* insert (c)/licensing information) */
4
5 #include "modest-tny-msg-view.h"
6 #include "modest-tny-stream-gtkhtml.h"
7 #include "modest-tny-msg-actions.h"
8
9 #include <tny-text-buffer-stream.h>
10 #include <string.h>
11 #include <regex.h>
12 #include <ctype.h>
13 #include <glib/gi18n.h>
14 #include <gtkhtml/gtkhtml.h>
15 #include <gtkhtml/gtkhtml-stream.h>
16
17 /* 'private'/'protected' functions */
18 static void     modest_tny_msg_view_class_init   (ModestTnyMsgViewClass *klass);
19 static void     modest_tny_msg_view_init         (ModestTnyMsgView *obj);
20 static void     modest_tny_msg_view_finalize     (GObject *obj);
21
22
23 static GSList*  get_url_matches (GString *txt);
24 static gboolean fill_gtkhtml_with_txt (ModestTnyMsgView *self, GtkHTML* gtkhtml, const gchar* txt, TnyMsgIface *msg);
25
26 static gboolean on_link_clicked (GtkWidget *widget, const gchar *uri,
27                                  ModestTnyMsgView *msg_view);
28 static gboolean on_url_requested (GtkWidget *widget, const gchar *uri,
29                                   GtkHTMLStream *stream,
30                                   ModestTnyMsgView *msg_view);
31 static gchar *construct_virtual_filename(const gchar *filename, const gint position, const gchar *id, const gboolean active);
32 static gchar *construct_virtual_filename_from_mime_part(TnyMsgMimePartIface *msg, const gint position);
33
34 #define ATTACHMENT_ID_INLINE "attachment-inline"
35 #define ATTACHMENT_ID_LINK "attachment-link"
36
37 gint virtual_filename_get_pos(const gchar *filename);
38 /*
39  * we need these regexps to find URLs in plain text e-mails
40  */
41 typedef struct _UrlMatchPattern UrlMatchPattern;
42 struct _UrlMatchPattern {
43         gchar   *regex;
44         regex_t *preg;
45         gchar   *prefix;
46         
47 };
48 #define MAIL_VIEWER_URL_MATCH_PATTERNS  {\
49         { "(file|http|ftp|https)://[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]+[-A-Za-z0-9_$%&=?/~#]",\
50           NULL, NULL },\
51         { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
52           NULL, "http://" },\
53         { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
54           NULL, "ftp://" },\
55         { "(voipto|callto|chatto|jabberto|xmpp):[-_a-z@0-9.\\+]+", \
56            NULL, NULL},                                             \
57         { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                    \
58           NULL, NULL},\
59         { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
60           NULL, "mailto:"}\
61         }
62
63
64 /* list my signals */
65 enum {
66         /* MY_SIGNAL_1, */
67         /* MY_SIGNAL_2, */
68         LAST_SIGNAL
69 };
70
71 typedef struct _ModestTnyMsgViewPrivate ModestTnyMsgViewPrivate;
72 struct _ModestTnyMsgViewPrivate {
73         GtkWidget *gtkhtml;
74         TnyMsgIface *msg;
75         ModestConf *conf;
76 };
77 #define MODEST_TNY_MSG_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
78                                                  MODEST_TYPE_TNY_MSG_VIEW, \
79                                                  ModestTnyMsgViewPrivate))
80 /* globals */
81 static GtkContainerClass *parent_class = NULL;
82
83 /* uncomment the following if you have defined any signals */
84 /* static guint signals[LAST_SIGNAL] = {0}; */
85
86 GType
87 modest_tny_msg_view_get_type (void)
88 {
89         static GType my_type = 0;
90         if (!my_type) {
91                 static const GTypeInfo my_info = {
92                         sizeof(ModestTnyMsgViewClass),
93                         NULL,           /* base init */
94                         NULL,           /* base finalize */
95                         (GClassInitFunc) modest_tny_msg_view_class_init,
96                         NULL,           /* class finalize */
97                         NULL,           /* class data */
98                         sizeof(ModestTnyMsgView),
99                         1,              /* n_preallocs */
100                         (GInstanceInitFunc) modest_tny_msg_view_init,
101                 };
102                 my_type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
103                                                   "ModestTnyMsgView",
104                                                   &my_info, 0);
105         }
106         return my_type;
107 }
108
109 static void
110 modest_tny_msg_view_class_init (ModestTnyMsgViewClass *klass)
111 {
112         GObjectClass *gobject_class;
113         gobject_class = (GObjectClass*) klass;
114
115         parent_class            = g_type_class_peek_parent (klass);
116         gobject_class->finalize = modest_tny_msg_view_finalize;
117
118         g_type_class_add_private (gobject_class, sizeof(ModestTnyMsgViewPrivate));
119 }
120
121 static void
122 modest_tny_msg_view_init (ModestTnyMsgView *obj)
123 {
124         ModestTnyMsgViewPrivate *priv;
125         
126         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(obj);
127
128         priv->msg = NULL;
129         
130         priv->gtkhtml = gtk_html_new();
131         
132         priv->conf = NULL;
133
134         gtk_html_set_editable        (GTK_HTML(priv->gtkhtml), FALSE);
135         gtk_html_allow_selection     (GTK_HTML(priv->gtkhtml), TRUE);
136         gtk_html_set_caret_mode      (GTK_HTML(priv->gtkhtml), FALSE);
137         gtk_html_set_blocking        (GTK_HTML(priv->gtkhtml), FALSE);
138         gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE);
139
140         g_signal_connect (G_OBJECT(priv->gtkhtml), "link_clicked",
141                           G_CALLBACK(on_link_clicked), obj);
142         
143         g_signal_connect (G_OBJECT(priv->gtkhtml), "url_requested",
144                           G_CALLBACK(on_url_requested), obj);
145 }
146         
147
148 static void
149 modest_tny_msg_view_finalize (GObject *obj)
150 {       
151         
152 }
153
154 GtkWidget*
155 modest_tny_msg_view_new (TnyMsgIface *msg, ModestConf *conf)
156 {
157         GObject *obj;
158         ModestTnyMsgView* self;
159         ModestTnyMsgViewPrivate *priv;
160         
161         obj  = G_OBJECT(g_object_new(MODEST_TYPE_TNY_MSG_VIEW, NULL));
162         self = MODEST_TNY_MSG_VIEW(obj);
163         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
164
165         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self),
166                                        GTK_POLICY_AUTOMATIC,
167                                        GTK_POLICY_AUTOMATIC);
168
169         if (priv->gtkhtml) 
170                 gtk_container_add (GTK_CONTAINER(obj), priv->gtkhtml);  
171         
172         if (msg)
173                 modest_tny_msg_view_set_message (self, msg);
174         
175         priv->conf = conf;
176
177         return GTK_WIDGET(self);
178 }
179
180
181
182 static gboolean
183 on_link_clicked (GtkWidget *widget, const gchar *uri,
184                                  ModestTnyMsgView *msg_view)
185 {
186         
187         if (g_str_has_prefix(uri, ATTACHMENT_ID_LINK)) {
188                 /* save or open attachment */
189                 g_message ("link-to-save: %s", uri); /* FIXME */
190                 return TRUE;
191         }
192         g_message ("link clicked: %s", uri); /* FIXME */
193         return FALSE;
194         
195 }
196
197
198 static TnyMsgMimePartIface *
199 find_cid_image (TnyMsgIface *msg, const gchar *cid)
200 {
201         TnyMsgMimePartIface *part = NULL;
202         GList *parts;
203
204         g_return_val_if_fail (msg, NULL);
205         g_return_val_if_fail (cid, NULL);
206         
207         parts  = (GList*) tny_msg_iface_get_parts (msg);
208         while (parts && !part) {
209                 const gchar *part_cid;
210                 part = TNY_MSG_MIME_PART_IFACE(parts->data);
211                 part_cid = tny_msg_mime_part_iface_get_content_id (part);
212                 printf("CMP:%s:%s\n", cid, part_cid);
213                 if (part_cid && strcmp (cid, part_cid) == 0)
214                         return part; /* we found it! */
215                 
216                 part = NULL;
217                 parts = parts->next;
218         }
219         
220         return part;
221 }
222
223
224 static TnyMsgMimePartIface *
225 find_attachment_by_filename (TnyMsgIface *msg, const gchar *fn)
226 {
227         TnyMsgMimePartIface *part = NULL;
228         GList *parts;
229         gchar *dummy;
230         gint pos;
231
232         g_return_val_if_fail (msg, NULL);
233         g_return_val_if_fail (fn, NULL);
234         
235         parts  = (GList*) tny_msg_iface_get_parts (msg);
236         pos = virtual_filename_get_pos(fn);
237         
238         if ((pos < 0) || (pos >= g_list_length(parts)))
239                 return NULL;
240         
241         part = g_list_nth_data(parts, pos);
242         
243         dummy = construct_virtual_filename_from_mime_part(part, pos);
244         if (strcmp(dummy, fn) == 0) {
245                 g_free(dummy);
246                 return part;
247         } else {
248                 g_free(dummy);
249                 return NULL;
250         }
251 }
252
253
254 static gboolean
255 on_url_requested (GtkWidget *widget, const gchar *uri,
256                   GtkHTMLStream *stream,
257                   ModestTnyMsgView *msg_view)
258 {
259         
260         ModestTnyMsgViewPrivate *priv;
261         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (msg_view);
262
263         g_message ("url requested: %s", uri);
264         
265         if (!modest_conf_get_bool(priv->conf,
266                     MODEST_CONF_MSG_VIEW_SHOW_ATTACHMENTS_INLINE, NULL))
267                 return TRUE; /* debatable */
268         
269         if (g_str_has_prefix (uri, "cid:")) {
270                 /* +4 ==> skip "cid:" */
271                 
272                 TnyMsgMimePartIface *part = find_cid_image (priv->msg, uri + 4);
273                 if (!part) {
274                         g_message ("%s not found", uri + 4);
275                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
276                 } else {
277                         TnyStreamIface *tny_stream =
278                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
279                         tny_msg_mime_part_iface_decode_to_stream (part,tny_stream);
280                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
281                 }
282         } else if (g_str_has_prefix (uri, ATTACHMENT_ID_INLINE)) {
283                 TnyMsgMimePartIface *part;
284                 part = find_attachment_by_filename (priv->msg, uri);
285                 if (!part) {
286                         g_message ("%s not found", uri);
287                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
288                 } else {
289                         TnyStreamIface *tny_stream =
290                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
291                         tny_msg_mime_part_iface_decode_to_stream (part,tny_stream);
292                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
293                 }
294         }
295         return TRUE;
296 }
297
298
299 typedef struct  {
300         guint offset;
301         guint len;
302         const gchar* prefix;
303 } url_match_t;
304
305
306 static gchar *
307 construct_virtual_filename(const gchar *filename,
308                            const gint position,
309                            const gchar *id,
310                            const gboolean active)
311 {
312         GString *s;
313         
314         if (position < 0)
315                 return "AttachmentInvalid";
316
317         s = g_string_new("");
318         if (active)
319                 g_string_append(s, ATTACHMENT_ID_INLINE);
320         else
321                 g_string_append(s, ATTACHMENT_ID_LINK);
322         g_string_append_printf(s, ":%d:", position);
323         if (id)
324                 g_string_append(s, id);
325         g_string_append_c(s, ':');
326         if (filename)
327                 g_string_append(s, filename);
328         g_string_append_c(s, ':');
329         return g_string_free(s, FALSE);
330 }
331
332
333 static gchar *
334 construct_virtual_filename_from_mime_part(TnyMsgMimePartIface *msg, const gint position)
335 {
336         const gchar *id, *filename;
337         const gboolean active = TRUE;
338         
339         filename = tny_msg_mime_part_iface_get_filename(
340                                                                         TNY_MSG_MIME_PART_IFACE(msg));
341         if (!filename)
342                 filename = "[unknown]";
343         id = tny_msg_mime_part_iface_get_content_id(
344                                                                         TNY_MSG_MIME_PART_IFACE(msg));
345         
346         return construct_virtual_filename(filename, position, id, active);
347 }
348
349 const gchar *
350 get_next_token(const gchar *s, gint *len)
351 {
352         gchar *i1, *i2;
353         i1 = (char *) s;
354         i2 = (char *) s;
355         
356         while (i2[0]) {
357                 if (i2[0] == ':')
358                         break;
359                 i2++;
360         }
361         if (!i2[0])
362                 return NULL;
363         *len = i2 - i1;
364         return ++i2;
365 }
366
367 /* maybe I should use libregexp */
368 gint
369 virtual_filename_get_pos(const gchar *filename)
370 {
371         const gchar *i1, *i2;
372         gint len, pos;
373         GString *dummy;
374         
375         /* check prefix */
376         if ((!g_str_has_prefix(filename, ATTACHMENT_ID_INLINE ":")) &&
377             (!g_str_has_prefix(filename, ATTACHMENT_ID_LINK ":")))
378                 return -1;
379
380         i2 = filename;
381         i2 = get_next_token(i2, &len);
382         i1 = i2;
383                 
384         /* get position */
385         i2 = get_next_token(i2, &len);
386         if (i2 == NULL)
387                 return -1;
388         dummy = g_string_new_len(i1, len);
389         pos = atoi(dummy->str);
390         g_string_free(dummy, FALSE);
391         return pos;
392 }       
393
394
395 static gchar *
396 attachments_as_html(ModestTnyMsgView *self, TnyMsgIface *msg)
397 {
398         ModestTnyMsgViewPrivate *priv;
399         gboolean attachments_found = FALSE;
400         GString *appendix;
401         const GList *attachment_list, *attachment;
402         const gchar *content_type, *filename, *id;
403         gchar *virtual_filename;
404         gboolean show_attachments_inline;
405         
406         if (!msg)
407                 return g_malloc0(1);
408         
409         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
410         
411         appendix = g_string_new("");
412         g_string_printf(appendix, "<HTML><BODY>\n<hr><h5>%s:</h5>\n", _("Attachments"));
413         
414         attachment_list = tny_msg_iface_get_parts(msg);
415         attachment = attachment_list;
416         while (attachment) {
417                 filename = "";
418                 content_type = tny_msg_mime_part_iface_get_content_type(
419                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
420                 if (!content_type)
421                         continue;
422
423                 if ((strcmp("image/jpeg", content_type) == 0) ||
424                         (strcmp("image/gif",  content_type) == 0)) {
425                         filename = tny_msg_mime_part_iface_get_filename(
426                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
427                         if (!filename)
428                                 filename = "[unknown]";
429                         else
430                                 attachments_found = TRUE;
431                         id = tny_msg_mime_part_iface_get_content_id(
432                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
433                         show_attachments_inline = modest_conf_get_bool(priv->conf,
434                                 MODEST_CONF_MSG_VIEW_SHOW_ATTACHMENTS_INLINE, NULL);
435                         if (show_attachments_inline) {
436                                 virtual_filename = construct_virtual_filename(filename,
437                                         g_list_position((GList *)attachment_list, (GList *) attachment),
438                                         id, TRUE);
439                                 g_string_append_printf(appendix, "<IMG src=\"%s\">\n<BR>", virtual_filename);
440                                 g_free(virtual_filename);
441                         }
442                         virtual_filename = construct_virtual_filename(filename,
443                                         g_list_position((GList *)attachment_list, (GList *) attachment),
444                                         id, FALSE);
445                         g_string_append_printf(appendix,
446                                 "<A href=\"%s\">%s</A>: %s<BR>\n",
447                                 virtual_filename, filename, content_type);
448                         g_free(virtual_filename);
449                 }
450                 attachment = attachment->next;
451         }
452         g_string_append(appendix, "</BODY></HTML>");
453         if (!attachments_found)
454                 g_string_assign(appendix, "");
455         return g_string_free(appendix, FALSE);
456 }
457
458 static void
459 hyperlinkify_plain_text (GString *txt)
460 {
461         GSList *cursor;
462         GSList *match_list = get_url_matches (txt);
463
464         /* we will work backwards, so the offsets stay valid */
465         for (cursor = match_list; cursor; cursor = cursor->next) {
466
467                 url_match_t *match = (url_match_t*) cursor->data;
468                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
469                 gchar *repl = NULL; /* replacement  */
470
471                 /* the prefix is NULL: use the one that is already there */
472                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
473                                         match->prefix ? match->prefix : "", url, url);
474
475                 /* replace the old thing with our hyperlink
476                  * replacement thing */
477                 g_string_erase  (txt, match->offset, match->len);
478                 g_string_insert (txt, match->offset, repl);
479                 
480                 g_free (url);
481                 g_free (repl);
482
483                 g_free (cursor->data);  
484         }
485         
486         g_slist_free (match_list);
487 }
488
489
490
491 static gchar *
492 convert_to_html (const gchar *data)
493 {
494         int              i;
495         gboolean         first_space = TRUE;
496         GString         *html;      
497         gsize           len;
498
499         if (!data)
500                 return NULL;
501
502         len = strlen (data);
503         html = g_string_sized_new (len + 100);  /* just a  guess... */
504         
505         g_string_append_printf (html,
506                                 "<html>"
507                                 "<head>"
508                                 "<meta http-equiv=\"content-type\""
509                                 " content=\"text/html; charset=utf8\">"
510                                 "</head>"
511                                 "<body><tt>");
512         
513         /* replace with special html chars where needed*/
514         for (i = 0; i != len; ++i)  {
515                 char    kar = data[i]; 
516                 switch (kar) {
517                         
518                 case 0:  break; /* ignore embedded \0s */       
519                 case '<' : g_string_append   (html, "&lt;"); break;
520                 case '>' : g_string_append   (html, "&gt;"); break;
521                 case '&' : g_string_append   (html, "&quot;"); break;
522                 case '\n': g_string_append   (html, "<br>\n"); break;
523                 default:
524                         if (kar == ' ') {
525                                 g_string_append (html, first_space ? " " : "&nbsp;");
526                                 first_space = FALSE;
527                         } else  if (kar == '\t')
528                                 g_string_append (html, "&nbsp; &nbsp;&nbsp;");
529                         else {
530                                 int charnum = 0;
531                                 first_space = TRUE;
532                                 /* optimization trick: accumulate 'normal' chars, then copy */
533                                 do {
534                                         kar = data [++charnum + i];
535                                         
536                                 } while ((i + charnum < len) &&
537                                          (kar > '>' || (kar != '<' && kar != '>'
538                                                         && kar != '&' && kar !=  ' '
539                                                         && kar != '\n' && kar != '\t')));
540                                 g_string_append_len (html, &data[i], charnum);
541                                 i += (charnum  - 1);
542                         }
543                 }
544         }
545         
546         g_string_append (html, "</tt></body></html>");
547         hyperlinkify_plain_text (html);
548
549         return g_string_free (html, FALSE);
550 }
551
552
553
554
555 static gint 
556 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
557 {
558         return match2->offset - match1->offset;
559 }
560
561
562
563 /*
564  * check if the match is inside an existing match... */
565 static void
566 chk_partial_match (const url_match_t *match, int* offset)
567 {
568         if (*offset >= match->offset && *offset < match->offset + match->len)
569                 *offset = -1;
570 }
571
572 static GSList*
573 get_url_matches (GString *txt)
574 {
575         regmatch_t rm;
576         int rv, i, offset = 0;
577         GSList *match_list = NULL;
578
579         static UrlMatchPattern patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
580         const size_t pattern_num = sizeof(patterns)/sizeof(UrlMatchPattern);
581
582         /* initalize the regexps */
583         for (i = 0; i != pattern_num; ++i) {
584                 patterns[i].preg = g_new0 (regex_t,1);
585                 g_assert(regcomp (patterns[i].preg, patterns[i].regex,
586                                   REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0);
587         }
588         /* find all the matches */
589         for (i = 0; i != pattern_num; ++i) {
590                 offset     = 0; 
591                 while (1) {
592                         int test_offset;
593                         if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) {
594                                 g_assert (rv == REG_NOMATCH); /* this should not happen */
595                                 break; /* try next regexp */ 
596                         }
597                         if (rm.rm_so == -1)
598                                 break;
599
600                         /* FIXME: optimize this */
601                         /* to avoid partial matches on something that was already found... */
602                         /* check_partial_match will put -1 in the data ptr if that is the case */
603                         test_offset = offset + rm.rm_so;
604                         g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset);
605                         
606                         /* make a list of our matches (<offset, len, prefix> tupels)*/
607                         if (test_offset != -1) {
608                                 url_match_t *match = g_new (url_match_t,1);
609                                 match->offset = offset + rm.rm_so;
610                                 match->len    = rm.rm_eo - rm.rm_so;
611                                 match->prefix = patterns[i].prefix;
612                                 match_list = g_slist_prepend (match_list, match);
613                         }
614                         offset += rm.rm_eo;
615                 }
616         }
617
618         for (i = 0; i != pattern_num; ++i) {
619                 regfree (patterns[i].preg);
620                 g_free  (patterns[i].preg);
621         } /* don't free patterns itself -- it's static */
622         
623         /* now sort the list, so the matches are in reverse order of occurence.
624          * that way, we can do the replacements starting from the end, so we don't need
625          * to recalculate the offsets
626          */
627         match_list = g_slist_sort (match_list,
628                                    (GCompareFunc)cmp_offsets_reverse); 
629         return match_list;      
630 }
631
632 static gboolean
633 fill_gtkhtml_with_txt (ModestTnyMsgView *self, GtkHTML* gtkhtml, const gchar* txt, TnyMsgIface *msg)
634 {
635         GString *html;
636         gchar *html_attachments;
637         
638         g_return_val_if_fail (gtkhtml, FALSE);
639         g_return_val_if_fail (txt, FALSE);
640
641         html = g_string_new(convert_to_html (txt));
642         html_attachments = attachments_as_html(self, msg);
643         g_string_append(html, html_attachments);
644
645         gtk_html_load_from_string (gtkhtml, html->str, html->len);
646         g_string_free (html, TRUE);
647         g_free(html_attachments);
648
649         return TRUE;
650 }
651
652
653
654 static gboolean
655 set_html_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body, TnyMsgIface *msg)
656 {
657         gchar *html_attachments;
658         TnyStreamIface *gtkhtml_stream; 
659         ModestTnyMsgViewPrivate *priv;
660         
661         g_return_val_if_fail (self, FALSE);
662         g_return_val_if_fail (tny_body, FALSE);
663         
664         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
665
666         gtkhtml_stream =
667                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new
668                                  (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
669         
670         tny_stream_iface_reset (gtkhtml_stream);
671         tny_msg_mime_part_iface_decode_to_stream (tny_body, gtkhtml_stream);
672         html_attachments = attachments_as_html(self, msg);
673         tny_stream_iface_write (gtkhtml_stream, html_attachments, strlen(html_attachments));
674         tny_stream_iface_reset (gtkhtml_stream);
675
676         g_object_unref (G_OBJECT(gtkhtml_stream));
677         g_free (html_attachments);
678         
679         return TRUE;
680 }
681
682
683 /* this is a hack --> we use the tny_text_buffer_stream to
684  * get the message text, then write to gtkhtml 'by hand' */
685 static gboolean
686 set_text_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body, TnyMsgIface *msg)
687 {
688         GtkTextBuffer *buf;
689         GtkTextIter begin, end;
690         TnyStreamIface* txt_stream;
691         gchar *txt;
692         ModestTnyMsgViewPrivate *priv;
693                 
694         g_return_val_if_fail (self, FALSE);
695         g_return_val_if_fail (tny_body, FALSE);
696
697         priv           = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
698         
699         buf            = gtk_text_buffer_new (NULL);
700         txt_stream     = TNY_STREAM_IFACE(tny_text_buffer_stream_new (buf));
701                 
702         tny_stream_iface_reset (txt_stream);
703         tny_msg_mime_part_iface_decode_to_stream (tny_body, txt_stream);
704         tny_stream_iface_reset (txt_stream);            
705         
706         gtk_text_buffer_get_bounds (buf, &begin, &end);
707         txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
708         
709         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml), txt, msg);
710
711         g_object_unref (G_OBJECT(txt_stream));
712         g_object_unref (G_OBJECT(buf));
713
714         g_free (txt);
715         return TRUE;
716 }
717
718 gchar *
719 modest_tny_msg_view_get_selected_text (ModestTnyMsgView *self)
720 {
721         ModestTnyMsgViewPrivate *priv;
722         gchar *sel;
723         GtkWidget *html;
724         int len;
725         GtkClipboard *clip;
726
727         g_return_val_if_fail (self, NULL);
728         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
729         html = priv->gtkhtml;
730         
731         /* I'm sure there is a better way to check for selected text */
732         sel = gtk_html_get_selection_html(GTK_HTML(html), &len);
733         if (!sel)
734                 return NULL;
735         
736         g_free(sel);
737         
738         clip = gtk_widget_get_clipboard(html, GDK_SELECTION_PRIMARY);
739         return gtk_clipboard_wait_for_text(clip);
740 }
741
742 void
743 modest_tny_msg_view_set_message (ModestTnyMsgView *self, TnyMsgIface *msg)
744 {
745         TnyMsgMimePartIface *body;
746         ModestTnyMsgViewPrivate *priv;
747
748         g_return_if_fail (self);
749         
750         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
751
752         priv->msg = msg;
753         
754         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml), "", msg);
755
756         if (!msg) 
757                 return;
758         
759         body = modest_tny_msg_actions_find_body_part (msg, "text/html");
760         if (body) {
761                 set_html_message (self, body, msg);
762                 return;
763         }
764         
765         body = modest_tny_msg_actions_find_body_part (msg, "text/plain");
766         if (body) {
767                 set_text_message (self, body, msg);
768                 return;
769         }
770
771         /* hmmmmm */
772         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml),
773                                 _("Unsupported message type"), msg);
774 }
775
776 void
777 modest_tny_msg_view_redraw (ModestTnyMsgView *self)
778 {
779         ModestTnyMsgViewPrivate *priv;
780
781         g_return_if_fail (self);
782         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
783         modest_tny_msg_view_set_message(self, priv->msg);
784 }