Initialize the gmime for upstream
[platform/upstream/gmime.git] / gmime / gmime-message.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2012 Jeffrey Stedfast
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public License
7  *  as published by the Free Software Foundation; either version 2.1
8  *  of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free
17  *  Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18  *  02110-1301, USA.
19  */
20
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <string.h>
27 #include <ctype.h>
28 #include <locale.h>
29
30 #include "gmime-message.h"
31 #include "gmime-multipart.h"
32 #include "gmime-multipart-signed.h"
33 #include "gmime-multipart-encrypted.h"
34 #include "gmime-part.h"
35 #include "gmime-utils.h"
36 #include "gmime-common.h"
37 #include "gmime-stream-mem.h"
38 #include "gmime-table-private.h"
39 #include "gmime-parse-utils.h"
40 #include "gmime-events.h"
41
42
43 /**
44  * SECTION: gmime-message
45  * @title: GMimeMessage
46  * @short_description: Messages
47  * @see_also:
48  *
49  * A #GMimeMessage represents an rfc822 message.
50  **/
51
52 extern GMimeEvent *_g_mime_header_list_get_changed_event (GMimeHeaderList *headers);
53
54 static void g_mime_message_class_init (GMimeMessageClass *klass);
55 static void g_mime_message_init (GMimeMessage *message, GMimeMessageClass *klass);
56 static void g_mime_message_finalize (GObject *object);
57
58 /* GMimeObject class methods */
59 static void message_prepend_header (GMimeObject *object, const char *header, const char *value);
60 static void message_append_header (GMimeObject *object, const char *header, const char *value);
61 static void message_set_header (GMimeObject *object, const char *header, const char *value);
62 static const char *message_get_header (GMimeObject *object, const char *header);
63 static gboolean message_remove_header (GMimeObject *object, const char *header);
64 static char *message_get_headers (GMimeObject *object);
65 static ssize_t message_write_to_stream (GMimeObject *object, GMimeStream *stream);
66 static void message_encode (GMimeObject *object, GMimeEncodingConstraint constraint);
67
68 static ssize_t write_structured (GMimeStream *stream, const char *name, const char *value);
69 static ssize_t write_addrspec (GMimeStream *stream, const char *name, const char *value);
70 static ssize_t write_received (GMimeStream *stream, const char *name, const char *value);
71 static ssize_t write_subject (GMimeStream *stream, const char *name, const char *value);
72 static ssize_t write_msgid (GMimeStream *stream, const char *name, const char *value);
73
74 static void to_list_changed (InternetAddressList *list, gpointer args, GMimeMessage *message);
75 static void cc_list_changed (InternetAddressList *list, gpointer args, GMimeMessage *message);
76 static void bcc_list_changed (InternetAddressList *list, gpointer args, GMimeMessage *message);
77
78
79 static GMimeObjectClass *parent_class = NULL;
80
81
82 static struct {
83         const char *name;
84         GMimeEventCallback changed_cb;
85 } recipient_types[] = {
86         { "To",  (GMimeEventCallback) to_list_changed  },
87         { "Cc",  (GMimeEventCallback) cc_list_changed  },
88         { "Bcc", (GMimeEventCallback) bcc_list_changed }
89 };
90
91 #define N_RECIPIENT_TYPES G_N_ELEMENTS (recipient_types)
92
93 static char *rfc822_headers[] = {
94         "Return-Path",
95         "Received",
96         "Date",
97         "From",
98         "Reply-To",
99         "Subject",
100         "Sender",
101         "To",
102         "Cc",
103 };
104
105
106 GType
107 g_mime_message_get_type (void)
108 {
109         static GType type = 0;
110         
111         if (!type) {
112                 static const GTypeInfo info = {
113                         sizeof (GMimeMessageClass),
114                         NULL, /* base_class_init */
115                         NULL, /* base_class_finalize */
116                         (GClassInitFunc) g_mime_message_class_init,
117                         NULL, /* class_finalize */
118                         NULL, /* class_data */
119                         sizeof (GMimeMessage),
120                         0,   /* n_preallocs */
121                         (GInstanceInitFunc) g_mime_message_init,
122                 };
123                 
124                 type = g_type_register_static (GMIME_TYPE_OBJECT, "GMimeMessage", &info, 0);
125         }
126         
127         return type;
128 }
129
130
131 static void
132 g_mime_message_class_init (GMimeMessageClass *klass)
133 {
134         GMimeObjectClass *object_class = GMIME_OBJECT_CLASS (klass);
135         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
136         
137         parent_class = g_type_class_ref (GMIME_TYPE_OBJECT);
138         
139         gobject_class->finalize = g_mime_message_finalize;
140         
141         object_class->prepend_header = message_prepend_header;
142         object_class->append_header = message_append_header;
143         object_class->remove_header = message_remove_header;
144         object_class->set_header = message_set_header;
145         object_class->get_header = message_get_header;
146         object_class->get_headers = message_get_headers;
147         object_class->write_to_stream = message_write_to_stream;
148         object_class->encode = message_encode;
149 }
150
151 static void
152 mime_part_headers_changed (GMimeHeaderList *headers, gpointer args, GMimeMessage *message)
153 {
154         /* clear the message's header stream */
155         g_mime_header_list_set_stream (((GMimeObject *) message)->headers, NULL);
156 }
157
158 static void
159 connect_changed_event (GMimeMessage *message, GMimeRecipientType type)
160 {
161         InternetAddressList *list;
162         
163         list = message->recipients[type];
164         
165         g_mime_event_add (list->priv, recipient_types[type].changed_cb, message);
166 }
167
168 static void
169 disconnect_changed_event (GMimeMessage *message, GMimeRecipientType type)
170 {
171         InternetAddressList *list;
172         
173         list = message->recipients[type];
174         
175         g_mime_event_remove (list->priv, recipient_types[type].changed_cb, message);
176 }
177
178 static void
179 block_changed_event (GMimeMessage *message, GMimeRecipientType type)
180 {
181         InternetAddressList *list;
182         
183         list = message->recipients[type];
184         
185         g_mime_event_block (list->priv, recipient_types[type].changed_cb, message);
186 }
187
188 static void
189 unblock_changed_event (GMimeMessage *message, GMimeRecipientType type)
190 {
191         InternetAddressList *list;
192         
193         list = message->recipients[type];
194         
195         g_mime_event_unblock (list->priv, recipient_types[type].changed_cb, message);
196 }
197
198 static void
199 g_mime_message_init (GMimeMessage *message, GMimeMessageClass *klass)
200 {
201         GMimeHeaderList *headers = ((GMimeObject *) message)->headers;
202         guint i;
203         
204         message->from = NULL;
205         message->reply_to = NULL;
206         message->recipients = g_new (InternetAddressList *, N_RECIPIENT_TYPES);
207         message->subject = NULL;
208         message->date = 0;
209         message->tz_offset = 0;
210         message->message_id = NULL;
211         message->mime_part = NULL;
212         
213         /* initialize recipient lists */
214         for (i = 0; i < N_RECIPIENT_TYPES; i++) {
215                 message->recipients[i] = internet_address_list_new ();
216                 connect_changed_event (message, i);
217         }
218         
219         g_mime_header_list_register_writer (headers, "Sender", write_addrspec);
220         g_mime_header_list_register_writer (headers, "From", write_addrspec);
221         g_mime_header_list_register_writer (headers, "To", write_addrspec);
222         g_mime_header_list_register_writer (headers, "Cc", write_addrspec);
223         g_mime_header_list_register_writer (headers, "Bcc", write_addrspec);
224         
225         g_mime_header_list_register_writer (headers, "Resent-Sender", write_addrspec);
226         g_mime_header_list_register_writer (headers, "Resent-From", write_addrspec);
227         g_mime_header_list_register_writer (headers, "Resent-To", write_addrspec);
228         g_mime_header_list_register_writer (headers, "Resent-Cc", write_addrspec);
229         g_mime_header_list_register_writer (headers, "Resent-Bcc", write_addrspec);
230         
231         g_mime_header_list_register_writer (headers, "Subject", write_subject);
232         g_mime_header_list_register_writer (headers, "Received", write_received);
233         g_mime_header_list_register_writer (headers, "Message-Id", write_msgid);
234         g_mime_header_list_register_writer (headers, "References", write_structured);
235 }
236
237 static void
238 g_mime_message_finalize (GObject *object)
239 {
240         GMimeMessage *message = (GMimeMessage *) object;
241         GMimeEvent *changed;
242         guint i;
243         
244         g_free (message->from);
245         g_free (message->reply_to);
246         
247         /* disconnect changed handlers */
248         for (i = 0; i < N_RECIPIENT_TYPES; i++) {
249                 disconnect_changed_event (message, i);
250                 g_object_unref (message->recipients[i]);
251         }
252         
253         g_free (message->recipients);
254         
255         g_free (message->subject);
256         
257         g_free (message->message_id);
258         
259         /* unref child mime part */
260         if (message->mime_part) {
261                 changed = _g_mime_header_list_get_changed_event (message->mime_part->headers);
262                 g_mime_event_remove (changed, (GMimeEventCallback) mime_part_headers_changed, message);
263                 g_object_unref (message->mime_part);
264         }
265         
266         G_OBJECT_CLASS (parent_class)->finalize (object);
267 }
268
269
270 typedef void (* token_skip_t) (const char **in);
271
272 struct _received_token {
273         char *token;
274         size_t len;
275         token_skip_t skip;
276 };
277
278 static void skip_atom     (const char **in);
279 static void skip_domain   (const char **in);
280 static void skip_addr     (const char **in);
281 static void skip_msgid    (const char **in);
282
283 static struct _received_token received_tokens[] = {
284         { "from ", 5, skip_domain   },
285         { "by ",   3, skip_domain   },
286         { "via ",  4, skip_atom     },
287         { "with ", 5, skip_atom     },
288         { "id ",   3, skip_msgid    },
289         { "for ",  4, skip_addr     }
290 };
291
292 static void
293 skip_atom (const char **in)
294 {
295         register const char *inptr;
296         
297         decode_lwsp (in);
298         inptr = *in;
299         while (is_atom (*inptr))
300                 inptr++;
301         *in = inptr;
302 }
303
304 static void
305 skip_comment (const char **in)
306 {
307         register const char *inptr = *in;
308         int depth = 1;
309         
310         if (*inptr == '(')
311                 inptr++;
312         
313         while (*inptr && depth > 0) {
314                 if (*inptr == '(')
315                         depth++;
316                 else if (*inptr == ')')
317                         depth--;
318                 inptr++;
319         }
320         
321         if (*inptr == ')')
322                 inptr++;
323         
324         *in = inptr;
325 }
326
327 static void
328 skip_quoted_string (const char **in)
329 {
330         const char *inptr = *in;
331         
332         decode_lwsp (&inptr);
333         if (*inptr == '"') {
334                 inptr++;
335                 while (*inptr && *inptr != '"') {
336                         if (*inptr == '\\')
337                                 inptr++;
338                         
339                         if (*inptr)
340                                 inptr++;
341                 }
342                 
343                 if (*inptr == '"')
344                         inptr++;
345         }
346         
347         *in = inptr;
348 }
349
350 static void
351 skip_word (const char **in)
352 {
353         decode_lwsp (in);
354         if (**in == '"') {
355                 skip_quoted_string (in);
356         } else {
357                 skip_atom (in);
358         }
359 }
360
361 static void
362 skip_domain_subliteral (const char **in)
363 {
364         const char *inptr = *in;
365         
366         while (*inptr && *inptr != '.' && *inptr != ']') {
367                 if (is_dtext (*inptr)) {
368                         inptr++;
369                 } else if (is_lwsp (*inptr)) {
370                         decode_lwsp (&inptr);
371                 } else {
372                         break;
373                 }
374         }
375         
376         *in = inptr;
377 }
378
379 static void
380 skip_domain_literal (const char **in)
381 {
382         const char *inptr = *in;
383         
384         decode_lwsp (&inptr);
385         while (*inptr && *inptr != ']') {
386                 skip_domain_subliteral (&inptr);
387                 if (*inptr && *inptr != ']')
388                         inptr++;
389         }
390         
391         *in = inptr;
392 }
393
394 static void
395 skip_domain (const char **in)
396 {
397         const char *save, *inptr = *in;
398         
399         while (inptr && *inptr) {
400                 decode_lwsp (&inptr);
401                 if (*inptr == '[') {
402                         /* domain literal */
403                         inptr++;
404                         skip_domain_literal (&inptr);
405                         if (*inptr == ']')
406                                 inptr++;
407                 } else {
408                         skip_atom (&inptr);
409                 }
410                 
411                 save = inptr;
412                 decode_lwsp (&inptr);
413                 if (*inptr != '.') {
414                         inptr = save;
415                         break;
416                 }
417                 
418                 inptr++;
419         }
420         
421         *in = inptr;
422 }
423
424 static void
425 skip_addrspec (const char **in)
426 {
427         const char *inptr = *in;
428         
429         decode_lwsp (&inptr);
430         skip_word (&inptr);
431         decode_lwsp (&inptr);
432         
433         while (*inptr == '.') {
434                 inptr++;
435                 skip_word (&inptr);
436                 decode_lwsp (&inptr);
437         }
438         
439         if (*inptr == '@') {
440                 inptr++;
441                 skip_domain (&inptr);
442         }
443         
444         *in = inptr;
445 }
446
447 static void
448 skip_addr (const char **in)
449 {
450         const char *inptr = *in;
451         
452         decode_lwsp (&inptr);
453         if (*inptr == '<') {
454                 inptr++;
455                 skip_addrspec (&inptr);
456                 if (*inptr == '>')
457                         inptr++;
458         } else {
459                 skip_addrspec (&inptr);
460         }
461         
462         *in = inptr;
463 }
464
465 static void
466 skip_msgid (const char **in)
467 {
468         const char *inptr = *in;
469         
470         decode_lwsp (&inptr);
471         if (*inptr == '<') {
472                 inptr++;
473                 skip_addrspec (&inptr);
474                 if (*inptr == '>')
475                         inptr++;
476         } else {
477                 skip_atom (&inptr);
478         }
479         
480         *in = inptr;
481 }
482
483
484 struct _received_part {
485         struct _received_part *next;
486         const char *start;
487         size_t len;
488 };
489
490 static ssize_t
491 write_received (GMimeStream *stream, const char *name, const char *value)
492 {
493         struct _received_part *parts, *part, *tail;
494         const char *inptr, *lwsp = NULL;
495         ssize_t nwritten;
496         GString *str;
497         size_t len;
498         guint i;
499         
500         while (is_lwsp (*value))
501                 value++;
502         
503         if (*value == '\0')
504                 return 0;
505         
506         str = g_string_new (name);
507         g_string_append_len (str, ": ", 2);
508         len = 10;
509         
510         tail = parts = part = g_alloca (sizeof (struct _received_part));
511         part->start = inptr = value;
512         part->next = NULL;
513         
514         while (*inptr) {
515                 for (i = 0; i < G_N_ELEMENTS (received_tokens); i++) {
516                         if (!strncmp (inptr, received_tokens[i].token, received_tokens[i].len)) {
517                                 if (inptr > part->start) {
518                                         g_assert (lwsp != NULL);
519                                         part->len = lwsp - part->start;
520                                         
521                                         part = g_alloca (sizeof (struct _received_part));
522                                         part->start = inptr;
523                                         part->next = NULL;
524                                         
525                                         tail->next = part;
526                                         tail = part;
527                                 }
528                                 
529                                 inptr += received_tokens[i].len;
530                                 received_tokens[i].skip (&inptr);
531                                 
532                                 lwsp = inptr;
533                                 while (is_lwsp (*inptr))
534                                         inptr++;
535                                 
536                                 if (*inptr == ';') {
537                                         inptr++;
538                                         
539                                         part->len = inptr - part->start;
540                                         
541                                         lwsp = inptr;
542                                         while (is_lwsp (*inptr))
543                                                 inptr++;
544                                         
545                                         part = g_alloca (sizeof (struct _received_part));
546                                         part->start = inptr;
547                                         part->next = NULL;
548                                         
549                                         tail->next = part;
550                                         tail = part;
551                                 }
552                                 
553                                 break;
554                         }
555                 }
556                 
557                 if (i == G_N_ELEMENTS (received_tokens)) {
558                         while (*inptr && !is_lwsp (*inptr))
559                                 inptr++;
560                         
561                         lwsp = inptr;
562                         while (is_lwsp (*inptr))
563                                 inptr++;
564                 }
565                 
566                 if (*inptr == '(') {
567                         skip_comment (&inptr);
568                         
569                         lwsp = inptr;
570                         while (is_lwsp (*inptr))
571                                 inptr++;
572                 }
573         }
574         
575         part->len = lwsp - part->start;
576         
577         lwsp = NULL;
578         part = parts;
579         do {
580                 len += lwsp ? part->start - lwsp : 0;
581                 if (len + part->len > GMIME_FOLD_LEN && part != parts) {
582                         g_string_append (str, "\n\t");
583                         len = 1;
584                 } else if (lwsp) {
585                         g_string_append_len (str, lwsp, (size_t) (part->start - lwsp));
586                 }
587                 
588                 g_string_append_len (str, part->start, part->len);
589                 lwsp = part->start + part->len;
590                 len += part->len;
591                 
592                 part = part->next;
593         } while (part != NULL);
594         
595         g_string_append_c (str, '\n');
596         
597         nwritten = g_mime_stream_write (stream, str->str, str->len);
598         g_string_free (str, TRUE);
599         
600         return nwritten;
601 }
602
603 static ssize_t
604 write_subject (GMimeStream *stream, const char *name, const char *value)
605 {
606         char *unfolded, *folded;
607         ssize_t n;
608         
609         unfolded = g_strdup_printf ("%s: %s\n", name, value);
610         folded = g_mime_utils_unstructured_header_fold (unfolded);
611         g_free (unfolded);
612         
613         n = g_mime_stream_write_string (stream, folded);
614         g_free (folded);
615         
616         return n;
617 }
618
619 static ssize_t
620 write_msgid (GMimeStream *stream, const char *name, const char *value)
621 {
622         /* we don't want to wrap the Message-Id header - seems to
623            break a lot of clients (and servers) */
624         return g_mime_stream_printf (stream, "%s: %s\n", name, value);
625 }
626
627 static ssize_t
628 write_structured (GMimeStream *stream, const char *name, const char *value)
629 {
630         char *unfolded, *folded;
631         ssize_t n;
632         
633         unfolded = g_strdup_printf ("%s: %s\n", name, value);
634         folded = g_mime_utils_structured_header_fold (unfolded);
635         g_free (unfolded);
636         
637         n = g_mime_stream_write_string (stream, folded);
638         g_free (folded);
639         
640         return n;
641 }
642
643 static ssize_t
644 write_addrspec (GMimeStream *stream, const char *name, const char *value)
645 {
646         InternetAddressList *addrlist;
647         GString *str;
648         ssize_t n;
649         
650         str = g_string_new (name);
651         g_string_append (str, ": ");
652         
653         if (value && (addrlist = internet_address_list_parse_string (value))) {
654                 internet_address_list_writer (addrlist, str);
655                 g_object_unref (addrlist);
656         }
657         
658         g_string_append_c (str, '\n');
659         
660         n = g_mime_stream_write (stream, str->str, str->len);
661         g_string_free (str, TRUE);
662         
663         return n;
664 }
665
666 enum {
667         HEADER_FROM,
668         HEADER_REPLY_TO,
669         HEADER_TO,
670         HEADER_CC,
671         HEADER_BCC,
672         HEADER_SUBJECT,
673         HEADER_DATE,
674         HEADER_MESSAGE_ID,
675         HEADER_MIME_VERSION,
676         HEADER_UNKNOWN
677 };
678
679 static const char *message_headers[] = {
680         "From",
681         "Reply-To",
682         "To",
683         "Cc",
684         "Bcc",
685         "Subject",
686         "Date",
687         "Message-Id",
688         "MIME-Version",
689 };
690
691 enum {
692         PREPEND,
693         APPEND,
694         SET
695 };
696
697 static void
698 message_add_recipients_from_string (GMimeMessage *message, int action, GMimeRecipientType type, const char *str)
699 {
700         InternetAddressList *recipients, *addrlist;
701         
702         recipients = g_mime_message_get_recipients (message, type);
703         
704         if (action == SET)
705                 internet_address_list_clear (recipients);
706         
707         if ((addrlist = internet_address_list_parse_string (str))) {
708                 if (action == PREPEND)
709                         internet_address_list_prepend (recipients, addrlist);
710                 else
711                         internet_address_list_append (recipients, addrlist);
712                 
713                 g_object_unref (addrlist);
714         }
715 }
716
717 static gboolean
718 process_header (GMimeObject *object, int action, const char *header, const char *value)
719 {
720         GMimeMessage *message = (GMimeMessage *) object;        
721         InternetAddressList *addrlist;
722         time_t date;
723         int offset;
724         guint i;
725         
726         for (i = 0; i < G_N_ELEMENTS (message_headers); i++) {
727                 if (!g_ascii_strcasecmp (message_headers[i], header))
728                         break;
729         }
730         
731         switch (i) {
732         case HEADER_FROM:
733                 g_free (message->from);
734                 if ((addrlist = internet_address_list_parse_string (value))) {
735                         message->from = internet_address_list_to_string (addrlist, FALSE);
736                         g_object_unref (addrlist);
737                 } else {
738                         message->from = NULL;
739                 }
740                 break;
741         case HEADER_REPLY_TO:
742                 g_free (message->reply_to);
743                 if ((addrlist = internet_address_list_parse_string (value))) {
744                         message->reply_to = internet_address_list_to_string (addrlist, FALSE);
745                         g_object_unref (addrlist);
746                 } else {
747                         message->reply_to = NULL;
748                 }
749                 break;
750         case HEADER_TO:
751                 block_changed_event (message, GMIME_RECIPIENT_TYPE_TO);
752                 message_add_recipients_from_string (message, action, GMIME_RECIPIENT_TYPE_TO, value);
753                 unblock_changed_event (message, GMIME_RECIPIENT_TYPE_TO);
754                 break;
755         case HEADER_CC:
756                 block_changed_event (message, GMIME_RECIPIENT_TYPE_CC);
757                 message_add_recipients_from_string (message, action, GMIME_RECIPIENT_TYPE_CC, value);
758                 unblock_changed_event (message, GMIME_RECIPIENT_TYPE_CC);
759                 break;
760         case HEADER_BCC:
761                 block_changed_event (message, GMIME_RECIPIENT_TYPE_BCC);
762                 message_add_recipients_from_string (message, action, GMIME_RECIPIENT_TYPE_BCC, value);
763                 unblock_changed_event (message, GMIME_RECIPIENT_TYPE_BCC);
764                 break;
765         case HEADER_SUBJECT:
766                 g_free (message->subject);
767                 message->subject = g_mime_utils_header_decode_text (value);
768                 break;
769         case HEADER_DATE:
770                 if (value) {
771                         date = g_mime_utils_header_decode_date (value, &offset);
772                         message->date = date;
773                         message->tz_offset = offset;
774                 }
775                 break;
776         case HEADER_MESSAGE_ID:
777                 g_free (message->message_id);
778                 message->message_id = g_mime_utils_decode_message_id (value);
779                 break;
780         case HEADER_MIME_VERSION:
781                 break;
782         default:
783                 return FALSE;
784         }
785         
786         return TRUE;
787 }
788
789 static void
790 message_prepend_header (GMimeObject *object, const char *header, const char *value)
791 {
792         GMimeMessage *message = (GMimeMessage *) object;
793         
794         /* Content-* headers don't belong on the message, they belong on the part. */
795         if (!g_ascii_strncasecmp ("Content-", header, 8)) {
796                 if (message->mime_part)
797                         g_mime_object_prepend_header (message->mime_part, header, value);
798                 return;
799         }
800         
801         if (!process_header (object, PREPEND, header, value))
802                 GMIME_OBJECT_CLASS (parent_class)->prepend_header (object, header, value);
803         else
804                 g_mime_header_list_prepend (object->headers, header, value);
805         
806         if (message->mime_part)
807                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
808 }
809
810 static void
811 message_append_header (GMimeObject *object, const char *header, const char *value)
812 {
813         GMimeMessage *message = (GMimeMessage *) object;
814         
815         /* Content-* headers don't belong on the message, they belong on the part. */
816         if (!g_ascii_strncasecmp ("Content-", header, 8)) {
817                 if (message->mime_part)
818                         g_mime_object_append_header (message->mime_part, header, value);
819                 return;
820         }
821         
822         if (!process_header (object, APPEND, header, value))
823                 GMIME_OBJECT_CLASS (parent_class)->append_header (object, header, value);
824         else
825                 g_mime_header_list_append (object->headers, header, value);
826         
827         if (message->mime_part)
828                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
829 }
830
831 static void
832 message_set_header (GMimeObject *object, const char *header, const char *value)
833 {
834         GMimeMessage *message = (GMimeMessage *) object;
835         
836         /* Content-* headers don't belong on the message, they belong on the part. */
837         if (!g_ascii_strncasecmp ("Content-", header, 8)) {
838                 if (message->mime_part)
839                         g_mime_object_set_header (message->mime_part, header, value);
840                 return;
841         }
842         
843         if (!process_header (object, SET, header, value))
844                 GMIME_OBJECT_CLASS (parent_class)->set_header (object, header, value);
845         else
846                 g_mime_header_list_set (object->headers, header, value);
847         
848         if (message->mime_part)
849                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
850 }
851
852 static const char *
853 message_get_header (GMimeObject *object, const char *header)
854 {
855         GMimeMessage *message = (GMimeMessage *) object;
856         const char *value;
857         
858         /* Content-* headers don't belong on the message, they belong on the part. */
859         if (g_ascii_strncasecmp ("Content-", header, 8) != 0) {
860                 if ((value = GMIME_OBJECT_CLASS (parent_class)->get_header (object, header)))
861                         return value;
862                 
863                 if (!g_ascii_strcasecmp ("MIME-Version", header))
864                         return "1.0";
865         } else if (message->mime_part) {
866                 return g_mime_object_get_header (message->mime_part, header);
867         }
868         
869         return NULL;
870 }
871
872 static gboolean
873 message_remove_header (GMimeObject *object, const char *header)
874 {
875         GMimeMessage *message = (GMimeMessage *) object;
876         InternetAddressList *addrlist;
877         GMimeRecipientType type;
878         guint i;
879         
880         /* Content-* headers don't belong on the message, they belong on the part. */
881         if (!g_ascii_strncasecmp ("Content-", header, 8)) {
882                 if (message->mime_part)
883                         return g_mime_object_remove_header (message->mime_part, header);
884                 
885                 return FALSE;
886         }
887         
888         for (i = 0; i < G_N_ELEMENTS (message_headers); i++) {
889                 if (!g_ascii_strcasecmp (message_headers[i], header))
890                         break;
891         }
892         
893         switch (i) {
894         case HEADER_FROM:
895                 g_free (message->from);
896                 message->from = NULL;
897                 break;
898         case HEADER_REPLY_TO:
899                 g_free (message->reply_to);
900                 message->reply_to = NULL;
901                 break;
902         case HEADER_TO:
903                 type = GMIME_RECIPIENT_TYPE_TO;
904                 block_changed_event (message, type);
905                 addrlist = message->recipients[type];
906                 internet_address_list_clear (addrlist);
907                 unblock_changed_event (message, type);
908                 break;
909         case HEADER_CC:
910                 type = GMIME_RECIPIENT_TYPE_CC;
911                 block_changed_event (message, type);
912                 addrlist = message->recipients[type];
913                 internet_address_list_clear (addrlist);
914                 unblock_changed_event (message, type);
915                 break;
916         case HEADER_BCC:
917                 type = GMIME_RECIPIENT_TYPE_BCC;
918                 block_changed_event (message, type);
919                 addrlist = message->recipients[type];
920                 internet_address_list_clear (addrlist);
921                 unblock_changed_event (message, type);
922                 break;
923         case HEADER_SUBJECT:
924                 g_free (message->subject);
925                 message->subject = NULL;
926                 break;
927         case HEADER_DATE:
928                 message->date = 0;
929                 message->tz_offset = 0;
930                 break;
931         case HEADER_MESSAGE_ID:
932                 g_free (message->message_id);
933                 message->message_id = NULL;
934                 break;
935         default:
936                 break;
937         }
938         
939         if (message->mime_part)
940                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
941         
942         return GMIME_OBJECT_CLASS (parent_class)->remove_header (object, header);
943 }
944
945
946 static char *
947 message_get_headers (GMimeObject *object)
948 {
949         GMimeMessage *message = (GMimeMessage *) object;
950         GMimeStream *stream;
951         GByteArray *ba;
952         char *str;
953         
954         ba = g_byte_array_new ();
955         stream = g_mime_stream_mem_new ();
956         g_mime_stream_mem_set_byte_array (GMIME_STREAM_MEM (stream), ba);
957         
958         if (message->mime_part && g_mime_header_list_get_stream (message->mime_part->headers)) {
959                 /* if the mime part has raw headers, then it contains the message headers as well */
960                 g_mime_header_list_write_to_stream (message->mime_part->headers, stream);
961         } else {
962                 g_mime_header_list_write_to_stream (object->headers, stream);
963                 if (message->mime_part) {
964                         if (g_mime_object_get_header (message->mime_part, "Content-Type") &&
965                             !g_mime_header_list_get (object->headers, "MIME-Version"))
966                                 g_mime_stream_write_string (stream, "MIME-Version: 1.0\n");
967                         g_mime_header_list_write_to_stream (message->mime_part->headers, stream);
968                 }
969         }
970         
971         g_object_unref (stream);
972         g_byte_array_append (ba, (unsigned char *) "", 1);
973         str = (char *) ba->data;
974         g_byte_array_free (ba, FALSE);
975         
976         return str;
977 }
978
979 static ssize_t
980 message_write_to_stream (GMimeObject *object, GMimeStream *stream)
981 {
982         GMimeMessage *message = (GMimeMessage *) object;
983         ssize_t nwritten, total = 0;
984         
985         if (message->mime_part) {
986                 if (!g_mime_header_list_get_stream (message->mime_part->headers)) {
987                         if ((nwritten = g_mime_header_list_write_to_stream (object->headers, stream)) == -1)
988                                 return -1;
989                         
990                         total += nwritten;
991                         
992                         if (!g_mime_header_list_get (object->headers, "MIME-Version")) {
993                                 if ((nwritten = g_mime_stream_write_string (stream, "MIME-Version: 1.0\n")) == -1)
994                                         return -1;
995                         }
996                         
997                         total += nwritten;
998                 }
999                 
1000                 if ((nwritten = g_mime_object_write_to_stream (message->mime_part, stream)) == -1)
1001                         return -1;
1002         } else {
1003                 if ((nwritten = g_mime_header_list_write_to_stream (object->headers, stream)) == -1)
1004                         return -1;
1005                 
1006                 total += nwritten;
1007                 
1008                 if ((nwritten = g_mime_stream_write (stream, "\n", 1)) == -1)
1009                         return -1;
1010         }
1011         
1012         total += nwritten;
1013         
1014         return total;
1015 }
1016
1017 static void
1018 message_encode (GMimeObject *object, GMimeEncodingConstraint constraint)
1019 {
1020         GMimeMessage *message = (GMimeMessage *) object;
1021         
1022         if (message->mime_part != NULL)
1023                 g_mime_object_encode (message->mime_part, constraint);
1024 }
1025
1026
1027
1028 /**
1029  * g_mime_message_new:
1030  * @pretty_headers: make pretty headers 
1031  *
1032  * If @pretty_headers is %TRUE, then the standard rfc822 headers are
1033  * initialized so as to put headers in a nice friendly order. This is
1034  * strictly a cosmetic thing, so if you are unsure, it is safe to say
1035  * no (%FALSE).
1036  *
1037  * Returns: an empty #GMimeMessage object.
1038  **/
1039 GMimeMessage *
1040 g_mime_message_new (gboolean pretty_headers)
1041 {
1042         GMimeHeaderList *headers;
1043         GMimeMessage *message;
1044         guint i;
1045         
1046         message = g_object_newv (GMIME_TYPE_MESSAGE, 0, NULL);
1047         
1048         if (pretty_headers) {
1049                 /* Populate with the "standard" rfc822 headers so we can have a standard order */
1050                 headers = ((GMimeObject *) message)->headers;
1051                 for (i = 0; i < G_N_ELEMENTS (rfc822_headers); i++) 
1052                         g_mime_header_list_set (headers, rfc822_headers[i], NULL);
1053         }
1054         
1055         return message;
1056 }
1057
1058
1059 /**
1060  * g_mime_message_set_sender:
1061  * @message: MIME Message to change
1062  * @sender: The name and address of the sender
1063  *
1064  * Set the sender's name and address on the MIME Message.
1065  * (ex: "\"Joe Sixpack\" &lt;joe@sixpack.org&gt;")
1066  **/
1067 void
1068 g_mime_message_set_sender (GMimeMessage *message, const char *sender)
1069 {
1070         InternetAddressList *addrlist;
1071         char *encoded;
1072         
1073         g_return_if_fail (GMIME_IS_MESSAGE (message));
1074         g_return_if_fail (sender != NULL);
1075         
1076         g_free (message->from);
1077         
1078         if ((addrlist = internet_address_list_parse_string (sender))) {
1079                 message->from = internet_address_list_to_string (addrlist, FALSE);
1080                 encoded = internet_address_list_to_string (addrlist, TRUE);
1081                 g_mime_header_list_set (GMIME_OBJECT (message)->headers, "From", encoded);
1082                 g_object_unref (addrlist);
1083                 g_free (encoded);
1084         } else {
1085                 g_mime_header_list_set (GMIME_OBJECT (message)->headers, "From", "");
1086                 message->from = NULL;
1087         }
1088         
1089         if (message->mime_part)
1090                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1091 }
1092
1093
1094 /**
1095  * g_mime_message_get_sender:
1096  * @message: MIME Message
1097  *
1098  * Gets the email address of the sender from @message.
1099  *
1100  * Returns: the sender's name and address of the MIME Message.
1101  **/
1102 const char *
1103 g_mime_message_get_sender (GMimeMessage *message)
1104 {
1105         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1106         
1107         return message->from;
1108 }
1109
1110
1111 /**
1112  * g_mime_message_set_reply_to:
1113  * @message: MIME Message to change
1114  * @reply_to: The Reply-To address
1115  *
1116  * Set the sender's Reply-To address on the MIME Message.
1117  **/
1118 void
1119 g_mime_message_set_reply_to (GMimeMessage *message, const char *reply_to)
1120 {
1121         g_return_if_fail (GMIME_IS_MESSAGE (message));
1122         g_return_if_fail (reply_to != NULL);
1123         
1124         g_free (message->reply_to);
1125         message->reply_to = g_mime_strdup_trim (reply_to);
1126         
1127         g_mime_header_list_set (GMIME_OBJECT (message)->headers, "Reply-To", message->reply_to);
1128         
1129         if (message->mime_part)
1130                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1131 }
1132
1133
1134 /**
1135  * g_mime_message_get_reply_to:
1136  * @message: MIME Message
1137  *
1138  * Gets the Reply-To address from @message.
1139  *
1140  * Returns: the sender's Reply-To address from the MIME Message.
1141  **/
1142 const char *
1143 g_mime_message_get_reply_to (GMimeMessage *message)
1144 {
1145         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1146         
1147         return message->reply_to;
1148 }
1149
1150
1151 static void
1152 sync_recipient_header (GMimeMessage *message, GMimeRecipientType type)
1153 {
1154         GMimeObject *object = (GMimeObject *) message;
1155         const char *name = recipient_types[type].name;
1156         InternetAddressList *list;
1157         char *string;
1158         
1159         /* sync the specified recipient header */
1160         if ((list = g_mime_message_get_recipients (message, type))) {
1161                 string = internet_address_list_to_string (list, TRUE);
1162                 g_mime_header_list_set (object->headers, name, string);
1163                 g_free (string);
1164         } else {
1165                 /* list should never be NULL... */
1166                 g_mime_header_list_set (object->headers, name, NULL);
1167         }
1168         
1169         if (message->mime_part)
1170                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1171 }
1172
1173 static void
1174 to_list_changed (InternetAddressList *list, gpointer args, GMimeMessage *message)
1175 {
1176         sync_recipient_header (message, GMIME_RECIPIENT_TYPE_TO);
1177 }
1178
1179 static void
1180 cc_list_changed (InternetAddressList *list, gpointer args, GMimeMessage *message)
1181 {
1182         sync_recipient_header (message, GMIME_RECIPIENT_TYPE_CC);
1183 }
1184
1185 static void
1186 bcc_list_changed (InternetAddressList *list, gpointer args, GMimeMessage *message)
1187 {
1188         sync_recipient_header (message, GMIME_RECIPIENT_TYPE_BCC);
1189 }
1190
1191
1192 /**
1193  * g_mime_message_add_recipient:
1194  * @message: MIME Message to change
1195  * @type: A #GMimeRecipientType
1196  * @name: The recipient's name (or %NULL)
1197  * @addr: The recipient's address
1198  *
1199  * Add a recipient of a chosen type to the MIME Message.
1200  **/
1201 void
1202 g_mime_message_add_recipient (GMimeMessage *message, GMimeRecipientType type, const char *name, const char *addr)
1203 {
1204         InternetAddressList *recipients;
1205         InternetAddress *ia;
1206         
1207         g_return_if_fail (GMIME_IS_MESSAGE (message));
1208         g_return_if_fail (type < N_RECIPIENT_TYPES);
1209         g_return_if_fail (addr != NULL);
1210         
1211         recipients = message->recipients[type];
1212         ia = internet_address_mailbox_new (name, addr);
1213         internet_address_list_add (recipients, ia);
1214         g_object_unref (ia);
1215         
1216         if (message->mime_part)
1217                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1218         
1219         g_mime_header_list_set_stream (((GMimeObject *) message)->headers, NULL);
1220 }
1221
1222
1223 /**
1224  * g_mime_message_get_recipients:
1225  * @message: MIME Message
1226  * @type: A #GMimeRecipientType
1227  *
1228  * Gets a list of recipients of type @type from @message.
1229  *
1230  * Returns: a list of recipients of a chosen type from the MIME
1231  * Message.
1232  **/
1233 InternetAddressList *
1234 g_mime_message_get_recipients (GMimeMessage *message, GMimeRecipientType type)
1235 {
1236         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1237         g_return_val_if_fail (type < N_RECIPIENT_TYPES, NULL);
1238         
1239         return message->recipients[type];
1240 }
1241
1242
1243 /**
1244  * g_mime_message_get_all_recipients:
1245  * @message: MIME Message
1246  *
1247  * Gets the complete list of recipients for @message.
1248  *
1249  * Returns: a newly allocated #InternetAddressList containing all
1250  * recipients of the message or %NULL if no recipients are set.
1251  **/
1252 InternetAddressList *
1253 g_mime_message_get_all_recipients (GMimeMessage *message)
1254 {
1255         InternetAddressList *recipients, *list = NULL;
1256         guint i;
1257         
1258         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1259         
1260         for (i = 0; i < N_RECIPIENT_TYPES; i++) {
1261                 recipients = message->recipients[i];
1262                 
1263                 if (internet_address_list_length (recipients) == 0)
1264                         continue;
1265                 
1266                 if (list == NULL)
1267                         list = internet_address_list_new ();
1268                 
1269                 internet_address_list_append (list, recipients);
1270         }
1271         
1272         return list;
1273 }
1274
1275
1276 /**
1277  * g_mime_message_set_subject:
1278  * @message: MIME Message
1279  * @subject: Subject string
1280  *
1281  * Set the unencoded UTF-8 Subject field on a MIME Message.
1282  **/
1283 void
1284 g_mime_message_set_subject (GMimeMessage *message, const char *subject)
1285 {
1286         char *encoded;
1287         
1288         g_return_if_fail (GMIME_IS_MESSAGE (message));
1289         g_return_if_fail (subject != NULL);
1290         
1291         g_free (message->subject);
1292         message->subject = g_mime_strdup_trim (subject);
1293         
1294         encoded = g_mime_utils_header_encode_text (message->subject);
1295         g_mime_object_set_header (GMIME_OBJECT (message), "Subject", encoded);
1296         g_free (encoded);
1297         
1298         if (message->mime_part)
1299                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1300 }
1301
1302
1303 /**
1304  * g_mime_message_get_subject:
1305  * @message: MIME Message
1306  *
1307  * Gets the message's subject.
1308  *
1309  * Returns: the unencoded UTF-8 Subject field on a MIME Message.
1310  **/
1311 const char *
1312 g_mime_message_get_subject (GMimeMessage *message)
1313 {
1314         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1315         
1316         return message->subject;
1317 }
1318
1319
1320 /**
1321  * g_mime_message_set_date:
1322  * @message: MIME Message
1323  * @date: a date to be used in the Date header
1324  * @tz_offset: timezone offset (in +/- hours)
1325  * 
1326  * Sets the Date header on a MIME Message.
1327  **/
1328 void
1329 g_mime_message_set_date (GMimeMessage *message, time_t date, int tz_offset)
1330 {
1331         char *str;
1332         
1333         g_return_if_fail (GMIME_IS_MESSAGE (message));
1334         
1335         message->date = date;
1336         message->tz_offset = tz_offset;
1337         
1338         str = g_mime_utils_header_format_date (date, tz_offset);
1339         g_mime_object_set_header (GMIME_OBJECT (message), "Date", str);
1340         g_free (str);
1341         
1342         if (message->mime_part)
1343                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1344 }
1345
1346
1347 /**
1348  * g_mime_message_get_date:
1349  * @message: MIME Message
1350  * @date: pointer to a date in time_t
1351  * @tz_offset: pointer to timezone offset (in +/- hours)
1352  * 
1353  * Stores the date in time_t format in @date. If @tz_offset is
1354  * non-%NULL, then the timezone offset in will be stored in
1355  * @tz_offset.
1356  **/
1357 void
1358 g_mime_message_get_date (GMimeMessage *message, time_t *date, int *tz_offset)
1359 {
1360         g_return_if_fail (GMIME_IS_MESSAGE (message));
1361         g_return_if_fail (date != NULL);
1362         
1363         *date = message->date;
1364         
1365         if (tz_offset)
1366                 *tz_offset = message->tz_offset;
1367 }
1368
1369
1370 /**
1371  * g_mime_message_get_date_as_string:
1372  * @message: MIME Message
1373  *
1374  * Gets the message's sent-date in string format.
1375  * 
1376  * Returns: a newly allocated string containing the Date header value.
1377  **/
1378 char *
1379 g_mime_message_get_date_as_string (GMimeMessage *message)
1380 {
1381         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1382         
1383         return g_mime_utils_header_format_date (message->date, message->tz_offset);
1384 }
1385
1386
1387 /**
1388  * g_mime_message_set_date_as_string:
1389  * @message: MIME Message
1390  * @str: a date string
1391  *
1392  * Sets the sent-date of the message.
1393  **/
1394 void
1395 g_mime_message_set_date_as_string (GMimeMessage *message, const char *str)
1396 {
1397         int tz_offset;
1398         time_t date;
1399         char *buf;
1400         
1401         g_return_if_fail (GMIME_IS_MESSAGE (message));
1402         
1403         date = g_mime_utils_header_decode_date (str, &tz_offset);
1404         message->tz_offset = tz_offset;
1405         message->date = date;
1406         
1407         buf = g_mime_utils_header_format_date (date, tz_offset);
1408         g_mime_object_set_header (GMIME_OBJECT (message), "Date", buf);
1409         g_free (buf);
1410         
1411         if (message->mime_part)
1412                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1413 }
1414
1415
1416 /**
1417  * g_mime_message_set_message_id: 
1418  * @message: MIME Message
1419  * @message_id: message-id (addr-spec portion)
1420  *
1421  * Set the Message-Id on a message.
1422  **/
1423 void
1424 g_mime_message_set_message_id (GMimeMessage *message, const char *message_id)
1425 {
1426         char *msgid;
1427         
1428         g_return_if_fail (GMIME_IS_MESSAGE (message));
1429         g_return_if_fail (message_id != NULL);
1430         
1431         g_free (message->message_id);
1432         message->message_id = g_mime_strdup_trim (message_id);
1433         
1434         msgid = g_strdup_printf ("<%s>", message_id);
1435         g_mime_object_set_header (GMIME_OBJECT (message), "Message-Id", msgid);
1436         g_free (msgid);
1437         
1438         if (message->mime_part)
1439                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1440 }
1441
1442
1443 /**
1444  * g_mime_message_get_message_id: 
1445  * @message: MIME Message
1446  *
1447  * Gets the Message-Id header of @message.
1448  *
1449  * Returns: the Message-Id of a message.
1450  **/
1451 const char *
1452 g_mime_message_get_message_id (GMimeMessage *message)
1453 {
1454         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1455         
1456         return message->message_id;
1457 }
1458
1459
1460 /**
1461  * g_mime_message_get_mime_part:
1462  * @message: MIME Message
1463  *
1464  * Gets the toplevel MIME part contained within @message.
1465  *
1466  * Returns: the toplevel MIME part of @message.
1467  **/
1468 GMimeObject *
1469 g_mime_message_get_mime_part (GMimeMessage *message)
1470 {
1471         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1472         
1473         if (message->mime_part == NULL)
1474                 return NULL;
1475         
1476         return message->mime_part;
1477 }
1478
1479
1480 /**
1481  * g_mime_message_set_mime_part:
1482  * @message: MIME Message
1483  * @mime_part: The root-level MIME Part
1484  *
1485  * Set the root-level MIME part of the message.
1486  **/
1487 void
1488 g_mime_message_set_mime_part (GMimeMessage *message, GMimeObject *mime_part)
1489 {
1490         GMimeEvent *changed;
1491         
1492         g_return_if_fail (mime_part == NULL || GMIME_IS_OBJECT (mime_part));
1493         g_return_if_fail (GMIME_IS_MESSAGE (message));
1494         
1495         if (message->mime_part == mime_part)
1496                 return;
1497         
1498         if (message->mime_part) {
1499                 changed = _g_mime_header_list_get_changed_event (message->mime_part->headers);
1500                 g_mime_event_remove (changed, (GMimeEventCallback) mime_part_headers_changed, message);
1501                 
1502                 g_mime_header_list_set_stream (message->mime_part->headers, NULL);
1503                 g_object_unref (message->mime_part);
1504         }
1505         
1506         if (mime_part) {
1507                 changed = _g_mime_header_list_get_changed_event (mime_part->headers);
1508                 g_mime_header_list_set_stream (mime_part->headers, NULL);
1509                 g_mime_event_add (changed, (GMimeEventCallback) mime_part_headers_changed, message);
1510                 g_object_ref (mime_part);
1511         }
1512         
1513         g_mime_header_list_set_stream (((GMimeObject *) message)->headers, NULL);
1514         
1515         message->mime_part = mime_part;
1516 }
1517
1518
1519 /**
1520  * g_mime_message_foreach:
1521  * @message: a #GMimeMessage
1522  * @callback: function to call on each of the mime parts contained by the mime message
1523  * @user_data: user-supplied callback data
1524  *
1525  * Recursively calls @callback on each of the mime parts in the mime message.
1526  **/
1527 void
1528 g_mime_message_foreach (GMimeMessage *message, GMimeObjectForeachFunc callback, gpointer user_data)
1529 {
1530         g_return_if_fail (GMIME_IS_MESSAGE (message));
1531         g_return_if_fail (callback != NULL);
1532         
1533         callback ((GMimeObject *) message, message->mime_part, user_data);
1534         
1535         if (GMIME_IS_MULTIPART (message->mime_part))
1536                 g_mime_multipart_foreach ((GMimeMultipart *) message->mime_part, callback, user_data);
1537 }
1538
1539 static gboolean
1540 part_is_textual (GMimeObject *mime_part)
1541 {
1542         GMimeContentType *type;
1543         
1544         type = g_mime_object_get_content_type (mime_part);
1545         
1546         return g_mime_content_type_is_type (type, "text", "*");
1547 }
1548
1549 static GMimeObject *
1550 multipart_guess_body (GMimeMultipart *multipart)
1551 {
1552         GMimeContentType *type;
1553         GMimeObject *mime_part;
1554         int count, i;
1555         
1556         if (GMIME_IS_MULTIPART_ENCRYPTED (multipart)) {
1557                 /* nothing more we can do */
1558                 return (GMimeObject *) multipart;
1559         }
1560         
1561         type = g_mime_object_get_content_type ((GMimeObject *) multipart);
1562         if (g_mime_content_type_is_type (type, "multipart", "alternative")) {
1563                 /* very likely that this is the body - leave it up to
1564                  * our caller to decide which format of the body it
1565                  * wants to use. */
1566                 return (GMimeObject *) multipart;
1567         }
1568         
1569         count = g_mime_multipart_get_count (multipart);
1570         
1571         if (count >= 1 && GMIME_IS_MULTIPART_SIGNED (multipart)) {
1572                 /* if the body is in here, it has to be the first part */
1573                 count = 1;
1574         }
1575         
1576         for (i = 0; i < count; i++) {
1577                 mime_part = g_mime_multipart_get_part (multipart, i);
1578                 
1579                 if (GMIME_IS_MULTIPART (mime_part)) {
1580                         if ((mime_part = multipart_guess_body ((GMimeMultipart *) mime_part)))
1581                                 return mime_part;
1582                 } else if (GMIME_IS_PART (mime_part)) {
1583                         if (part_is_textual (mime_part))
1584                                 return mime_part;
1585                 }
1586         }
1587         
1588         return NULL;
1589 }
1590
1591
1592 /**
1593  * g_mime_message_get_body:
1594  * @message: MIME Message
1595  *
1596  * Attempts to identify the MIME part containing the body of the
1597  * message.
1598  *
1599  * Returns: a #GMimeObject containing the textual content that appears
1600  * to be the main body of the message.
1601  *
1602  * Note: This function is NOT guarenteed to always work as it
1603  * makes some assumptions that are not necessarily true. It is
1604  * recommended that you traverse the MIME structure yourself.
1605  **/
1606 GMimeObject *
1607 g_mime_message_get_body (GMimeMessage *message)
1608 {
1609         GMimeObject *mime_part;
1610         
1611         g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1612         
1613         if (!(mime_part = message->mime_part))
1614                 return NULL;
1615         
1616         if (GMIME_IS_MULTIPART (mime_part))
1617                 return multipart_guess_body ((GMimeMultipart *) mime_part);
1618         else if (GMIME_IS_PART (mime_part) && part_is_textual (mime_part))
1619                 return mime_part;
1620         
1621         return NULL;
1622 }