Updated for string-utils namespace changes.
[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, 2000 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 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 General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23  * USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <ctype.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <errno.h>
34
35 #include <gal/util/e-iconv.h>
36
37 #include <e-util/e-time-utils.h>
38
39 #include "camel-mime-message.h"
40 #include "camel-multipart.h"
41 #include "camel-stream-mem.h"
42 #include "camel-string-utils.h"
43 #include "camel-url.h"
44
45 #include "camel-stream-filter.h"
46 #include "camel-stream-null.h"
47 #include "camel-mime-filter-charset.h"
48 #include "camel-mime-filter-bestenc.h"
49
50 #define d(x)
51
52 /* these 2 below should be kept in sync */
53 typedef enum {
54         HEADER_UNKNOWN,
55         HEADER_FROM,
56         HEADER_REPLY_TO,
57         HEADER_SUBJECT,
58         HEADER_TO,
59         HEADER_RESENT_TO,
60         HEADER_CC,
61         HEADER_RESENT_CC,
62         HEADER_BCC,
63         HEADER_RESENT_BCC,
64         HEADER_DATE,
65         HEADER_MESSAGE_ID
66 } CamelHeaderType;
67
68 static char *header_names[] = {
69         /* dont include HEADER_UNKNOWN string */
70         "From", "Reply-To", "Subject", "To", "Resent-To", "Cc", "Resent-Cc",
71         "Bcc", "Resent-Bcc", "Date", "Message-Id", NULL
72 };
73
74 static GHashTable *header_name_table;
75
76 static CamelMimePartClass *parent_class = NULL;
77
78 static char *recipient_names[] = {
79         "To", "Cc", "Bcc", "Resent-To", "Resent-Cc", "Resent-Bcc", NULL
80 };
81
82 static int write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream);
83 static void add_header (CamelMedium *medium, const char *header_name, const void *header_value);
84 static void set_header (CamelMedium *medium, const char *header_name, const void *header_value);
85 static void remove_header (CamelMedium *medium, const char *header_name);
86 static int construct_from_parser (CamelMimePart *, CamelMimeParser *);
87 static void unref_recipient (gpointer key, gpointer value, gpointer user_data);
88
89 /* Returns the class for a CamelMimeMessage */
90 #define CMM_CLASS(so) CAMEL_MIME_MESSAGE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
91 #define CDW_CLASS(so) CAMEL_DATA_WRAPPER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
92 #define CMD_CLASS(so) CAMEL_MEDIUM_CLASS (CAMEL_OBJECT_GET_CLASS(so))
93
94 static void
95 camel_mime_message_class_init (CamelMimeMessageClass *camel_mime_message_class)
96 {
97         CamelDataWrapperClass *camel_data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (camel_mime_message_class);
98         CamelMimePartClass *camel_mime_part_class = CAMEL_MIME_PART_CLASS (camel_mime_message_class);
99         CamelMediumClass *camel_medium_class = CAMEL_MEDIUM_CLASS (camel_mime_message_class);
100         int i;
101         
102         parent_class = CAMEL_MIME_PART_CLASS(camel_type_get_global_classfuncs (camel_mime_part_get_type ()));
103
104         header_name_table = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
105         for (i=0;header_names[i];i++)
106                 g_hash_table_insert (header_name_table, header_names[i], GINT_TO_POINTER(i+1));
107
108         /* virtual method overload */
109         camel_data_wrapper_class->write_to_stream = write_to_stream;
110
111         camel_medium_class->add_header = add_header;
112         camel_medium_class->set_header = set_header;
113         camel_medium_class->remove_header = remove_header;
114         
115         camel_mime_part_class->construct_from_parser = construct_from_parser;
116 }
117
118
119 static void
120 camel_mime_message_init (gpointer object, gpointer klass)
121 {
122         CamelMimeMessage *mime_message = (CamelMimeMessage *)object;
123         int i;
124         
125         camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (object), "message/rfc822");
126
127         mime_message->recipients =  g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
128         for (i=0;recipient_names[i];i++) {
129                 g_hash_table_insert(mime_message->recipients, recipient_names[i], camel_internet_address_new());
130         }
131
132         mime_message->subject = NULL;
133         mime_message->reply_to = NULL;
134         mime_message->from = NULL;
135         mime_message->date = CAMEL_MESSAGE_DATE_CURRENT;
136         mime_message->date_offset = 0;
137         mime_message->date_received = CAMEL_MESSAGE_DATE_CURRENT;
138         mime_message->date_received_offset = 0;
139         mime_message->message_id = NULL;
140 }
141
142 static void           
143 camel_mime_message_finalize (CamelObject *object)
144 {
145         CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
146         
147         g_free (message->subject);
148         
149         g_free (message->message_id);
150         
151         if (message->reply_to)
152                 camel_object_unref ((CamelObject *)message->reply_to);
153         
154         if (message->from)
155                 camel_object_unref ((CamelObject *)message->from);
156         
157         g_hash_table_foreach (message->recipients, unref_recipient, NULL);
158         g_hash_table_destroy (message->recipients);
159 }
160
161
162 CamelType
163 camel_mime_message_get_type (void)
164 {
165         static CamelType camel_mime_message_type = CAMEL_INVALID_TYPE;
166         
167         if (camel_mime_message_type == CAMEL_INVALID_TYPE)      {
168                 camel_mime_message_type = camel_type_register (camel_mime_part_get_type(), "CamelMimeMessage",
169                                                                sizeof (CamelMimeMessage),
170                                                                sizeof (CamelMimeMessageClass),
171                                                                (CamelObjectClassInitFunc) camel_mime_message_class_init,
172                                                                NULL,
173                                                                (CamelObjectInitFunc) camel_mime_message_init,
174                                                                (CamelObjectFinalizeFunc) camel_mime_message_finalize);
175         }
176         
177         return camel_mime_message_type;
178 }
179
180 static void
181 unref_recipient (gpointer key, gpointer value, gpointer user_data)
182 {
183         camel_object_unref (CAMEL_OBJECT (value));
184 }
185
186 CamelMimeMessage *
187 camel_mime_message_new (void) 
188 {
189         CamelMimeMessage *mime_message;
190         mime_message = CAMEL_MIME_MESSAGE (camel_object_new (CAMEL_MIME_MESSAGE_TYPE));
191         
192         return mime_message;
193 }
194
195 /* **** Date: */
196
197 void
198 camel_mime_message_set_date (CamelMimeMessage *message,  time_t date, int offset)
199 {
200         char *datestr;
201         
202         g_assert(message);
203         
204         if (date == CAMEL_MESSAGE_DATE_CURRENT) {
205                 struct tm local;
206                 int tz;
207                 
208                 date = time(0);
209                 e_localtime_with_offset(date, &local, &tz);
210                 offset = (((tz/60/60) * 100) + (tz/60 % 60));
211         }
212         message->date = date;
213         message->date_offset = offset;
214         
215         datestr = header_format_date (date, offset);
216         CAMEL_MEDIUM_CLASS (parent_class)->set_header ((CamelMedium *)message, "Date", datestr);
217         g_free (datestr);
218 }
219
220 time_t
221 camel_mime_message_get_date (CamelMimeMessage *msg, int *offset)
222 {
223         if (offset)
224                 *offset = msg->date_offset;
225         
226         return msg->date;
227 }
228
229 time_t
230 camel_mime_message_get_date_received (CamelMimeMessage *msg, int *offset)
231 {
232         if (msg->date_received == CAMEL_MESSAGE_DATE_CURRENT) {
233                 const char *received;
234                 
235                 received = camel_medium_get_header ((CamelMedium *)msg, "received");
236                 if (received)
237                         received = strrchr (received, ';');
238                 if (received)
239                         msg->date_received = header_decode_date (received + 1, &msg->date_received_offset);
240         }
241         
242         if (offset)
243                 *offset = msg->date_received_offset;
244         
245         return msg->date_received;
246 }
247
248 /* **** Message-Id: */
249
250 void
251 camel_mime_message_set_message_id (CamelMimeMessage *mime_message, const char *message_id)
252 {
253         char *id;
254         
255         g_assert (mime_message);
256         
257         g_free (mime_message->message_id);
258         
259         if (message_id) {
260                 id = g_strstrip (g_strdup (message_id));
261         } else {
262                 id = header_msgid_generate ();
263         }
264         
265         mime_message->message_id = id;
266         id = g_strdup_printf ("<%s>", mime_message->message_id);
267         CAMEL_MEDIUM_CLASS (parent_class)->set_header (CAMEL_MEDIUM (mime_message), "Message-Id", id);
268         g_free (id);
269 }
270
271 const char *
272 camel_mime_message_get_message_id (CamelMimeMessage *mime_message)
273 {
274         g_assert (mime_message);
275         
276         return mime_message->message_id;
277 }
278
279 /* **** Reply-To: */
280
281 void
282 camel_mime_message_set_reply_to (CamelMimeMessage *msg, const CamelInternetAddress *reply_to)
283 {
284         char *addr;
285         
286         g_assert(msg);
287         
288         if (msg->reply_to) {
289                 camel_object_unref ((CamelObject *)msg->reply_to);
290                 msg->reply_to = NULL;
291         }
292         
293         if (reply_to == NULL) {
294                 CAMEL_MEDIUM_CLASS (parent_class)->remove_header (CAMEL_MEDIUM (msg), "Reply-To");
295                 return;
296         }
297         
298         msg->reply_to = (CamelInternetAddress *)camel_address_new_clone ((CamelAddress *)reply_to);
299         addr = camel_address_encode ((CamelAddress *)msg->reply_to);
300         CAMEL_MEDIUM_CLASS (parent_class)->set_header (CAMEL_MEDIUM (msg), "Reply-To", addr);
301         g_free (addr);
302 }
303
304 const CamelInternetAddress *
305 camel_mime_message_get_reply_to (CamelMimeMessage *mime_message)
306 {
307         g_assert (mime_message);
308         
309         /* TODO: ref for threading? */
310         
311         return mime_message->reply_to;
312 }
313
314 /* **** Subject: */
315
316 void
317 camel_mime_message_set_subject (CamelMimeMessage *mime_message, const char *subject)
318 {
319         char *text;
320         
321         g_assert(mime_message);
322         
323         g_free (mime_message->subject);
324         mime_message->subject = g_strstrip (g_strdup (subject));
325         text = header_encode_string((unsigned char *)mime_message->subject);
326         CAMEL_MEDIUM_CLASS(parent_class)->set_header(CAMEL_MEDIUM (mime_message), "Subject", text);
327         g_free (text);
328 }
329
330 const char *
331 camel_mime_message_get_subject (CamelMimeMessage *mime_message)
332 {
333         g_assert(mime_message);
334         
335         return mime_message->subject;
336 }
337
338 /* *** From: */
339
340 /* Thought: Since get_from/set_from are so rarely called, it is probably not useful
341    to cache the from (and reply_to) addresses as InternetAddresses internally, we
342    could just get it from the headers and reprocess every time. */
343 void
344 camel_mime_message_set_from (CamelMimeMessage *msg, const CamelInternetAddress *from)
345 {
346         char *addr;
347         
348         g_assert(msg);
349         
350         if (msg->from) {
351                 camel_object_unref((CamelObject *)msg->from);
352                 msg->from = NULL;
353         }
354         
355         if (from == NULL || camel_address_length((CamelAddress *)from) == 0) {
356                 CAMEL_MEDIUM_CLASS(parent_class)->remove_header(CAMEL_MEDIUM(msg), "From");
357                 return;
358         }
359         
360         msg->from = (CamelInternetAddress *)camel_address_new_clone((CamelAddress *)from);
361         addr = camel_address_encode((CamelAddress *)msg->from);
362         CAMEL_MEDIUM_CLASS (parent_class)->set_header(CAMEL_MEDIUM(msg), "From", addr);
363         g_free(addr);
364 }
365
366 const CamelInternetAddress *
367 camel_mime_message_get_from (CamelMimeMessage *mime_message)
368 {
369         g_assert (mime_message);
370         
371         /* TODO: we should really ref this for multi-threading to work */
372         
373         return mime_message->from;
374 }
375
376 /*  **** To: Cc: Bcc: */
377
378 void
379 camel_mime_message_set_recipients(CamelMimeMessage *mime_message, const char *type, const CamelInternetAddress *r)
380 {
381         char *text;
382         CamelInternetAddress *addr;
383         
384         g_assert(mime_message);
385         
386         addr = g_hash_table_lookup (mime_message->recipients, type);
387         if (addr == NULL) {
388                 g_warning ("trying to set a non-valid receipient type: %s", type);
389                 return;
390         }
391         
392         if (r == NULL || camel_address_length ((CamelAddress *)r) == 0) {
393                 camel_address_remove ((CamelAddress *)addr, -1);
394                 CAMEL_MEDIUM_CLASS (parent_class)->remove_header (CAMEL_MEDIUM (mime_message), type);
395                 return;
396         }
397         
398         /* note this does copy, and not append (cat) */
399         camel_address_copy ((CamelAddress *)addr, (const CamelAddress *)r);
400         
401         /* and sync our headers */
402         text = camel_address_encode (CAMEL_ADDRESS (addr));
403         CAMEL_MEDIUM_CLASS (parent_class)->set_header (CAMEL_MEDIUM (mime_message), type, text);
404         g_free(text);
405 }
406
407 void
408 camel_mime_message_set_source (CamelMimeMessage *mime_message, const char *src)
409 {
410         CamelURL *url;
411         char *uri;
412         
413         g_assert (mime_message);
414         
415         url = camel_url_new (src, NULL);
416         if (url) {
417                 uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
418                 camel_medium_add_header (CAMEL_MEDIUM (mime_message), "X-Evolution-Source", uri);
419                 g_free (uri);
420                 camel_url_free (url);
421         }
422 }
423
424 const char *
425 camel_mime_message_get_source (CamelMimeMessage *mime_message)
426 {
427         const char *src;
428         
429         g_assert(mime_message);
430         
431         src = camel_medium_get_header (CAMEL_MEDIUM (mime_message), "X-Evolution-Source");
432         if (src) {
433                 while (*src && isspace ((unsigned) *src))
434                         ++src;
435         }
436         return src;
437 }
438
439 const CamelInternetAddress *
440 camel_mime_message_get_recipients (CamelMimeMessage *mime_message, const char *type)
441 {
442         g_assert(mime_message);
443         
444         return g_hash_table_lookup (mime_message->recipients, type);
445 }
446
447 /* mime_message */
448 static int
449 construct_from_parser (CamelMimePart *dw, CamelMimeParser *mp)
450 {
451         char *buf;
452         size_t len;
453         int state;
454         int ret;
455         int err;
456
457         d(printf("constructing mime-message\n"));
458
459         d(printf("mime_message::construct_from_parser()\n"));
460
461         /* let the mime-part construct the guts ... */
462         ret = ((CamelMimePartClass *)parent_class)->construct_from_parser(dw, mp);
463
464         if (ret == -1)
465                 return -1;
466
467         /* ... then clean up the follow-on state */
468         state = camel_mime_parser_step (mp, &buf, &len);
469         switch (state) {
470         case HSCAN_EOF: case HSCAN_FROM_END: /* these doesn't belong to us */
471                 camel_mime_parser_unstep (mp);
472         case HSCAN_MESSAGE_END:
473                 break;
474         default:
475                 g_error ("Bad parser state: Expecing MESSAGE_END or EOF or EOM, got: %d", camel_mime_parser_state (mp));
476                 camel_mime_parser_unstep (mp);
477                 return -1;
478         }
479
480         d(printf("mime_message::construct_from_parser() leaving\n"));
481         err = camel_mime_parser_errno(mp);
482         if (err != 0) {
483                 errno = err;
484                 ret = -1;
485         }
486
487         return ret;
488 }
489
490 static int
491 write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream)
492 {
493         CamelMimeMessage *mm = CAMEL_MIME_MESSAGE (data_wrapper);
494         
495         /* force mandatory headers ... */
496         if (mm->from == NULL) {
497                 /* FIXME: should we just abort?  Should we make one up? */
498                 g_warning ("No from set for message");
499                 camel_medium_set_header ((CamelMedium *)mm, "From", "");
500         }
501         if (!camel_medium_get_header ((CamelMedium *)mm, "Date"))
502                 camel_mime_message_set_date (mm, CAMEL_MESSAGE_DATE_CURRENT, 0);
503         
504         if (mm->subject == NULL)
505                 camel_mime_message_set_subject (mm, "No Subject");
506         
507         if (mm->message_id == NULL)
508                 camel_mime_message_set_message_id (mm, NULL);
509         
510         /* FIXME: "To" header needs to be set explicitly as well ... */
511         
512         if (!camel_medium_get_header ((CamelMedium *)mm, "Mime-Version"))
513                 camel_medium_set_header ((CamelMedium *)mm, "Mime-Version", "1.0");
514         
515         return CAMEL_DATA_WRAPPER_CLASS (parent_class)->write_to_stream (data_wrapper, stream);
516 }
517
518 /* FIXME: check format of fields. */
519 static gboolean
520 process_header (CamelMedium *medium, const char *header_name, const char *header_value)
521 {
522         CamelHeaderType header_type;
523         CamelMimeMessage *message = CAMEL_MIME_MESSAGE (medium);
524         CamelInternetAddress *addr;
525         const char *charset;
526         
527         header_type = (CamelHeaderType)g_hash_table_lookup (header_name_table, header_name);
528         switch (header_type) {
529         case HEADER_FROM:
530                 if (message->from)
531                         camel_object_unref (CAMEL_OBJECT (message->from));
532                 message->from = camel_internet_address_new ();
533                 camel_address_decode (CAMEL_ADDRESS (message->from), header_value);
534                 break;
535         case HEADER_REPLY_TO:
536                 if (message->reply_to)
537                         camel_object_unref (CAMEL_OBJECT (message->reply_to));
538                 message->reply_to = camel_internet_address_new ();
539                 camel_address_decode (CAMEL_ADDRESS (message->reply_to), header_value);
540                 break;
541         case HEADER_SUBJECT:
542                 g_free (message->subject);
543                 if (((CamelMimePart *) message)->content_type) {
544                         charset = header_content_type_param (((CamelMimePart *) message)->content_type, "charset");
545                         charset = e_iconv_charset_name (charset);
546                 } else
547                         charset = NULL;
548                 message->subject = g_strstrip (header_decode_string (header_value, charset));
549                 break;
550         case HEADER_TO:
551         case HEADER_CC:
552         case HEADER_BCC:
553         case HEADER_RESENT_TO:
554         case HEADER_RESENT_CC:
555         case HEADER_RESENT_BCC:
556                 addr = g_hash_table_lookup (message->recipients, header_name);
557                 if (header_value)
558                         camel_address_decode (CAMEL_ADDRESS (addr), header_value);
559                 else
560                         camel_address_remove (CAMEL_ADDRESS (addr), -1);
561                 break;
562         case HEADER_DATE:
563                 if (header_value) {
564                         message->date = header_decode_date (header_value, &message->date_offset);
565                 } else {
566                         message->date = CAMEL_MESSAGE_DATE_CURRENT;
567                         message->date_offset = 0;
568                 }
569                 break;
570         case HEADER_MESSAGE_ID:
571                 g_free (message->message_id);
572                 if (header_value)
573                         message->message_id = header_msgid_decode (header_value);
574                 else
575                         message->message_id = NULL;
576                 break;
577         default:
578                 return FALSE;
579         }
580         
581         return TRUE;
582 }
583
584 static void
585 set_header (CamelMedium *medium, const char *header_name, const void *header_value)
586 {
587         process_header (medium, header_name, header_value);
588         parent_class->parent_class.set_header (medium, header_name, header_value);
589 }
590
591 static void
592 add_header (CamelMedium *medium, const char *header_name, const void *header_value)
593 {
594         /* if we process it, then it must be forced unique as well ... */
595         if (process_header (medium, header_name, header_value))
596                 parent_class->parent_class.set_header (medium, header_name, header_value);
597         else
598                 parent_class->parent_class.add_header (medium, header_name, header_value);
599 }
600
601 static void
602 remove_header (CamelMedium *medium, const char *header_name)
603 {
604         process_header (medium, header_name, NULL);
605         parent_class->parent_class.remove_header (medium, header_name);
606 }
607
608 typedef gboolean (*CamelPartFunc)(CamelMimeMessage *, CamelMimePart *, void *data);
609
610 static gboolean
611 message_foreach_part_rec (CamelMimeMessage *msg, CamelMimePart *part, CamelPartFunc callback, void *data)
612 {
613         CamelDataWrapper *containee;
614         int parts, i;
615         int go = TRUE;
616         
617         if (callback (msg, part, data) == FALSE)
618                 return FALSE;
619         
620         containee = camel_medium_get_content_object (CAMEL_MEDIUM (part));
621         
622         if (containee == NULL)
623                 return go;
624         
625         /* using the object types is more accurate than using the mime/types */
626         if (CAMEL_IS_MULTIPART (containee)) {
627                 parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
628                 for (i = 0; go && i < parts; i++) {
629                         CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
630                         
631                         go = message_foreach_part_rec (msg, part, callback, data);
632                 }
633         } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
634                 go = message_foreach_part_rec (msg, (CamelMimePart *)containee, callback, data);
635         }
636         
637         return go;
638 }
639
640 /* dont make this public yet, it might need some more thinking ... */
641 /* MPZ */
642 static void
643 camel_mime_message_foreach_part (CamelMimeMessage *msg, CamelPartFunc callback, void *data)
644 {
645         message_foreach_part_rec (msg, (CamelMimePart *)msg, callback, data);
646 }
647
648 static gboolean
649 check_8bit (CamelMimeMessage *msg, CamelMimePart *part, void *data)
650 {
651         CamelMimePartEncodingType encoding;
652         int *has8bit = data;
653         
654         /* check this part, and stop as soon as we are done */
655         encoding = camel_mime_part_get_encoding (part);
656         
657         *has8bit = encoding == CAMEL_MIME_PART_ENCODING_8BIT || encoding == CAMEL_MIME_PART_ENCODING_BINARY;
658         
659         return !(*has8bit);
660 }
661
662 gboolean
663 camel_mime_message_has_8bit_parts (CamelMimeMessage *msg)
664 {
665         int has8bit = FALSE;
666         
667         camel_mime_message_foreach_part (msg, check_8bit, &has8bit);
668         
669         return has8bit;
670 }
671
672 /* finds the best charset and transfer encoding for a given part */
673 static CamelMimePartEncodingType
674 find_best_encoding (CamelMimePart *part, CamelBestencRequired required, CamelBestencEncoding enctype, char **charsetp)
675 {
676         const char *charsetin = NULL;
677         char *charset = NULL;
678         CamelStream *null;
679         CamelStreamFilter *filter;
680         CamelMimeFilterCharset *charenc = NULL;
681         CamelMimeFilterBestenc *bestenc;
682         int idb, idc = -1;
683         gboolean istext;
684         unsigned int flags, callerflags;
685         CamelMimePartEncodingType encoding;
686         CamelDataWrapper *content;
687         
688         /* we use all these weird stream things so we can do it with streams, and
689            not have to read the whole lot into memory - although i have a feeling
690            it would make things a fair bit simpler to do so ... */
691         
692         d(printf("starting to check part\n"));
693         
694         content = camel_medium_get_content_object ((CamelMedium *)part);
695         if (content == NULL) {
696                 /* charset might not be right here, but it'll get the right stuff
697                    if it is ever set */
698                 *charsetp = NULL;
699                 return CAMEL_MIME_PART_ENCODING_DEFAULT;
700         }
701         
702         istext = header_content_type_is (part->content_type, "text", "*");
703         if (istext) {
704                 flags = CAMEL_BESTENC_GET_CHARSET | CAMEL_BESTENC_GET_ENCODING;
705                 enctype |= CAMEL_BESTENC_TEXT;
706         } else {
707                 flags = CAMEL_BESTENC_GET_ENCODING;
708         }
709         
710         /* when building the message, any encoded parts are translated already */
711         flags |= CAMEL_BESTENC_LF_IS_CRLF;
712         /* and get any flags the caller passed in */
713         callerflags = (required & CAMEL_BESTENC_NO_FROM);
714         flags |= callerflags;
715         
716         /* first a null stream, so any filtering is thrown away; we only want the sideeffects */
717         null = (CamelStream *)camel_stream_null_new ();
718         filter = camel_stream_filter_new_with_stream (null);
719         
720         /* if we're not looking for the best charset, then use the one we have */
721         if (istext && (required & CAMEL_BESTENC_GET_CHARSET) == 0
722             && (charsetin = header_content_type_param (part->content_type, "charset"))) {
723                 /* if libunicode doesn't support it, we dont really have utf8 anyway, so
724                    we dont need a converter */
725                 charenc = camel_mime_filter_charset_new_convert ("UTF-8", charsetin);
726                 if (charenc != NULL)
727                         idc = camel_stream_filter_add (filter, (CamelMimeFilter *)charenc);
728                 charsetin = NULL;
729         }
730         
731         bestenc = camel_mime_filter_bestenc_new (flags);
732         idb = camel_stream_filter_add (filter, (CamelMimeFilter *)bestenc);
733         d(printf("writing to checking stream\n"));
734         camel_data_wrapper_write_to_stream (content, (CamelStream *)filter);
735         camel_stream_filter_remove (filter, idb);
736         if (idc != -1) {
737                 camel_stream_filter_remove (filter, idc);
738                 camel_object_unref ((CamelObject *)charenc);
739                 charenc = NULL;
740         }
741         
742         if (istext)
743                 charsetin = camel_mime_filter_bestenc_get_best_charset (bestenc);
744         
745         d(printf("charsetin = %s\n", charsetin ? charsetin : "(null)"));
746         
747         /* if we have US-ASCII, or we're not doing text, we dont need to bother with the rest */
748         if (charsetin != NULL && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
749                 charset = g_strdup (charsetin);
750                 
751                 d(printf("have charset, trying conversion/etc\n"));
752                 
753                 /* now the 'bestenc' can has told us what the best encoding is, we can use that to create
754                    a charset conversion filter as well, and then re-add the bestenc to filter the
755                    result to find the best encoding to use as well */
756                 
757                 charenc = camel_mime_filter_charset_new_convert ("UTF-8", charset);
758                 
759                 /* eek, libunicode doesn't undertand this charset anyway, then the 'utf8' we
760                    thought we had is really the native format, in which case, we just treat
761                    it as binary data (and take the result we have so far) */
762                 
763                 if (charenc != NULL) {
764                         /* otherwise, try another pass, converting to the real charset */
765                         
766                         camel_mime_filter_reset ((CamelMimeFilter *)bestenc);
767                         camel_mime_filter_bestenc_set_flags (bestenc, CAMEL_BESTENC_GET_ENCODING |
768                                                              CAMEL_BESTENC_LF_IS_CRLF | callerflags);
769                         
770                         camel_stream_filter_add (filter, (CamelMimeFilter *)charenc);
771                         camel_stream_filter_add (filter, (CamelMimeFilter *)bestenc);
772                         
773                         /* and write it to the new stream */
774                         camel_data_wrapper_write_to_stream (content, (CamelStream *)filter);
775                         
776                         camel_object_unref ((CamelObject *)charenc);
777                 }
778         }
779         
780         encoding = camel_mime_filter_bestenc_get_best_encoding (bestenc, enctype);
781         
782         camel_object_unref ((CamelObject *)filter);
783         camel_object_unref ((CamelObject *)bestenc);
784         camel_object_unref ((CamelObject *)null);
785         
786         d(printf("done, best encoding = %d\n", encoding));
787         
788         if (charsetp)
789                 *charsetp = charset;
790         else
791                 g_free (charset);
792         
793         return encoding;
794 }
795
796 struct _enc_data {
797         CamelBestencRequired required;
798         CamelBestencEncoding enctype;
799 };
800
801 static gboolean
802 best_encoding (CamelMimeMessage *msg, CamelMimePart *part, void *datap)
803 {
804         struct _enc_data *data = datap;
805         CamelMimePartEncodingType encoding;
806         CamelDataWrapper *wrapper;
807         char *charset;
808         
809         wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part));
810         if (!wrapper)
811                 return FALSE;
812         
813         /* we only care about actual content objects */
814         if (!CAMEL_IS_MULTIPART (wrapper) && !CAMEL_IS_MIME_MESSAGE (wrapper)) {
815                 encoding = find_best_encoding (part, data->required, data->enctype, &charset);
816                 /* we always set the encoding, if we got this far.  GET_CHARSET implies
817                    also GET_ENCODING */
818                 camel_mime_part_set_encoding (part, encoding);
819                 
820                 if ((data->required & CAMEL_BESTENC_GET_CHARSET) != 0) {
821                         if (header_content_type_is (part->content_type, "text", "*")) {
822                                 char *newct;
823                                 
824                                 /* FIXME: ick, the part content_type interface needs fixing bigtime */
825                                 header_content_type_set_param (part->content_type, "charset",
826                                                                charset ? charset : "us-ascii");
827                                 newct = header_content_type_format (part->content_type);
828                                 if (newct) {
829                                         d(printf("Setting content-type to %s\n", newct));
830                                         
831                                         camel_mime_part_set_content_type (part, newct);
832                                         g_free (newct);
833                                 }
834                         }
835                 }
836         }
837         
838         return TRUE;
839 }
840
841 void
842 camel_mime_message_set_best_encoding (CamelMimeMessage *msg, CamelBestencRequired required, CamelBestencEncoding enctype)
843 {
844         struct _enc_data data;
845         
846         if ((required & (CAMEL_BESTENC_GET_ENCODING|CAMEL_BESTENC_GET_CHARSET)) == 0)
847                 return;
848         
849         data.required = required;
850         data.enctype = enctype;
851         
852         camel_mime_message_foreach_part (msg, best_encoding, &data);
853 }
854
855 void
856 camel_mime_message_encode_8bit_parts (CamelMimeMessage *mime_message)
857 {
858         camel_mime_message_set_best_encoding (mime_message, CAMEL_BESTENC_GET_ENCODING, CAMEL_BESTENC_7BIT);
859 }
860
861
862 struct _check_content_id {
863         CamelMimePart *part;
864         const char *content_id;
865 };
866
867 static gboolean
868 check_content_id (CamelMimeMessage *message, CamelMimePart *part, void *data)
869 {
870         struct _check_content_id *check = (struct _check_content_id *) data;
871         const char *content_id;
872         gboolean found;
873         
874         content_id = camel_mime_part_get_content_id (part);
875         
876         found = content_id && !strcmp (content_id, check->content_id) ? TRUE : FALSE;
877         if (found) {
878                 check->part = part;
879                 camel_object_ref (CAMEL_OBJECT (part));
880         }
881         
882         return !found;
883 }
884
885 CamelMimePart *
886 camel_mime_message_get_part_by_content_id (CamelMimeMessage *message, const char *id)
887 {
888         struct _check_content_id check;
889         
890         g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
891         
892         if (id == NULL)
893                 return NULL;
894         
895         check.content_id = id;
896         check.part = NULL;
897         
898         camel_mime_message_foreach_part (message, check_content_id, &check);
899         
900         return check.part;
901 }
902
903 static char *tz_months[] = {
904         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
905         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
906 };
907
908 static char *tz_days[] = {
909         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
910 };
911
912 char *
913 camel_mime_message_build_mbox_from (CamelMimeMessage *message)
914 {
915         struct _header_raw *header = ((CamelMimePart *)message)->headers;
916         GString *out = g_string_new("From ");
917         char *ret;
918         const char *tmp;
919         time_t thetime;
920         int offset;
921         struct tm tm;
922         
923         tmp = header_raw_find (&header, "Sender", NULL);
924         if (tmp == NULL)
925                 tmp = header_raw_find (&header, "From", NULL);
926         if (tmp != NULL) {
927                 struct _header_address *addr = header_address_decode (tmp, NULL);
928                 
929                 tmp = NULL;
930                 if (addr) {
931                         if (addr->type == HEADER_ADDRESS_NAME) {
932                                 g_string_append (out, addr->v.addr);
933                                 tmp = "";
934                         }
935                         header_address_unref (addr);
936                 }
937         }
938         
939         if (tmp == NULL)
940                 g_string_append (out, "unknown@nodomain.now.au");
941         
942         /* try use the received header to get the date */
943         tmp = header_raw_find (&header, "Received", NULL);
944         if (tmp) {
945                 tmp = strrchr(tmp, ';');
946                 if (tmp)
947                         tmp++;
948         }
949         
950         /* if there isn't one, try the Date field */
951         if (tmp == NULL)
952                 tmp = header_raw_find (&header, "Date", NULL);
953         
954         thetime = header_decode_date (tmp, &offset);
955         thetime += ((offset / 100) * (60 * 60)) + (offset % 100) * 60;
956         gmtime_r (&thetime, &tm);
957         g_string_append_printf (out, " %s %s %2d %02d:%02d:%02d %4d\n",
958                                 tz_days[tm.tm_wday], tz_months[tm.tm_mon],
959                                 tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
960                                 tm.tm_year + 1900);
961         
962         ret = out->str;
963         g_string_free (out, FALSE);
964         
965         return ret;
966 }