Properly detect the body in multipart/related messages
[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 static TnyMimePart* modest_tny_msg_find_body_part_from_mime_part (TnyMimePart *msg, gboolean want_html);
62 static TnyMimePart* modest_tny_msg_find_calendar_from_mime_part  (TnyMimePart *msg);
63
64 TnyMsg*
65 modest_tny_msg_new (const gchar* mailto, const gchar* from, const gchar *cc,
66                     const gchar *bcc, const gchar* subject,
67                     const gchar *references, const gchar *in_reply_to,
68                     const gchar *body,
69                     GList *attachments, gint *attached, GError **err)
70 {
71         TnyMsg *new_msg;
72         TnyHeader *header;
73         gchar *content_type;
74         gint tmp_attached = 0;
75
76         /* Create new msg */
77         new_msg = modest_formatter_create_message (NULL, TRUE, (attachments != NULL), FALSE);
78         header  = tny_msg_get_header (new_msg);
79
80         if ((from != NULL) && (strlen(from) > 0)) {
81                 tny_header_set_from (TNY_HEADER (header), from);
82                 tny_header_set_replyto (TNY_HEADER (header), from);
83         }
84         if ((mailto != NULL) && (strlen(mailto) > 0)) {
85                 gchar *removed_to = modest_text_utils_remove_duplicate_addresses (mailto);
86                 tny_header_set_to (TNY_HEADER (header), removed_to);
87                 g_free (removed_to);
88         }
89         if ((cc != NULL) && (strlen(cc) > 0))
90                 tny_header_set_cc (TNY_HEADER (header), cc);
91         if ((bcc != NULL) && (strlen(bcc) > 0))
92                 tny_header_set_bcc (TNY_HEADER (header), bcc);
93
94         if ((subject != NULL) && (strlen(subject) > 0))
95                 tny_header_set_subject (TNY_HEADER (header), subject);
96
97         content_type = get_content_type(body);
98
99         /* set modest as the X-Mailer
100          * we could this in the platform factory, but then the header
101          * would show up before all the others.
102          */
103         tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "X-Mailer", "Modest "
104                                        VERSION);
105
106         if (references)
107                 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "References", references);
108
109         if (in_reply_to)
110                 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "In-Reply-To", in_reply_to);
111
112         /* Add the body of the new mail */
113         /* This is needed even if body is NULL or empty. */
114         add_body_part (new_msg, body, content_type);
115         g_free (content_type);
116
117         /* Add attachments */
118         if (attachments)
119                 tmp_attached = add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, err);
120         if (attached)
121                 *attached = tmp_attached;
122         if (header)
123                 g_object_unref(header);
124
125         return new_msg;
126 }
127
128 TnyMsg*
129 modest_tny_msg_new_html_plain (const gchar* mailto, const gchar* from, const gchar *cc,
130                                const gchar *bcc, const gchar* subject, 
131                                const gchar *references, const gchar *in_reply_to,
132                                const gchar *html_body, const gchar *plain_body,
133                                GList *attachments, GList *images, gint *attached, GError **err)
134 {
135         TnyMsg *new_msg;
136         TnyHeader *header;
137         gchar *content_type;
138         gint tmp_attached;
139         
140         /* Create new msg */
141         new_msg = modest_formatter_create_message (NULL, FALSE, (attachments != NULL), (images != NULL));
142         header  = tny_msg_get_header (new_msg);
143         
144         if ((from != NULL) && (strlen(from) > 0)) {
145                 tny_header_set_from (TNY_HEADER (header), from);
146                 tny_header_set_replyto (TNY_HEADER (header), from);
147         }
148         if ((mailto != NULL) && (strlen(mailto) > 0)) 
149                 tny_header_set_to (TNY_HEADER (header), mailto);
150         if ((cc != NULL) && (strlen(cc) > 0)) 
151                 tny_header_set_cc (TNY_HEADER (header), cc);
152         if ((bcc != NULL) && (strlen(bcc) > 0)) 
153                 tny_header_set_bcc (TNY_HEADER (header), bcc);
154         
155         if ((subject != NULL) && (strlen(subject) > 0)) 
156                 tny_header_set_subject (TNY_HEADER (header), subject);
157
158         content_type = get_content_type(plain_body);
159         
160         /* set modest as the X-Mailer
161          * we could this in the platform factory, but then the header
162          * would show up before all the others.
163          */
164         tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "X-Mailer", "Modest "
165                                        VERSION);
166
167         if (references)
168                 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "References", references);
169
170         if (in_reply_to)
171                 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "In-Reply-To", in_reply_to);
172
173         /* Add the body of the new mail */
174         add_body_part (new_msg, plain_body, content_type);
175         add_html_body_part (new_msg, html_body);
176         g_free (content_type);
177
178         /* Add attachments */
179         tmp_attached = add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, err);
180         if (attached)
181                 *attached = tmp_attached;
182         add_images (new_msg, images, err);
183         if (header)
184                 g_object_unref(header);
185
186         return new_msg;
187 }
188
189
190 /* FIXME: this func copy from modest-mail-operation: refactor */
191 static TnyMimePart *
192 add_body_part (TnyMsg *msg, 
193                const gchar *body,
194                const gchar *content_type)
195 {
196         TnyMimePart *text_body_part = NULL;
197         TnyStream *text_body_stream;
198
199         /* Create the stream */
200         text_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
201                                         (body, (body ? strlen(body) : 0)));
202
203         text_body_part = modest_formatter_create_body_part (NULL, msg);
204
205         /* Construct MIME part */
206         tny_stream_reset (text_body_stream);
207         tny_mime_part_construct (text_body_part,
208                                  text_body_stream,
209                                  content_type, "7bit");
210         tny_stream_reset (text_body_stream);
211
212         g_object_unref (G_OBJECT(text_body_part));
213
214         /* Clean */
215         g_object_unref (text_body_stream);
216
217         return text_body_part;
218 }
219
220 static TnyMimePart *
221 add_html_body_part (TnyMsg *msg, 
222                     const gchar *body)
223 {
224         TnyMimePart *html_body_part = NULL;
225         TnyStream *html_body_stream;
226
227         /* Create the stream */
228         html_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
229                                        (body, (body) ? strlen(body) : 0));
230
231         /* Create body part if needed */
232         html_body_part = modest_formatter_create_body_part (NULL, msg);
233
234         /* Construct MIME part */
235         tny_stream_reset (html_body_stream);
236         tny_mime_part_construct (html_body_part,
237                                  html_body_stream,
238                                  "text/html; charset=utf-8", 
239                                  "7bit"); /* Sometimes it might be needed 
240                                              to make this one a 8bit! */
241         tny_stream_reset (html_body_stream);
242
243         g_object_unref (G_OBJECT(html_body_part));
244
245         /* Clean */
246         g_object_unref (html_body_stream);
247
248         return html_body_part;
249 }
250
251 static TnyMimePart *
252 copy_mime_part (TnyMimePart *part, GError **err)
253 {
254         TnyMimePart *result = NULL;
255         const gchar *attachment_content_type;
256         const gchar *attachment_filename;
257         const gchar *attachment_cid;
258         TnyList *parts;
259         TnyIterator *iterator;
260         TnyStream *attachment_stream;
261         const gchar *enc;
262         gint ret;
263         
264         if (TNY_IS_MSG (part)) {
265                 g_object_ref (part);
266                 return part;
267         }
268
269         result = tny_platform_factory_new_mime_part (
270                 modest_runtime_get_platform_factory());
271
272         attachment_content_type = tny_mime_part_get_content_type (part);
273
274         /* get mime part headers */
275         attachment_filename = tny_mime_part_get_filename (part);
276         attachment_cid = tny_mime_part_get_content_id (part);
277         
278         /* fill the stream */
279         attachment_stream = tny_mime_part_get_decoded_stream (part);
280         enc = tny_mime_part_get_transfer_encoding (part);
281         if (attachment_stream == NULL) {
282                 if (err != NULL && *err == NULL)
283                         g_set_error (err, MODEST_MAIL_OPERATION_ERROR, MODEST_MAIL_OPERATION_ERROR_FILE_IO, _("TODO: couldn't retrieve attachment"));
284                 g_object_unref (result);
285                 return NULL;
286         } else {
287                 ret = tny_stream_reset (attachment_stream);
288                 ret = tny_mime_part_construct (result,
289                                                attachment_stream,
290                                                attachment_content_type, 
291                                                enc);
292                 ret = tny_stream_reset (attachment_stream);
293         }
294         
295         /* set other mime part fields */
296         tny_mime_part_set_filename (result, attachment_filename);
297         tny_mime_part_set_content_id (result, attachment_cid);
298
299         /* copy subparts */
300         parts = tny_simple_list_new ();
301         tny_mime_part_get_parts (part, parts);
302         iterator = tny_list_create_iterator (parts);
303         while (!tny_iterator_is_done (iterator)) {
304                 TnyMimePart *subpart = TNY_MIME_PART (tny_iterator_get_current (iterator));
305                 if (subpart) {
306                         const gchar *subpart_cid;
307                         TnyMimePart *subpart_copy = copy_mime_part (subpart, err);
308                         if (subpart_copy != NULL) {
309                                 subpart_cid = tny_mime_part_get_content_id (subpart);
310                                 tny_mime_part_add_part (result, subpart_copy);
311                                 if (subpart_cid)
312                                         tny_mime_part_set_content_id (result, subpart_cid);
313                                 g_object_unref (subpart_copy);
314                         }
315                         g_object_unref (subpart);
316                 }
317
318                 tny_iterator_next (iterator);
319         }
320         g_object_unref (iterator);
321         g_object_unref (parts);
322         g_object_unref (attachment_stream);
323
324         return result;
325 }
326
327 static gint
328 add_attachments (TnyMimePart *part, GList *attachments_list, gboolean add_inline, GError **err)
329 {
330         GList *pos;
331         TnyMimePart *attachment_part, *old_attachment;
332         gint ret;
333         gint attached = 0;
334
335         for (pos = (GList *)attachments_list; pos; pos = pos->next) {
336
337                 old_attachment = pos->data;
338                 if (!tny_mime_part_is_purged (old_attachment)) {
339                         gchar *old_cid;
340                         old_cid = g_strdup (tny_mime_part_get_content_id (old_attachment));
341                         attachment_part = copy_mime_part (old_attachment, err);
342                         if (attachment_part != NULL) {
343                                 if (add_inline) {
344                                         tny_mime_part_set_header_pair (attachment_part, "Content-Disposition",
345                                                                        "inline");
346                                 } else {
347                                         const gchar *filename;
348
349                                         filename = tny_mime_part_get_filename (old_attachment);
350                                         if (filename) {
351                                                 /* If the mime part has a filename do not set it again
352                                                    because Camel won't replace the old one. Instead it
353                                                    will append the filename to the old one and that will
354                                                    mislead email clients */
355                                                 if (!tny_mime_part_get_filename (attachment_part))
356                                                         tny_mime_part_set_filename (attachment_part, filename);
357                                         } else {
358                                                 tny_mime_part_set_header_pair (attachment_part, "Content-Disposition",
359                                                                                "attachment");
360                                         }
361                                 }
362                                 if (!TNY_IS_MSG (old_attachment))  {
363                                         tny_mime_part_set_transfer_encoding (TNY_MIME_PART (attachment_part), "base64");
364                                 }
365                                 ret = tny_mime_part_add_part (TNY_MIME_PART (part), attachment_part);
366                                 attached++;
367                                 if (old_cid)
368                                         tny_mime_part_set_content_id (attachment_part, old_cid);
369                                 g_object_unref (attachment_part);
370                         }
371                         g_free (old_cid);
372                 }
373         }
374         return attached;
375 }
376
377 static void
378 add_images (TnyMsg *msg, GList *images_list, GError **err)
379 {
380         TnyMimePart *related_part = NULL;
381         const gchar *content_type;
382
383         content_type = tny_mime_part_get_content_type (TNY_MIME_PART (msg));
384
385         if ((content_type != NULL) && !strcasecmp (content_type, "multipart/related")) {
386                 related_part = g_object_ref (msg);
387         } else if ((content_type != NULL) && !strcasecmp (content_type, "multipart/mixed")) {
388                 TnyList *parts = TNY_LIST (tny_simple_list_new ());
389                 TnyIterator *iter = NULL;
390                 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
391                 iter = tny_list_create_iterator (parts);
392
393                 while (!tny_iterator_is_done (iter)) {
394                         TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
395                         if (part && !g_ascii_strcasecmp (tny_mime_part_get_content_type (part), "multipart/related")) {
396                                 related_part = part;
397                                 break;
398                         }
399                         if (part)
400                                 g_object_unref (part);
401                         tny_iterator_next (iter);
402                 }
403                 g_object_unref (iter);
404                 g_object_unref (parts);
405         }
406
407         if (related_part != NULL) {
408                 /* TODO: attach images in their proper place */
409                 add_attachments (related_part, images_list, TRUE, err);
410                 g_object_unref (related_part);
411         }
412 }
413
414
415 gchar * 
416 modest_tny_msg_get_body (TnyMsg *msg, gboolean want_html, gboolean *is_html)
417 {
418         TnyStream *stream;
419         TnyMimePart *body;
420         GtkTextBuffer *buf;
421         GtkTextIter start, end;
422         gchar *to_quote;
423         gboolean result_was_html = TRUE;
424
425         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
426         
427         body = modest_tny_msg_find_body_part(msg, want_html);
428         if (!body)
429                 return NULL;
430
431         buf = gtk_text_buffer_new (NULL);
432         stream = TNY_STREAM (tny_gtk_text_buffer_stream_new (buf));
433         tny_stream_reset (stream);
434         tny_mime_part_decode_to_stream (body, stream, NULL);
435         tny_stream_reset (stream);
436         
437         gtk_text_buffer_get_bounds (buf, &start, &end);
438         to_quote = gtk_text_buffer_get_text (buf, &start, &end, FALSE);
439         if (tny_mime_part_content_type_is (body, "text/plain")) {
440                 gchar *to_quote_converted = modest_text_utils_convert_to_html (to_quote);
441                 g_free (to_quote);
442                 to_quote = to_quote_converted;
443                 result_was_html = FALSE;
444         }
445
446         g_object_unref (buf);
447         g_object_unref (G_OBJECT(stream));
448         g_object_unref (G_OBJECT(body));
449
450         if (is_html != NULL)
451                 *is_html = result_was_html;
452
453         return to_quote;
454 }
455
456
457 static TnyMimePart*
458 modest_tny_msg_find_body_part_in_alternative (TnyMimePart *msg, gboolean want_html)
459 {
460         TnyList *parts;
461         TnyIterator *iter;
462         TnyMimePart *part = NULL, *related = NULL;
463         TnyMimePart *first_part = NULL;
464         const gchar *desired_mime_type = want_html ? "text/html" : "text/plain";
465
466         parts = TNY_LIST (tny_simple_list_new());
467         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
468
469         for (iter  = tny_list_create_iterator(parts);
470              !tny_iterator_is_done (iter);
471              tny_iterator_next (iter)) {
472                 gchar *content_type;
473                 gboolean is_body;
474
475                 part = TNY_MIME_PART (tny_iterator_get_current (iter));
476
477                 if (first_part == NULL) {
478                         g_object_ref (part);
479                         first_part = part;
480                 }
481
482                 is_body = FALSE;
483                 content_type = g_ascii_strdown (tny_mime_part_get_content_type (part), -1);
484                 is_body = g_str_has_prefix (content_type, desired_mime_type);
485                 if (is_body)
486                         break;
487
488                 /* Makes no sense to look for related MIME parts if we
489                    only want the plain text parts */
490                 if (want_html && g_str_has_prefix (content_type, "multipart/related")) {
491                         /* In an alternative the last part is supposed
492                            to be the richest */
493                         if (related)
494                                 g_object_unref (related);
495                         related = g_object_ref (part);
496                 }
497
498                 g_object_unref (part);
499                 part = NULL;
500
501         }
502         g_object_unref (iter);
503         g_object_unref (parts);
504
505         if (part == NULL) {
506                 if (related) {
507                         TnyMimePart *retval;
508                         if (first_part)
509                                 g_object_unref (first_part);
510                         retval = modest_tny_msg_find_body_part_from_mime_part (related, want_html);
511                         g_object_unref (related);
512                         return retval;
513                 } else {
514                         return first_part;
515                 }
516         } else {
517                 if (first_part)
518                         g_object_unref (first_part);
519                 if (related)
520                         g_object_unref (related);
521                 return part;
522         }
523 }
524
525 static TnyMimePart*
526 modest_tny_msg_find_body_part_from_mime_part (TnyMimePart *msg, gboolean want_html)
527 {
528         TnyMimePart *part = NULL;
529         TnyList *parts = NULL;
530         TnyIterator *iter = NULL;
531         gchar *header_content_type;
532         gchar *header_content_type_lower = NULL;
533         gboolean is_related = FALSE;
534
535         if (!msg)
536                 return NULL;
537
538         /* If it's an application multipart, then we don't get into as we don't
539          * support them (for example application/sml or wap messages */
540         header_content_type = modest_tny_mime_part_get_header_value (msg, "Content-Type");
541         if (header_content_type) {
542                 header_content_type = g_strstrip (header_content_type);
543                 header_content_type_lower = g_ascii_strdown (header_content_type, -1);
544         }
545         if (header_content_type_lower &&
546             g_str_has_prefix (header_content_type_lower, "multipart/") &&
547             !g_str_has_prefix (header_content_type_lower, "multipart/signed") &&
548             strstr (header_content_type_lower, "application/")) {
549                 g_free (header_content_type_lower);
550                 g_free (header_content_type);
551                 return NULL;
552         }
553         if (header_content_type_lower && 
554             g_str_has_prefix (header_content_type_lower, "multipart/alternative")) {
555                 g_free (header_content_type_lower);
556                 g_free (header_content_type);
557                 return modest_tny_msg_find_body_part_in_alternative (msg, want_html);
558         }
559
560         if (header_content_type_lower &&
561             g_str_has_prefix (header_content_type_lower, "multipart/related"))
562                 is_related = TRUE;
563
564         g_free (header_content_type_lower);
565         g_free (header_content_type);
566
567         parts = TNY_LIST (tny_simple_list_new());
568         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
569
570         /* no parts? assume it's single-part message */
571         iter  = tny_list_create_iterator (parts);
572         if (tny_iterator_is_done(iter)) {
573                 gchar *content_type;
574                 gboolean is_text_part;
575
576                 content_type = modest_tny_mime_part_get_content_type (msg);
577                 if (content_type == NULL)
578                         goto frees;
579                 is_text_part = g_str_has_prefix (content_type, "text/");
580                 g_free (content_type);
581
582                 /* if this part cannot be a supported body return NULL */
583                 if (is_text_part)
584                         part = TNY_MIME_PART (g_object_ref(G_OBJECT(msg)));
585         } else {
586                 TnyMimePart *fallback = NULL;
587                 do {
588                         gchar *tmp, *content_type = NULL;
589                         gboolean has_content_disp_name = FALSE;
590
591                         part = TNY_MIME_PART(tny_iterator_get_current (iter));
592
593                         if (!part) {
594                                 g_warning ("%s: not a valid mime part", __FUNCTION__);
595                                 tny_iterator_next (iter);
596                                 continue;
597                         }
598
599                         /* it's a message --> ignore */
600                         if (part && TNY_IS_MSG (part)) {
601                                 g_object_unref (part);
602                                 part = NULL;
603                                 tny_iterator_next (iter);
604                                 continue;
605                         }                       
606
607                         /* we need to strdown the content type, because
608                          * tny_mime_part_has_content_type does not do it...
609                          */
610                         content_type = g_ascii_strdown (tny_mime_part_get_content_type (part), -1);
611
612                         /* mime-parts with a content-disposition header (either 'inline' or 'attachment')
613                          * and a 'name=' thingy cannot be body parts
614                          */
615
616                         tmp = modest_tny_mime_part_get_header_value (part, "Content-Disposition");
617                         if (tmp) {
618                                 gchar *content_disp = g_ascii_strdown(tmp, -1);
619                                 g_free (tmp);
620                                 has_content_disp_name = g_strstr_len (content_disp, strlen(content_disp), "name=") != NULL;
621                                 g_free (content_disp);
622                         }
623
624                         if (g_str_has_prefix (content_type, "text/") &&
625                             !has_content_disp_name &&
626                             !modest_tny_mime_part_is_attachment_for_modest (part)) {
627                                 /* We found that some multipart/related emails include
628                                    empty text/plain parts at the end that could confuse the body
629                                    detection algorithm */
630                                 if (is_related && g_str_has_prefix (content_type, "text/plain")) {
631                                         fallback = g_object_ref (part);
632                                 } else {
633                                         /* we found the body. Doesn't have to be the desired mime part, first
634                                            text/ part in a multipart/mixed is the body */
635                                         g_free (content_type);
636                                         break;
637                                 }
638                         } else if (g_str_has_prefix(content_type, "multipart/alternative")) {
639
640                                 /* multipart? recurse! */
641                                 g_object_unref (part);
642                                 g_free (content_type);
643                                 part = modest_tny_msg_find_body_part_in_alternative (part, want_html);
644                                 if (part)
645                                         break;
646
647                         } else  if (g_str_has_prefix(content_type, "multipart")) {
648
649                                 /* multipart? recurse! */
650                                 g_object_unref (part);
651                                 g_free (content_type);
652                                 part = modest_tny_msg_find_body_part_from_mime_part (part, want_html);
653                                 if (part)
654                                         break;
655                         } else {
656                                 g_free (content_type);
657                         }
658
659                         if (part) {
660                                 g_object_unref (G_OBJECT(part));
661                                 part = NULL;
662                         }
663                         tny_iterator_next (iter);
664
665                 } while (!tny_iterator_is_done(iter));
666
667                 if (!part && fallback)
668                         part = g_object_ref (fallback);
669                 if (fallback)
670                         g_object_unref (fallback);
671         }
672
673  frees:
674         g_object_unref (G_OBJECT(iter));
675         g_object_unref (G_OBJECT(parts));
676
677         return part;
678 }
679
680 static TnyMimePart*
681 modest_tny_msg_find_calendar_from_mime_part (TnyMimePart *msg)
682 {
683         TnyMimePart *part = NULL;
684         TnyList *parts = NULL;
685         TnyIterator *iter = NULL;
686         gchar *header_content_type;
687         gchar *header_content_type_lower = NULL;
688         
689         if (!msg)
690                 return NULL;
691
692         /* If it's an application multipart, then we don't get into as we don't
693          * support them (for example application/sml or wap messages */
694         header_content_type = modest_tny_mime_part_get_header_value (msg, "Content-Type");
695         if (header_content_type) {
696                 header_content_type = g_strstrip (header_content_type);
697                 header_content_type_lower = g_ascii_strdown (header_content_type, -1);
698         }
699         if (header_content_type_lower && 
700             g_str_has_prefix (header_content_type_lower, "multipart/") &&
701             !g_str_has_prefix (header_content_type_lower, "multipart/signed") &&
702             strstr (header_content_type_lower, "application/")) {
703                 g_free (header_content_type_lower);
704                 g_free (header_content_type);
705                 return NULL;
706         }       
707         g_free (header_content_type_lower);
708         g_free (header_content_type);
709
710         parts = TNY_LIST (tny_simple_list_new());
711         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
712
713         iter  = tny_list_create_iterator(parts);
714
715         /* no parts? assume it's single-part message */
716         if (tny_iterator_is_done(iter)) {
717                 gchar *content_type;
718                 gboolean is_calendar_part;
719                 g_object_unref (G_OBJECT(iter));
720                 content_type = modest_tny_mime_part_get_content_type (msg);
721                 if (content_type == NULL)
722                         return NULL;
723                 is_calendar_part = 
724                         g_str_has_prefix (content_type, "text/calendar");
725                 g_free (content_type);
726                 /* if this part cannot be a supported body return NULL */
727                 if (!is_calendar_part) {
728                         return NULL;
729                 } else {
730                         return TNY_MIME_PART (g_object_ref(G_OBJECT(msg)));
731                 }
732         } else {
733                 do {
734                         gchar *tmp, *content_type = NULL;
735                         gboolean has_content_disp_name = FALSE;
736
737                         part = TNY_MIME_PART(tny_iterator_get_current (iter));
738
739                         if (!part) {
740                                 g_warning ("%s: not a valid mime part", __FUNCTION__);
741                                 tny_iterator_next (iter);
742                                 continue;
743                         }
744
745                         /* it's a message --> ignore */
746                         if (part && TNY_IS_MSG (part)) {
747                                 g_object_unref (part);
748                                 part = NULL;
749                                 tny_iterator_next (iter);
750                                 continue;
751                         }                       
752
753                         /* we need to strdown the content type, because
754                          * tny_mime_part_has_content_type does not do it...
755                          */
756                         content_type = g_ascii_strdown (tny_mime_part_get_content_type (part), -1);
757                         
758                         /* mime-parts with a content-disposition header (either 'inline' or 'attachment')
759                          * and a 'name=' thingy cannot be body parts
760                          */
761                                 
762                         tmp = modest_tny_mime_part_get_header_value (part, "Content-Disposition");
763                         if (tmp) {
764                                 gchar *content_disp = g_ascii_strdown(tmp, -1);
765                                 g_free (tmp);
766                                 has_content_disp_name = g_strstr_len (content_disp, strlen(content_disp), "name=") != NULL;
767                                 g_free (content_disp);
768                         }
769                         
770                         if (g_str_has_prefix (content_type, "text/calendar") && 
771                             !has_content_disp_name &&
772                             !modest_tny_mime_part_is_attachment_for_modest (part)) {
773                                 /* we found the body. Doesn't have to be the desired mime part, first
774                                    text/ part in a mixed is the body */
775                                 g_free (content_type);
776                                 break;
777
778                         } else  if (g_str_has_prefix(content_type, "multipart")) {
779
780                                 /* multipart? recurse! */
781                                 g_object_unref (part);
782                                 g_free (content_type);
783                                 part = modest_tny_msg_find_calendar_from_mime_part (part);
784                                 if (part)
785                                         break;
786                         } else
787                                 g_free (content_type);
788                         
789                         if (part) {
790                                 g_object_unref (G_OBJECT(part));
791                                 part = NULL;
792                         }
793                         
794                         tny_iterator_next (iter);
795                         
796                 } while (!tny_iterator_is_done(iter));
797         }
798         
799         g_object_unref (G_OBJECT(iter));
800         g_object_unref (G_OBJECT(parts));
801
802         return part; /* this maybe NULL, this is not an error; some message just don't have a body
803                       * part */
804 }
805
806
807 TnyMimePart*
808 modest_tny_msg_find_body_part (TnyMsg *msg, gboolean want_html)
809 {
810         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
811         
812         return modest_tny_msg_find_body_part_from_mime_part (TNY_MIME_PART(msg),
813                                                              want_html);
814 }
815
816 TnyMimePart*
817 modest_tny_msg_find_calendar (TnyMsg *msg)
818 {
819         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
820         
821         return modest_tny_msg_find_calendar_from_mime_part (TNY_MIME_PART(msg));
822 }
823
824
825 #define MODEST_TNY_MSG_PARENT_UID "parent-uid"
826
827 static TnyMsg *
828 create_reply_forward_mail (TnyMsg *msg, TnyHeader *header, const gchar *from,
829                            const gchar *signature, gboolean is_reply,
830                            guint type /*ignored*/, GList *attachments)
831 {
832         TnyMsg *new_msg;
833         TnyHeader *new_header;
834         gchar *old_subject;
835         gchar *new_subject;
836         TnyMimePart *body = NULL;
837         TnyMimePart *html_body = NULL;
838         ModestFormatter *formatter;
839         gboolean no_text_part;
840         gchar *parent_uid;
841         gboolean forward_as_attach = FALSE;
842
843         if (header)
844                 g_object_ref (header);
845         else
846                 header = tny_msg_get_header (msg);
847
848         /* Get body from original msg. Always look for the text/plain
849            part of the message to create the reply/forwarded mail */
850         if (msg != NULL) {
851                 body   = modest_tny_msg_find_body_part (msg, FALSE);
852                 html_body = modest_tny_msg_find_body_part (msg, TRUE);
853         }
854
855         if (modest_conf_get_bool (modest_runtime_get_conf (), MODEST_CONF_PREFER_FORMATTED_TEXT,
856                                   NULL))
857                 formatter = modest_formatter_new ("text/html", signature);
858         else
859                 formatter = modest_formatter_new ("text/plain", signature);
860
861
862         /* if we don't have a text-part */
863         no_text_part = (!body) || (strcmp (tny_mime_part_get_content_type (body), "text/html")==0);
864
865         /* when we're reply, include the text part if we have it, or nothing otherwise. */
866         if (is_reply)
867                 new_msg = modest_formatter_quote  (formatter, body, header,
868                                                     attachments);
869         else {
870                 if (no_text_part || (html_body && (strcmp (tny_mime_part_get_content_type (html_body), "text/html")==0))) {
871                         forward_as_attach = TRUE;
872                         new_msg = modest_formatter_attach (formatter, msg, header);
873                 } else {
874                         forward_as_attach = FALSE;
875                         new_msg = modest_formatter_inline  (formatter, body, header,
876                                                             attachments);
877                 }
878         }
879
880         g_object_unref (G_OBJECT(formatter));
881         if (body)
882                 g_object_unref (G_OBJECT(body));
883         if (html_body)
884                 g_object_unref (G_OBJECT(html_body));
885
886         /* Fill the header */
887         new_header = tny_msg_get_header (new_msg);
888         tny_header_set_from (new_header, from);
889         tny_header_set_replyto (new_header, from);
890
891         /* Change the subject */
892         old_subject = tny_header_dup_subject (header);
893         new_subject =
894                 (gchar *) modest_text_utils_derived_subject (old_subject, is_reply);
895         g_free (old_subject);
896         tny_header_set_subject (new_header, (const gchar *) new_subject);
897         g_free (new_subject);
898
899         /* get the parent uid, and set it as a gobject property on the new msg */
900         parent_uid = modest_tny_folder_get_header_unique_id (header);
901         g_object_set_data_full (G_OBJECT(new_msg), MODEST_TNY_MSG_PARENT_UID,
902                                 parent_uid, g_free);
903
904         /* set modest as the X-Mailer
905          * we could this in the platform factory, but then the header
906          * would show up before all the others.
907          */
908         tny_mime_part_set_header_pair (TNY_MIME_PART (msg), "X-Mailer", "Modest "
909                                        VERSION);
910
911         /* Clean */
912         g_object_unref (G_OBJECT (new_header));
913         g_object_unref (G_OBJECT (header));
914         /* ugly to unref it here instead of in the calling func */
915
916         if (!is_reply & !forward_as_attach) {
917                 add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, NULL);
918         }
919
920         return new_msg;
921 }
922
923 const gchar*
924 modest_tny_msg_get_parent_uid (TnyMsg *msg)
925 {
926         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
927         
928         return g_object_get_data (G_OBJECT(msg), MODEST_TNY_MSG_PARENT_UID);
929 }
930
931 static gchar *get_signed_protocol (TnyMimePart *part)
932 {
933         TnyList *header_pairs;
934         TnyIterator *iterator;
935         gchar *result = NULL;
936
937         header_pairs = TNY_LIST (tny_simple_list_new ());
938         tny_mime_part_get_header_pairs (part, header_pairs);
939         iterator = tny_list_create_iterator (header_pairs);
940
941         while (!result && !tny_iterator_is_done (iterator)) {
942                 TnyPair *pair;
943                 const gchar *name;
944
945                 pair = TNY_PAIR (tny_iterator_get_current (iterator));
946                 name = tny_pair_get_name (pair);
947                 if (name && !g_ascii_strcasecmp (name, "Content-Type")) {
948                         const gchar *s;
949                         s = tny_pair_get_value (pair);
950                         if (s) {
951                                 s = strstr (s, "protocol=");
952                                 if (s) {
953                                         const gchar *t;
954                                         s += 9;
955                                         if (*s == '\"') {
956                                                 s++;
957                                                 t = strstr (s, "\"");
958                                         } else {
959                                                 t = strstr (s, ";");
960                                         }
961                                         result = g_strndup (s, t - s);
962                                 }
963                         }
964                 }
965
966                 g_object_unref (pair);
967                 tny_iterator_next (iterator);
968         }
969
970         g_object_unref (iterator);
971         g_object_unref (header_pairs);
972
973         return result;
974 }
975
976 TnyMimePart *
977 modest_tny_msg_get_attachments_parent (TnyMsg *msg)
978 {
979         TnyMimePart *result;
980         const gchar *content_type;
981
982         result = NULL;
983
984         content_type = tny_mime_part_get_content_type (TNY_MIME_PART (msg));
985         if (content_type && !strcmp (content_type, "multipart/signed")) {
986                 TnyList *msg_children;
987                 TnyIterator *iterator;
988                 gchar *signed_protocol;
989
990                 msg_children = TNY_LIST (tny_simple_list_new ());
991                 tny_mime_part_get_parts (TNY_MIME_PART (msg), msg_children);
992
993                 iterator = tny_list_create_iterator (msg_children);
994                 signed_protocol = get_signed_protocol (TNY_MIME_PART (msg));
995                         
996                 while (!result && !tny_iterator_is_done (iterator)) {
997                         TnyMimePart *part;
998
999                         part = TNY_MIME_PART (tny_iterator_get_current (iterator));
1000                         if (signed_protocol) {
1001                                 const gchar *part_content_type;
1002
1003                                 part_content_type = tny_mime_part_get_content_type (part);
1004                                 if (part_content_type && g_ascii_strcasecmp (part_content_type, signed_protocol)) {
1005                                         result = g_object_ref (part);
1006                                 }
1007                         } else {
1008                                 result = g_object_ref (part);
1009                         }
1010
1011                         g_object_unref (part);
1012                         tny_iterator_next (iterator);
1013                 }
1014
1015                 g_object_unref (iterator);
1016                 g_free (signed_protocol);
1017                 g_object_unref (msg_children);
1018         }
1019         if (result == NULL) {
1020                 result = g_object_ref (msg);
1021         }
1022
1023         return result;
1024 }
1025
1026
1027
1028 static void
1029 add_if_attachment (gpointer data, gpointer user_data)
1030 {
1031         TnyMimePart *part;
1032         GList **attachments_list;
1033
1034         part = TNY_MIME_PART (data);
1035         attachments_list = ((GList **) user_data);
1036
1037         if (!tny_mime_part_is_purged (part) && ((tny_mime_part_is_attachment (part))||(TNY_IS_MSG (part)))) {
1038                 *attachments_list = g_list_prepend (*attachments_list, part);
1039                 g_object_ref (part);
1040         }
1041 }
1042
1043 TnyMsg* 
1044 modest_tny_msg_create_forward_msg (TnyMsg *msg, 
1045                                    const gchar *from,
1046                                    const gchar *signature,
1047                                    ModestTnyMsgForwardType forward_type)
1048 {
1049         TnyMsg *new_msg;
1050         TnyList *parts = NULL;
1051         GList *attachments_list = NULL;
1052         TnyMimePart *part_to_check = NULL;
1053
1054         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
1055
1056         part_to_check = modest_tny_msg_get_attachments_parent (TNY_MSG (msg));
1057         
1058         /* Add attachments */
1059         parts = TNY_LIST (tny_simple_list_new());
1060         tny_mime_part_get_parts (TNY_MIME_PART (part_to_check), parts);
1061         tny_list_foreach (parts, add_if_attachment, &attachments_list);
1062
1063         new_msg = create_reply_forward_mail (msg, NULL, from, signature, FALSE, forward_type,
1064                                              attachments_list);
1065
1066         /* Clean */
1067         if (attachments_list) {
1068                 g_list_foreach (attachments_list, (GFunc) g_object_unref, NULL);
1069                 g_list_free (attachments_list);
1070         }
1071         g_object_unref (G_OBJECT (parts));
1072         g_object_unref (part_to_check);
1073
1074         return new_msg;
1075 }
1076
1077
1078
1079 static gint
1080 count_addresses (const gchar* addresses)
1081 {
1082         gint count = 1;
1083
1084         if (!addresses)
1085                 return 0;
1086         
1087         while (*addresses) {
1088                 if (*addresses == ',' || *addresses == ';')
1089                         ++count;
1090                 ++addresses;
1091         }
1092         
1093         return count;
1094 }
1095
1096 static void
1097 remove_undisclosed_recipients (gchar **recipients)
1098 {
1099         GSList *addresses, *node;
1100         gboolean is_first;
1101         GString *result;
1102
1103         g_return_if_fail (recipients);
1104         addresses = modest_text_utils_split_addresses_list (*recipients);
1105
1106         is_first = TRUE;
1107         result = g_string_new ("");
1108         for (node = addresses; node != NULL; node = g_slist_next (node)) {
1109                 const gchar *address = (const gchar *) node->data;
1110
1111                 if (address && strstr (address, "undisclosed-recipients"))
1112                         continue;
1113
1114                 if (is_first)
1115                         is_first = FALSE;
1116                 else
1117                         result = g_string_append (result, ", ");
1118
1119                 result = g_string_append (result, address);
1120         }
1121         g_slist_foreach (addresses, (GFunc)g_free, NULL);
1122         g_slist_free (addresses);
1123
1124         g_free (*recipients);
1125         *recipients = g_string_free (result, FALSE);
1126 }
1127
1128
1129 /* get the new To:, based on the old header,
1130  * result is newly allocated or NULL in case of error
1131  * */
1132 static gchar*
1133 get_new_to (TnyMsg *msg, TnyHeader *header, const gchar* from,
1134             ModestTnyMsgReplyMode reply_mode)
1135 {
1136         const gchar *reply_header = "Reply-To:";
1137         const gchar *from_header = "From:";
1138         gchar* old_reply_to;
1139         gchar* old_from;
1140         gchar* new_to;
1141         gchar* tmp;
1142         
1143         /* according to RFC2369 (http://www.faqs.org/rfcs/rfc2369.html), we
1144          * can identify Mailing-List posts by the List-Help header.
1145          * for mailing lists, both the Reply-To: and From: should be included
1146          * in the new To:; for now, we're ignoring List-Post
1147          */
1148         gchar* list_help = modest_tny_mime_part_get_header_value (TNY_MIME_PART(msg), 
1149                                                                   "List-Help");
1150         gboolean is_mailing_list = (list_help != NULL);
1151         g_free (list_help);
1152
1153
1154         /* reply to sender, use ReplyTo or From */
1155         old_reply_to = modest_tny_mime_part_get_header_value (TNY_MIME_PART(msg), 
1156                                                               "Reply-To"); 
1157         old_from     = tny_header_dup_from (header);
1158
1159         if (!old_from && !old_reply_to) {
1160                 g_debug ("%s: failed to get either Reply-To: or From: from header",
1161                            __FUNCTION__);
1162                 return NULL;
1163         }
1164
1165         /* Prevent DoS attacks caused by malformed emails */
1166         if (old_from) {
1167                 gchar *tmp = old_from;
1168                 old_from = modest_text_utils_get_secure_header ((const gchar *) tmp, from_header);
1169                 g_free (tmp);
1170         }
1171         if (old_reply_to) {
1172                 gchar *tmp = old_reply_to;
1173                 old_reply_to = modest_text_utils_get_secure_header ((const gchar *) tmp, reply_header);
1174                 g_free (tmp);
1175         }
1176
1177         /* for mailing lists, use both Reply-To and From if we did a
1178          * 'Reply All:'
1179          * */
1180         if (is_mailing_list && reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL &&
1181             old_reply_to && old_from && strcmp (old_from, old_reply_to) != 0)
1182                 new_to = g_strjoin (",", old_reply_to, old_from, NULL);
1183         else
1184                 /* otherwise use either Reply-To: (preferred) or From: */
1185                 new_to = g_strdup (old_reply_to ? old_reply_to : old_from);
1186         g_free (old_from);
1187         g_free (old_reply_to);
1188
1189         /* in case of ReplyAll, we need to add the Recipients in the old To: */
1190         if (reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL) {
1191                 gchar *old_to = tny_header_dup_to (header);
1192                 if (!old_to) 
1193                         g_debug ("%s: no To: address found in source mail",
1194                                    __FUNCTION__);
1195                 else {
1196                         /* append the old To: */
1197                         gchar *tmp = g_strjoin (",", new_to, old_to, NULL);
1198                         g_free (new_to);
1199                         new_to = tmp;
1200                         g_free (old_to);
1201                 }
1202
1203                 /* remove duplicate entries */
1204                 gchar *tmp = modest_text_utils_remove_duplicate_addresses (new_to);
1205                 g_free (new_to);
1206                 new_to = tmp;
1207                 
1208                 /* now, strip me (the new From:) from the new_to, but only if
1209                  * there are >1 addresses there */
1210                 if (count_addresses (new_to) > 1) {
1211                         gchar *tmp = modest_text_utils_remove_address (new_to, from);
1212                         g_free (new_to);
1213                         new_to = tmp;
1214                 }
1215         }
1216
1217         tmp = modest_text_utils_simplify_recipients (new_to);
1218         remove_undisclosed_recipients  (&tmp);
1219         g_free (new_to);
1220         new_to = tmp;
1221
1222         return new_to;
1223 }
1224
1225
1226 /* get the new Cc:, based on the old header,
1227  * result is newly allocated or NULL in case of error */
1228 static gchar*
1229 get_new_cc (TnyHeader *header, const gchar* from, const gchar *new_to)
1230 {
1231         gchar *old_cc, *result, *dup;
1232
1233         old_cc = tny_header_dup_cc (header);
1234         if (!old_cc)
1235                 return NULL;
1236
1237         /* remove me (the new From:) from the Cc: list */
1238         dup =  modest_text_utils_remove_address (old_cc, from);
1239
1240         if (new_to) {
1241                 gchar **to_parts, **current;
1242
1243                 to_parts = g_strsplit (new_to, ",", 0);
1244                 for (current = to_parts; current && *current != '\0'; current++) {
1245                         gchar *dup2;
1246
1247                         dup2 = modest_text_utils_remove_address (dup, g_strstrip (*current));
1248                         g_free (dup);
1249                         dup = dup2;
1250                 }
1251                 g_strfreev (to_parts);
1252         }
1253
1254         result = modest_text_utils_remove_duplicate_addresses (dup);
1255         g_free (dup);
1256         dup = result;
1257         result = modest_text_utils_simplify_recipients (dup);
1258         remove_undisclosed_recipients  (&result);
1259         g_free (dup);
1260         g_free (old_cc);
1261         return result;
1262 }
1263
1264 void 
1265 modest_tny_msg_get_references (TnyMsg *msg, gchar **message_id, gchar **references, gchar **in_reply_to)
1266 {
1267         TnyList *headers;
1268         TnyIterator *iterator;
1269         gchar *l_message_id;
1270         gchar *l_references;
1271         gchar *l_in_reply_to;
1272
1273         g_return_if_fail (TNY_IS_MSG (msg));
1274
1275         l_message_id = NULL;
1276         l_references = NULL;
1277         l_in_reply_to = NULL;
1278
1279         headers = TNY_LIST (tny_simple_list_new ());
1280         tny_mime_part_get_header_pairs (TNY_MIME_PART (msg), headers);
1281
1282         iterator = tny_list_create_iterator (headers);
1283         while (!tny_iterator_is_done (iterator)) {
1284                 TnyPair *pair;
1285                 const gchar *name;
1286
1287                 pair = TNY_PAIR (tny_iterator_get_current (iterator));
1288                 name = tny_pair_get_name (pair);
1289                 if (!g_ascii_strcasecmp (name, "References")) {
1290                         if (l_references) g_free (l_references);
1291                         l_references = g_strdup (tny_pair_get_value (pair));
1292                 } else if (!g_ascii_strcasecmp (name, "In-Reply-To")) {
1293                         if (l_in_reply_to) g_free (l_in_reply_to);
1294                         l_in_reply_to = g_strdup (tny_pair_get_value (pair));
1295                 } else if (!g_ascii_strcasecmp (name, "Message-ID")) {
1296                         if (l_message_id) g_free (l_message_id);
1297                         l_message_id = g_strdup (tny_pair_get_value (pair));
1298                 }
1299
1300                 g_object_unref (pair);
1301                 tny_iterator_next (iterator);
1302         }
1303
1304         g_object_unref (iterator);
1305         g_object_unref (headers);
1306
1307         if (message_id) {
1308                 *message_id = l_message_id;
1309         } else {
1310                 g_free (l_message_id);
1311         }
1312
1313         if (in_reply_to) {
1314                 *in_reply_to = l_in_reply_to;
1315         } else {
1316                 g_free (l_in_reply_to);
1317         }
1318
1319         if (references) {
1320                 *references = l_references;
1321         } else {
1322                 g_free (l_references);
1323         }
1324 }
1325
1326 static void
1327 remove_line_breaks (gchar *str)
1328 {
1329         gchar *needle = g_strrstr (str, "\r\n");
1330         if (needle)
1331                 *needle = '\0';
1332 }
1333
1334 static void 
1335 set_references (TnyMsg *reply_msg, TnyMsg *original_msg)
1336 {
1337         gchar *orig_references, *orig_in_reply_to, *orig_message_id;
1338         gchar *references, *in_reply_to;
1339
1340         modest_tny_msg_get_references (original_msg, &orig_message_id, &orig_references, &orig_in_reply_to);
1341
1342         references = NULL;
1343         in_reply_to = NULL;
1344
1345         if (orig_message_id)
1346                 in_reply_to = g_strdup (orig_message_id);
1347
1348         if (orig_references) {
1349                 if (orig_message_id)
1350                         references = g_strconcat (orig_references, "\n        ", orig_message_id, NULL);
1351                 else
1352                         references = g_strdup (orig_references);
1353
1354         } else if (orig_in_reply_to) {
1355                 if (orig_message_id)
1356                         references = g_strconcat (orig_in_reply_to, "\n        ", orig_message_id, NULL);
1357                 else
1358                         references = g_strdup (orig_in_reply_to);
1359         } else if (orig_message_id) {
1360                 references = g_strdup (orig_message_id);
1361         }
1362
1363         g_free (orig_references);
1364         g_free (orig_in_reply_to);
1365         g_free (orig_message_id);
1366
1367         if (in_reply_to) {
1368                 remove_line_breaks (in_reply_to);
1369                 tny_mime_part_set_header_pair (TNY_MIME_PART (reply_msg), "In-Reply-To", in_reply_to);
1370                 g_free (in_reply_to);
1371         }
1372         if (references) {
1373                 remove_line_breaks (references);
1374                 tny_mime_part_set_header_pair (TNY_MIME_PART (reply_msg), "References", references);
1375                 g_free (references);
1376         }
1377 }
1378
1379 TnyMsg*
1380 modest_tny_msg_create_reply_msg (TnyMsg *msg,
1381                                  TnyHeader *header,
1382                                  const gchar *from,
1383                                  const gchar *signature,
1384                                  ModestTnyMsgReplyType reply_type,
1385                                  ModestTnyMsgReplyMode reply_mode)
1386 {
1387         TnyMsg *new_msg = NULL;
1388         TnyHeader *new_header;
1389         gchar *new_to = NULL;
1390         TnyList *parts = NULL;
1391         GList *attachments_list = NULL;
1392         TnyMimePart *part_to_check = NULL;
1393
1394         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
1395
1396         part_to_check = modest_tny_msg_get_attachments_parent (TNY_MSG (msg));
1397
1398         parts = TNY_LIST (tny_simple_list_new());
1399         tny_mime_part_get_parts (TNY_MIME_PART (part_to_check), parts);
1400         tny_list_foreach (parts, add_if_attachment, &attachments_list);
1401
1402         new_msg = create_reply_forward_mail (msg, header, from, signature, TRUE, reply_type,
1403                                              attachments_list);
1404
1405         set_references (new_msg, msg);
1406         if (attachments_list != NULL) {
1407                 g_list_foreach (attachments_list, (GFunc) g_object_unref, NULL);
1408                 g_list_free (attachments_list);
1409         }
1410         g_object_unref (parts);
1411
1412         /* Fill the header */
1413         if (header)
1414                 g_object_ref (header);
1415         else
1416                 header = tny_msg_get_header (msg);
1417
1418         
1419         new_header = tny_msg_get_header(new_msg);
1420         new_to = get_new_to (msg, header, from, reply_mode);
1421         if (!new_to)
1422                 g_debug ("%s: failed to get new To:", __FUNCTION__);
1423         else {
1424                 tny_header_set_to (new_header, new_to);
1425         }
1426
1427         if (reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL) {
1428                 gchar *new_cc = get_new_cc (header, from, new_to);
1429                 if (new_cc) { 
1430                         tny_header_set_cc (new_header, new_cc);
1431                         g_free (new_cc);
1432                 }
1433         }
1434
1435         if (new_to)
1436                 g_free (new_to);
1437
1438         /* Clean */
1439         g_object_unref (G_OBJECT (new_header));
1440         g_object_unref (G_OBJECT (header));
1441         g_object_unref (G_OBJECT (part_to_check));
1442
1443         return new_msg;
1444 }
1445
1446 TnyMsg*
1447 modest_tny_msg_create_reply_calendar_msg (TnyMsg *msg,
1448                                           TnyHeader *header,
1449                                           const gchar *from,
1450                                           const gchar *signature,
1451                                           TnyList *headers)
1452 {
1453         TnyMsg *new_msg = NULL;
1454         TnyIterator *iterator;
1455
1456         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
1457
1458         new_msg = modest_tny_msg_create_reply_msg (msg, header, from, signature,  
1459                                                    MODEST_TNY_MSG_REPLY_TYPE_QUOTE, MODEST_TNY_MSG_REPLY_MODE_SENDER);
1460
1461         iterator = tny_list_create_iterator (headers);
1462         while (!tny_iterator_is_done (iterator)) {
1463                 TnyPair *pair = TNY_PAIR (tny_iterator_get_current (iterator));
1464
1465                 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg),
1466                                                tny_pair_get_name (pair),
1467                                                tny_pair_get_value (pair));
1468                 g_object_unref (pair);
1469                 tny_iterator_next (iterator);
1470         }
1471         g_object_unref (iterator);
1472
1473         return new_msg;
1474 }
1475
1476
1477 static gboolean
1478 is_ascii(const gchar *s)
1479 {
1480         if (!s)
1481                 return TRUE;
1482         while (s[0]) {
1483                 if (s[0] & 128 || s[0] < 32)
1484                         return FALSE;
1485                 s++;
1486         }
1487         return TRUE;
1488 }
1489
1490 static char *
1491 get_content_type(const gchar *s)
1492 {
1493         GString *type;
1494         
1495         type = g_string_new("text/plain");
1496         if (!is_ascii(s)) {
1497                 if (g_utf8_validate(s, -1, NULL)) {
1498                         g_string_append(type, "; charset=\"utf-8\"");
1499                 } else {
1500                         /* it should be impossible to reach this, but better safe than sorry */
1501                         g_debug("invalid utf8 in message");
1502                         g_string_append(type, "; charset=\"latin1\"");
1503                 }
1504         }
1505         return g_string_free(type, FALSE);
1506 }
1507
1508 guint64
1509 modest_tny_msg_estimate_size (const gchar *plain_body, const gchar *html_body,
1510                               guint64 parts_count,
1511                               guint64 parts_size)
1512 {
1513         guint64 result;
1514
1515         /* estimation of headers size */
1516         result = 1024;
1517
1518         /* We add a 20% of size due to the increase in 7bit encoding */
1519         if (plain_body) {
1520                 result += strlen (plain_body) * 120 / 100;
1521         }
1522         if (html_body) {
1523                 result += strlen (html_body) * 120 / 100;
1524         }
1525
1526         /* 256 bytes per additional part because of their headers */
1527         result += parts_count * 256;
1528
1529         /* 150% of increase per encoding */
1530         result += parts_size * 3 / 2;
1531
1532         return result;
1533 }
1534
1535 GSList *
1536 modest_tny_msg_header_get_all_recipients_list (TnyHeader *header)
1537 {
1538         GSList *recipients = NULL;
1539         gchar *from = NULL, *to = NULL, *cc = NULL, *bcc = NULL;
1540         gchar *after_remove, *joined;
1541
1542         if (header == NULL)
1543                 return NULL;
1544
1545         from = tny_header_dup_from (header);
1546         to = tny_header_dup_to (header);
1547         cc = tny_header_dup_cc (header);
1548         bcc = tny_header_dup_bcc (header);
1549
1550         joined = modest_text_utils_join_addresses (from, to, cc, bcc);
1551         after_remove = modest_text_utils_remove_duplicate_addresses (joined);
1552         g_free (joined);
1553
1554         recipients = modest_text_utils_split_addresses_list (after_remove);
1555         g_free (after_remove);
1556
1557         if (from)
1558                 g_free (from);
1559         if (to)
1560                 g_free (to);
1561         if (cc)
1562                 g_free (cc);
1563         if (bcc)
1564                 g_free (bcc);
1565
1566         return recipients;
1567 }
1568
1569 GSList *
1570 modest_tny_msg_get_all_recipients_list (TnyMsg *msg)
1571 {
1572         TnyHeader *header = NULL;
1573         GSList *recipients = NULL;
1574
1575         if (msg == NULL)
1576                 return NULL;
1577
1578         header = tny_msg_get_header (msg);
1579         if (header == NULL)
1580                 return NULL;
1581
1582         recipients = modest_tny_msg_header_get_all_recipients_list (header);
1583         g_object_unref (header);
1584
1585         return recipients;
1586 }
1587