1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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.
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.
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>
48 #endif /*HAVE_CONFIG_H */
50 #include <modest-tny-msg.h>
51 #include "modest-text-utils.h"
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);
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);
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,
69 GList *attachments, gint *attached, GError **err)
74 gint tmp_attached = 0;
77 new_msg = modest_formatter_create_message (NULL, TRUE, (attachments != NULL), FALSE);
78 header = tny_msg_get_header (new_msg);
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);
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);
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);
94 if ((subject != NULL) && (strlen(subject) > 0))
95 tny_header_set_subject (TNY_HEADER (header), subject);
97 content_type = get_content_type(body);
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.
103 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "X-Mailer", "Modest "
107 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "References", references);
110 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "In-Reply-To", in_reply_to);
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);
117 /* Add attachments */
119 tmp_attached = add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, err);
121 *attached = tmp_attached;
123 g_object_unref(header);
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)
141 new_msg = modest_formatter_create_message (NULL, FALSE, (attachments != NULL), (images != NULL));
142 header = tny_msg_get_header (new_msg);
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);
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);
155 if ((subject != NULL) && (strlen(subject) > 0))
156 tny_header_set_subject (TNY_HEADER (header), subject);
158 content_type = get_content_type(plain_body);
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.
164 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "X-Mailer", "Modest "
168 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "References", references);
171 tny_mime_part_set_header_pair (TNY_MIME_PART (new_msg), "In-Reply-To", in_reply_to);
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);
178 /* Add attachments */
179 tmp_attached = add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, err);
181 *attached = tmp_attached;
182 add_images (new_msg, images, err);
184 g_object_unref(header);
190 /* FIXME: this func copy from modest-mail-operation: refactor */
192 add_body_part (TnyMsg *msg,
194 const gchar *content_type)
196 TnyMimePart *text_body_part = NULL;
197 TnyStream *text_body_stream;
199 /* Create the stream */
200 text_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
201 (body, (body ? strlen(body) : 0)));
203 text_body_part = modest_formatter_create_body_part (NULL, msg);
205 /* Construct MIME part */
206 tny_stream_reset (text_body_stream);
207 tny_mime_part_construct (text_body_part,
209 content_type, "7bit");
210 tny_stream_reset (text_body_stream);
212 g_object_unref (G_OBJECT(text_body_part));
215 g_object_unref (text_body_stream);
217 return text_body_part;
221 add_html_body_part (TnyMsg *msg,
224 TnyMimePart *html_body_part = NULL;
225 TnyStream *html_body_stream;
227 /* Create the stream */
228 html_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
229 (body, (body) ? strlen(body) : 0));
231 /* Create body part if needed */
232 html_body_part = modest_formatter_create_body_part (NULL, msg);
234 /* Construct MIME part */
235 tny_stream_reset (html_body_stream);
236 tny_mime_part_construct (html_body_part,
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);
243 g_object_unref (G_OBJECT(html_body_part));
246 g_object_unref (html_body_stream);
248 return html_body_part;
252 copy_mime_part (TnyMimePart *part, GError **err)
254 TnyMimePart *result = NULL;
255 const gchar *attachment_content_type;
256 const gchar *attachment_filename;
257 const gchar *attachment_cid;
259 TnyIterator *iterator;
260 TnyStream *attachment_stream;
264 if (TNY_IS_MSG (part)) {
269 result = tny_platform_factory_new_mime_part (
270 modest_runtime_get_platform_factory());
272 attachment_content_type = tny_mime_part_get_content_type (part);
274 /* get mime part headers */
275 attachment_filename = tny_mime_part_get_filename (part);
276 attachment_cid = tny_mime_part_get_content_id (part);
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);
287 ret = tny_stream_reset (attachment_stream);
288 ret = tny_mime_part_construct (result,
290 attachment_content_type,
292 ret = tny_stream_reset (attachment_stream);
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);
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));
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);
312 tny_mime_part_set_content_id (result, subpart_cid);
313 g_object_unref (subpart_copy);
315 g_object_unref (subpart);
318 tny_iterator_next (iterator);
320 g_object_unref (iterator);
321 g_object_unref (parts);
322 g_object_unref (attachment_stream);
328 add_attachments (TnyMimePart *part, GList *attachments_list, gboolean add_inline, GError **err)
331 TnyMimePart *attachment_part, *old_attachment;
335 for (pos = (GList *)attachments_list; pos; pos = pos->next) {
337 old_attachment = pos->data;
338 if (!tny_mime_part_is_purged (old_attachment)) {
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) {
344 tny_mime_part_set_header_pair (attachment_part, "Content-Disposition",
347 const gchar *filename;
349 filename = tny_mime_part_get_filename (old_attachment);
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);
358 tny_mime_part_set_header_pair (attachment_part, "Content-Disposition",
362 if (!TNY_IS_MSG (old_attachment)) {
363 tny_mime_part_set_transfer_encoding (TNY_MIME_PART (attachment_part), "base64");
365 ret = tny_mime_part_add_part (TNY_MIME_PART (part), attachment_part);
368 tny_mime_part_set_content_id (attachment_part, old_cid);
369 g_object_unref (attachment_part);
378 add_images (TnyMsg *msg, GList *images_list, GError **err)
380 TnyMimePart *related_part = NULL;
381 const gchar *content_type;
383 content_type = tny_mime_part_get_content_type (TNY_MIME_PART (msg));
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);
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")) {
400 g_object_unref (part);
401 tny_iterator_next (iter);
403 g_object_unref (iter);
404 g_object_unref (parts);
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);
416 modest_tny_msg_get_body (TnyMsg *msg, gboolean want_html, gboolean *is_html)
421 GtkTextIter start, end;
423 gboolean result_was_html = TRUE;
425 g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
427 body = modest_tny_msg_find_body_part(msg, want_html);
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);
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);
442 to_quote = to_quote_converted;
443 result_was_html = FALSE;
446 g_object_unref (buf);
447 g_object_unref (G_OBJECT(stream));
448 g_object_unref (G_OBJECT(body));
451 *is_html = result_was_html;
458 modest_tny_msg_find_body_part_in_alternative (TnyMimePart *msg, gboolean want_html)
462 TnyMimePart *part = NULL, *related = NULL;
463 TnyMimePart *first_part = NULL;
464 const gchar *desired_mime_type = want_html ? "text/html" : "text/plain";
466 parts = TNY_LIST (tny_simple_list_new());
467 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
469 for (iter = tny_list_create_iterator(parts);
470 !tny_iterator_is_done (iter);
471 tny_iterator_next (iter)) {
475 part = TNY_MIME_PART (tny_iterator_get_current (iter));
477 if (first_part == NULL) {
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);
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
494 g_object_unref (related);
495 related = g_object_ref (part);
498 g_object_unref (part);
502 g_object_unref (iter);
503 g_object_unref (parts);
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);
518 g_object_unref (first_part);
520 g_object_unref (related);
526 modest_tny_msg_find_body_part_from_mime_part (TnyMimePart *msg, gboolean want_html)
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;
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);
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);
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);
560 if (header_content_type_lower &&
561 g_str_has_prefix (header_content_type_lower, "multipart/related"))
564 g_free (header_content_type_lower);
565 g_free (header_content_type);
567 parts = TNY_LIST (tny_simple_list_new());
568 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
570 /* no parts? assume it's single-part message */
571 iter = tny_list_create_iterator (parts);
572 if (tny_iterator_is_done(iter)) {
574 gboolean is_text_part;
576 content_type = modest_tny_mime_part_get_content_type (msg);
577 if (content_type == NULL)
579 is_text_part = g_str_has_prefix (content_type, "text/");
580 g_free (content_type);
582 /* if this part cannot be a supported body return NULL */
584 part = TNY_MIME_PART (g_object_ref(G_OBJECT(msg)));
586 TnyMimePart *fallback = NULL;
588 gchar *tmp, *content_type = NULL;
589 gboolean has_content_disp_name = FALSE;
591 part = TNY_MIME_PART(tny_iterator_get_current (iter));
594 g_warning ("%s: not a valid mime part", __FUNCTION__);
595 tny_iterator_next (iter);
599 /* it's a message --> ignore */
600 if (part && TNY_IS_MSG (part)) {
601 g_object_unref (part);
603 tny_iterator_next (iter);
607 /* we need to strdown the content type, because
608 * tny_mime_part_has_content_type does not do it...
610 content_type = g_ascii_strdown (tny_mime_part_get_content_type (part), -1);
612 /* mime-parts with a content-disposition header (either 'inline' or 'attachment')
613 * and a 'name=' thingy cannot be body parts
616 tmp = modest_tny_mime_part_get_header_value (part, "Content-Disposition");
618 gchar *content_disp = g_ascii_strdown(tmp, -1);
620 has_content_disp_name = g_strstr_len (content_disp, strlen(content_disp), "name=") != NULL;
621 g_free (content_disp);
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);
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);
638 } else if (g_str_has_prefix(content_type, "multipart/alternative")) {
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);
647 } else if (g_str_has_prefix(content_type, "multipart")) {
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);
656 g_free (content_type);
660 g_object_unref (G_OBJECT(part));
663 tny_iterator_next (iter);
665 } while (!tny_iterator_is_done(iter));
667 if (!part && fallback)
668 part = g_object_ref (fallback);
670 g_object_unref (fallback);
674 g_object_unref (G_OBJECT(iter));
675 g_object_unref (G_OBJECT(parts));
681 modest_tny_msg_find_calendar_from_mime_part (TnyMimePart *msg)
683 TnyMimePart *part = NULL;
684 TnyList *parts = NULL;
685 TnyIterator *iter = NULL;
686 gchar *header_content_type;
687 gchar *header_content_type_lower = NULL;
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);
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);
707 g_free (header_content_type_lower);
708 g_free (header_content_type);
710 parts = TNY_LIST (tny_simple_list_new());
711 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
713 iter = tny_list_create_iterator(parts);
715 /* no parts? assume it's single-part message */
716 if (tny_iterator_is_done(iter)) {
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)
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) {
730 return TNY_MIME_PART (g_object_ref(G_OBJECT(msg)));
734 gchar *tmp, *content_type = NULL;
735 gboolean has_content_disp_name = FALSE;
737 part = TNY_MIME_PART(tny_iterator_get_current (iter));
740 g_warning ("%s: not a valid mime part", __FUNCTION__);
741 tny_iterator_next (iter);
745 /* it's a message --> ignore */
746 if (part && TNY_IS_MSG (part)) {
747 g_object_unref (part);
749 tny_iterator_next (iter);
753 /* we need to strdown the content type, because
754 * tny_mime_part_has_content_type does not do it...
756 content_type = g_ascii_strdown (tny_mime_part_get_content_type (part), -1);
758 /* mime-parts with a content-disposition header (either 'inline' or 'attachment')
759 * and a 'name=' thingy cannot be body parts
762 tmp = modest_tny_mime_part_get_header_value (part, "Content-Disposition");
764 gchar *content_disp = g_ascii_strdown(tmp, -1);
766 has_content_disp_name = g_strstr_len (content_disp, strlen(content_disp), "name=") != NULL;
767 g_free (content_disp);
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);
778 } else if (g_str_has_prefix(content_type, "multipart")) {
780 /* multipart? recurse! */
781 g_object_unref (part);
782 g_free (content_type);
783 part = modest_tny_msg_find_calendar_from_mime_part (part);
787 g_free (content_type);
790 g_object_unref (G_OBJECT(part));
794 tny_iterator_next (iter);
796 } while (!tny_iterator_is_done(iter));
799 g_object_unref (G_OBJECT(iter));
800 g_object_unref (G_OBJECT(parts));
802 return part; /* this maybe NULL, this is not an error; some message just don't have a body
808 modest_tny_msg_find_body_part (TnyMsg *msg, gboolean want_html)
810 g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
812 return modest_tny_msg_find_body_part_from_mime_part (TNY_MIME_PART(msg),
817 modest_tny_msg_find_calendar (TnyMsg *msg)
819 g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
821 return modest_tny_msg_find_calendar_from_mime_part (TNY_MIME_PART(msg));
825 #define MODEST_TNY_MSG_PARENT_UID "parent-uid"
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)
833 TnyHeader *new_header;
836 TnyMimePart *body = NULL;
837 TnyMimePart *html_body = NULL;
838 ModestFormatter *formatter;
839 gboolean no_text_part;
841 gboolean forward_as_attach = FALSE;
844 g_object_ref (header);
846 header = tny_msg_get_header (msg);
848 /* Get body from original msg. Always look for the text/plain
849 part of the message to create the reply/forwarded mail */
851 body = modest_tny_msg_find_body_part (msg, FALSE);
852 html_body = modest_tny_msg_find_body_part (msg, TRUE);
855 if (modest_conf_get_bool (modest_runtime_get_conf (), MODEST_CONF_PREFER_FORMATTED_TEXT,
857 formatter = modest_formatter_new ("text/html", signature);
859 formatter = modest_formatter_new ("text/plain", signature);
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);
865 /* when we're reply, include the text part if we have it, or nothing otherwise. */
867 new_msg = modest_formatter_quote (formatter, body, header,
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);
874 forward_as_attach = FALSE;
875 new_msg = modest_formatter_inline (formatter, body, header,
880 g_object_unref (G_OBJECT(formatter));
882 g_object_unref (G_OBJECT(body));
884 g_object_unref (G_OBJECT(html_body));
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);
891 /* Change the subject */
892 old_subject = tny_header_dup_subject (header);
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);
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,
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.
908 tny_mime_part_set_header_pair (TNY_MIME_PART (msg), "X-Mailer", "Modest "
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 */
916 if (!is_reply & !forward_as_attach) {
917 add_attachments (TNY_MIME_PART (new_msg), attachments, FALSE, NULL);
924 modest_tny_msg_get_parent_uid (TnyMsg *msg)
926 g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
928 return g_object_get_data (G_OBJECT(msg), MODEST_TNY_MSG_PARENT_UID);
931 static gchar *get_signed_protocol (TnyMimePart *part)
933 TnyList *header_pairs;
934 TnyIterator *iterator;
935 gchar *result = NULL;
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);
941 while (!result && !tny_iterator_is_done (iterator)) {
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")) {
949 s = tny_pair_get_value (pair);
951 s = strstr (s, "protocol=");
957 t = strstr (s, "\"");
961 result = g_strndup (s, t - s);
966 g_object_unref (pair);
967 tny_iterator_next (iterator);
970 g_object_unref (iterator);
971 g_object_unref (header_pairs);
977 modest_tny_msg_get_attachments_parent (TnyMsg *msg)
980 const gchar *content_type;
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;
990 msg_children = TNY_LIST (tny_simple_list_new ());
991 tny_mime_part_get_parts (TNY_MIME_PART (msg), msg_children);
993 iterator = tny_list_create_iterator (msg_children);
994 signed_protocol = get_signed_protocol (TNY_MIME_PART (msg));
996 while (!result && !tny_iterator_is_done (iterator)) {
999 part = TNY_MIME_PART (tny_iterator_get_current (iterator));
1000 if (signed_protocol) {
1001 const gchar *part_content_type;
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);
1008 result = g_object_ref (part);
1011 g_object_unref (part);
1012 tny_iterator_next (iterator);
1015 g_object_unref (iterator);
1016 g_free (signed_protocol);
1017 g_object_unref (msg_children);
1019 if (result == NULL) {
1020 result = g_object_ref (msg);
1029 add_if_attachment (gpointer data, gpointer user_data)
1032 GList **attachments_list;
1034 part = TNY_MIME_PART (data);
1035 attachments_list = ((GList **) user_data);
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);
1044 modest_tny_msg_create_forward_msg (TnyMsg *msg,
1046 const gchar *signature,
1047 ModestTnyMsgForwardType forward_type)
1050 TnyList *parts = NULL;
1051 GList *attachments_list = NULL;
1052 TnyMimePart *part_to_check = NULL;
1054 g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
1056 part_to_check = modest_tny_msg_get_attachments_parent (TNY_MSG (msg));
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);
1063 new_msg = create_reply_forward_mail (msg, NULL, from, signature, FALSE, forward_type,
1067 if (attachments_list) {
1068 g_list_foreach (attachments_list, (GFunc) g_object_unref, NULL);
1069 g_list_free (attachments_list);
1071 g_object_unref (G_OBJECT (parts));
1072 g_object_unref (part_to_check);
1080 count_addresses (const gchar* addresses)
1087 while (*addresses) {
1088 if (*addresses == ',' || *addresses == ';')
1097 remove_undisclosed_recipients (gchar **recipients)
1099 GSList *addresses, *node;
1103 g_return_if_fail (recipients);
1104 addresses = modest_text_utils_split_addresses_list (*recipients);
1107 result = g_string_new ("");
1108 for (node = addresses; node != NULL; node = g_slist_next (node)) {
1109 const gchar *address = (const gchar *) node->data;
1111 if (address && strstr (address, "undisclosed-recipients"))
1117 result = g_string_append (result, ", ");
1119 result = g_string_append (result, address);
1121 g_slist_foreach (addresses, (GFunc)g_free, NULL);
1122 g_slist_free (addresses);
1124 g_free (*recipients);
1125 *recipients = g_string_free (result, FALSE);
1129 /* get the new To:, based on the old header,
1130 * result is newly allocated or NULL in case of error
1133 get_new_to (TnyMsg *msg, TnyHeader *header, const gchar* from,
1134 ModestTnyMsgReplyMode reply_mode)
1136 const gchar *reply_header = "Reply-To:";
1137 const gchar *from_header = "From:";
1138 gchar* old_reply_to;
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
1148 gchar* list_help = modest_tny_mime_part_get_header_value (TNY_MIME_PART(msg),
1150 gboolean is_mailing_list = (list_help != NULL);
1154 /* reply to sender, use ReplyTo or From */
1155 old_reply_to = modest_tny_mime_part_get_header_value (TNY_MIME_PART(msg),
1157 old_from = tny_header_dup_from (header);
1159 if (!old_from && !old_reply_to) {
1160 g_debug ("%s: failed to get either Reply-To: or From: from header",
1165 /* Prevent DoS attacks caused by malformed emails */
1167 gchar *tmp = old_from;
1168 old_from = modest_text_utils_get_secure_header ((const gchar *) tmp, from_header);
1172 gchar *tmp = old_reply_to;
1173 old_reply_to = modest_text_utils_get_secure_header ((const gchar *) tmp, reply_header);
1177 /* for mailing lists, use both Reply-To and From if we did a
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);
1184 /* otherwise use either Reply-To: (preferred) or From: */
1185 new_to = g_strdup (old_reply_to ? old_reply_to : old_from);
1187 g_free (old_reply_to);
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);
1193 g_debug ("%s: no To: address found in source mail",
1196 /* append the old To: */
1197 gchar *tmp = g_strjoin (",", new_to, old_to, NULL);
1203 /* remove duplicate entries */
1204 gchar *tmp = modest_text_utils_remove_duplicate_addresses (new_to);
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);
1217 tmp = modest_text_utils_simplify_recipients (new_to);
1218 remove_undisclosed_recipients (&tmp);
1226 /* get the new Cc:, based on the old header,
1227 * result is newly allocated or NULL in case of error */
1229 get_new_cc (TnyHeader *header, const gchar* from, const gchar *new_to)
1231 gchar *old_cc, *result, *dup;
1233 old_cc = tny_header_dup_cc (header);
1237 /* remove me (the new From:) from the Cc: list */
1238 dup = modest_text_utils_remove_address (old_cc, from);
1241 gchar **to_parts, **current;
1243 to_parts = g_strsplit (new_to, ",", 0);
1244 for (current = to_parts; current && *current != '\0'; current++) {
1247 dup2 = modest_text_utils_remove_address (dup, g_strstrip (*current));
1251 g_strfreev (to_parts);
1254 result = modest_text_utils_remove_duplicate_addresses (dup);
1257 result = modest_text_utils_simplify_recipients (dup);
1258 remove_undisclosed_recipients (&result);
1265 modest_tny_msg_get_references (TnyMsg *msg, gchar **message_id, gchar **references, gchar **in_reply_to)
1268 TnyIterator *iterator;
1269 gchar *l_message_id;
1270 gchar *l_references;
1271 gchar *l_in_reply_to;
1273 g_return_if_fail (TNY_IS_MSG (msg));
1275 l_message_id = NULL;
1276 l_references = NULL;
1277 l_in_reply_to = NULL;
1279 headers = TNY_LIST (tny_simple_list_new ());
1280 tny_mime_part_get_header_pairs (TNY_MIME_PART (msg), headers);
1282 iterator = tny_list_create_iterator (headers);
1283 while (!tny_iterator_is_done (iterator)) {
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));
1300 g_object_unref (pair);
1301 tny_iterator_next (iterator);
1304 g_object_unref (iterator);
1305 g_object_unref (headers);
1308 *message_id = l_message_id;
1310 g_free (l_message_id);
1314 *in_reply_to = l_in_reply_to;
1316 g_free (l_in_reply_to);
1320 *references = l_references;
1322 g_free (l_references);
1327 remove_line_breaks (gchar *str)
1329 gchar *needle = g_strrstr (str, "\r\n");
1335 set_references (TnyMsg *reply_msg, TnyMsg *original_msg)
1337 gchar *orig_references, *orig_in_reply_to, *orig_message_id;
1338 gchar *references, *in_reply_to;
1340 modest_tny_msg_get_references (original_msg, &orig_message_id, &orig_references, &orig_in_reply_to);
1345 if (orig_message_id)
1346 in_reply_to = g_strdup (orig_message_id);
1348 if (orig_references) {
1349 if (orig_message_id)
1350 references = g_strconcat (orig_references, "\n ", orig_message_id, NULL);
1352 references = g_strdup (orig_references);
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);
1358 references = g_strdup (orig_in_reply_to);
1359 } else if (orig_message_id) {
1360 references = g_strdup (orig_message_id);
1363 g_free (orig_references);
1364 g_free (orig_in_reply_to);
1365 g_free (orig_message_id);
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);
1373 remove_line_breaks (references);
1374 tny_mime_part_set_header_pair (TNY_MIME_PART (reply_msg), "References", references);
1375 g_free (references);
1380 modest_tny_msg_create_reply_msg (TnyMsg *msg,
1383 const gchar *signature,
1384 ModestTnyMsgReplyType reply_type,
1385 ModestTnyMsgReplyMode reply_mode)
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;
1394 g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
1396 part_to_check = modest_tny_msg_get_attachments_parent (TNY_MSG (msg));
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);
1402 new_msg = create_reply_forward_mail (msg, header, from, signature, TRUE, reply_type,
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);
1410 g_object_unref (parts);
1412 /* Fill the header */
1414 g_object_ref (header);
1416 header = tny_msg_get_header (msg);
1419 new_header = tny_msg_get_header(new_msg);
1420 new_to = get_new_to (msg, header, from, reply_mode);
1422 g_debug ("%s: failed to get new To:", __FUNCTION__);
1424 tny_header_set_to (new_header, new_to);
1427 if (reply_mode == MODEST_TNY_MSG_REPLY_MODE_ALL) {
1428 gchar *new_cc = get_new_cc (header, from, new_to);
1430 tny_header_set_cc (new_header, new_cc);
1439 g_object_unref (G_OBJECT (new_header));
1440 g_object_unref (G_OBJECT (header));
1441 g_object_unref (G_OBJECT (part_to_check));
1447 modest_tny_msg_create_reply_calendar_msg (TnyMsg *msg,
1450 const gchar *signature,
1453 TnyMsg *new_msg = NULL;
1454 TnyIterator *iterator;
1456 g_return_val_if_fail (msg && TNY_IS_MSG(msg), NULL);
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);
1461 iterator = tny_list_create_iterator (headers);
1462 while (!tny_iterator_is_done (iterator)) {
1463 TnyPair *pair = TNY_PAIR (tny_iterator_get_current (iterator));
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);
1471 g_object_unref (iterator);
1478 is_ascii(const gchar *s)
1483 if (s[0] & 128 || s[0] < 32)
1491 get_content_type(const gchar *s)
1495 type = g_string_new("text/plain");
1497 if (g_utf8_validate(s, -1, NULL)) {
1498 g_string_append(type, "; charset=\"utf-8\"");
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\"");
1505 return g_string_free(type, FALSE);
1509 modest_tny_msg_estimate_size (const gchar *plain_body, const gchar *html_body,
1510 guint64 parts_count,
1515 /* estimation of headers size */
1518 /* We add a 20% of size due to the increase in 7bit encoding */
1520 result += strlen (plain_body) * 120 / 100;
1523 result += strlen (html_body) * 120 / 100;
1526 /* 256 bytes per additional part because of their headers */
1527 result += parts_count * 256;
1529 /* 150% of increase per encoding */
1530 result += parts_size * 3 / 2;
1536 modest_tny_msg_header_get_all_recipients_list (TnyHeader *header)
1538 GSList *recipients = NULL;
1539 gchar *from = NULL, *to = NULL, *cc = NULL, *bcc = NULL;
1540 gchar *after_remove, *joined;
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);
1550 joined = modest_text_utils_join_addresses (from, to, cc, bcc);
1551 after_remove = modest_text_utils_remove_duplicate_addresses (joined);
1554 recipients = modest_text_utils_split_addresses_list (after_remove);
1555 g_free (after_remove);
1570 modest_tny_msg_get_all_recipients_list (TnyMsg *msg)
1572 TnyHeader *header = NULL;
1573 GSList *recipients = NULL;
1578 header = tny_msg_get_header (msg);
1582 recipients = modest_tny_msg_header_get_all_recipients_list (header);
1583 g_object_unref (header);