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