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  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This library is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
19  *	    Michael Zucchi <notzed@ximian.com>
20  *          Jeffrey Stedfast <fejj@ximian.com>
21  */
22 
23 #include "evolution-data-server-config.h"
24 
25 #include <ctype.h>
26 #include <errno.h>
27 #include <stdio.h>
28 #include <string.h>
29 
30 #include "camel-iconv.h"
31 #include "camel-mime-filter-bestenc.h"
32 #include "camel-mime-filter-charset.h"
33 #include "camel-mime-message.h"
34 #include "camel-multipart.h"
35 #include "camel-stream-filter.h"
36 #include "camel-stream-mem.h"
37 #include "camel-stream-null.h"
38 #include "camel-string-utils.h"
39 #include "camel-url.h"
40 
41 #ifdef G_OS_WIN32
42 #ifdef gmtime_r
43 #undef gmtime_r
44 #endif
45 
46 /* The gmtime() in Microsoft's C library is MT-safe */
47 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
48 #endif
49 #define d(x)
50 
51 struct _CamelMimeMessagePrivate {
52 	/* header fields */
53 	time_t date;
54 	gint date_offset;	/* GMT offset */
55 
56 	/* cached internal copy */
57 	time_t date_received;
58 	gint date_received_offset;	/* GMT offset */
59 
60 	gchar *subject;
61 
62 	gchar *message_id;
63 
64 	CamelInternetAddress *reply_to;
65 	CamelInternetAddress *from;
66 
67 	GHashTable *recipients;	/* hash table of CamelInternetAddress's */
68 };
69 
70 /* these 2 below should be kept in sync */
71 typedef enum {
72 	HEADER_UNKNOWN,
73 	HEADER_FROM,
74 	HEADER_REPLY_TO,
75 	HEADER_SUBJECT,
76 	HEADER_TO,
77 	HEADER_RESENT_TO,
78 	HEADER_CC,
79 	HEADER_RESENT_CC,
80 	HEADER_BCC,
81 	HEADER_RESENT_BCC,
82 	HEADER_DATE,
83 	HEADER_MESSAGE_ID
84 } CamelHeaderType;
85 
86 static const gchar *header_names[] = {
87 	/* dont include HEADER_UNKNOWN string */
88 	"From", "Reply-To", "Subject", "To", "Resent-To", "Cc", "Resent-Cc",
89 	"Bcc", "Resent-Bcc", "Date", "Message-ID", NULL
90 };
91 
92 static const gchar *recipient_names[] = {
93 	"To", "Cc", "Bcc", "Resent-To", "Resent-Cc", "Resent-Bcc", NULL
94 };
95 
96 static GHashTable *header_name_table;
97 
G_DEFINE_TYPE_WITH_PRIVATE(CamelMimeMessage,camel_mime_message,CAMEL_TYPE_MIME_PART)98 G_DEFINE_TYPE_WITH_PRIVATE (CamelMimeMessage, camel_mime_message, CAMEL_TYPE_MIME_PART)
99 
100 /* FIXME: check format of fields. */
101 static gboolean
102 process_header (CamelMedium *medium,
103                 const gchar *name,
104                 const gchar *value)
105 {
106 	CamelHeaderType header_type;
107 	CamelMimeMessage *message = CAMEL_MIME_MESSAGE (medium);
108 	CamelInternetAddress *addr;
109 	const gchar *charset;
110 	gchar *unfolded;
111 
112 	header_type = (CamelHeaderType) GPOINTER_TO_INT (g_hash_table_lookup (header_name_table, name));
113 	switch (header_type) {
114 	case HEADER_FROM:
115 		addr = camel_internet_address_new ();
116 		unfolded = camel_header_unfold (value);
117 		if (camel_address_decode ((CamelAddress *) addr, unfolded) <= 0) {
118 			g_object_unref (addr);
119 		} else {
120 			if (message->priv->from)
121 				g_object_unref (message->priv->from);
122 			message->priv->from = addr;
123 		}
124 		g_free (unfolded);
125 		break;
126 	case HEADER_REPLY_TO:
127 		addr = camel_internet_address_new ();
128 		unfolded = camel_header_unfold (value);
129 		if (camel_address_decode ((CamelAddress *) addr, unfolded) <= 0) {
130 			g_object_unref (addr);
131 		} else {
132 			if (message->priv->reply_to)
133 				g_object_unref (message->priv->reply_to);
134 			message->priv->reply_to = addr;
135 		}
136 		g_free (unfolded);
137 		break;
138 	case HEADER_SUBJECT:
139 		g_free (message->priv->subject);
140 		if (camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (message))) {
141 			charset = camel_content_type_param (camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (message)), "charset");
142 			charset = camel_iconv_charset_name (charset);
143 		} else
144 			charset = NULL;
145 
146 		unfolded = camel_header_unfold (value);
147 		message->priv->subject = g_strstrip (camel_header_decode_string (unfolded, charset));
148 		g_free (unfolded);
149 		break;
150 	case HEADER_TO:
151 	case HEADER_CC:
152 	case HEADER_BCC:
153 	case HEADER_RESENT_TO:
154 	case HEADER_RESENT_CC:
155 	case HEADER_RESENT_BCC:
156 		addr = g_hash_table_lookup (message->priv->recipients, name);
157 		if (value) {
158 			unfolded = camel_header_unfold (value);
159 			camel_address_decode (CAMEL_ADDRESS (addr), unfolded);
160 			g_free (unfolded);
161 		} else {
162 			camel_address_remove (CAMEL_ADDRESS (addr), -1);
163 		}
164 		return FALSE;
165 	case HEADER_DATE:
166 		if (value) {
167 			message->priv->date = camel_header_decode_date (value, &message->priv->date_offset);
168 		} else {
169 			message->priv->date = CAMEL_MESSAGE_DATE_CURRENT;
170 			message->priv->date_offset = 0;
171 		}
172 		break;
173 	case HEADER_MESSAGE_ID:
174 		g_free (message->priv->message_id);
175 		if (value)
176 			message->priv->message_id = camel_header_msgid_decode (value);
177 		else
178 			message->priv->message_id = NULL;
179 		break;
180 	default:
181 		return FALSE;
182 	}
183 
184 	return TRUE;
185 }
186 
187 static void
unref_recipient(gpointer key,gpointer value,gpointer user_data)188 unref_recipient (gpointer key,
189                  gpointer value,
190                  gpointer user_data)
191 {
192 	g_object_unref (value);
193 }
194 
195 static void
mime_message_ensure_required_headers(CamelMimeMessage * message)196 mime_message_ensure_required_headers (CamelMimeMessage *message)
197 {
198 	CamelMedium *medium = CAMEL_MEDIUM (message);
199 
200 	if (message->priv->from == NULL) {
201 		camel_medium_set_header (medium, "From", "");
202 	}
203 	if (!camel_medium_get_header (medium, "Date"))
204 		camel_mime_message_set_date (
205 			message, CAMEL_MESSAGE_DATE_CURRENT, 0);
206 
207 	if (message->priv->subject == NULL)
208 		camel_mime_message_set_subject (message, "No Subject");
209 
210 	if (message->priv->message_id == NULL)
211 		camel_mime_message_set_message_id (message, NULL);
212 
213 	/* FIXME: "To" header needs to be set explicitly as well ... */
214 
215 	/* Use uppercase MIME, to follow RFC 2045 convention (and to not trigger
216 	   some spam-detection rules on some servers/clients). */
217 	if (!camel_medium_get_header (medium, "MIME-Version"))
218 		camel_medium_set_header (medium, "MIME-Version", "1.0");
219 }
220 
221 static void
mime_message_dispose(GObject * object)222 mime_message_dispose (GObject *object)
223 {
224 	CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
225 
226 	g_clear_object (&message->priv->reply_to);
227 	g_clear_object (&message->priv->from);
228 
229 	/* Chain up to parent's dispose() method. */
230 	G_OBJECT_CLASS (camel_mime_message_parent_class)->dispose (object);
231 }
232 
233 static void
mime_message_finalize(GObject * object)234 mime_message_finalize (GObject *object)
235 {
236 	CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
237 
238 	g_free (message->priv->subject);
239 	g_free (message->priv->message_id);
240 
241 	g_hash_table_foreach (message->priv->recipients, unref_recipient, NULL);
242 	g_hash_table_destroy (message->priv->recipients);
243 
244 	/* Chain up to parent's finalize() method. */
245 	G_OBJECT_CLASS (camel_mime_message_parent_class)->finalize (object);
246 }
247 
248 static gssize
mime_message_write_to_stream_sync(CamelDataWrapper * data_wrapper,CamelStream * stream,GCancellable * cancellable,GError ** error)249 mime_message_write_to_stream_sync (CamelDataWrapper *data_wrapper,
250                                    CamelStream *stream,
251                                    GCancellable *cancellable,
252                                    GError **error)
253 {
254 	CamelMimeMessage *message;
255 
256 	message = CAMEL_MIME_MESSAGE (data_wrapper);
257 	mime_message_ensure_required_headers (message);
258 
259 	/* Chain up to parent's write_to_stream_sync() method. */
260 	return CAMEL_DATA_WRAPPER_CLASS (camel_mime_message_parent_class)->
261 		write_to_stream_sync (
262 		data_wrapper, stream, cancellable, error);
263 }
264 
265 static gssize
mime_message_write_to_output_stream_sync(CamelDataWrapper * data_wrapper,GOutputStream * output_stream,GCancellable * cancellable,GError ** error)266 mime_message_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
267                                           GOutputStream *output_stream,
268                                           GCancellable *cancellable,
269                                           GError **error)
270 {
271 	CamelMimeMessage *message;
272 
273 	message = CAMEL_MIME_MESSAGE (data_wrapper);
274 	mime_message_ensure_required_headers (message);
275 
276 	/* Chain up to parent's write_to_output_stream_sync() method. */
277 	return CAMEL_DATA_WRAPPER_CLASS (camel_mime_message_parent_class)->
278 		write_to_output_stream_sync (
279 		data_wrapper, output_stream, cancellable, error);
280 }
281 
282 static void
mime_message_add_header(CamelMedium * medium,const gchar * name,const gchar * value)283 mime_message_add_header (CamelMedium *medium,
284                          const gchar *name,
285                          const gchar *value)
286 {
287 	CamelMediumClass *medium_class;
288 
289 	medium_class = CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class);
290 
291 	/* if we process it, then it must be forced unique as well ... */
292 	if (process_header (medium, name, value))
293 		medium_class->set_header (medium, name, value);
294 	else
295 		medium_class->add_header (medium, name, value);
296 }
297 
298 static void
mime_message_set_header(CamelMedium * medium,const gchar * name,const gchar * value)299 mime_message_set_header (CamelMedium *medium,
300                          const gchar *name,
301                          const gchar *value)
302 {
303 	process_header (medium, name, value);
304 
305 	/* Chain up to parent's set_header() method. */
306 	CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (medium, name, value);
307 }
308 
309 static void
mime_message_remove_header(CamelMedium * medium,const gchar * name)310 mime_message_remove_header (CamelMedium *medium,
311                             const gchar *name)
312 {
313 	process_header (medium, name, NULL);
314 
315 	/* Chain up to parent's remove_header() method. */
316 	CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (medium, name);
317 }
318 
319 static gboolean
mime_message_construct_from_parser_sync(CamelMimePart * dw,CamelMimeParser * mp,GCancellable * cancellable,GError ** error)320 mime_message_construct_from_parser_sync (CamelMimePart *dw,
321                                          CamelMimeParser *mp,
322                                          GCancellable *cancellable,
323                                          GError **error)
324 {
325 	CamelMimePartClass *mime_part_class;
326 	gchar *buf;
327 	gsize len;
328 	gint state;
329 	gint err;
330 	gboolean success;
331 
332 	/* let the mime-part construct the guts ... */
333 	mime_part_class = CAMEL_MIME_PART_CLASS (camel_mime_message_parent_class);
334 	success = mime_part_class->construct_from_parser_sync (
335 		dw, mp, cancellable, error);
336 
337 	if (!success)
338 		return FALSE;
339 
340 	/* ... then clean up the follow-on state */
341 	state = camel_mime_parser_step (mp, &buf, &len);
342 	switch (state) {
343 	case CAMEL_MIME_PARSER_STATE_EOF:
344 	case CAMEL_MIME_PARSER_STATE_FROM_END:
345 		/* these doesn't belong to us */
346 		camel_mime_parser_unstep (mp);
347 	case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
348 		break;
349 	default:
350 		g_error ("Bad parser state: Expecing MESSAGE_END or EOF or EOM, got: %u", camel_mime_parser_state (mp));
351 		camel_mime_parser_unstep (mp);
352 		return FALSE;
353 	}
354 
355 	err = camel_mime_parser_errno (mp);
356 	if (err != 0) {
357 		errno = err;
358 		g_set_error (
359 			error, G_IO_ERROR,
360 			g_io_error_from_errno (errno),
361 			"%s", g_strerror (errno));
362 		success = FALSE;
363 	}
364 
365 	return success;
366 }
367 
368 static void
camel_mime_message_class_init(CamelMimeMessageClass * class)369 camel_mime_message_class_init (CamelMimeMessageClass *class)
370 {
371 	GObjectClass *object_class;
372 	CamelDataWrapperClass *data_wrapper_class;
373 	CamelMimePartClass *mime_part_class;
374 	CamelMediumClass *medium_class;
375 	gint ii;
376 
377 	object_class = G_OBJECT_CLASS (class);
378 	object_class->dispose = mime_message_dispose;
379 	object_class->finalize = mime_message_finalize;
380 
381 	data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
382 	data_wrapper_class->write_to_stream_sync = mime_message_write_to_stream_sync;
383 	data_wrapper_class->decode_to_stream_sync = mime_message_write_to_stream_sync;
384 	data_wrapper_class->write_to_output_stream_sync = mime_message_write_to_output_stream_sync;
385 	data_wrapper_class->decode_to_output_stream_sync = mime_message_write_to_output_stream_sync;
386 
387 	medium_class = CAMEL_MEDIUM_CLASS (class);
388 	medium_class->add_header = mime_message_add_header;
389 	medium_class->set_header = mime_message_set_header;
390 	medium_class->remove_header = mime_message_remove_header;
391 
392 	mime_part_class = CAMEL_MIME_PART_CLASS (class);
393 	mime_part_class->construct_from_parser_sync = mime_message_construct_from_parser_sync;
394 
395 	header_name_table = g_hash_table_new (
396 		camel_strcase_hash, camel_strcase_equal);
397 	for (ii = 0; header_names[ii] != NULL; ii++)
398 		g_hash_table_insert (
399 			header_name_table,
400 			(gpointer) header_names[ii],
401 			GINT_TO_POINTER (ii + 1));
402 }
403 
404 static void
camel_mime_message_init(CamelMimeMessage * mime_message)405 camel_mime_message_init (CamelMimeMessage *mime_message)
406 {
407 	gint ii;
408 
409 	mime_message->priv = camel_mime_message_get_instance_private (mime_message);
410 
411 	mime_message->priv->recipients = g_hash_table_new (
412 		camel_strcase_hash, camel_strcase_equal);
413 	for (ii = 0; recipient_names[ii] != NULL; ii++) {
414 		g_hash_table_insert (
415 			mime_message->priv->recipients,
416 			(gpointer) recipient_names[ii],
417 			camel_internet_address_new ());
418 	}
419 
420 	mime_message->priv->subject = NULL;
421 	mime_message->priv->reply_to = NULL;
422 	mime_message->priv->from = NULL;
423 	mime_message->priv->date = CAMEL_MESSAGE_DATE_CURRENT;
424 	mime_message->priv->date_offset = 0;
425 	mime_message->priv->date_received = CAMEL_MESSAGE_DATE_CURRENT;
426 	mime_message->priv->date_received_offset = 0;
427 	mime_message->priv->message_id = NULL;
428 }
429 
430 /**
431  * camel_mime_message_new:
432  *
433  * Create a new #CamelMimeMessage object.
434  *
435  * Returns: a new #CamelMimeMessage object
436  **/
437 CamelMimeMessage *
camel_mime_message_new(void)438 camel_mime_message_new (void)
439 {
440 	return g_object_new (CAMEL_TYPE_MIME_MESSAGE, NULL);
441 }
442 
443 /* **** Date: */
444 
445 /**
446  * camel_mime_message_set_date:
447  * @message: a #CamelMimeMessage object
448  * @date: a time_t date or %CAMEL_MESSAGE_DATE_CURRENT to use the current local date and time
449  * @offset: an offset from UTC in decimal number using the +HHMM format (for instance an offset
450  *   of -10:45 is -1045). If @date set to %CAMEL_MESSAGE_DATE_CURRENT, this parameter is ignored
451  *
452  * Set the date on a message.
453  *
454  * In most cases, this is used to set the current date:
455  * |[<!-- language="C" -->
456  * camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
457  * ]|
458  **/
459 void
camel_mime_message_set_date(CamelMimeMessage * message,time_t date,gint offset)460 camel_mime_message_set_date (CamelMimeMessage *message,
461                              time_t date,
462                              gint offset)
463 {
464 	gchar *datestr;
465 
466 	g_return_if_fail (message);
467 
468 	if (date == CAMEL_MESSAGE_DATE_CURRENT) {
469 		struct tm local;
470 		gint tz;
471 
472 		date = time (NULL);
473 		camel_localtime_with_offset (date, &local, &tz);
474 		offset = (((tz / 60 / 60) * 100) + (tz / 60 % 60));
475 	}
476 	message->priv->date = date;
477 	message->priv->date_offset = offset;
478 
479 	datestr = camel_header_format_date (date, offset);
480 	CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header ((CamelMedium *) message, "Date", datestr);
481 	g_free (datestr);
482 }
483 
484 /**
485  * camel_mime_message_get_date:
486  * @message: a #CamelMimeMessage object
487  * @offset: (out): output for the UTC offset
488  *
489  * Get the date and UTC offset of a message.
490  * See camel_mime_message_set_date() for information about the @offset format.
491  *
492  * Returns: the date of the message
493  **/
494 time_t
camel_mime_message_get_date(CamelMimeMessage * msg,gint * offset)495 camel_mime_message_get_date (CamelMimeMessage *msg,
496                              gint *offset)
497 {
498 	if (offset)
499 		*offset = msg->priv->date_offset;
500 
501 	return msg->priv->date;
502 }
503 
504 /**
505  * camel_mime_message_get_date_received:
506  * @message: a #CamelMimeMessage object
507  * @offset: (out): output for the UTC offset
508  *
509  * Get the received date and UTC offset of a message.
510  * See camel_mime_message_set_date() for information about the @offset format.
511  *
512  * Returns: the received date of the message
513  **/
514 time_t
camel_mime_message_get_date_received(CamelMimeMessage * msg,gint * offset)515 camel_mime_message_get_date_received (CamelMimeMessage *msg,
516                                       gint *offset)
517 {
518 	if (msg->priv->date_received == CAMEL_MESSAGE_DATE_CURRENT) {
519 		const gchar *received;
520 
521 		received = camel_medium_get_header ((CamelMedium *) msg, "received");
522 		if (received)
523 			received = strrchr (received, ';');
524 		if (received)
525 			msg->priv->date_received = camel_header_decode_date (received + 1, &msg->priv->date_received_offset);
526 	}
527 
528 	if (offset)
529 		*offset = msg->priv->date_received_offset;
530 
531 	return msg->priv->date_received;
532 }
533 
534 /* **** Message-ID: */
535 
536 /**
537  * camel_mime_message_set_message_id:
538  * @message: a #CamelMimeMessage object
539  * @message_id: (nullable): id of the message, or %NULL to generate a new one using the from address
540  *
541  * Set the message-id on a message.
542  **/
543 void
camel_mime_message_set_message_id(CamelMimeMessage * mime_message,const gchar * message_id)544 camel_mime_message_set_message_id (CamelMimeMessage *mime_message,
545                                    const gchar *message_id)
546 {
547 	gchar *id;
548 
549 	g_return_if_fail (mime_message);
550 
551 	g_free (mime_message->priv->message_id);
552 
553 	if (message_id) {
554 		id = g_strstrip (g_strdup (message_id));
555 	} else {
556 		CamelInternetAddress *from;
557 		const gchar *domain = NULL;
558 
559 		from = camel_mime_message_get_from (mime_message);
560 		if (from && camel_internet_address_get (from, 0, NULL, &domain) && domain) {
561 			const gchar *at = strchr (domain, '@');
562 			if (at)
563 				domain = at + 1;
564 			else
565 				domain = NULL;
566 		}
567 
568 		id = camel_header_msgid_generate (domain);
569 	}
570 
571 	mime_message->priv->message_id = id;
572 	id = g_strdup_printf ("<%s>", mime_message->priv->message_id);
573 	CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (mime_message), "Message-ID", id);
574 	g_free (id);
575 }
576 
577 /**
578  * camel_mime_message_get_message_id:
579  * @message: a #CamelMimeMessage object
580  *
581  * Get the message-id of a message.
582  *
583  * Returns: (nullable): the message-id of a message
584  **/
585 const gchar *
camel_mime_message_get_message_id(CamelMimeMessage * mime_message)586 camel_mime_message_get_message_id (CamelMimeMessage *mime_message)
587 {
588 	g_return_val_if_fail (mime_message, NULL);
589 
590 	return mime_message->priv->message_id;
591 }
592 
593 /* **** Reply-To: */
594 
595 /**
596  * camel_mime_message_set_reply_to:
597  * @message: a #CamelMimeMessage object
598  * @reply_to: (nullable): a #CamelInternetAddress object
599  *
600  * Set the Reply-To of a message.
601  **/
602 void
camel_mime_message_set_reply_to(CamelMimeMessage * msg,CamelInternetAddress * reply_to)603 camel_mime_message_set_reply_to (CamelMimeMessage *msg,
604                                  CamelInternetAddress *reply_to)
605 {
606 	gchar *addr;
607 
608 	g_return_if_fail (msg);
609 
610 	g_clear_object (&msg->priv->reply_to);
611 
612 	if (reply_to == NULL) {
613 		CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (msg), "Reply-To");
614 		return;
615 	}
616 
617 	msg->priv->reply_to = (CamelInternetAddress *) camel_address_new_clone ((CamelAddress *) reply_to);
618 	addr = camel_address_encode ((CamelAddress *) msg->priv->reply_to);
619 	CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (msg), "Reply-To", addr);
620 	g_free (addr);
621 }
622 
623 /**
624  * camel_mime_message_get_reply_to:
625  * @message: a #CamelMimeMessage object
626  *
627  * Get the Reply-To of a message.
628  *
629  * Returns: (transfer none) (nullable): the Reply-To address of the message
630  **/
631 CamelInternetAddress *
camel_mime_message_get_reply_to(CamelMimeMessage * mime_message)632 camel_mime_message_get_reply_to (CamelMimeMessage *mime_message)
633 {
634 	g_return_val_if_fail (mime_message, NULL);
635 
636 	/* TODO: ref for threading? */
637 
638 	return mime_message->priv->reply_to;
639 }
640 
641 /* **** Subject: */
642 
643 /**
644  * camel_mime_message_set_subject:
645  * @message: a #CamelMimeMessage object
646  * @subject: (nullable): UTF-8 message subject
647  *
648  * Set the subject text of a message.
649  **/
650 void
camel_mime_message_set_subject(CamelMimeMessage * message,const gchar * subject)651 camel_mime_message_set_subject (CamelMimeMessage *message,
652                                 const gchar *subject)
653 {
654 	gchar *text;
655 
656 	g_return_if_fail (message);
657 
658 	g_free (message->priv->subject);
659 
660 	if (subject) {
661 		message->priv->subject = g_strstrip (g_strdup (subject));
662 		text = camel_header_encode_string ((guchar *) message->priv->subject);
663 	} else {
664 		message->priv->subject = NULL;
665 		text = NULL;
666 	}
667 
668 	CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (message), "Subject", text);
669 	g_free (text);
670 }
671 
672 /**
673  * camel_mime_message_get_subject:
674  * @message: a #CamelMimeMessage object
675  *
676  * Get the UTF-8 subject text of a message.
677  *
678  * Returns: (nullable): the message subject
679  **/
680 const gchar *
camel_mime_message_get_subject(CamelMimeMessage * mime_message)681 camel_mime_message_get_subject (CamelMimeMessage *mime_message)
682 {
683 	g_return_val_if_fail (mime_message, NULL);
684 
685 	return mime_message->priv->subject;
686 }
687 
688 /* *** From: */
689 
690 /* Thought: Since get_from/set_from are so rarely called, it is probably not useful
691  * to cache the from (and reply_to) addresses as InternetAddresses internally, we
692  * could just get it from the headers and reprocess every time. */
693 
694 /**
695  * camel_mime_message_set_from:
696  * @message: a #CamelMimeMessage object
697  * @from: (nullable): a #CamelInternetAddress object
698  *
699  * Set the from address of a message.
700  **/
701 void
camel_mime_message_set_from(CamelMimeMessage * msg,CamelInternetAddress * from)702 camel_mime_message_set_from (CamelMimeMessage *msg,
703                              CamelInternetAddress *from)
704 {
705 	gchar *addr;
706 
707 	g_return_if_fail (msg);
708 
709 	g_clear_object (&msg->priv->from);
710 
711 	if (from == NULL || camel_address_length ((CamelAddress *) from) == 0) {
712 		CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (msg), "From");
713 		return;
714 	}
715 
716 	msg->priv->from = (CamelInternetAddress *) camel_address_new_clone ((CamelAddress *) from);
717 	addr = camel_address_encode ((CamelAddress *) msg->priv->from);
718 	CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (msg), "From", addr);
719 	g_free (addr);
720 }
721 
722 /**
723  * camel_mime_message_get_from:
724  * @message: a #CamelMimeMessage object
725  *
726  * Get the from address of a message.
727  *
728  * Returns: (transfer none) (nullable): the from address of the message
729  **/
730 CamelInternetAddress *
camel_mime_message_get_from(CamelMimeMessage * mime_message)731 camel_mime_message_get_from (CamelMimeMessage *mime_message)
732 {
733 	g_return_val_if_fail (mime_message, NULL);
734 
735 	/* TODO: we should really ref this for multi-threading to work */
736 
737 	return mime_message->priv->from;
738 }
739 
740 /*  **** To: Cc: Bcc: */
741 
742 /**
743  * camel_mime_message_set_recipients:
744  * @message: a #CamelMimeMessage object
745  * @type: recipient type (one of #CAMEL_RECIPIENT_TYPE_TO, #CAMEL_RECIPIENT_TYPE_CC, or #CAMEL_RECIPIENT_TYPE_BCC)
746  * @recipients: (nullable): a #CamelInternetAddress with the recipient addresses set or %NULL
747  *   to remove the already defined one
748  *
749  * Set the recipients of a message.
750  **/
751 void
camel_mime_message_set_recipients(CamelMimeMessage * mime_message,const gchar * type,CamelInternetAddress * r)752 camel_mime_message_set_recipients (CamelMimeMessage *mime_message,
753                                    const gchar *type,
754                                    CamelInternetAddress *r)
755 {
756 	gchar *text;
757 	CamelInternetAddress *addr;
758 
759 	g_return_if_fail (mime_message);
760 
761 	addr = g_hash_table_lookup (mime_message->priv->recipients, type);
762 	if (addr == NULL) {
763 		g_warning ("trying to set a non-valid receipient type: %s", type);
764 		return;
765 	}
766 
767 	if (r == NULL || camel_address_length ((CamelAddress *) r) == 0) {
768 		camel_address_remove ((CamelAddress *) addr, -1);
769 		CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (mime_message), type);
770 		return;
771 	}
772 
773 	/* note this does copy, and not append (cat) */
774 	camel_address_copy ((CamelAddress *) addr, (CamelAddress *) r);
775 
776 	/* and sync our headers */
777 	text = camel_address_encode (CAMEL_ADDRESS (addr));
778 	CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (mime_message), type, text);
779 	g_free (text);
780 }
781 
782 /**
783  * camel_mime_message_get_recipients:
784  * @message: a #CamelMimeMessage object
785  * @type: recipient type
786  *
787  * Get the message recipients of a specified type.
788  *
789  * Returns: (transfer none) (nullable): the requested recipients
790  **/
791 CamelInternetAddress *
camel_mime_message_get_recipients(CamelMimeMessage * mime_message,const gchar * type)792 camel_mime_message_get_recipients (CamelMimeMessage *mime_message,
793                                    const gchar *type)
794 {
795 	g_return_val_if_fail (mime_message, NULL);
796 
797 	return g_hash_table_lookup (mime_message->priv->recipients, type);
798 }
799 
800 /**
801  * camel_mime_message_set_source:
802  * @message: a #CamelMimeMessage object
803  * @source_uid: (nullable): the uid of the source account
804  *
805  * Set the UID of the source account of the message.
806  **/
807 void
camel_mime_message_set_source(CamelMimeMessage * message,const gchar * source_uid)808 camel_mime_message_set_source (CamelMimeMessage *message,
809                                const gchar *source_uid)
810 {
811 	CamelMedium *medium;
812 	const gchar *name;
813 
814 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
815 
816 	/* FIXME The header name is Evolution-specific.
817 	 *       "X" header prefix should be configurable
818 	 *       somehow, perhaps through CamelSession. */
819 
820 	name = "X-Evolution-Source";
821 	medium = CAMEL_MEDIUM (message);
822 
823 	camel_medium_remove_header (medium, name);
824 	if (source_uid != NULL)
825 		camel_medium_add_header (medium, name, source_uid);
826 }
827 
828 /**
829  * camel_mime_message_get_source:
830  * @message: a #CamelMimeMessage object
831  *
832  * Get the UID of the source account of the message.
833  *
834  * Returns: (nullable): the uid of the source account
835  **/
836 const gchar *
camel_mime_message_get_source(CamelMimeMessage * message)837 camel_mime_message_get_source (CamelMimeMessage *message)
838 {
839 	CamelMedium *medium;
840 	const gchar *name;
841 	const gchar *src;
842 
843 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
844 
845 	/* FIXME The header name is Evolution-specific.
846 	 *       "X" header prefix should be configurable
847 	 *       somehow, perhaps through CamelSession. */
848 
849 	name = "X-Evolution-Source";
850 	medium = CAMEL_MEDIUM (message);
851 
852 	src = camel_medium_get_header (medium, name);
853 	if (src != NULL) {
854 		while (*src && isspace ((unsigned) *src))
855 			++src;
856 	}
857 
858 	return src;
859 }
860 
861 static gboolean
message_foreach_part_rec(CamelMimeMessage * msg,CamelMimePart * part,CamelMimePart * parent_part,CamelForeachPartFunc callback,gpointer data)862 message_foreach_part_rec (CamelMimeMessage *msg,
863                           CamelMimePart *part,
864 			  CamelMimePart *parent_part,
865                           CamelForeachPartFunc callback,
866                           gpointer data)
867 {
868 	CamelDataWrapper *containee;
869 	gint parts, i;
870 	gint go = TRUE;
871 
872 	if (callback (msg, part, parent_part, data) == FALSE)
873 		return FALSE;
874 
875 	containee = camel_medium_get_content (CAMEL_MEDIUM (part));
876 
877 	if (containee == NULL)
878 		return go;
879 
880 	/* using the object types is more accurate than using the mime/types */
881 	if (CAMEL_IS_MULTIPART (containee)) {
882 		parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
883 		for (i = 0; go && i < parts; i++) {
884 			CamelMimePart *mpart = camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
885 
886 			go = message_foreach_part_rec (msg, mpart, part, callback, data);
887 		}
888 	} else if (CAMEL_IS_MIME_MESSAGE (containee)) {
889 		go = message_foreach_part_rec (msg, (CamelMimePart *) containee, part, callback, data);
890 	}
891 
892 	return go;
893 }
894 
895 /**
896  * camel_mime_message_foreach_part:
897  * @message: a #CamelMimeMessage
898  * @callback: (scope call): a #CamelForeachPartFunc callback to call for each part
899  * @user_data: (closure callback): user data passed to the @callback
900  *
901  * Calls @callback for each part of the @message, including the message itself.
902  * The traverse of the @message parts can be stopped when the @callback
903  * returns %FALSE.
904  *
905  * Since: 3.34
906  **/
907 void
camel_mime_message_foreach_part(CamelMimeMessage * message,CamelForeachPartFunc callback,gpointer user_data)908 camel_mime_message_foreach_part (CamelMimeMessage *message,
909 				 CamelForeachPartFunc callback,
910 				 gpointer user_data)
911 {
912 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
913 	g_return_if_fail (callback != NULL);
914 
915 	message_foreach_part_rec (message, CAMEL_MIME_PART (message), NULL, callback, user_data);
916 }
917 
918 static gboolean
check_8bit(CamelMimeMessage * msg,CamelMimePart * part,CamelMimePart * parent_part,gpointer data)919 check_8bit (CamelMimeMessage *msg,
920             CamelMimePart *part,
921             CamelMimePart *parent_part,
922             gpointer data)
923 {
924 	CamelTransferEncoding encoding;
925 	gint *has8bit = data;
926 
927 	/* check this part, and stop as soon as we are done */
928 	encoding = camel_mime_part_get_encoding (part);
929 
930 	*has8bit = encoding == CAMEL_TRANSFER_ENCODING_8BIT || encoding == CAMEL_TRANSFER_ENCODING_BINARY;
931 
932 	return !(*has8bit);
933 }
934 
935 /**
936  * camel_mime_message_has_8bit_parts:
937  * @message: a #CamelMimeMessage object
938  *
939  * Find out if a message contains 8bit or binary encoded parts.
940  *
941  * Returns: %TRUE if the message contains 8bit parts or %FALSE otherwise
942  **/
943 gboolean
camel_mime_message_has_8bit_parts(CamelMimeMessage * msg)944 camel_mime_message_has_8bit_parts (CamelMimeMessage *msg)
945 {
946 	gint has8bit = FALSE;
947 
948 	camel_mime_message_foreach_part (msg, check_8bit, &has8bit);
949 
950 	return has8bit;
951 }
952 
953 static gboolean
mime_part_is_attachment(CamelMimePart * mp)954 mime_part_is_attachment (CamelMimePart *mp)
955 {
956 	const CamelContentDisposition *content_disposition;
957 
958 	content_disposition = camel_mime_part_get_content_disposition (mp);
959 
960 	return content_disposition &&
961 	       content_disposition->disposition &&
962 	       g_ascii_strcasecmp (content_disposition->disposition, "attachment") == 0;
963 }
964 
965 /* finds the best charset and transfer encoding for a given part */
966 static CamelTransferEncoding
find_best_encoding(CamelMimePart * part,CamelBestencRequired required,CamelBestencEncoding enctype,gchar ** charsetp)967 find_best_encoding (CamelMimePart *part,
968                     CamelBestencRequired required,
969                     CamelBestencEncoding enctype,
970                     gchar **charsetp)
971 {
972 	CamelMimeFilter *charenc = NULL;
973 	CamelTransferEncoding encoding;
974 	CamelMimeFilter *bestenc;
975 	guint flags, callerflags;
976 	CamelDataWrapper *content;
977 	CamelStream *filter;
978 	const gchar *charsetin = NULL;
979 	gchar *charset = NULL;
980 	CamelStream *null;
981 	gint idb, idc = -1;
982 	gboolean istext;
983 
984 	/* we use all these weird stream things so we can do it with streams, and
985 	 * not have to read the whole lot into memory - although i have a feeling
986 	 * it would make things a fair bit simpler to do so ... */
987 
988 	d (printf ("starting to check part\n"));
989 
990 	content = camel_medium_get_content ((CamelMedium *) part);
991 	if (content == NULL) {
992 		/* charset might not be right here, but it'll get the right stuff
993 		 * if it is ever set */
994 		*charsetp = NULL;
995 		return CAMEL_TRANSFER_ENCODING_DEFAULT;
996 	}
997 
998 	istext = camel_content_type_is (camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (part)), "text", "*");
999 	if (istext) {
1000 		flags = CAMEL_BESTENC_GET_CHARSET | CAMEL_BESTENC_GET_ENCODING;
1001 		enctype |= CAMEL_BESTENC_TEXT;
1002 	} else {
1003 		flags = CAMEL_BESTENC_GET_ENCODING;
1004 	}
1005 
1006 	/* when building the message, any encoded parts are translated already */
1007 	flags |= CAMEL_BESTENC_LF_IS_CRLF;
1008 	/* and get any flags the caller passed in */
1009 	callerflags = (required & CAMEL_BESTENC_NO_FROM);
1010 	flags |= callerflags;
1011 
1012 	/* first a null stream, so any filtering is thrown away; we only want the sideeffects */
1013 	null = (CamelStream *) camel_stream_null_new ();
1014 	filter = camel_stream_filter_new (null);
1015 
1016 	/* if we're looking for the best charset, then we need to convert to UTF-8 */
1017 	if (istext && (required & CAMEL_BESTENC_GET_CHARSET) != 0
1018 	    && (charsetin = camel_content_type_param (camel_data_wrapper_get_mime_type_field (content), "charset"))) {
1019 		charenc = camel_mime_filter_charset_new (charsetin, "UTF-8");
1020 		if (charenc != NULL)
1021 			idc = camel_stream_filter_add (
1022 				CAMEL_STREAM_FILTER (filter), charenc);
1023 		charsetin = NULL;
1024 	}
1025 
1026 	bestenc = camel_mime_filter_bestenc_new (flags);
1027 	idb = camel_stream_filter_add (
1028 		CAMEL_STREAM_FILTER (filter), bestenc);
1029 	d (printf ("writing to checking stream\n"));
1030 	camel_data_wrapper_decode_to_stream_sync (content, filter, NULL, NULL);
1031 	camel_stream_filter_remove (CAMEL_STREAM_FILTER (filter), idb);
1032 	if (idc != -1) {
1033 		camel_stream_filter_remove (CAMEL_STREAM_FILTER (filter), idc);
1034 		g_object_unref (charenc);
1035 		charenc = NULL;
1036 	}
1037 
1038 	if (istext && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
1039 		charsetin = camel_mime_filter_bestenc_get_best_charset (
1040 			CAMEL_MIME_FILTER_BESTENC (bestenc));
1041 		d (printf ("best charset = %s\n", charsetin ? charsetin : "(null)"));
1042 		charset = g_strdup (charsetin);
1043 
1044 		charsetin = camel_content_type_param (camel_data_wrapper_get_mime_type_field (content), "charset");
1045 	} else {
1046 		charset = NULL;
1047 	}
1048 
1049 	/* if we have US-ASCII, or we're not doing text, we dont need to bother with the rest */
1050 	if (istext && charsetin && charset && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
1051 		d (printf ("have charset, trying conversion/etc\n"));
1052 
1053 		/* now that 'bestenc' has told us what the best encoding is, we can use that to create
1054 		 * a charset conversion filter as well, and then re-add the bestenc to filter the
1055 		 * result to find the best encoding to use as well */
1056 
1057 		charenc = camel_mime_filter_charset_new (charsetin, charset);
1058 		if (charenc != NULL) {
1059 			/* otherwise, try another pass, converting to the real charset */
1060 
1061 			camel_mime_filter_reset (bestenc);
1062 			camel_mime_filter_bestenc_set_flags (
1063 				CAMEL_MIME_FILTER_BESTENC (bestenc),
1064 				CAMEL_BESTENC_GET_ENCODING |
1065 				CAMEL_BESTENC_LF_IS_CRLF | callerflags);
1066 
1067 			camel_stream_filter_add (
1068 				CAMEL_STREAM_FILTER (filter), charenc);
1069 			camel_stream_filter_add (
1070 				CAMEL_STREAM_FILTER (filter), bestenc);
1071 
1072 			/* and write it to the new stream */
1073 			camel_data_wrapper_write_to_stream_sync (
1074 				content, filter, NULL, NULL);
1075 
1076 			g_object_unref (charenc);
1077 		}
1078 	}
1079 
1080 	encoding = camel_mime_filter_bestenc_get_best_encoding (
1081 		CAMEL_MIME_FILTER_BESTENC (bestenc), enctype);
1082 
1083 	g_object_unref (filter);
1084 	g_object_unref (bestenc);
1085 	g_object_unref (null);
1086 
1087 	d (printf ("done, best encoding = %d\n", encoding));
1088 
1089 	if (charsetp)
1090 		*charsetp = charset;
1091 	else
1092 		g_free (charset);
1093 
1094 	return encoding;
1095 }
1096 
1097 struct _enc_data {
1098 	CamelBestencRequired required;
1099 	CamelBestencEncoding enctype;
1100 };
1101 
1102 static gboolean
best_encoding(CamelMimeMessage * msg,CamelMimePart * part,CamelMimePart * parent_part,gpointer datap)1103 best_encoding (CamelMimeMessage *msg,
1104                CamelMimePart *part,
1105                CamelMimePart *parent_part,
1106                gpointer datap)
1107 {
1108 	struct _enc_data *data = datap;
1109 	CamelTransferEncoding encoding;
1110 	CamelDataWrapper *wrapper;
1111 	gchar *charset;
1112 
1113 	/* Keep attachments untouched. */
1114 	if (mime_part_is_attachment (part))
1115 		return TRUE;
1116 
1117 	wrapper = camel_medium_get_content (CAMEL_MEDIUM (part));
1118 	if (!wrapper)
1119 		return FALSE;
1120 
1121 	/* we only care about actual content objects */
1122 	if (!CAMEL_IS_MULTIPART (wrapper) && !CAMEL_IS_MIME_MESSAGE (wrapper)) {
1123 		encoding = find_best_encoding (part, data->required, data->enctype, &charset);
1124 		/* we always set the encoding, if we got this far.  GET_CHARSET implies
1125 		 * also GET_ENCODING */
1126 		camel_mime_part_set_encoding (part, encoding);
1127 
1128 		if ((data->required & CAMEL_BESTENC_GET_CHARSET) != 0) {
1129 			if (camel_content_type_is (camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (part)), "text", "*")) {
1130 				gchar *newct;
1131 
1132 				/* FIXME: ick, the part content_type interface needs fixing bigtime */
1133 				camel_content_type_set_param (
1134 					camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (part)), "charset",
1135 					charset ? charset : "us-ascii");
1136 				newct = camel_content_type_format (camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (part)));
1137 				if (newct) {
1138 					d (printf ("Setting content-type to %s\n", newct));
1139 
1140 					camel_mime_part_set_content_type (part, newct);
1141 					g_free (newct);
1142 				}
1143 			}
1144 		}
1145 
1146 		g_free (charset);
1147 	}
1148 
1149 	return TRUE;
1150 }
1151 
1152 /**
1153  * camel_mime_message_set_best_encoding:
1154  * @message: a #CamelMimeMessage object
1155  * @required: a bitwise ORing of #CAMEL_BESTENC_GET_ENCODING and #CAMEL_BESTENC_GET_CHARSET
1156  * @enctype: an encoding to enforce
1157  *
1158  * Re-encode all message parts to conform with the required encoding rules.
1159  *
1160  * If @enctype is #CAMEL_BESTENC_7BIT, then all parts will be re-encoded into
1161  * one of the 7bit transfer encodings. If @enctype is #CAMEL_BESTENC_8BIT, all
1162  * parts will be re-encoded to either a 7bit encoding or, if the part is 8bit
1163  * text, allowed to stay 8bit. If @enctype is #CAMEL_BESTENC_BINARY, then binary
1164  * parts will be encoded as binary and 8bit textual parts will be encoded as 8bit.
1165  **/
1166 void
camel_mime_message_set_best_encoding(CamelMimeMessage * msg,CamelBestencRequired required,CamelBestencEncoding enctype)1167 camel_mime_message_set_best_encoding (CamelMimeMessage *msg,
1168                                       CamelBestencRequired required,
1169                                       CamelBestencEncoding enctype)
1170 {
1171 	struct _enc_data data;
1172 
1173 	if ((required & (CAMEL_BESTENC_GET_ENCODING | CAMEL_BESTENC_GET_CHARSET)) == 0)
1174 		return;
1175 
1176 	data.required = required;
1177 	data.enctype = enctype;
1178 
1179 	camel_mime_message_foreach_part (msg, best_encoding, &data);
1180 }
1181 
1182 /**
1183  * camel_mime_message_encode_8bit_parts:
1184  * @message: a #CamelMimeMessage object
1185  *
1186  * Encode all message parts to a suitable transfer encoding for transport (7bit clean).
1187  **/
1188 void
camel_mime_message_encode_8bit_parts(CamelMimeMessage * mime_message)1189 camel_mime_message_encode_8bit_parts (CamelMimeMessage *mime_message)
1190 {
1191 	camel_mime_message_set_best_encoding (mime_message, CAMEL_BESTENC_GET_ENCODING, CAMEL_BESTENC_7BIT);
1192 }
1193 
1194 struct _check_content_id {
1195 	CamelMimePart *part;
1196 	const gchar *content_id;
1197 };
1198 
1199 static gboolean
check_content_id(CamelMimeMessage * message,CamelMimePart * part,CamelMimePart * parent_part,gpointer data)1200 check_content_id (CamelMimeMessage *message,
1201                   CamelMimePart *part,
1202                   CamelMimePart *parent_part,
1203                   gpointer data)
1204 {
1205 	struct _check_content_id *check = (struct _check_content_id *) data;
1206 	const gchar *content_id;
1207 	gboolean found;
1208 
1209 	content_id = camel_mime_part_get_content_id (part);
1210 
1211 	found = content_id && !strcmp (content_id, check->content_id) ? TRUE : FALSE;
1212 	if (found)
1213 		check->part = g_object_ref (part);
1214 
1215 	return !found;
1216 }
1217 
1218 /**
1219  * camel_mime_message_get_part_by_content_id:
1220  * @message: a #CamelMimeMessage object
1221  * @content_id: content-id to search for
1222  *
1223  * Get a MIME part by id from a message.
1224  *
1225  * Returns: (transfer none) (nullable): the MIME part with the requested id, or %NULL if not found
1226  **/
1227 CamelMimePart *
camel_mime_message_get_part_by_content_id(CamelMimeMessage * message,const gchar * id)1228 camel_mime_message_get_part_by_content_id (CamelMimeMessage *message,
1229                                            const gchar *id)
1230 {
1231 	struct _check_content_id check;
1232 
1233 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
1234 
1235 	if (id == NULL)
1236 		return NULL;
1237 
1238 	check.content_id = id;
1239 	check.part = NULL;
1240 
1241 	camel_mime_message_foreach_part (message, check_content_id, &check);
1242 
1243 	return check.part;
1244 }
1245 
1246 static const gchar tz_months[][4] = {
1247 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
1248 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1249 };
1250 
1251 static const gchar tz_days[][4] = {
1252 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1253 };
1254 
1255 /**
1256  * camel_mime_message_build_mbox_from:
1257  * @message: a #CamelMimeMessage object
1258  *
1259  * Build an MBox from-line from @message.
1260  *
1261  * Returns: an MBox from-line suitable for use in an mbox file
1262  **/
1263 gchar *
camel_mime_message_build_mbox_from(CamelMimeMessage * message)1264 camel_mime_message_build_mbox_from (CamelMimeMessage *message)
1265 {
1266 	const CamelNameValueArray *headers;
1267 	GString *out = g_string_new ("From ");
1268 	const gchar *tmp;
1269 	time_t thetime;
1270 	gint offset;
1271 	struct tm tm;
1272 
1273 	headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
1274 	tmp = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Sender");
1275 	if (tmp == NULL)
1276 		tmp = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "From");
1277 	if (tmp != NULL) {
1278 		CamelHeaderAddress *addr = camel_header_address_decode (tmp, NULL);
1279 
1280 		tmp = NULL;
1281 		if (addr) {
1282 			if (addr->type == CAMEL_HEADER_ADDRESS_NAME) {
1283 				g_string_append (out, addr->v.addr);
1284 				tmp = "";
1285 			}
1286 			camel_header_address_unref (addr);
1287 		}
1288 	}
1289 
1290 	if (tmp == NULL)
1291 		g_string_append (out, "unknown@nodomain.now.au");
1292 
1293 	/* try use the received header to get the date */
1294 	tmp = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Received");
1295 	if (tmp) {
1296 		tmp = strrchr (tmp, ';');
1297 		if (tmp)
1298 			tmp++;
1299 	}
1300 
1301 	/* if there isn't one, try the Date field */
1302 	if (tmp == NULL)
1303 		tmp = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Date");
1304 
1305 	thetime = camel_header_decode_date (tmp, &offset);
1306 	thetime += ((offset / 100) * (60 * 60)) + (offset % 100) * 60;
1307 	gmtime_r (&thetime, &tm);
1308 	g_string_append_printf (
1309 		out, " %s %s %2d %02d:%02d:%02d %4d\n",
1310 		tz_days[tm.tm_wday],
1311 		tz_months[tm.tm_mon],
1312 		tm.tm_mday,
1313 		tm.tm_hour,
1314 		tm.tm_min,
1315 		tm.tm_sec,
1316 		tm.tm_year + 1900);
1317 
1318 	return g_string_free (out, FALSE);
1319 }
1320 
1321 static gboolean
find_attachment(CamelMimeMessage * msg,CamelMimePart * part,CamelMimePart * parent_part,gpointer data)1322 find_attachment (CamelMimeMessage *msg,
1323                  CamelMimePart *part,
1324                  CamelMimePart *parent_part,
1325                  gpointer data)
1326 {
1327 	const CamelContentDisposition *cd;
1328 	CamelContentType *ct, *parent_ct = NULL;
1329 	gboolean *found = (gboolean *) data;
1330 
1331 	g_return_val_if_fail (part != NULL, FALSE);
1332 
1333 	if (*found)
1334 		return FALSE;
1335 
1336 	ct = camel_mime_part_get_content_type (part);
1337 	cd = camel_mime_part_get_content_disposition (part);
1338 
1339 	if (parent_part)
1340 		parent_ct = camel_mime_part_get_content_type (parent_part);
1341 
1342 	*found = camel_content_disposition_is_attachment_ex (cd, ct, parent_ct);
1343 
1344 	return !(*found);
1345 }
1346 
1347 /**
1348  * camel_mime_message_has_attachment:
1349  * @message: a #CamelMimeMessage object
1350  *
1351  * Returns whether message contains at least one attachment part.
1352  *
1353  * Since: 2.28
1354  **/
1355 gboolean
camel_mime_message_has_attachment(CamelMimeMessage * message)1356 camel_mime_message_has_attachment (CamelMimeMessage *message)
1357 {
1358 	gboolean found = FALSE;
1359 
1360 	g_return_val_if_fail (message != NULL, FALSE);
1361 
1362 	camel_mime_message_foreach_part (message, find_attachment, &found);
1363 
1364 	return found;
1365 }
1366 
1367 static void
dumpline(const gchar * indent,guint8 * data,gsize data_len)1368 dumpline (const gchar *indent,
1369           guint8 *data,
1370           gsize data_len)
1371 {
1372 	gint j;
1373 	gchar *gutter;
1374 	guint gutter_size;
1375 
1376 	g_return_if_fail (data_len <= 16);
1377 
1378 	gutter_size = ((16 - data_len) * 3) + 4;
1379 	gutter = alloca (gutter_size + 1);
1380 	memset (gutter, ' ', gutter_size);
1381 	gutter[gutter_size] = 0;
1382 
1383 	printf ("%s    ", indent);
1384 	/* Hex dump */
1385 	for (j = 0; j < data_len; j++)
1386 		printf ("%s%02x", j > 0 ? " " : "", data[j]);
1387 
1388 	/* ASCII dump */
1389 	printf ("%s", gutter);
1390 	for (j = 0; j < data_len; j++) {
1391 		printf ("%c", isprint (data[j]) ? data[j] : '.');
1392 	}
1393 	printf ("\n");
1394 }
1395 
1396 static void
cmm_dump_rec(CamelMimeMessage * msg,CamelMimePart * part,gint body,gint depth)1397 cmm_dump_rec (CamelMimeMessage *msg,
1398               CamelMimePart *part,
1399               gint body,
1400               gint depth)
1401 {
1402 	CamelDataWrapper *containee;
1403 	gint parts, i;
1404 	gint go = TRUE;
1405 	gchar *s;
1406 	const GByteArray *data;
1407 
1408 	g_return_if_fail (CAMEL_IS_MIME_PART (part));
1409 
1410 	s = alloca (depth + 1);
1411 	memset (s, ' ', depth);
1412 	s[depth] = 0;
1413 	/* yes this leaks, so what its only debug stuff */
1414 	printf ("%sclass: %s\n", s, G_OBJECT_TYPE_NAME (part));
1415 	printf ("%smime-type: %s\n", s, camel_content_type_format (camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (part))));
1416 
1417 	containee = camel_medium_get_content ((CamelMedium *) part);
1418 
1419 	if (containee == NULL)
1420 		return;
1421 
1422 	printf ("%scontent class: %s\n", s, G_OBJECT_TYPE_NAME (containee));
1423 	printf ("%scontent mime-type: %s\n", s, camel_content_type_format (camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (containee))));
1424 
1425 	data = camel_data_wrapper_get_byte_array (containee);
1426 	if (body && data) {
1427 		guint t = 0;
1428 
1429 		printf ("%scontent len %d\n", s, data->len);
1430 		for (t = 0; t < data->len / 16; t++)
1431 			dumpline (s, &data->data[t * 16], 16);
1432 		if (data->len % 16)
1433 			dumpline (s, &data->data[t * 16], data->len % 16);
1434 	}
1435 
1436 	/* using the object types is more accurate than using the mime/types */
1437 	if (CAMEL_IS_MULTIPART (containee)) {
1438 		parts = camel_multipart_get_number ((CamelMultipart *) containee);
1439 		for (i = 0; go && i < parts; i++) {
1440 			CamelMimePart *mpart = camel_multipart_get_part ((CamelMultipart *) containee, i);
1441 
1442 			cmm_dump_rec (msg, mpart, body, depth + 2);
1443 		}
1444 	} else if (CAMEL_IS_MIME_MESSAGE (containee)) {
1445 		cmm_dump_rec (msg, (CamelMimePart *) containee, body, depth + 2);
1446 	}
1447 }
1448 
1449 /**
1450  * camel_mime_message_dump:
1451  * @message: a #CamelMimeMessage
1452  * @body: whether to dump also message body
1453  *
1454  * Dump information about the mime message to stdout.
1455  *
1456  * If body is TRUE, then dump body content of the message as well.
1457  **/
1458 void
camel_mime_message_dump(CamelMimeMessage * message,gint body)1459 camel_mime_message_dump (CamelMimeMessage *message,
1460                          gint body)
1461 {
1462 	cmm_dump_rec (message, (CamelMimePart *) message, body, 0);
1463 }
1464