ba5c456c088466e9e43808888cabf39c9efbbd3a
[modest] / src / modest-tny-msg.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <string.h>
31 #include <gtkhtml/gtkhtml.h>
32 #include <tny-gtk-text-buffer-stream.h>
33 #include <tny-simple-list.h>
34 #include <tny-folder.h>
35 #include <modest-runtime.h>
36 #include <modest-defs.h>
37 #include "modest-formatter.h"
38 #include <tny-camel-mem-stream.h>
39 #include <tny-camel-mime-part.h>
40 #include <glib/gprintf.h>
41 #include <modest-tny-folder.h>
42 #include "modest-tny-mime-part.h"
43 #include <modest-error.h>
44
45
46 #ifdef HAVE_CONFIG_H
47 #include <config.h>
48 #endif /*HAVE_CONFIG_H */
49
50 #include <modest-tny-msg.h>
51 #include "modest-text-utils.h"
52
53 static TnyMimePart * add_body_part (TnyMsg *msg, const gchar *body,
54                                     const gchar *content_type);
55 static TnyMimePart * add_html_body_part (TnyMsg *msg, const gchar *body);
56 static gint add_attachments (TnyMimePart *part, GList *attachments_list, gboolean add_inline, GError **err);
57 static void add_images (TnyMsg *msg, GList *attachments_list, GError **err);
58 static char * get_content_type(const gchar *s);
59 static gboolean is_ascii(const gchar *s);
60
61
62 TnyMsg*
63 modest_tny_msg_new (const gchar* mailto, const gchar* from, const gchar *cc,
64                     const gchar *bcc, const gchar* subject, const gchar *body,
65                     GList *attachments, gint *attached, GError **err)
66 {
67         TnyMsg *new_msg;
68         TnyHeader *header;
69         gchar *content_type;
70         gint tmp_attached = 0;
71         
72         /* Create new msg */
73         new_msg = modest_formatter_create_message (NULL, TRUE, (attachments != NULL), FALSE);
74         header  = tny_msg_get_header (new_msg);
75         
76         if ((from != NULL) && (strlen(from) > 0)) {
77                 tny_header_set_from (TNY_HEADER (header), from);
78                 tny_header_set_replyto (TNY_HEADER (header), from);
79         }
80         if ((mailto != NULL) && (strlen(mailto) > 0)) {
81                 gchar *removed_to = modest_text_utils_remove_duplicate_addresses (mailto);
82                 tny_header_set_to (TNY_HEADER (header), removed_to);
83                 g_free (removed_to);
84         }
85         if ((cc != NULL) && (strlen(cc) > 0)) 
86                 tny_header_set_cc (TNY_HEADER (header), cc);
87         if ((bcc != NULL) && (strlen(bcc) > 0)) 
88                 tny_header_set_bcc (TNY_HEADER (header), bcc);
89         
90         if ((subject != NULL) && (strlen(subject) > 0)) 
91                 tny_header_set_subject (TNY_HEADER (header), subject);
92
93         content_type = get_content_type(body);
94
95         /* set modest as the X-Mailer
96          * we could this in the platform factory, but then the header
97          * would show up before all the others.
98          */
99         tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "X-Mailer", "Modest "
100                                        VERSION);
101         
102         /* Add the body of the new mail */
103         /* This is needed even if body is NULL or empty. */
104         add_body_part (new_msg, body, content_type);
105         g_free (content_type);
106                        
107         /* Add attachments */
108         if (attachments)
109                 tmp_attached = add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, err);
110         if (attached)
111                 *attached = tmp_attached;
112         if (header)
113                 g_object_unref(header);
114
115         return new_msg;
116 }
117
118 TnyMsg*
119 modest_tny_msg_new_html_plain (const gchar* mailto, const gchar* from, const gchar *cc,
120                                const gchar *bcc, const gchar* subject, 
121                                const gchar *html_body, const gchar *plain_body,
122                                GList *attachments, GList *images, gint *attached, GError **err)
123 {
124         TnyMsg *new_msg;
125         TnyHeader *header;
126         gchar *content_type;
127         gint tmp_attached;
128         
129         /* Create new msg */
130         new_msg = modest_formatter_create_message (NULL, FALSE, (attachments != NULL), (images != NULL));
131         header  = tny_msg_get_header (new_msg);
132         
133         if ((from != NULL) && (strlen(from) > 0)) {
134                 tny_header_set_from (TNY_HEADER (header), from);
135                 tny_header_set_replyto (TNY_HEADER (header), from);
136         }
137         if ((mailto != NULL) && (strlen(mailto) > 0)) 
138                 tny_header_set_to (TNY_HEADER (header), mailto);
139         if ((cc != NULL) && (strlen(cc) > 0)) 
140                 tny_header_set_cc (TNY_HEADER (header), cc);
141         if ((bcc != NULL) && (strlen(bcc) > 0)) 
142                 tny_header_set_bcc (TNY_HEADER (header), bcc);
143         
144         if ((subject != NULL) && (strlen(subject) > 0)) 
145                 tny_header_set_subject (TNY_HEADER (header), subject);
146
147         content_type = get_content_type(plain_body);
148         
149         /* set modest as the X-Mailer
150          * we could this in the platform factory, but then the header
151          * would show up before all the others.
152          */
153         tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "X-Mailer", "Modest "
154                                        VERSION);
155
156         /* Add the body of the new mail */      
157         add_body_part (new_msg, plain_body, content_type);
158         add_html_body_part (new_msg, html_body);
159         g_free (content_type);
160                        
161         /* Add attachments */
162         tmp_attached = add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, err);
163         if (attached)
164                 *attached = tmp_attached;
165         add_images (new_msg, images, err);
166         if (header)
167                 g_object_unref(header);
168
169         return new_msg;
170 }
171
172
173 /* FIXME: this func copy from modest-mail-operation: refactor */
174 static TnyMimePart *
175 add_body_part (TnyMsg *msg, 
176                const gchar *body,
177                const gchar *content_type)
178 {
179         TnyMimePart *text_body_part = NULL;
180         TnyStream *text_body_stream;
181
182         /* Create the stream */
183         text_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
184                                         (body, (body ? strlen(body) : 0)));
185
186         text_body_part = modest_formatter_create_body_part (NULL, msg);
187
188         /* Construct MIME part */
189         tny_stream_reset (text_body_stream);
190         tny_mime_part_construct (text_body_part,
191                                  text_body_stream,
192                                  content_type, "7bit");
193         tny_stream_reset (text_body_stream);
194
195         g_object_unref (G_OBJECT(text_body_part));
196
197         /* Clean */
198         g_object_unref (text_body_stream);
199
200         return text_body_part;
201 }
202
203 static TnyMimePart *
204 add_html_body_part (TnyMsg *msg, 
205                     const gchar *body)
206 {
207         TnyMimePart *html_body_part = NULL;
208         TnyStream *html_body_stream;
209
210         /* Create the stream */
211         html_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
212                                         (body, strlen(body)));
213
214         /* Create body part if needed */
215         html_body_part = modest_formatter_create_body_part (NULL, msg);
216
217         /* Construct MIME part */
218         tny_stream_reset (html_body_stream);
219         tny_mime_part_construct (html_body_part,
220                                  html_body_stream,
221                                  "text/html; charset=utf-8", 
222                                  "7bit"); /* Sometimes it might be needed 
223                                              to make this one a 8bit! */
224         tny_stream_reset (html_body_stream);
225
226         g_object_unref (G_OBJECT(html_body_part));
227
228         /* Clean */
229         g_object_unref (html_body_stream);
230
231         return html_body_part;
232 }
233
234 static TnyMimePart *
235 copy_mime_part (TnyMimePart *part, GError **err)
236 {
237         TnyMimePart *result = NULL;
238         const gchar *attachment_content_type;
239         const gchar *attachment_filename;
240         const gchar *attachment_cid;
241         TnyList *parts;
242         TnyIterator *iterator;
243         TnyStream *attachment_stream;
244         const gchar *enc;
245         gint ret;
246         
247         if (TNY_IS_MSG (part)) {
248                 g_object_ref (part);
249                 return part;
250         }
251
252         result = tny_platform_factory_new_mime_part (
253                 modest_runtime_get_platform_factory());
254
255         attachment_content_type = tny_mime_part_get_content_type (part);
256
257         /* get mime part headers */
258         attachment_filename = tny_mime_part_get_filename (part);
259         attachment_cid = tny_mime_part_get_content_id (part);
260         
261         /* fill the stream */
262         attachment_stream = tny_mime_part_get_decoded_stream (part);
263         enc = tny_mime_part_get_transfer_encoding (part);
264         if (attachment_stream == NULL) {
265                 if (err != NULL && *err == NULL)
266                         g_set_error (err, MODEST_MAIL_OPERATION_ERROR, MODEST_MAIL_OPERATION_ERROR_FILE_IO, _("TODO: couldn't retrieve attachment"));
267                 g_object_unref (result);
268                 return NULL;
269         } else {
270                 ret = tny_stream_reset (attachment_stream);
271                 ret = tny_mime_part_construct (result,
272                                                attachment_stream,
273                                                attachment_content_type, 
274                                                enc);
275                 ret = tny_stream_reset (attachment_stream);
276         }
277         
278         /* set other mime part fields */
279         tny_mime_part_set_filename (result, attachment_filename);
280         tny_mime_part_set_content_id (result, attachment_cid);
281
282         /* copy subparts */
283         parts = tny_simple_list_new ();
284         tny_mime_part_get_parts (part, parts);
285         iterator = tny_list_create_iterator (parts);
286         while (!tny_iterator_is_done (iterator)) {
287                 TnyMimePart *subpart = TNY_MIME_PART (tny_iterator_get_current (iterator));
288                 if (subpart) {
289                         const gchar *subpart_cid;
290                         TnyMimePart *subpart_copy = copy_mime_part (subpart, err);
291                         if (subpart_copy != NULL) {
292                                 subpart_cid = tny_mime_part_get_content_id (subpart);
293                                 tny_mime_part_add_part (result, subpart_copy);
294                                 if (subpart_cid)
295                                         tny_mime_part_set_content_id (result, subpart_cid);
296                                 g_object_unref (subpart_copy);
297                         }
298                         g_object_unref (subpart);
299                 }
300
301                 tny_iterator_next (iterator);
302         }
303         g_object_unref (iterator);
304         g_object_unref (parts);
305         g_object_unref (attachment_stream);
306
307         return result;
308 }
309
310 static gint
311 add_attachments (TnyMimePart *part, GList *attachments_list, gboolean add_inline, GError **err)
312 {
313         GList *pos;
314         TnyMimePart *attachment_part, *old_attachment;
315         gint ret;
316         gint attached = 0;
317
318         for (pos = (GList *)attachments_list; pos; pos = pos->next) {
319
320                 old_attachment = pos->data;
321                 if (!tny_mime_part_is_purged (old_attachment)) {
322                         const gchar *old_cid;
323                         old_cid = tny_mime_part_get_content_id (old_attachment);
324                         attachment_part = copy_mime_part (old_attachment, err);
325                         if (attachment_part != NULL) {
326                                 if (add_inline) {
327                                         tny_mime_part_set_header_pair (attachment_part, "Content-Disposition",
328                                                                        "inline");
329                                 } else {
330                                         const gchar *filename;
331                                         filename = tny_mime_part_get_filename (old_attachment);
332                                         if (filename)
333                                                 tny_mime_part_set_filename (attachment_part, filename);
334                                         else
335                                                 tny_mime_part_set_header_pair (attachment_part, "Content-Disposition",
336                                                                                "attachment");
337                                 }
338                                 tny_mime_part_set_transfer_encoding (TNY_MIME_PART (attachment_part), "base64");
339                                 ret = tny_mime_part_add_part (TNY_MIME_PART (part), attachment_part);
340                                 attached++;
341                                 if (old_cid)
342                                         tny_mime_part_set_content_id (attachment_part, old_cid);
343                                 g_object_unref (attachment_part);
344                         }
345                 }
346         }
347         return attached;
348 }
349
350 static void
351 add_images (TnyMsg *msg, GList *images_list, GError **err)
352 {
353         TnyMimePart *related_part = NULL;
354         const gchar *content_type;
355
356         content_type = tny_mime_part_get_content_type (TNY_MIME_PART (msg));
357
358         if ((content_type != NULL) && !strcasecmp (content_type, "multipart/related")) {
359                 related_part = g_object_ref (msg);
360         } else if ((content_type != NULL) && !strcasecmp (content_type, "multipart/mixed")) {
361                 TnyList *parts = TNY_LIST (tny_simple_list_new ());
362                 TnyIterator *iter = NULL;
363                 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
364                 iter = tny_list_create_iterator (parts);
365
366                 while (!tny_iterator_is_done (iter)) {
367                         TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
368                         if (part && !g_strcasecmp (tny_mime_part_get_content_type (part), "multipart/related")) {
369                                 related_part = part;
370                                 break;
371                         }
372                         if (part)
373                                 g_object_unref (part);
374                         tny_iterator_next (iter);
375                 }
376                 g_object_unref (iter);
377                 g_object_unref (parts);
378         }
379
380         if (related_part != NULL) {
381                 /* TODO: attach images in their proper place */
382                 add_attachments (related_part, images_list, TRUE, err);
383                 g_object_unref (related_part);
384         }
385 }
386
387
388 gchar * 
389 modest_tny_msg_get_body (TnyMsg *msg, gboolean want_html, gboolean *is_html)
390 {
391         TnyStream *stream;
392         TnyMimePart *body;
393         GtkTextBuffer *buf;
394         GtkTextIter start, end;
395         gchar *to_quote;
396         gboolean result_was_html = TRUE;
397
398         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
399         
400         body = modest_tny_msg_find_body_part(msg, want_html);
401         if (!body)
402                 return NULL;
403
404         buf = gtk_text_buffer_new (NULL);
405         stream = TNY_STREAM (tny_gtk_text_buffer_stream_new (buf));
406         tny_stream_reset (stream);
407         tny_mime_part_decode_to_stream (body, stream, NULL);
408         tny_stream_reset (stream);
409         
410         gtk_text_buffer_get_bounds (buf, &start, &end);
411         to_quote = gtk_text_buffer_get_text (buf, &start, &end, FALSE);
412         if (tny_mime_part_content_type_is (body, "text/plain")) {
413                 gchar *to_quote_converted = modest_text_utils_convert_to_html (to_quote);
414                 g_free (to_quote);
415                 to_quote = to_quote_converted;
416                 result_was_html = FALSE;
417         }
418
419         g_object_unref (buf);
420         g_object_unref (G_OBJECT(stream));
421         g_object_unref (G_OBJECT(body));
422
423         if (is_html != NULL)
424                 *is_html = result_was_html;
425
426         return to_quote;
427 }
428
429
430 TnyMimePart*
431 modest_tny_msg_find_body_part_from_mime_part (TnyMimePart *msg, gboolean want_html)
432 {
433         const gchar *desired_mime_type = want_html ? "text/html" : "text/plain";
434         TnyMimePart *part = NULL;
435         TnyList *parts = NULL;
436         TnyIterator *iter = NULL;
437         gchar *header_content_type;
438         gchar *header_content_type_lower = NULL;
439         
440         if (!msg)
441                 return NULL;
442
443         /* If it's an application multipart, then we don't get into as we don't
444          * support them (for example application/sml or wap messages */
445         header_content_type = modest_tny_mime_part_get_header_value (msg, "Content-Type");
446         if (header_content_type) {
447                 header_content_type = g_strstrip (header_content_type);
448                 header_content_type_lower = g_ascii_strdown (header_content_type, -1);
449         }
450         if (header_content_type_lower && 
451             g_str_has_prefix (header_content_type_lower, "multipart/") &&
452             !g_str_has_prefix (header_content_type_lower, "multipart/signed") &&
453             strstr (header_content_type_lower, "application/")) {
454                 g_free (header_content_type_lower);
455                 g_free (header_content_type);
456                 return NULL;
457         }       
458         g_free (header_content_type_lower);
459         g_free (header_content_type);
460
461         parts = TNY_LIST (tny_simple_list_new());
462         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
463
464         iter  = tny_list_create_iterator(parts);
465
466         /* no parts? assume it's single-part message */
467         if (tny_iterator_is_done(iter)) {
468                 gchar *content_type;
469                 gboolean is_text_part;
470                 g_object_unref (G_OBJECT(iter));
471                 content_type = modest_tny_mime_part_get_content_type (msg);
472                 if (content_type == NULL)
473                         return NULL;
474                 is_text_part = 
475                         g_str_has_prefix (content_type, "text/");
476                 g_free (content_type);
477                 /* if this part cannot be a supported body return NULL */
478                 if (!is_text_part) {
479                         return NULL;
480                 } else {
481                         return TNY_MIME_PART (g_object_ref(G_OBJECT(msg)));
482                 }
483         } else {
484                 do {
485                         gchar *tmp, *content_type = NULL;
486                         gboolean has_content_disp_name = FALSE;
487
488                         part = TNY_MIME_PART(tny_iterator_get_current (iter));
489
490                         if (!part) {
491                                 g_warning ("%s: not a valid mime part", __FUNCTION__);
492                                 tny_iterator_next (iter);
493                                 continue;
494                         }
495
496                         /* it's a message --> ignore */
497                         if (part && TNY_IS_MSG (part)) {
498                                 g_object_unref (part);
499                                 part = NULL;
500                                 tny_iterator_next (iter);
501                                 continue;
502                         }                       
503
504                         /* we need to strdown the content type, because
505                          * tny_mime_part_has_content_type does not do it...
506                          */
507                         content_type = g_ascii_strdown (tny_mime_part_get_content_type (part), -1);
508                         
509                         /* mime-parts with a content-disposition header (either 'inline' or 'attachment')
510                          * and a 'name=' thingy cannot be body parts
511                          */
512                                 
513                         tmp = modest_tny_mime_part_get_header_value (part, "Content-Disposition");
514                         if (tmp) {
515                                 gchar *content_disp = g_ascii_strdown(tmp, -1);
516                                 g_free (tmp);
517                                 has_content_disp_name = g_strstr_len (content_disp, strlen(content_disp), "name=") != NULL;
518                                 g_free (content_disp);
519                         }
520                         
521                         if (g_str_has_prefix (content_type, desired_mime_type) && 
522                             !has_content_disp_name &&
523                             !modest_tny_mime_part_is_attachment_for_modest (part)) {
524                                 /* we found the desired mime-type! */
525                                 g_free (content_type);
526                                 break;
527
528                         } else  if (g_str_has_prefix(content_type, "multipart")) {
529
530                                 /* multipart? recurse! */
531                                 g_object_unref (part);
532                                 g_free (content_type);
533                                 part = modest_tny_msg_find_body_part_from_mime_part (part, want_html);
534                                 if (part)
535                                         break;
536                         } else
537                                 g_free (content_type);
538                         
539                         if (part) {
540                                 g_object_unref (G_OBJECT(part));
541                                 part = NULL;
542                         }
543                         
544                         tny_iterator_next (iter);
545                         
546                 } while (!tny_iterator_is_done(iter));
547         }
548         
549         g_object_unref (G_OBJECT(iter));
550         g_object_unref (G_OBJECT(parts));
551
552         /* if were trying to find an HTML part and couldn't find it,
553          * try to find a text/plain part instead
554          */
555         if (!part && want_html) 
556                 return modest_tny_msg_find_body_part_from_mime_part (msg, FALSE);
557         else
558                 return part; /* this maybe NULL, this is not an error; some message just don't have a body
559                               * part */
560 }
561
562
563 TnyMimePart*
564 modest_tny_msg_find_body_part (TnyMsg *msg, gboolean want_html)
565 {
566         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
567         
568         return modest_tny_msg_find_body_part_from_mime_part (TNY_MIME_PART(msg),
569                                                              want_html);
570 }
571
572
573 #define MODEST_TNY_MSG_PARENT_UID "parent-uid"
574
575 static TnyMsg *
576 create_reply_forward_mail (TnyMsg *msg, TnyHeader *header, const gchar *from,
577                            const gchar *signature, gboolean is_reply,
578                            guint type /*ignored*/, GList *attachments)
579 {
580         TnyMsg *new_msg;
581         TnyHeader *new_header;
582         gchar *old_subject;
583         gchar *new_subject;
584         TnyMimePart *body = NULL;
585         ModestFormatter *formatter;
586         gchar *subject_prefix;
587         gboolean no_text_part;
588         gchar *parent_uid;
589
590         if (header)
591                 g_object_ref (header);
592         else
593                 header = tny_msg_get_header (msg);
594
595         /* Get body from original msg. Always look for the text/plain
596            part of the message to create the reply/forwarded mail */
597         if (msg != NULL)
598                 body   = modest_tny_msg_find_body_part (msg, FALSE);
599
600         if (modest_conf_get_bool (modest_runtime_get_conf (), MODEST_CONF_PREFER_FORMATTED_TEXT,
601                                   NULL))
602                 formatter = modest_formatter_new ("text/html", signature);
603         else
604                 formatter = modest_formatter_new ("text/plain", signature);
605
606
607         /* if we don't have a text-part */
608         no_text_part = (!body) || (strcmp (tny_mime_part_get_content_type (body), "text/html")==0);
609
610         /* when we're reply, include the text part if we have it, or nothing otherwise. */
611         if (is_reply)
612                 new_msg = modest_formatter_quote  (formatter, no_text_part ? NULL: body, header,
613                                                     attachments);
614         else {
615                 /* for attachements; inline if there is a text part, and include the
616                  * full old mail if there was none */
617                 if (no_text_part) {
618                         new_msg = modest_formatter_attach (formatter, msg, header);
619                 } else { 
620                         new_msg = modest_formatter_inline  (formatter, body, header,
621                                                             attachments);
622                 }
623         }
624
625         g_object_unref (G_OBJECT(formatter));
626         if (body)
627                 g_object_unref (G_OBJECT(body));
628
629         /* Fill the header */
630         new_header = tny_msg_get_header (new_msg);
631         tny_header_set_from (new_header, from);
632         tny_header_set_replyto (new_header, from);
633
634         /* Change the subject */
635         if (is_reply)
636                 subject_prefix = g_strconcat (_("mail_va_re"), ":", NULL);
637         else
638                 subject_prefix = g_strconcat (_("mail_va_fw"), ":", NULL);
639         old_subject = tny_header_dup_subject (header);
640         new_subject =
641                 (gchar *) modest_text_utils_derived_subject (old_subject,
642                                                              subject_prefix);
643         g_free (old_subject);
644         g_free (subject_prefix);
645         tny_header_set_subject (new_header, (const gchar *) new_subject);
646         g_free (new_subject);
647
648         /* get the parent uid, and set it as a gobject property on the new msg */
649         parent_uid = modest_tny_folder_get_header_unique_id (header);
650         g_object_set_data_full (G_OBJECT(new_msg), MODEST_TNY_MSG_PARENT_UID,
651                                 parent_uid, g_free);
652
653         /* set modest as the X-Mailer
654          * we could this in the platform factory, but then the header
655          * would show up before all the others.
656          */
657         tny_mime_part_set_header_pair (TNY_MIME_PART (msg), "X-Mailer", "Modest "
658                                        VERSION);
659
660         /* Clean */
661         g_object_unref (G_OBJECT (new_header));
662         g_object_unref (G_OBJECT (header));
663         /* ugly to unref it here instead of in the calling func */
664
665         if (!is_reply & !no_text_part) {
666                 add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, NULL);
667         }
668
669         return new_msg;
670 }
671
672 const gchar*
673 modest_tny_msg_get_parent_uid (TnyMsg *msg)
674 {
675         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
676         
677         return g_object_get_data (G_OBJECT(msg), MODEST_TNY_MSG_PARENT_UID);
678 }
679
680
681
682 static void
683 add_if_attachment (gpointer data, gpointer user_data)
684 {
685         TnyMimePart *part;
686         GList **attachments_list;
687
688         part = TNY_MIME_PART (data);
689         attachments_list = ((GList **) user_data);
690
691         if ((tny_mime_part_is_attachment (part))||(TNY_IS_MSG (part))) {
692                 *attachments_list = g_list_prepend (*attachments_list, part);
693                 g_object_ref (part);
694         }
695 }
696
697 TnyMsg* 
698 modest_tny_msg_create_forward_msg (TnyMsg *msg, 
699                                    const gchar *from,
700                                    const gchar *signature,
701                                    ModestTnyMsgForwardType forward_type)
702 {
703         TnyMsg *new_msg;
704         TnyList *parts = NULL;
705         GList *attachments_list = NULL;
706
707         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
708         
709         /* Add attachments */
710         parts = TNY_LIST (tny_simple_list_new());
711         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
712         tny_list_foreach (parts, add_if_attachment, &attachments_list);
713
714         new_msg = create_reply_forward_mail (msg, NULL, from, signature, FALSE, forward_type,
715                                              attachments_list);
716
717         /* Clean */
718         if (attachments_list) {
719                 g_list_foreach (attachments_list, (GFunc) g_object_unref, NULL);
720                 g_list_free (attachments_list);
721         }
722         g_object_unref (G_OBJECT (parts));
723
724         return new_msg;
725 }
726
727
728
729 static gint
730 count_addresses (const gchar* addresses)
731 {
732         gint count = 1;
733
734         if (!addresses)
735                 return 0;
736         
737         while (*addresses) {
738                 if (*addresses == ',' || *addresses == ';')
739                         ++count;
740                 ++addresses;
741         }
742         
743         return count;
744 }
745
746
747 /* get the new To:, based on the old header,
748  * result is newly allocated or NULL in case of error
749  * */
750 static gchar*
751 get_new_to (TnyMsg *msg, TnyHeader *header, const gchar* from,
752             ModestTnyMsgReplyMode reply_mode)
753 {
754         gchar* old_reply_to;
755         gchar* old_from;
756         gchar* new_to;
757         
758         /* according to RFC2369 (http://www.faqs.org/rfcs/rfc2369.html), we
759          * can identify Mailing-List posts by the List-Help header.
760          * for mailing lists, both the Reply-To: and From: should be included
761          * in the new To:; for now, we're ignoring List-Post
762          */
763         gchar* list_help = modest_tny_mime_part_get_header_value (TNY_MIME_PART(msg), 
764                                                                   "List-Help");
765         gboolean is_mailing_list = (list_help != NULL);
766         g_free (list_help);
767
768
769         /* reply to sender, use ReplyTo or From */
770         old_reply_to = modest_tny_mime_part_get_header_value (TNY_MIME_PART(msg), 
771                                                               "Reply-To"); 
772         old_from     = tny_header_dup_from (header);
773         
774         if (!old_from && !old_reply_to) {
775                 g_warning ("%s: failed to get either Reply-To: or From: from header",
776                            __FUNCTION__);
777                 return NULL;
778         }
779         
780         /* for mailing lists, use both Reply-To and From if we did a
781          * 'Reply All:'
782          * */
783         if (is_mailing_list && reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL &&
784             old_reply_to && old_from && strcmp (old_from, old_reply_to) != 0)
785                 new_to = g_strjoin (",", old_reply_to, old_from, NULL);
786         else
787                 /* otherwise use either Reply-To: (preferred) or From: */
788                 new_to = g_strdup (old_reply_to ? old_reply_to : old_from);
789         g_free (old_from);
790         g_free (old_reply_to);
791
792         /* in case of ReplyAll, we need to add the Recipients in the old To: */
793         if (reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL) {
794                 gchar *old_to = tny_header_dup_to (header);
795                 if (!old_to) 
796                         g_warning ("%s: no To: address found in source mail",
797                                    __FUNCTION__);
798                 else {
799                         /* append the old To: */
800                         gchar *tmp = g_strjoin (",", new_to, old_to, NULL);
801                         g_free (new_to);
802                         new_to = tmp;
803                         g_free (old_to);
804                 }
805
806                 /* remove duplicate entries */
807                 gchar *tmp = modest_text_utils_remove_duplicate_addresses (new_to);
808                 g_free (new_to);
809                 new_to = tmp;
810                 
811                 /* now, strip me (the new From:) from the new_to, but only if
812                  * there are >1 addresses there */
813                 if (count_addresses (new_to) > 1) {
814                         gchar *tmp = modest_text_utils_remove_address (new_to, from);
815                         g_free (new_to);
816                         new_to = tmp;
817                 }
818         }
819
820         return new_to;
821 }
822
823
824 /* get the new Cc:, based on the old header,
825  * result is newly allocated or NULL in case of error */
826 static gchar*
827 get_new_cc (TnyHeader *header, const gchar* from)
828 {
829         gchar *old_cc, *result, *dup;
830
831         old_cc = tny_header_dup_cc (header);
832         if (!old_cc)
833                 return NULL;
834
835         /* remove me (the new From:) from the Cc: list */
836         dup =  modest_text_utils_remove_address (old_cc, from);
837         result = modest_text_utils_remove_duplicate_addresses (dup);
838         g_free (dup);
839         g_free (old_cc);
840         return result;
841 }
842
843
844 TnyMsg* 
845 modest_tny_msg_create_reply_msg (TnyMsg *msg,
846                                  TnyHeader *header,
847                                  const gchar *from,
848                                  const gchar *signature,
849                                  ModestTnyMsgReplyType reply_type,
850                                  ModestTnyMsgReplyMode reply_mode)
851 {
852         TnyMsg *new_msg = NULL;
853         TnyHeader *new_header;
854         gchar *new_to = NULL;
855         TnyList *parts = NULL;
856         GList *attachments_list = NULL;
857
858         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
859         
860         parts = TNY_LIST (tny_simple_list_new());
861         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
862         tny_list_foreach (parts, add_if_attachment, &attachments_list);
863
864         new_msg = create_reply_forward_mail (msg, header, from, signature, TRUE, reply_type,
865                                              attachments_list);
866         if (attachments_list != NULL) {
867                 g_list_foreach (attachments_list, (GFunc) g_object_unref, NULL);
868                 g_list_free (attachments_list);
869         }
870         g_object_unref (parts);
871
872         /* Fill the header */
873         if (header)
874                 g_object_ref (header);
875         else
876                 header = tny_msg_get_header (msg);
877
878         
879         new_header = tny_msg_get_header(new_msg);
880         new_to = get_new_to (msg, header, from, reply_mode);
881         if (!new_to)
882                 g_warning ("%s: failed to get new To:", __FUNCTION__);
883         else {
884                 tny_header_set_to (new_header, new_to);
885                 g_free (new_to);
886         }
887
888         if (reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL) {
889                 gchar *new_cc = get_new_cc (header, from);
890                 if (new_cc) { 
891                         tny_header_set_cc (new_header, new_cc);
892                         g_free (new_cc);
893                 }
894         }
895
896         /* Clean */
897         g_object_unref (G_OBJECT (new_header));
898         g_object_unref (G_OBJECT (header));
899
900         return new_msg;
901 }
902
903
904 static gboolean
905 is_ascii(const gchar *s)
906 {
907         if (!s)
908                 return TRUE;
909         while (s[0]) {
910                 if (s[0] & 128 || s[0] < 32)
911                         return FALSE;
912                 s++;
913         }
914         return TRUE;
915 }
916
917 static char *
918 get_content_type(const gchar *s)
919 {
920         GString *type;
921         
922         type = g_string_new("text/plain");
923         if (!is_ascii(s)) {
924                 if (g_utf8_validate(s, -1, NULL)) {
925                         g_string_append(type, "; charset=\"utf-8\"");
926                 } else {
927                         /* it should be impossible to reach this, but better safe than sorry */
928                         g_warning("invalid utf8 in message");
929                         g_string_append(type, "; charset=\"latin1\"");
930                 }
931         }
932         return g_string_free(type, FALSE);
933 }
934
935 guint64
936 modest_tny_msg_estimate_size (const gchar *plain_body, const gchar *html_body,
937                               guint64 parts_count,
938                               guint64 parts_size)
939 {
940         guint64 result;
941
942         /* estimation of headers size */
943         result = 1024;
944
945         /* We add a 20% of size due to the increase in 7bit encoding */
946         if (plain_body) {
947                 result += strlen (plain_body) * 120 / 100;
948         }
949         if (html_body) {
950                 result += strlen (html_body) * 120 / 100;
951         }
952
953         /* 256 bytes per additional part because of their headers */
954         result += parts_count * 256;
955
956         /* 150% of increase per encoding */
957         result += parts_size * 3 / 2;
958
959         return result;
960 }
961
962 GSList *
963 modest_tny_msg_header_get_all_recipients_list (TnyHeader *header)
964 {
965         GSList *recipients = NULL;
966         gchar *from = NULL, *to = NULL, *cc = NULL, *bcc = NULL;
967
968         if (header == NULL)
969                 return NULL;
970
971         from = tny_header_dup_from (header);
972         to = tny_header_dup_to (header);
973         cc = tny_header_dup_cc (header);
974         bcc = tny_header_dup_bcc (header);
975
976         recipients = NULL;
977         if (from)
978                 recipients = g_slist_concat (recipients, modest_text_utils_split_addresses_list (from));
979         if (to)
980                 recipients = g_slist_concat (recipients, modest_text_utils_split_addresses_list (to));
981         if (cc)
982                 recipients = g_slist_concat (recipients, modest_text_utils_split_addresses_list (cc));
983         if (bcc)
984                 recipients = g_slist_concat (recipients, modest_text_utils_split_addresses_list (bcc));
985
986         g_free (from);
987         g_free (to);
988         g_free (cc);
989         g_free (bcc);
990
991         return recipients;
992 }
993
994 GSList *
995 modest_tny_msg_get_all_recipients_list (TnyMsg *msg)
996 {
997         TnyHeader *header = NULL;
998         GSList *recipients = NULL;
999
1000         if (msg == NULL)
1001                 return NULL;
1002
1003         header = tny_msg_get_header (msg);
1004         if (header == NULL)
1005                 return NULL;
1006
1007         recipients = modest_tny_msg_header_get_all_recipients_list (header);
1008         g_object_unref (header);
1009
1010         return recipients;
1011 }
1012