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 */
5 * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
6 * Michael Zucchi <notzed@ximian.com>
7 * Jeffrey Stedfast <fejj@ximian.com>
9 * Copyright 1999-2003 Ximian, Inc. (www.ximian.com)
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.
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.
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
35 #include <libedataserver/e-iconv.h>
36 #include <libedataserver/e-time-utils.h>
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"
49 /* Undef the similar macro from pthread.h, it doesn't check if
50 * gmtime() returns NULL.
54 /* The gmtime() in Microsoft's C library is MT-safe */
55 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
59 extern int camel_verbose_debug;
61 /* these 2 below should be kept in sync */
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
83 static GHashTable *header_name_table;
85 static CamelMimePartClass *parent_class = NULL;
87 static char *recipient_names[] = {
88 "To", "Cc", "Bcc", "Resent-To", "Resent-Cc", "Resent-Bcc", NULL
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);
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))
104 camel_mime_message_class_init (CamelMimeMessageClass *camel_mime_message_class)
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);
111 parent_class = CAMEL_MIME_PART_CLASS (camel_type_get_global_classfuncs (camel_mime_part_get_type ()));
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));
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;
121 camel_medium_class->add_header = add_header;
122 camel_medium_class->set_header = set_header;
123 camel_medium_class->remove_header = remove_header;
125 camel_mime_part_class->construct_from_parser = construct_from_parser;
129 camel_mime_message_init (gpointer object, gpointer klass)
131 CamelMimeMessage *mime_message = (CamelMimeMessage *)object;
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());
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;
150 camel_mime_message_finalize (CamelObject *object)
152 CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
154 g_free (message->subject);
156 g_free (message->message_id);
158 if (message->reply_to)
159 camel_object_unref ((CamelObject *)message->reply_to);
162 camel_object_unref ((CamelObject *)message->from);
164 g_hash_table_foreach (message->recipients, unref_recipient, NULL);
165 g_hash_table_destroy (message->recipients);
169 camel_mime_message_get_type (void)
171 static CamelType camel_mime_message_type = CAMEL_INVALID_TYPE;
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,
179 (CamelObjectInitFunc) camel_mime_message_init,
180 (CamelObjectFinalizeFunc) camel_mime_message_finalize);
183 return camel_mime_message_type;
187 unref_recipient (gpointer key, gpointer value, gpointer user_data)
189 camel_object_unref (value);
194 * camel_mime_message_new:
196 * Create a new #CamelMimeMessage object.
198 * Returns a new #CamelMimeMessage object
201 camel_mime_message_new (void)
203 CamelMimeMessage *mime_message;
204 mime_message = CAMEL_MIME_MESSAGE (camel_object_new (CAMEL_MIME_MESSAGE_TYPE));
213 * camel_mime_message_set_date:
214 * @message: a #CamelMimeMessage object
215 * @date: a time_t date
216 * @offset: an offset from GMT
218 * Set the date on a message.
221 camel_mime_message_set_date (CamelMimeMessage *message, time_t date, int offset)
227 if (date == CAMEL_MESSAGE_DATE_CURRENT) {
232 e_localtime_with_offset(date, &local, &tz);
233 offset = (((tz/60/60) * 100) + (tz/60 % 60));
235 message->date = date;
236 message->date_offset = offset;
238 datestr = camel_header_format_date (date, offset);
239 CAMEL_MEDIUM_CLASS (parent_class)->set_header ((CamelMedium *)message, "Date", datestr);
245 * camel_mime_message_get_date:
246 * @message: a #CamelMimeMessage object
247 * @offset: output for the GMT offset
249 * Get the date and GMT offset of a message.
251 * Returns the date of the message
254 camel_mime_message_get_date (CamelMimeMessage *msg, int *offset)
257 *offset = msg->date_offset;
264 * camel_mime_message_get_date_received:
265 * @message: a #CamelMimeMessage object
266 * @offset: output for the GMT offset
268 * Get the received date and GMT offset of a message.
270 * Returns the received date of the message
273 camel_mime_message_get_date_received (CamelMimeMessage *msg, int *offset)
275 if (msg->date_received == CAMEL_MESSAGE_DATE_CURRENT) {
276 const char *received;
278 received = camel_medium_get_header ((CamelMedium *)msg, "received");
280 received = strrchr (received, ';');
282 msg->date_received = camel_header_decode_date (received + 1, &msg->date_received_offset);
286 *offset = msg->date_received_offset;
288 return msg->date_received;
291 /* **** Message-Id: */
294 * camel_mime_message_set_message_id:
295 * @message: a #CamelMimeMessage object
296 * @message_id: id of the message
298 * Set the message-id on a message.
301 camel_mime_message_set_message_id (CamelMimeMessage *mime_message, const char *message_id)
305 g_assert (mime_message);
307 g_free (mime_message->message_id);
310 id = g_strstrip (g_strdup (message_id));
312 id = camel_header_msgid_generate ();
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);
323 * camel_mime_message_get_message_id:
324 * @message: a #CamelMimeMessage object
326 * Get the message-id of a message.
328 * Returns the message-id of a message
331 camel_mime_message_get_message_id (CamelMimeMessage *mime_message)
333 g_assert (mime_message);
335 return mime_message->message_id;
342 * camel_mime_message_set_reply_to:
343 * @message: a #CamelMimeMessage object
344 * @reply_to: a #CamelInternetAddress object
346 * Set the Reply-To of a message.
349 camel_mime_message_set_reply_to (CamelMimeMessage *msg, const CamelInternetAddress *reply_to)
356 camel_object_unref ((CamelObject *)msg->reply_to);
357 msg->reply_to = NULL;
360 if (reply_to == NULL) {
361 CAMEL_MEDIUM_CLASS (parent_class)->remove_header (CAMEL_MEDIUM (msg), "Reply-To");
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);
373 * camel_mime_message_get_reply_to:
374 * @message: a #CamelMimeMessage object
376 * Get the Reply-To of a message.
378 * Returns the Reply-Toa ddress of the message
380 const CamelInternetAddress *
381 camel_mime_message_get_reply_to (CamelMimeMessage *mime_message)
383 g_assert (mime_message);
385 /* TODO: ref for threading? */
387 return mime_message->reply_to;
393 * camel_mime_message_set_subject:
394 * @message: a #CamelMimeMessage object
395 * @subject: UTF-8 message subject
397 * Set the subject text of a message.
400 camel_mime_message_set_subject (CamelMimeMessage *mime_message, const char *subject)
404 g_assert(mime_message);
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);
415 * camel_mime_message_get_subject:
416 * @message: a #CamelMimeMessage object
418 * Get the UTF-8 subject text of a message.
420 * Returns the message subject
423 camel_mime_message_get_subject (CamelMimeMessage *mime_message)
425 g_assert(mime_message);
427 return mime_message->subject;
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. */
437 * camel_mime_message_set_from:
438 * @message: a #CamelMimeMessage object
439 * @from: a #CamelInternetAddress object
441 * Set the from address of a message.
444 camel_mime_message_set_from (CamelMimeMessage *msg, const CamelInternetAddress *from)
451 camel_object_unref((CamelObject *)msg->from);
455 if (from == NULL || camel_address_length((CamelAddress *)from) == 0) {
456 CAMEL_MEDIUM_CLASS(parent_class)->remove_header(CAMEL_MEDIUM(msg), "From");
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);
468 * camel_mime_message_get_from:
469 * @message: a #CamelMimeMessage object
471 * Get the from address of a message.
473 * Returns the from address of the message
475 const CamelInternetAddress *
476 camel_mime_message_get_from (CamelMimeMessage *mime_message)
478 g_assert (mime_message);
480 /* TODO: we should really ref this for multi-threading to work */
482 return mime_message->from;
485 /* **** To: Cc: Bcc: */
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
493 * Set the recipients of a message.
496 camel_mime_message_set_recipients(CamelMimeMessage *mime_message, const char *type, const CamelInternetAddress *r)
499 CamelInternetAddress *addr;
501 g_assert(mime_message);
503 addr = g_hash_table_lookup (mime_message->recipients, type);
505 g_warning ("trying to set a non-valid receipient type: %s", type);
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);
515 /* note this does copy, and not append (cat) */
516 camel_address_copy ((CamelAddress *)addr, (const CamelAddress *)r);
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);
526 * camel_mime_message_get_recipients:
527 * @message: a #CamelMimeMessage object
528 * @type: recipient type
530 * Get the message recipients of a specified type.
532 * Returns the requested recipients
534 const CamelInternetAddress *
535 camel_mime_message_get_recipients (CamelMimeMessage *mime_message, const char *type)
537 g_assert(mime_message);
539 return g_hash_table_lookup (mime_message->recipients, type);
544 camel_mime_message_set_source (CamelMimeMessage *mime_message, const char *src)
549 g_assert (mime_message);
551 url = camel_url_new (src, NULL);
553 uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
554 camel_medium_add_header (CAMEL_MEDIUM (mime_message), "X-Evolution-Source", uri);
556 camel_url_free (url);
561 camel_mime_message_get_source (CamelMimeMessage *mime_message)
565 g_assert(mime_message);
567 src = camel_medium_get_header (CAMEL_MEDIUM (mime_message), "X-Evolution-Source");
569 while (*src && isspace ((unsigned) *src))
577 construct_from_parser (CamelMimePart *dw, CamelMimeParser *mp)
585 d(printf("constructing mime-message\n"));
587 d(printf("mime_message::construct_from_parser()\n"));
589 /* let the mime-part construct the guts ... */
590 ret = ((CamelMimePartClass *)parent_class)->construct_from_parser(dw, mp);
595 /* ... then clean up the follow-on state */
596 state = camel_mime_parser_step (mp, &buf, &len);
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:
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);
608 d(printf("mime_message::construct_from_parser() leaving\n"));
609 err = camel_mime_parser_errno(mp);
619 write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream)
621 CamelMimeMessage *mm = CAMEL_MIME_MESSAGE (data_wrapper);
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", "");
629 if (!camel_medium_get_header ((CamelMedium *)mm, "Date"))
630 camel_mime_message_set_date (mm, CAMEL_MESSAGE_DATE_CURRENT, 0);
632 if (mm->subject == NULL)
633 camel_mime_message_set_subject (mm, "No Subject");
635 if (mm->message_id == NULL)
636 camel_mime_message_set_message_id (mm, NULL);
638 /* FIXME: "To" header needs to be set explicitly as well ... */
640 if (!camel_medium_get_header ((CamelMedium *)mm, "Mime-Version"))
641 camel_medium_set_header ((CamelMedium *)mm, "Mime-Version", "1.0");
643 return CAMEL_DATA_WRAPPER_CLASS (parent_class)->write_to_stream (data_wrapper, stream);
646 /* FIXME: check format of fields. */
648 process_header (CamelMedium *medium, const char *name, const char *value)
650 CamelHeaderType header_type;
651 CamelMimeMessage *message = CAMEL_MIME_MESSAGE (medium);
652 CamelInternetAddress *addr;
655 header_type = (CamelHeaderType) g_hash_table_lookup (header_name_table, name);
656 switch (header_type) {
658 addr = camel_internet_address_new();
659 if (camel_address_decode((CamelAddress *)addr, value) <= 0) {
660 camel_object_unref(addr);
663 camel_object_unref(message->from);
664 message->from = addr;
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);
672 if (message->reply_to)
673 camel_object_unref(message->reply_to);
674 message->reply_to = addr;
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);
684 message->subject = g_strstrip (camel_header_decode_string (value, charset));
689 case HEADER_RESENT_TO:
690 case HEADER_RESENT_CC:
691 case HEADER_RESENT_BCC:
692 addr = g_hash_table_lookup (message->recipients, name);
694 camel_address_decode (CAMEL_ADDRESS (addr), value);
696 camel_address_remove (CAMEL_ADDRESS (addr), -1);
700 message->date = camel_header_decode_date (value, &message->date_offset);
702 message->date = CAMEL_MESSAGE_DATE_CURRENT;
703 message->date_offset = 0;
706 case HEADER_MESSAGE_ID:
707 g_free (message->message_id);
709 message->message_id = camel_header_msgid_decode (value);
711 message->message_id = NULL;
721 set_header (CamelMedium *medium, const char *name, const void *value)
723 process_header (medium, name, value);
724 parent_class->parent_class.set_header (medium, name, value);
728 add_header (CamelMedium *medium, const char *name, const void *value)
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);
734 parent_class->parent_class.add_header (medium, name, value);
738 remove_header (CamelMedium *medium, const char *name)
740 process_header (medium, name, NULL);
741 parent_class->parent_class.remove_header (medium, name);
744 typedef gboolean (*CamelPartFunc)(CamelMimeMessage *, CamelMimePart *, void *data);
747 message_foreach_part_rec (CamelMimeMessage *msg, CamelMimePart *part, CamelPartFunc callback, void *data)
749 CamelDataWrapper *containee;
753 if (callback (msg, part, data) == FALSE)
756 containee = camel_medium_get_content_object (CAMEL_MEDIUM (part));
758 if (containee == NULL)
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);
767 go = message_foreach_part_rec (msg, mpart, callback, data);
769 } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
770 go = message_foreach_part_rec (msg, (CamelMimePart *)containee, callback, data);
776 /* dont make this public yet, it might need some more thinking ... */
779 camel_mime_message_foreach_part (CamelMimeMessage *msg, CamelPartFunc callback, void *data)
781 message_foreach_part_rec (msg, (CamelMimePart *)msg, callback, data);
785 check_8bit (CamelMimeMessage *msg, CamelMimePart *part, void *data)
787 CamelTransferEncoding encoding;
790 /* check this part, and stop as soon as we are done */
791 encoding = camel_mime_part_get_encoding (part);
793 *has8bit = encoding == CAMEL_TRANSFER_ENCODING_8BIT || encoding == CAMEL_TRANSFER_ENCODING_BINARY;
800 * camel_mime_message_has_8bit_parts:
801 * @message: a #CamelMimeMessage object
803 * Find out if a message contains 8bit or binary encoded parts.
805 * Returns %TRUE if the message contains 8bit parts or %FALSE otherwise
808 camel_mime_message_has_8bit_parts (CamelMimeMessage *msg)
812 camel_mime_message_foreach_part (msg, check_8bit, &has8bit);
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)
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;
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 ... */
837 d(printf("starting to check part\n"));
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
844 return CAMEL_TRANSFER_ENCODING_DEFAULT;
847 istext = camel_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*");
849 flags = CAMEL_BESTENC_GET_CHARSET | CAMEL_BESTENC_GET_ENCODING;
850 enctype |= CAMEL_BESTENC_TEXT;
852 flags = CAMEL_BESTENC_GET_ENCODING;
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;
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);
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");
870 idc = camel_stream_filter_add (filter, (CamelMimeFilter *)charenc);
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);
880 camel_stream_filter_remove (filter, idc);
881 camel_object_unref (charenc);
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);
890 charsetin = camel_content_type_param (content->mime_type, "charset");
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"));
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 */
903 charenc = camel_mime_filter_charset_new_convert (charsetin, charset);
904 if (charenc != NULL) {
905 /* otherwise, try another pass, converting to the real charset */
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);
911 camel_stream_filter_add (filter, (CamelMimeFilter *)charenc);
912 camel_stream_filter_add (filter, (CamelMimeFilter *)bestenc);
914 /* and write it to the new stream */
915 camel_data_wrapper_write_to_stream (content, (CamelStream *)filter);
917 camel_object_unref (charenc);
921 encoding = camel_mime_filter_bestenc_get_best_encoding (bestenc, enctype);
923 camel_object_unref (filter);
924 camel_object_unref (bestenc);
925 camel_object_unref (null);
927 d(printf("done, best encoding = %d\n", encoding));
938 CamelBestencRequired required;
939 CamelBestencEncoding enctype;
943 best_encoding (CamelMimeMessage *msg, CamelMimePart *part, void *datap)
945 struct _enc_data *data = datap;
946 CamelTransferEncoding encoding;
947 CamelDataWrapper *wrapper;
950 wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part));
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
959 camel_mime_part_set_encoding (part, encoding);
961 if ((data->required & CAMEL_BESTENC_GET_CHARSET) != 0) {
962 if (camel_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*")) {
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);
970 d(printf("Setting content-type to %s\n", newct));
972 camel_mime_part_set_content_type (part, newct);
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
991 * Re-encode all message parts to conform with the required encoding rules.
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.
1000 camel_mime_message_set_best_encoding (CamelMimeMessage *msg, CamelBestencRequired required, CamelBestencEncoding enctype)
1002 struct _enc_data data;
1004 if ((required & (CAMEL_BESTENC_GET_ENCODING|CAMEL_BESTENC_GET_CHARSET)) == 0)
1007 data.required = required;
1008 data.enctype = enctype;
1010 camel_mime_message_foreach_part (msg, best_encoding, &data);
1015 * camel_mime_message_encode_8bit_parts:
1016 * @message: a #CamelMimeMessage object
1018 * Encode all message parts to a suitable transfer encoding for transport (7bit clean).
1021 camel_mime_message_encode_8bit_parts (CamelMimeMessage *mime_message)
1023 camel_mime_message_set_best_encoding (mime_message, CAMEL_BESTENC_GET_ENCODING, CAMEL_BESTENC_7BIT);
1027 struct _check_content_id {
1028 CamelMimePart *part;
1029 const char *content_id;
1033 check_content_id (CamelMimeMessage *message, CamelMimePart *part, void *data)
1035 struct _check_content_id *check = (struct _check_content_id *) data;
1036 const char *content_id;
1039 content_id = camel_mime_part_get_content_id (part);
1041 found = content_id && !strcmp (content_id, check->content_id) ? TRUE : FALSE;
1044 camel_object_ref (part);
1052 * camel_mime_message_get_part_by_content_id:
1053 * @message: a #CamelMimeMessage object
1054 * @content_id: content-id to search for
1056 * Get a MIME part by id from a message.
1058 * Returns the MIME part with the requested id or %NULL if not found
1061 camel_mime_message_get_part_by_content_id (CamelMimeMessage *message, const char *id)
1063 struct _check_content_id check;
1065 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
1070 check.content_id = id;
1073 camel_mime_message_foreach_part (message, check_content_id, &check);
1078 static const char tz_months[][4] = {
1079 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1080 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1083 static const char tz_days[][4] = {
1084 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1089 * camel_mime_message_build_mbox_from:
1090 * @message: a #CamelMimeMessage object
1092 * Build an MBox from-line from @message.
1094 * Returns an MBox from-line suitable for use in an mbox file
1097 camel_mime_message_build_mbox_from (CamelMimeMessage *message)
1099 struct _camel_header_raw *header = ((CamelMimePart *)message)->headers;
1100 GString *out = g_string_new("From ");
1107 tmp = camel_header_raw_find (&header, "Sender", NULL);
1109 tmp = camel_header_raw_find (&header, "From", NULL);
1111 struct _camel_header_address *addr = camel_header_address_decode (tmp, NULL);
1115 if (addr->type == CAMEL_HEADER_ADDRESS_NAME) {
1116 g_string_append (out, addr->v.addr);
1119 camel_header_address_unref (addr);
1124 g_string_append (out, "unknown@nodomain.now.au");
1126 /* try use the received header to get the date */
1127 tmp = camel_header_raw_find (&header, "Received", NULL);
1129 tmp = strrchr(tmp, ';');
1134 /* if there isn't one, try the Date field */
1136 tmp = camel_header_raw_find (&header, "Date", NULL);
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,
1147 g_string_free (out, FALSE);
1153 cmm_dump_rec(CamelMimeMessage *msg, CamelMimePart *part, int body, int depth)
1155 CamelDataWrapper *containee;
1160 s = alloca(depth+1);
1161 memset(s, ' ', depth);
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));
1167 containee = camel_medium_get_content_object((CamelMedium *)part);
1169 if (containee == NULL)
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));
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);
1181 cmm_dump_rec(msg, mpart, body, depth+2);
1183 } else if (CAMEL_IS_MIME_MESSAGE(containee)) {
1184 cmm_dump_rec(msg, (CamelMimePart *)containee, body, depth+2);
1189 * camel_mime_message_dump:
1193 * Dump information about the mime message to stdout.
1195 * If body is TRUE, then dump body content of the message as well (currently unimplemented).
1198 camel_mime_message_dump(CamelMimeMessage *msg, int body)
1200 cmm_dump_rec(msg, (CamelMimePart *)msg, body, 0);