Fixes FwNULL 14/16
[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         
589         if (header)
590                 g_object_ref (header);
591         else
592                 header = tny_msg_get_header (msg);
593
594         /* Get body from original msg. Always look for the text/plain
595            part of the message to create the reply/forwarded mail */
596         if (msg != NULL)
597                 body   = modest_tny_msg_find_body_part (msg, FALSE);
598
599         if (modest_conf_get_bool (modest_runtime_get_conf (), MODEST_CONF_PREFER_FORMATTED_TEXT,
600                                   NULL))
601                 formatter = modest_formatter_new ("text/html", signature);
602         else
603                 formatter = modest_formatter_new ("text/plain", signature);
604
605
606         /* if we don't have a text-part */
607         no_text_part = (!body) || (strcmp (tny_mime_part_get_content_type (body), "text/html")==0);
608
609         /* when we're reply, include the text part if we have it, or nothing otherwise. */
610         if (is_reply)
611                 new_msg = modest_formatter_quote  (formatter, no_text_part ? NULL: body, header,
612                                                     attachments);
613         else {
614                 /* for attachements; inline if there is a text part, and include the
615                  * full old mail if there was none */
616                 if (no_text_part) {
617                         new_msg = modest_formatter_attach (formatter, msg, header);
618                 } else { 
619                         new_msg = modest_formatter_inline  (formatter, body, header,
620                                                             attachments);
621                 }
622         }
623
624         g_object_unref (G_OBJECT(formatter));
625         if (body)
626                 g_object_unref (G_OBJECT(body));
627
628         /* Fill the header */
629         new_header = tny_msg_get_header (new_msg);      
630         tny_header_set_from (new_header, from);
631         tny_header_set_replyto (new_header, from);
632
633         /* Change the subject */
634         if (is_reply)
635                 subject_prefix = g_strconcat (_("mail_va_re"), ":", NULL);
636         else
637                 subject_prefix = g_strconcat (_("mail_va_fw"), ":", NULL);
638         old_subject = tny_header_dup_subject (header);
639         new_subject =
640                 (gchar *) modest_text_utils_derived_subject (old_subject,
641                                                              subject_prefix);
642         g_free (old_subject);
643         g_free (subject_prefix);
644         tny_header_set_subject (new_header, (const gchar *) new_subject);
645         g_free (new_subject);
646         
647         /* get the parent uid, and set it as a gobject property on the new msg */
648         if (new_msg) {
649                 gchar* 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
654         /* set modest as the X-Mailer
655          * we could this in the platform factory, but then the header
656          * would show up before all the others.
657          */
658         tny_mime_part_set_header_pair (TNY_MIME_PART (msg), "X-Mailer", "Modest "
659                                        VERSION);
660
661         /* Clean */
662         g_object_unref (G_OBJECT (new_header));
663         g_object_unref (G_OBJECT (header));
664         /* ugly to unref it here instead of in the calling func */
665
666         if (!is_reply & !no_text_part) {
667                 add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, NULL);
668         }
669
670         return new_msg;
671 }
672
673 const gchar*
674 modest_tny_msg_get_parent_uid (TnyMsg *msg)
675 {
676         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
677         
678         return g_object_get_data (G_OBJECT(msg), MODEST_TNY_MSG_PARENT_UID);
679 }
680
681
682
683 static void
684 add_if_attachment (gpointer data, gpointer user_data)
685 {
686         TnyMimePart *part;
687         GList **attachments_list;
688
689         part = TNY_MIME_PART (data);
690         attachments_list = ((GList **) user_data);
691
692         if ((tny_mime_part_is_attachment (part))||(TNY_IS_MSG (part))) {
693                 *attachments_list = g_list_prepend (*attachments_list, part);
694                 g_object_ref (part);
695         }
696 }
697
698 TnyMsg* 
699 modest_tny_msg_create_forward_msg (TnyMsg *msg, 
700                                    const gchar *from,
701                                    const gchar *signature,
702                                    ModestTnyMsgForwardType forward_type)
703 {
704         TnyMsg *new_msg;
705         TnyList *parts = NULL;
706         GList *attachments_list = NULL;
707
708         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
709         
710         /* Add attachments */
711         parts = TNY_LIST (tny_simple_list_new());
712         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
713         tny_list_foreach (parts, add_if_attachment, &attachments_list);
714
715         new_msg = create_reply_forward_mail (msg, NULL, from, signature, FALSE, forward_type,
716                                              attachments_list);
717
718         /* Clean */
719         if (attachments_list) {
720                 g_list_foreach (attachments_list, (GFunc) g_object_unref, NULL);
721                 g_list_free (attachments_list);
722         }
723         g_object_unref (G_OBJECT (parts));
724
725         return new_msg;
726 }
727
728
729
730 static gint
731 count_addresses (const gchar* addresses)
732 {
733         gint count = 1;
734
735         if (!addresses)
736                 return 0;
737         
738         while (*addresses) {
739                 if (*addresses == ',' || *addresses == ';')
740                         ++count;
741                 ++addresses;
742         }
743         
744         return count;
745 }
746
747
748 /* get the new To:, based on the old header,
749  * result is newly allocated or NULL in case of error
750  * */
751 static gchar*
752 get_new_to (TnyMsg *msg, TnyHeader *header, const gchar* from,
753             ModestTnyMsgReplyMode reply_mode)
754 {
755         gchar* old_reply_to;
756         gchar* old_from;
757         gchar* new_to;
758         
759         /* according to RFC2369 (http://www.faqs.org/rfcs/rfc2369.html), we
760          * can identify Mailing-List posts by the List-Help header.
761          * for mailing lists, both the Reply-To: and From: should be included
762          * in the new To:; for now, we're ignoring List-Post
763          */
764         gchar* list_help = modest_tny_mime_part_get_header_value (TNY_MIME_PART(msg), 
765                                                                   "List-Help");
766         gboolean is_mailing_list = (list_help != NULL);
767         g_free (list_help);
768
769
770         /* reply to sender, use ReplyTo or From */
771         old_reply_to = modest_tny_mime_part_get_header_value (TNY_MIME_PART(msg), 
772                                                               "Reply-To"); 
773         old_from     = tny_header_dup_from (header);
774         
775         if (!old_from && !old_reply_to) {
776                 g_warning ("%s: failed to get either Reply-To: or From: from header",
777                            __FUNCTION__);
778                 return NULL;
779         }
780         
781         /* for mailing lists, use both Reply-To and From if we did a
782          * 'Reply All:'
783          * */
784         if (is_mailing_list && reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL &&
785             old_reply_to && old_from && strcmp (old_from, old_reply_to) != 0)
786                 new_to = g_strjoin (",", old_reply_to, old_from, NULL);
787         else
788                 /* otherwise use either Reply-To: (preferred) or From: */
789                 new_to = g_strdup (old_reply_to ? old_reply_to : old_from);
790         g_free (old_from);
791         g_free (old_reply_to);
792
793         /* in case of ReplyAll, we need to add the Recipients in the old To: */
794         if (reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL) {
795                 gchar *old_to = tny_header_dup_to (header);
796                 if (!old_to) 
797                         g_warning ("%s: no To: address found in source mail",
798                                    __FUNCTION__);
799                 else {
800                         /* append the old To: */
801                         gchar *tmp = g_strjoin (",", new_to, old_to, NULL);
802                         g_free (new_to);
803                         new_to = tmp;
804                         g_free (old_to);
805                 }
806
807                 /* remove duplicate entries */
808                 gchar *tmp = modest_text_utils_remove_duplicate_addresses (new_to);
809                 g_free (new_to);
810                 new_to = tmp;
811                 
812                 /* now, strip me (the new From:) from the new_to, but only if
813                  * there are >1 addresses there */
814                 if (count_addresses (new_to) > 1) {
815                         gchar *tmp = modest_text_utils_remove_address (new_to, from);
816                         g_free (new_to);
817                         new_to = tmp;
818                 }
819         }
820
821         return new_to;
822 }
823
824
825 /* get the new Cc:, based on the old header,
826  * result is newly allocated or NULL in case of error */
827 static gchar*
828 get_new_cc (TnyHeader *header, const gchar* from)
829 {
830         gchar *old_cc, *result, *dup;
831
832         old_cc = tny_header_dup_cc (header);
833         if (!old_cc)
834                 return NULL;
835
836         /* remove me (the new From:) from the Cc: list */
837         dup =  modest_text_utils_remove_address (old_cc, from);
838         result = modest_text_utils_remove_duplicate_addresses (dup);
839         g_free (dup);
840         g_free (old_cc);
841         return result;
842 }
843
844
845 TnyMsg* 
846 modest_tny_msg_create_reply_msg (TnyMsg *msg,
847                                  TnyHeader *header,
848                                  const gchar *from,
849                                  const gchar *signature,
850                                  ModestTnyMsgReplyType reply_type,
851                                  ModestTnyMsgReplyMode reply_mode)
852 {
853         TnyMsg *new_msg = NULL;
854         TnyHeader *new_header;
855         gchar *new_to = NULL;
856         TnyList *parts = NULL;
857         GList *attachments_list = NULL;
858
859         g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
860         
861         parts = TNY_LIST (tny_simple_list_new());
862         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
863         tny_list_foreach (parts, add_if_attachment, &attachments_list);
864
865         new_msg = create_reply_forward_mail (msg, header, from, signature, TRUE, reply_type,
866                                              attachments_list);
867         if (attachments_list != NULL) {
868                 g_list_foreach (attachments_list, (GFunc) g_object_unref, NULL);
869                 g_list_free (attachments_list);
870         }
871         if (parts)
872                 g_object_unref (G_OBJECT (parts));
873
874         /* Fill the header */
875         if (header)
876                 g_object_ref (header);
877         else
878                 header = tny_msg_get_header (msg);
879
880         
881         new_header = tny_msg_get_header(new_msg);
882         new_to = get_new_to (msg, header, from, reply_mode);
883         if (!new_to)
884                 g_warning ("%s: failed to get new To:", __FUNCTION__);
885         else {
886                 tny_header_set_to (new_header, new_to);
887                 g_free (new_to);
888         }
889
890         if (reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL) {
891                 gchar *new_cc = get_new_cc (header, from);
892                 if (new_cc) { 
893                         tny_header_set_cc (new_header, new_cc);
894                         g_free (new_cc);
895                 }
896         }
897
898         /* Clean */
899         g_object_unref (G_OBJECT (new_header));
900         g_object_unref (G_OBJECT (header));
901
902         return new_msg;
903 }
904
905
906 static gboolean
907 is_ascii(const gchar *s)
908 {
909         if (!s)
910                 return TRUE;
911         while (s[0]) {
912                 if (s[0] & 128 || s[0] < 32)
913                         return FALSE;
914                 s++;
915         }
916         return TRUE;
917 }
918
919 static char *
920 get_content_type(const gchar *s)
921 {
922         GString *type;
923         
924         type = g_string_new("text/plain");
925         if (!is_ascii(s)) {
926                 if (g_utf8_validate(s, -1, NULL)) {
927                         g_string_append(type, "; charset=\"utf-8\"");
928                 } else {
929                         /* it should be impossible to reach this, but better safe than sorry */
930                         g_warning("invalid utf8 in message");
931                         g_string_append(type, "; charset=\"latin1\"");
932                 }
933         }
934         return g_string_free(type, FALSE);
935 }
936
937 guint64
938 modest_tny_msg_estimate_size (const gchar *plain_body, const gchar *html_body,
939                               guint64 parts_count,
940                               guint64 parts_size)
941 {
942         guint64 result;
943
944         /* estimation of headers size */
945         result = 1024;
946
947         /* We add a 20% of size due to the increase in 7bit encoding */
948         if (plain_body) {
949                 result += strlen (plain_body) * 120 / 100;
950         }
951         if (html_body) {
952                 result += strlen (html_body) * 120 / 100;
953         }
954
955         /* 256 bytes per additional part because of their headers */
956         result += parts_count * 256;
957
958         /* 150% of increase per encoding */
959         result += parts_size * 3 / 2;
960
961         return result;
962 }
963
964 GSList *
965 modest_tny_msg_header_get_all_recipients_list (TnyHeader *header)
966 {
967         GSList *recipients = NULL;
968         gchar *from = NULL, *to = NULL, *cc = NULL, *bcc = NULL;
969
970         if (header == NULL)
971                 return NULL;
972
973         from = tny_header_dup_from (header);
974         to = tny_header_dup_to (header);
975         cc = tny_header_dup_cc (header);
976         bcc = tny_header_dup_bcc (header);
977
978         recipients = NULL;
979         if (from)
980                 recipients = g_slist_concat (recipients, modest_text_utils_split_addresses_list (from));
981         if (to)
982                 recipients = g_slist_concat (recipients, modest_text_utils_split_addresses_list (to));
983         if (cc)
984                 recipients = g_slist_concat (recipients, modest_text_utils_split_addresses_list (cc));
985         if (bcc)
986                 recipients = g_slist_concat (recipients, modest_text_utils_split_addresses_list (bcc));
987
988         g_free (from);
989         g_free (to);
990         g_free (cc);
991         g_free (bcc);
992
993         return recipients;
994 }
995
996 GSList *
997 modest_tny_msg_get_all_recipients_list (TnyMsg *msg)
998 {
999         TnyHeader *header = NULL;
1000         GSList *recipients = NULL;
1001
1002         if (msg == NULL)
1003                 return NULL;
1004
1005         header = tny_msg_get_header (msg);
1006         if (header == NULL)
1007                 return NULL;
1008
1009         recipients = modest_tny_msg_header_get_all_recipients_list (header);
1010         g_object_unref (header);
1011
1012         return recipients;
1013 }
1014