Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-mime-message.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
2 /* camel-mime-message.c : class for a mime_message */
3
4 /* 
5  * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
6  *          Michael Zucchi <notzed@ximian.com>
7  *          Jeffrey Stedfast <fejj@ximian.com>
8  *
9  * Copyright 1999-2003 Ximian, Inc. (www.ximian.com)
10  *
11  * This program is free software; you can redistribute it and/or 
12  * modify it under the terms of version 2 of the GNU Lesser General Public 
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
23  * USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <ctype.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <string.h>
34
35 #include <libedataserver/e-iconv.h>
36 #include <libedataserver/e-time-utils.h>
37
38 #include "camel-mime-filter-bestenc.h"
39 #include "camel-mime-filter-charset.h"
40 #include "camel-mime-message.h"
41 #include "camel-multipart.h"
42 #include "camel-stream-filter.h"
43 #include "camel-stream-mem.h"
44 #include "camel-stream-null.h"
45 #include "camel-string-utils.h"
46 #include "camel-url.h"
47
48 #ifdef G_OS_WIN32
49 /* Undef the similar macro from pthread.h, it doesn't check if
50  * gmtime() returns NULL.
51  */
52 #undef gmtime_r
53
54 /* The gmtime() in Microsoft's C library is MT-safe */
55 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
56 #endif
57 #define d(x)
58
59 extern int camel_verbose_debug;
60
61 /* these 2 below should be kept in sync */
62 typedef enum {
63         HEADER_UNKNOWN,
64         HEADER_FROM,
65         HEADER_REPLY_TO,
66         HEADER_SUBJECT,
67         HEADER_TO,
68         HEADER_RESENT_TO,
69         HEADER_CC,
70         HEADER_RESENT_CC,
71         HEADER_BCC,
72         HEADER_RESENT_BCC,
73         HEADER_DATE,
74         HEADER_MESSAGE_ID
75 } CamelHeaderType;
76
77 static char *header_names[] = {
78         /* dont include HEADER_UNKNOWN string */
79         "From", "Reply-To", "Subject", "To", "Resent-To", "Cc", "Resent-Cc",
80         "Bcc", "Resent-Bcc", "Date", "Message-Id", NULL
81 };
82
83 static GHashTable *header_name_table;
84
85 static CamelMimePartClass *parent_class = NULL;
86
87 static char *recipient_names[] = {
88         "To", "Cc", "Bcc", "Resent-To", "Resent-Cc", "Resent-Bcc", NULL
89 };
90
91 static ssize_t write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream);
92 static void add_header (CamelMedium *medium, const char *name, const void *value);
93 static void set_header (CamelMedium *medium, const char *name, const void *value);
94 static void remove_header (CamelMedium *medium, const char *name);
95 static int construct_from_parser (CamelMimePart *, CamelMimeParser *);
96 static void unref_recipient (gpointer key, gpointer value, gpointer user_data);
97
98 /* Returns the class for a CamelMimeMessage */
99 #define CMM_CLASS(so) CAMEL_MIME_MESSAGE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
100 #define CDW_CLASS(so) CAMEL_DATA_WRAPPER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
101 #define CMD_CLASS(so) CAMEL_MEDIUM_CLASS (CAMEL_OBJECT_GET_CLASS(so))
102
103 static void
104 camel_mime_message_class_init (CamelMimeMessageClass *camel_mime_message_class)
105 {
106         CamelDataWrapperClass *camel_data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (camel_mime_message_class);
107         CamelMimePartClass *camel_mime_part_class = CAMEL_MIME_PART_CLASS (camel_mime_message_class);
108         CamelMediumClass *camel_medium_class = CAMEL_MEDIUM_CLASS (camel_mime_message_class);
109         int i;
110         
111         parent_class = CAMEL_MIME_PART_CLASS (camel_type_get_global_classfuncs (camel_mime_part_get_type ()));
112         
113         header_name_table = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
114         for (i = 0;header_names[i]; i++)
115                 g_hash_table_insert (header_name_table, header_names[i], GINT_TO_POINTER(i+1));
116
117         /* virtual method overload */
118         camel_data_wrapper_class->write_to_stream = write_to_stream;
119         camel_data_wrapper_class->decode_to_stream = write_to_stream;
120
121         camel_medium_class->add_header = add_header;
122         camel_medium_class->set_header = set_header;
123         camel_medium_class->remove_header = remove_header;
124
125         camel_mime_part_class->construct_from_parser = construct_from_parser;
126 }
127
128 static void
129 camel_mime_message_init (gpointer object, gpointer klass)
130 {
131         CamelMimeMessage *mime_message = (CamelMimeMessage *)object;
132         int i;
133         
134         mime_message->recipients =  g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
135         for (i=0;recipient_names[i];i++) {
136                 g_hash_table_insert(mime_message->recipients, recipient_names[i], camel_internet_address_new());
137         }
138
139         mime_message->subject = NULL;
140         mime_message->reply_to = NULL;
141         mime_message->from = NULL;
142         mime_message->date = CAMEL_MESSAGE_DATE_CURRENT;
143         mime_message->date_offset = 0;
144         mime_message->date_received = CAMEL_MESSAGE_DATE_CURRENT;
145         mime_message->date_received_offset = 0;
146         mime_message->message_id = NULL;
147 }
148
149 static void           
150 camel_mime_message_finalize (CamelObject *object)
151 {
152         CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
153         
154         g_free (message->subject);
155         
156         g_free (message->message_id);
157         
158         if (message->reply_to)
159                 camel_object_unref ((CamelObject *)message->reply_to);
160         
161         if (message->from)
162                 camel_object_unref ((CamelObject *)message->from);
163         
164         g_hash_table_foreach (message->recipients, unref_recipient, NULL);
165         g_hash_table_destroy (message->recipients);
166 }
167
168 CamelType
169 camel_mime_message_get_type (void)
170 {
171         static CamelType camel_mime_message_type = CAMEL_INVALID_TYPE;
172         
173         if (camel_mime_message_type == CAMEL_INVALID_TYPE)      {
174                 camel_mime_message_type = camel_type_register (camel_mime_part_get_type(), "CamelMimeMessage",
175                                                                sizeof (CamelMimeMessage),
176                                                                sizeof (CamelMimeMessageClass),
177                                                                (CamelObjectClassInitFunc) camel_mime_message_class_init,
178                                                                NULL,
179                                                                (CamelObjectInitFunc) camel_mime_message_init,
180                                                                (CamelObjectFinalizeFunc) camel_mime_message_finalize);
181         }
182         
183         return camel_mime_message_type;
184 }
185
186 static void
187 unref_recipient (gpointer key, gpointer value, gpointer user_data)
188 {
189         camel_object_unref (value);
190 }
191
192
193 /**
194  * camel_mime_message_new:
195  *
196  * Create a new #CamelMimeMessage object.
197  *
198  * Returns a new #CamelMimeMessage object
199  **/
200 CamelMimeMessage *
201 camel_mime_message_new (void) 
202 {
203         CamelMimeMessage *mime_message;
204         mime_message = CAMEL_MIME_MESSAGE (camel_object_new (CAMEL_MIME_MESSAGE_TYPE));
205         
206         return mime_message;
207 }
208
209 /* **** Date: */
210
211
212 /**
213  * camel_mime_message_set_date:
214  * @message: a #CamelMimeMessage object
215  * @date: a time_t date
216  * @offset: an offset from GMT
217  *
218  * Set the date on a message.
219  **/
220 void
221 camel_mime_message_set_date (CamelMimeMessage *message,  time_t date, int offset)
222 {
223         char *datestr;
224         
225         g_assert(message);
226         
227         if (date == CAMEL_MESSAGE_DATE_CURRENT) {
228                 struct tm local;
229                 int tz;
230                 
231                 date = time(0);
232                 e_localtime_with_offset(date, &local, &tz);
233                 offset = (((tz/60/60) * 100) + (tz/60 % 60));
234         }
235         message->date = date;
236         message->date_offset = offset;
237         
238         datestr = camel_header_format_date (date, offset);
239         CAMEL_MEDIUM_CLASS (parent_class)->set_header ((CamelMedium *)message, "Date", datestr);
240         g_free (datestr);
241 }
242
243
244 /**
245  * camel_mime_message_get_date:
246  * @message: a #CamelMimeMessage object
247  * @offset: output for the GMT offset
248  *
249  * Get the date and GMT offset of a message.
250  *
251  * Returns the date of the message
252  **/
253 time_t
254 camel_mime_message_get_date (CamelMimeMessage *msg, int *offset)
255 {
256         if (offset)
257                 *offset = msg->date_offset;
258         
259         return msg->date;
260 }
261
262
263 /**
264  * camel_mime_message_get_date_received:
265  * @message: a #CamelMimeMessage object
266  * @offset: output for the GMT offset
267  *
268  * Get the received date and GMT offset of a message.
269  *
270  * Returns the received date of the message
271  **/
272 time_t
273 camel_mime_message_get_date_received (CamelMimeMessage *msg, int *offset)
274 {
275         if (msg->date_received == CAMEL_MESSAGE_DATE_CURRENT) {
276                 const char *received;
277                 
278                 received = camel_medium_get_header ((CamelMedium *)msg, "received");
279                 if (received)
280                         received = strrchr (received, ';');
281                 if (received)
282                         msg->date_received = camel_header_decode_date (received + 1, &msg->date_received_offset);
283         }
284         
285         if (offset)
286                 *offset = msg->date_received_offset;
287         
288         return msg->date_received;
289 }
290
291 /* **** Message-Id: */
292
293 /**
294  * camel_mime_message_set_message_id:
295  * @message: a #CamelMimeMessage object
296  * @message_id: id of the message
297  *
298  * Set the message-id on a message.
299  **/
300 void
301 camel_mime_message_set_message_id (CamelMimeMessage *mime_message, const char *message_id)
302 {
303         char *id;
304         
305         g_assert (mime_message);
306         
307         g_free (mime_message->message_id);
308         
309         if (message_id) {
310                 id = g_strstrip (g_strdup (message_id));
311         } else {
312                 id = camel_header_msgid_generate ();
313         }
314         
315         mime_message->message_id = id;
316         id = g_strdup_printf ("<%s>", mime_message->message_id);
317         CAMEL_MEDIUM_CLASS (parent_class)->set_header (CAMEL_MEDIUM (mime_message), "Message-Id", id);
318         g_free (id);
319 }
320
321
322 /**
323  * camel_mime_message_get_message_id:
324  * @message: a #CamelMimeMessage object
325  *
326  * Get the message-id of a message.
327  *
328  * Returns the message-id of a message
329  **/
330 const char *
331 camel_mime_message_get_message_id (CamelMimeMessage *mime_message)
332 {
333         g_assert (mime_message);
334         
335         return mime_message->message_id;
336 }
337
338 /* **** Reply-To: */
339
340
341 /**
342  * camel_mime_message_set_reply_to:
343  * @message: a #CamelMimeMessage object
344  * @reply_to: a #CamelInternetAddress object
345  *
346  * Set the Reply-To of a message.
347  **/
348 void
349 camel_mime_message_set_reply_to (CamelMimeMessage *msg, const CamelInternetAddress *reply_to)
350 {
351         char *addr;
352         
353         g_assert(msg);
354         
355         if (msg->reply_to) {
356                 camel_object_unref ((CamelObject *)msg->reply_to);
357                 msg->reply_to = NULL;
358         }
359         
360         if (reply_to == NULL) {
361                 CAMEL_MEDIUM_CLASS (parent_class)->remove_header (CAMEL_MEDIUM (msg), "Reply-To");
362                 return;
363         }
364         
365         msg->reply_to = (CamelInternetAddress *)camel_address_new_clone ((CamelAddress *)reply_to);
366         addr = camel_address_encode ((CamelAddress *)msg->reply_to);
367         CAMEL_MEDIUM_CLASS (parent_class)->set_header (CAMEL_MEDIUM (msg), "Reply-To", addr);
368         g_free (addr);
369 }
370
371
372 /**
373  * camel_mime_message_get_reply_to:
374  * @message: a #CamelMimeMessage object
375  *
376  * Get the Reply-To of a message.
377  *
378  * Returns the Reply-Toa ddress of the message
379  **/
380 const CamelInternetAddress *
381 camel_mime_message_get_reply_to (CamelMimeMessage *mime_message)
382 {
383         g_assert (mime_message);
384         
385         /* TODO: ref for threading? */
386         
387         return mime_message->reply_to;
388 }
389
390 /* **** Subject: */
391
392 /**
393  * camel_mime_message_set_subject:
394  * @message: a #CamelMimeMessage object
395  * @subject: UTF-8 message subject
396  *
397  * Set the subject text of a message.
398  **/
399 void
400 camel_mime_message_set_subject (CamelMimeMessage *mime_message, const char *subject)
401 {
402         char *text;
403         
404         g_assert(mime_message);
405         
406         g_free (mime_message->subject);
407         mime_message->subject = g_strstrip (g_strdup (subject));
408         text = camel_header_encode_string((unsigned char *)mime_message->subject);
409         CAMEL_MEDIUM_CLASS(parent_class)->set_header(CAMEL_MEDIUM (mime_message), "Subject", text);
410         g_free (text);
411 }
412
413
414 /**
415  * camel_mime_message_get_subject:
416  * @message: a #CamelMimeMessage object
417  *
418  * Get the UTF-8 subject text of a message.
419  *
420  * Returns the message subject
421  **/
422 const char *
423 camel_mime_message_get_subject (CamelMimeMessage *mime_message)
424 {
425         g_assert(mime_message);
426         
427         return mime_message->subject;
428 }
429
430 /* *** From: */
431
432 /* Thought: Since get_from/set_from are so rarely called, it is probably not useful
433    to cache the from (and reply_to) addresses as InternetAddresses internally, we
434    could just get it from the headers and reprocess every time. */
435
436 /**
437  * camel_mime_message_set_from:
438  * @message: a #CamelMimeMessage object
439  * @from: a #CamelInternetAddress object
440  *
441  * Set the from address of a message.
442  **/
443 void
444 camel_mime_message_set_from (CamelMimeMessage *msg, const CamelInternetAddress *from)
445 {
446         char *addr;
447         
448         g_assert(msg);
449         
450         if (msg->from) {
451                 camel_object_unref((CamelObject *)msg->from);
452                 msg->from = NULL;
453         }
454         
455         if (from == NULL || camel_address_length((CamelAddress *)from) == 0) {
456                 CAMEL_MEDIUM_CLASS(parent_class)->remove_header(CAMEL_MEDIUM(msg), "From");
457                 return;
458         }
459         
460         msg->from = (CamelInternetAddress *)camel_address_new_clone((CamelAddress *)from);
461         addr = camel_address_encode((CamelAddress *)msg->from);
462         CAMEL_MEDIUM_CLASS (parent_class)->set_header(CAMEL_MEDIUM(msg), "From", addr);
463         g_free(addr);
464 }
465
466
467 /**
468  * camel_mime_message_get_from:
469  * @message: a #CamelMimeMessage object
470  *
471  * Get the from address of a message.
472  *
473  * Returns the from address of the message
474  **/
475 const CamelInternetAddress *
476 camel_mime_message_get_from (CamelMimeMessage *mime_message)
477 {
478         g_assert (mime_message);
479         
480         /* TODO: we should really ref this for multi-threading to work */
481         
482         return mime_message->from;
483 }
484
485 /*  **** To: Cc: Bcc: */
486
487 /**
488  * camel_mime_message_set_recipients:
489  * @message: a #CamelMimeMessage object
490  * @type: recipient type (one of #CAMEL_RECIPIENT_TYPE_TO, #CAMEL_RECIPIENT_TYPE_CC, or #CAMEL_RECIPIENT_TYPE_BCC)
491  * @recipients: a #CamelInternetAddress with the recipient addresses set
492  *
493  * Set the recipients of a message.
494  **/
495 void
496 camel_mime_message_set_recipients(CamelMimeMessage *mime_message, const char *type, const CamelInternetAddress *r)
497 {
498         char *text;
499         CamelInternetAddress *addr;
500         
501         g_assert(mime_message);
502         
503         addr = g_hash_table_lookup (mime_message->recipients, type);
504         if (addr == NULL) {
505                 g_warning ("trying to set a non-valid receipient type: %s", type);
506                 return;
507         }
508         
509         if (r == NULL || camel_address_length ((CamelAddress *)r) == 0) {
510                 camel_address_remove ((CamelAddress *)addr, -1);
511                 CAMEL_MEDIUM_CLASS (parent_class)->remove_header (CAMEL_MEDIUM (mime_message), type);
512                 return;
513         }
514         
515         /* note this does copy, and not append (cat) */
516         camel_address_copy ((CamelAddress *)addr, (const CamelAddress *)r);
517         
518         /* and sync our headers */
519         text = camel_address_encode (CAMEL_ADDRESS (addr));
520         CAMEL_MEDIUM_CLASS (parent_class)->set_header (CAMEL_MEDIUM (mime_message), type, text);
521         g_free(text);
522 }
523
524
525 /**
526  * camel_mime_message_get_recipients:
527  * @message: a #CamelMimeMessage object
528  * @type: recipient type
529  *
530  * Get the message recipients of a specified type.
531  *
532  * Returns the requested recipients
533  **/
534 const CamelInternetAddress *
535 camel_mime_message_get_recipients (CamelMimeMessage *mime_message, const char *type)
536 {
537         g_assert(mime_message);
538         
539         return g_hash_table_lookup (mime_message->recipients, type);
540 }
541
542
543 void
544 camel_mime_message_set_source (CamelMimeMessage *mime_message, const char *src)
545 {
546         CamelURL *url;
547         char *uri;
548         
549         g_assert (mime_message);
550         
551         url = camel_url_new (src, NULL);
552         if (url) {
553                 uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
554                 camel_medium_add_header (CAMEL_MEDIUM (mime_message), "X-Evolution-Source", uri);
555                 g_free (uri);
556                 camel_url_free (url);
557         }
558 }
559
560 const char *
561 camel_mime_message_get_source (CamelMimeMessage *mime_message)
562 {
563         const char *src;
564         
565         g_assert(mime_message);
566         
567         src = camel_medium_get_header (CAMEL_MEDIUM (mime_message), "X-Evolution-Source");
568         if (src) {
569                 while (*src && isspace ((unsigned) *src))
570                         ++src;
571         }
572         return src;
573 }
574
575 /* mime_message */
576 static int
577 construct_from_parser (CamelMimePart *dw, CamelMimeParser *mp)
578 {
579         char *buf;
580         size_t len;
581         int state;
582         int ret;
583         int err;
584
585         d(printf("constructing mime-message\n"));
586
587         d(printf("mime_message::construct_from_parser()\n"));
588
589         /* let the mime-part construct the guts ... */
590         ret = ((CamelMimePartClass *)parent_class)->construct_from_parser(dw, mp);
591
592         if (ret == -1)
593                 return -1;
594
595         /* ... then clean up the follow-on state */
596         state = camel_mime_parser_step (mp, &buf, &len);
597         switch (state) {
598         case CAMEL_MIME_PARSER_STATE_EOF: case CAMEL_MIME_PARSER_STATE_FROM_END: /* these doesn't belong to us */
599                 camel_mime_parser_unstep (mp);
600         case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
601                 break;
602         default:
603                 g_error ("Bad parser state: Expecing MESSAGE_END or EOF or EOM, got: %u", camel_mime_parser_state (mp));
604                 camel_mime_parser_unstep (mp);
605                 return -1;
606         }
607
608         d(printf("mime_message::construct_from_parser() leaving\n"));
609         err = camel_mime_parser_errno(mp);
610         if (err != 0) {
611                 errno = err;
612                 ret = -1;
613         }
614
615         return ret;
616 }
617
618 static ssize_t
619 write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream)
620 {
621         CamelMimeMessage *mm = CAMEL_MIME_MESSAGE (data_wrapper);
622         
623         /* force mandatory headers ... */
624         if (mm->from == NULL) {
625                 /* FIXME: should we just abort?  Should we make one up? */
626                 g_warning ("No from set for message");
627                 camel_medium_set_header ((CamelMedium *)mm, "From", "");
628         }
629         if (!camel_medium_get_header ((CamelMedium *)mm, "Date"))
630                 camel_mime_message_set_date (mm, CAMEL_MESSAGE_DATE_CURRENT, 0);
631         
632         if (mm->subject == NULL)
633                 camel_mime_message_set_subject (mm, "No Subject");
634         
635         if (mm->message_id == NULL)
636                 camel_mime_message_set_message_id (mm, NULL);
637         
638         /* FIXME: "To" header needs to be set explicitly as well ... */
639         
640         if (!camel_medium_get_header ((CamelMedium *)mm, "Mime-Version"))
641                 camel_medium_set_header ((CamelMedium *)mm, "Mime-Version", "1.0");
642         
643         return CAMEL_DATA_WRAPPER_CLASS (parent_class)->write_to_stream (data_wrapper, stream);
644 }
645
646 /* FIXME: check format of fields. */
647 static gboolean
648 process_header (CamelMedium *medium, const char *name, const char *value)
649 {
650         CamelHeaderType header_type;
651         CamelMimeMessage *message = CAMEL_MIME_MESSAGE (medium);
652         CamelInternetAddress *addr;
653         const char *charset;
654         
655         header_type = (CamelHeaderType) g_hash_table_lookup (header_name_table, name);
656         switch (header_type) {
657         case HEADER_FROM:
658                 addr = camel_internet_address_new();
659                 if (camel_address_decode((CamelAddress *)addr, value) <= 0) {
660                         camel_object_unref(addr);
661                 } else {
662                         if (message->from)
663                                 camel_object_unref(message->from);
664                         message->from = addr;
665                 }
666                 break;
667         case HEADER_REPLY_TO:
668                 addr = camel_internet_address_new();
669                 if (camel_address_decode((CamelAddress *)addr, value) <= 0) {
670                         camel_object_unref(addr);
671                 } else {
672                         if (message->reply_to)
673                                 camel_object_unref(message->reply_to);
674                         message->reply_to = addr;
675                 }
676                 break;
677         case HEADER_SUBJECT:
678                 g_free (message->subject);
679                 if (((CamelDataWrapper *) message)->mime_type) {
680                         charset = camel_content_type_param (((CamelDataWrapper *) message)->mime_type, "charset");
681                         charset = e_iconv_charset_name (charset);
682                 } else
683                         charset = NULL;
684                 message->subject = g_strstrip (camel_header_decode_string (value, charset));
685                 break;
686         case HEADER_TO:
687         case HEADER_CC:
688         case HEADER_BCC:
689         case HEADER_RESENT_TO:
690         case HEADER_RESENT_CC:
691         case HEADER_RESENT_BCC:
692                 addr = g_hash_table_lookup (message->recipients, name);
693                 if (value)
694                         camel_address_decode (CAMEL_ADDRESS (addr), value);
695                 else
696                         camel_address_remove (CAMEL_ADDRESS (addr), -1);
697                 return FALSE;
698         case HEADER_DATE:
699                 if (value) {
700                         message->date = camel_header_decode_date (value, &message->date_offset);
701                 } else {
702                         message->date = CAMEL_MESSAGE_DATE_CURRENT;
703                         message->date_offset = 0;
704                 }
705                 break;
706         case HEADER_MESSAGE_ID:
707                 g_free (message->message_id);
708                 if (value)
709                         message->message_id = camel_header_msgid_decode (value);
710                 else
711                         message->message_id = NULL;
712                 break;
713         default:
714                 return FALSE;
715         }
716         
717         return TRUE;
718 }
719
720 static void
721 set_header (CamelMedium *medium, const char *name, const void *value)
722 {
723         process_header (medium, name, value);
724         parent_class->parent_class.set_header (medium, name, value);
725 }
726
727 static void
728 add_header (CamelMedium *medium, const char *name, const void *value)
729 {
730         /* if we process it, then it must be forced unique as well ... */
731         if (process_header (medium, name, value))
732                 parent_class->parent_class.set_header (medium, name, value);
733         else
734                 parent_class->parent_class.add_header (medium, name, value);
735 }
736
737 static void
738 remove_header (CamelMedium *medium, const char *name)
739 {
740         process_header (medium, name, NULL);
741         parent_class->parent_class.remove_header (medium, name);
742 }
743
744 typedef gboolean (*CamelPartFunc)(CamelMimeMessage *, CamelMimePart *, void *data);
745
746 static gboolean
747 message_foreach_part_rec (CamelMimeMessage *msg, CamelMimePart *part, CamelPartFunc callback, void *data)
748 {
749         CamelDataWrapper *containee;
750         int parts, i;
751         int go = TRUE;
752         
753         if (callback (msg, part, data) == FALSE)
754                 return FALSE;
755         
756         containee = camel_medium_get_content_object (CAMEL_MEDIUM (part));
757         
758         if (containee == NULL)
759                 return go;
760         
761         /* using the object types is more accurate than using the mime/types */
762         if (CAMEL_IS_MULTIPART (containee)) {
763                 parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
764                 for (i = 0; go && i < parts; i++) {
765                         CamelMimePart *mpart = camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
766                         
767                         go = message_foreach_part_rec (msg, mpart, callback, data);
768                 }
769         } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
770                 go = message_foreach_part_rec (msg, (CamelMimePart *)containee, callback, data);
771         }
772         
773         return go;
774 }
775
776 /* dont make this public yet, it might need some more thinking ... */
777 /* MPZ */
778 static void
779 camel_mime_message_foreach_part (CamelMimeMessage *msg, CamelPartFunc callback, void *data)
780 {
781         message_foreach_part_rec (msg, (CamelMimePart *)msg, callback, data);
782 }
783
784 static gboolean
785 check_8bit (CamelMimeMessage *msg, CamelMimePart *part, void *data)
786 {
787         CamelTransferEncoding encoding;
788         int *has8bit = data;
789         
790         /* check this part, and stop as soon as we are done */
791         encoding = camel_mime_part_get_encoding (part);
792         
793         *has8bit = encoding == CAMEL_TRANSFER_ENCODING_8BIT || encoding == CAMEL_TRANSFER_ENCODING_BINARY;
794         
795         return !(*has8bit);
796 }
797
798
799 /**
800  * camel_mime_message_has_8bit_parts:
801  * @message: a #CamelMimeMessage object
802  *
803  * Find out if a message contains 8bit or binary encoded parts.
804  *
805  * Returns %TRUE if the message contains 8bit parts or %FALSE otherwise
806  **/
807 gboolean
808 camel_mime_message_has_8bit_parts (CamelMimeMessage *msg)
809 {
810         int has8bit = FALSE;
811         
812         camel_mime_message_foreach_part (msg, check_8bit, &has8bit);
813         
814         return has8bit;
815 }
816
817 /* finds the best charset and transfer encoding for a given part */
818 static CamelTransferEncoding
819 find_best_encoding (CamelMimePart *part, CamelBestencRequired required, CamelBestencEncoding enctype, char **charsetp)
820 {
821         CamelMimeFilterCharset *charenc = NULL;
822         CamelTransferEncoding encoding;
823         CamelMimeFilterBestenc *bestenc;
824         unsigned int flags, callerflags;
825         CamelDataWrapper *content;
826         CamelStreamFilter *filter;
827         const char *charsetin = NULL;
828         char *charset = NULL;
829         CamelStream *null;
830         int idb, idc = -1;
831         gboolean istext;
832         
833         /* we use all these weird stream things so we can do it with streams, and
834            not have to read the whole lot into memory - although i have a feeling
835            it would make things a fair bit simpler to do so ... */
836         
837         d(printf("starting to check part\n"));
838         
839         content = camel_medium_get_content_object ((CamelMedium *)part);
840         if (content == NULL) {
841                 /* charset might not be right here, but it'll get the right stuff
842                    if it is ever set */
843                 *charsetp = NULL;
844                 return CAMEL_TRANSFER_ENCODING_DEFAULT;
845         }
846         
847         istext = camel_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*");
848         if (istext) {
849                 flags = CAMEL_BESTENC_GET_CHARSET | CAMEL_BESTENC_GET_ENCODING;
850                 enctype |= CAMEL_BESTENC_TEXT;
851         } else {
852                 flags = CAMEL_BESTENC_GET_ENCODING;
853         }
854         
855         /* when building the message, any encoded parts are translated already */
856         flags |= CAMEL_BESTENC_LF_IS_CRLF;
857         /* and get any flags the caller passed in */
858         callerflags = (required & CAMEL_BESTENC_NO_FROM);
859         flags |= callerflags;
860         
861         /* first a null stream, so any filtering is thrown away; we only want the sideeffects */
862         null = (CamelStream *)camel_stream_null_new ();
863         filter = camel_stream_filter_new_with_stream (null);
864         
865         /* if we're looking for the best charset, then we need to convert to UTF-8 */
866         if (istext && (required & CAMEL_BESTENC_GET_CHARSET) != 0
867             && (charsetin = camel_content_type_param (content->mime_type, "charset"))) {
868                 charenc = camel_mime_filter_charset_new_convert (charsetin, "UTF-8");
869                 if (charenc != NULL)
870                         idc = camel_stream_filter_add (filter, (CamelMimeFilter *)charenc);
871                 charsetin = NULL;
872         }
873         
874         bestenc = camel_mime_filter_bestenc_new (flags);
875         idb = camel_stream_filter_add (filter, (CamelMimeFilter *)bestenc);
876         d(printf("writing to checking stream\n"));
877         camel_data_wrapper_decode_to_stream (content, (CamelStream *)filter);
878         camel_stream_filter_remove (filter, idb);
879         if (idc != -1) {
880                 camel_stream_filter_remove (filter, idc);
881                 camel_object_unref (charenc);
882                 charenc = NULL;
883         }
884         
885         if (istext && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
886                 charsetin = camel_mime_filter_bestenc_get_best_charset (bestenc);
887                 d(printf("best charset = %s\n", charsetin ? charsetin : "(null)"));
888                 charset = g_strdup (charsetin);
889                 
890                 charsetin = camel_content_type_param (content->mime_type, "charset");
891         } else {
892                 charset = NULL;
893         }
894         
895         /* if we have US-ASCII, or we're not doing text, we dont need to bother with the rest */
896         if (istext && charsetin && charset && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
897                 d(printf("have charset, trying conversion/etc\n"));
898                 
899                 /* now that 'bestenc' has told us what the best encoding is, we can use that to create
900                    a charset conversion filter as well, and then re-add the bestenc to filter the
901                    result to find the best encoding to use as well */
902                 
903                 charenc = camel_mime_filter_charset_new_convert (charsetin, charset);
904                 if (charenc != NULL) {
905                         /* otherwise, try another pass, converting to the real charset */
906                         
907                         camel_mime_filter_reset ((CamelMimeFilter *)bestenc);
908                         camel_mime_filter_bestenc_set_flags (bestenc, CAMEL_BESTENC_GET_ENCODING |
909                                                              CAMEL_BESTENC_LF_IS_CRLF | callerflags);
910                         
911                         camel_stream_filter_add (filter, (CamelMimeFilter *)charenc);
912                         camel_stream_filter_add (filter, (CamelMimeFilter *)bestenc);
913                         
914                         /* and write it to the new stream */
915                         camel_data_wrapper_write_to_stream (content, (CamelStream *)filter);
916                         
917                         camel_object_unref (charenc);
918                 }
919         }
920         
921         encoding = camel_mime_filter_bestenc_get_best_encoding (bestenc, enctype);
922         
923         camel_object_unref (filter);
924         camel_object_unref (bestenc);
925         camel_object_unref (null);
926         
927         d(printf("done, best encoding = %d\n", encoding));
928         
929         if (charsetp)
930                 *charsetp = charset;
931         else
932                 g_free (charset);
933         
934         return encoding;
935 }
936
937 struct _enc_data {
938         CamelBestencRequired required;
939         CamelBestencEncoding enctype;
940 };
941
942 static gboolean
943 best_encoding (CamelMimeMessage *msg, CamelMimePart *part, void *datap)
944 {
945         struct _enc_data *data = datap;
946         CamelTransferEncoding encoding;
947         CamelDataWrapper *wrapper;
948         char *charset;
949         
950         wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part));
951         if (!wrapper)
952                 return FALSE;
953         
954         /* we only care about actual content objects */
955         if (!CAMEL_IS_MULTIPART (wrapper) && !CAMEL_IS_MIME_MESSAGE (wrapper)) {
956                 encoding = find_best_encoding (part, data->required, data->enctype, &charset);
957                 /* we always set the encoding, if we got this far.  GET_CHARSET implies
958                    also GET_ENCODING */
959                 camel_mime_part_set_encoding (part, encoding);
960                 
961                 if ((data->required & CAMEL_BESTENC_GET_CHARSET) != 0) {
962                         if (camel_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*")) {
963                                 char *newct;
964                                 
965                                 /* FIXME: ick, the part content_type interface needs fixing bigtime */
966                                 camel_content_type_set_param (((CamelDataWrapper *) part)->mime_type, "charset",
967                                                                charset ? charset : "us-ascii");
968                                 newct = camel_content_type_format (((CamelDataWrapper *) part)->mime_type);
969                                 if (newct) {
970                                         d(printf("Setting content-type to %s\n", newct));
971                                         
972                                         camel_mime_part_set_content_type (part, newct);
973                                         g_free (newct);
974                                 }
975                         }
976                 }
977                 
978                 g_free (charset);
979         }
980         
981         return TRUE;
982 }
983
984
985 /**
986  * camel_mime_message_set_best_encoding:
987  * @message: a #CamelMimeMessage object
988  * @required: a bitwise ORing of #CAMEL_BESTENC_GET_ENCODING and #CAMEL_BESTENC_GET_CHARSET
989  * @enctype: an encoding to enforce
990  *
991  * Re-encode all message parts to conform with the required encoding rules.
992  *
993  * If @enctype is #CAMEL_BESTENC_7BIT, then all parts will be re-encoded into
994  * one of the 7bit transfer encodings. If @enctype is #CAMEL_BESTENC_8bit, all
995  * parts will be re-encoded to either a 7bit encoding or, if the part is 8bit
996  * text, allowed to stay 8bit. If @enctype is #CAMEL_BESTENC_BINARY, then binary
997  * parts will be encoded as binary and 8bit textual parts will be encoded as 8bit.
998  **/
999 void
1000 camel_mime_message_set_best_encoding (CamelMimeMessage *msg, CamelBestencRequired required, CamelBestencEncoding enctype)
1001 {
1002         struct _enc_data data;
1003         
1004         if ((required & (CAMEL_BESTENC_GET_ENCODING|CAMEL_BESTENC_GET_CHARSET)) == 0)
1005                 return;
1006         
1007         data.required = required;
1008         data.enctype = enctype;
1009         
1010         camel_mime_message_foreach_part (msg, best_encoding, &data);
1011 }
1012
1013
1014 /**
1015  * camel_mime_message_encode_8bit_parts:
1016  * @message: a #CamelMimeMessage object
1017  *
1018  * Encode all message parts to a suitable transfer encoding for transport (7bit clean).
1019  **/
1020 void
1021 camel_mime_message_encode_8bit_parts (CamelMimeMessage *mime_message)
1022 {
1023         camel_mime_message_set_best_encoding (mime_message, CAMEL_BESTENC_GET_ENCODING, CAMEL_BESTENC_7BIT);
1024 }
1025
1026
1027 struct _check_content_id {
1028         CamelMimePart *part;
1029         const char *content_id;
1030 };
1031
1032 static gboolean
1033 check_content_id (CamelMimeMessage *message, CamelMimePart *part, void *data)
1034 {
1035         struct _check_content_id *check = (struct _check_content_id *) data;
1036         const char *content_id;
1037         gboolean found;
1038         
1039         content_id = camel_mime_part_get_content_id (part);
1040         
1041         found = content_id && !strcmp (content_id, check->content_id) ? TRUE : FALSE;
1042         if (found) {
1043                 check->part = part;
1044                 camel_object_ref (part);
1045         }
1046         
1047         return !found;
1048 }
1049
1050
1051 /**
1052  * camel_mime_message_get_part_by_content_id:
1053  * @message: a #CamelMimeMessage object
1054  * @content_id: content-id to search for
1055  *
1056  * Get a MIME part by id from a message.
1057  *
1058  * Returns the MIME part with the requested id or %NULL if not found
1059  **/
1060 CamelMimePart *
1061 camel_mime_message_get_part_by_content_id (CamelMimeMessage *message, const char *id)
1062 {
1063         struct _check_content_id check;
1064         
1065         g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
1066         
1067         if (id == NULL)
1068                 return NULL;
1069         
1070         check.content_id = id;
1071         check.part = NULL;
1072         
1073         camel_mime_message_foreach_part (message, check_content_id, &check);
1074         
1075         return check.part;
1076 }
1077
1078 static const char tz_months[][4] = {
1079         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1080         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1081 };
1082
1083 static const char tz_days[][4] = {
1084         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1085 };
1086
1087
1088 /**
1089  * camel_mime_message_build_mbox_from:
1090  * @message: a #CamelMimeMessage object
1091  *
1092  * Build an MBox from-line from @message.
1093  *
1094  * Returns an MBox from-line suitable for use in an mbox file
1095  **/
1096 char *
1097 camel_mime_message_build_mbox_from (CamelMimeMessage *message)
1098 {
1099         struct _camel_header_raw *header = ((CamelMimePart *)message)->headers;
1100         GString *out = g_string_new("From ");
1101         char *ret;
1102         const char *tmp;
1103         time_t thetime;
1104         int offset;
1105         struct tm tm;
1106         
1107         tmp = camel_header_raw_find (&header, "Sender", NULL);
1108         if (tmp == NULL)
1109                 tmp = camel_header_raw_find (&header, "From", NULL);
1110         if (tmp != NULL) {
1111                 struct _camel_header_address *addr = camel_header_address_decode (tmp, NULL);
1112                 
1113                 tmp = NULL;
1114                 if (addr) {
1115                         if (addr->type == CAMEL_HEADER_ADDRESS_NAME) {
1116                                 g_string_append (out, addr->v.addr);
1117                                 tmp = "";
1118                         }
1119                         camel_header_address_unref (addr);
1120                 }
1121         }
1122         
1123         if (tmp == NULL)
1124                 g_string_append (out, "unknown@nodomain.now.au");
1125         
1126         /* try use the received header to get the date */
1127         tmp = camel_header_raw_find (&header, "Received", NULL);
1128         if (tmp) {
1129                 tmp = strrchr(tmp, ';');
1130                 if (tmp)
1131                         tmp++;
1132         }
1133         
1134         /* if there isn't one, try the Date field */
1135         if (tmp == NULL)
1136                 tmp = camel_header_raw_find (&header, "Date", NULL);
1137         
1138         thetime = camel_header_decode_date (tmp, &offset);
1139         thetime += ((offset / 100) * (60 * 60)) + (offset % 100) * 60;
1140         gmtime_r (&thetime, &tm);
1141         g_string_append_printf (out, " %s %s %2d %02d:%02d:%02d %4d\n",
1142                                 tz_days[tm.tm_wday], tz_months[tm.tm_mon],
1143                                 tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
1144                                 tm.tm_year + 1900);
1145         
1146         ret = out->str;
1147         g_string_free (out, FALSE);
1148         
1149         return ret;
1150 }
1151
1152 static void
1153 cmm_dump_rec(CamelMimeMessage *msg, CamelMimePart *part, int body, int depth)
1154 {
1155         CamelDataWrapper *containee;
1156         int parts, i;
1157         int go = TRUE;
1158         char *s;
1159
1160         s = alloca(depth+1);
1161         memset(s, ' ', depth);
1162         s[depth] = 0;
1163         /* yes this leaks, so what its only debug stuff */
1164         printf("%sclass: %s\n", s, ((CamelObject *)part)->klass->name);
1165         printf("%smime-type: %s\n", s, camel_content_type_format(((CamelDataWrapper *)part)->mime_type));
1166
1167         containee = camel_medium_get_content_object((CamelMedium *)part);
1168         
1169         if (containee == NULL)
1170                 return;
1171
1172         printf("%scontent class: %s\n", s, ((CamelObject *)containee)->klass->name);
1173         printf("%scontent mime-type: %s\n", s, camel_content_type_format(((CamelDataWrapper *)containee)->mime_type));
1174         
1175         /* using the object types is more accurate than using the mime/types */
1176         if (CAMEL_IS_MULTIPART(containee)) {
1177                 parts = camel_multipart_get_number((CamelMultipart *)containee);
1178                 for (i = 0; go && i < parts; i++) {
1179                         CamelMimePart *mpart = camel_multipart_get_part((CamelMultipart *)containee, i);
1180                         
1181                         cmm_dump_rec(msg, mpart, body, depth+2);
1182                 }
1183         } else if (CAMEL_IS_MIME_MESSAGE(containee)) {
1184                 cmm_dump_rec(msg, (CamelMimePart *)containee, body, depth+2);
1185         }
1186 }
1187
1188 /**
1189  * camel_mime_message_dump:
1190  * @msg: 
1191  * @body: 
1192  * 
1193  * Dump information about the mime message to stdout.
1194  *
1195  * If body is TRUE, then dump body content of the message as well (currently unimplemented).
1196  **/
1197 void
1198 camel_mime_message_dump(CamelMimeMessage *msg, int body)
1199 {
1200         cmm_dump_rec(msg, (CamelMimePart *)msg, body, 0);
1201 }