1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  * Copyright (C) 1997-2013 Stuart Parmenter and others,
4  *                         See the file AUTHORS for a list.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19  * 02111-1307, USA.
20  */
21 
22 /* DESIGN NOTES.
23    MESSAGE_COPY_CONTENT define is an attempt to reduce memory usage of balsa.
24    When it is defined, The message date is stored in one place only (in
25    libmutt structures). This should reduce memory usage to some extent.
26    However, it is not implemented very extensively at the present moment
27    and the memory usage reduction is hardly noticeable.
28    - Lack of inline functions in C increases program complexity. This cost
29    can be accepted.
30    - thorough analysis of memory usage is needed.
31 */
32 
33 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
34 # include "config.h"
35 #endif                          /* HAVE_CONFIG_H */
36 
37 #include <ctype.h>
38 #include <string.h>
39 #include <stdlib.h>
40 #include <fcntl.h>
41 #include <glib.h>
42 
43 #include "libbalsa.h"
44 #include "libbalsa_private.h"
45 
46 /* needed for truncate_string */
47 #include "misc.h"
48 
49 #include "mime-stream-shared.h"
50 #include <glib/gi18n.h>
51 
52 #include <gmime/gmime.h>
53 
54 static void libbalsa_message_class_init(LibBalsaMessageClass * klass);
55 static void libbalsa_message_init(LibBalsaMessage * message);
56 
57 static void libbalsa_message_finalize(GObject * object);
58 
59 
60 static GObjectClass *parent_class = NULL;
61 
62 GType
libbalsa_message_get_type()63 libbalsa_message_get_type()
64 {
65     static GType libbalsa_message_type = 0;
66 
67     if (!libbalsa_message_type) {
68 	static const GTypeInfo libbalsa_message_info = {
69 	    sizeof(LibBalsaMessageClass),
70             NULL,               /* base_init */
71             NULL,               /* base_finalize */
72 	    (GClassInitFunc) libbalsa_message_class_init,
73             NULL,               /* class_finalize */
74             NULL,               /* class_data */
75 	    sizeof(LibBalsaMessage),
76             0,                  /* n_preallocs */
77 	    (GInstanceInitFunc) libbalsa_message_init
78 	};
79 
80 	libbalsa_message_type =
81 	    g_type_register_static(G_TYPE_OBJECT, "LibBalsaMessage",
82                                    &libbalsa_message_info, 0);
83     }
84 
85     return libbalsa_message_type;
86 }
87 
88 static void
libbalsa_message_init(LibBalsaMessage * message)89 libbalsa_message_init(LibBalsaMessage * message)
90 {
91     message->headers = g_new0(LibBalsaMessageHeaders, 1);
92     message->flags = 0;
93     message->mailbox = NULL;
94     message->sender = NULL;
95     message->subj = NULL;
96     message->references = NULL;
97     message->in_reply_to = NULL;
98     message->message_id = NULL;
99     message->subtype = 0;
100     message->parameters = NULL;
101     message->body_ref = 0;
102     message->body_list = NULL;
103     message->has_all_headers = 0;
104 #ifdef HAVE_GPGME
105     message->prot_state = LIBBALSA_MSG_PROTECT_NONE;
106     message->force_key_id = NULL;
107 #endif
108 }
109 
110 
111 static void
libbalsa_message_class_init(LibBalsaMessageClass * klass)112 libbalsa_message_class_init(LibBalsaMessageClass * klass)
113 {
114     GObjectClass *object_class = G_OBJECT_CLASS(klass);
115 
116     parent_class = g_type_class_peek_parent(klass);
117 
118     object_class->finalize = libbalsa_message_finalize;
119 }
120 
121 LibBalsaMessage *
libbalsa_message_new(void)122 libbalsa_message_new(void)
123 {
124     LibBalsaMessage *message;
125 
126     message = g_object_new(LIBBALSA_TYPE_MESSAGE, NULL);
127 
128     return message;
129 }
130 
131 /* libbalsa_message_finalize:
132    finalize methods must leave object in 'sane' state.
133    This means NULLifing released pointers.
134 */
135 static void
libbalsa_message_finalize(GObject * object)136 libbalsa_message_finalize(GObject * object)
137 {
138     LibBalsaMessage *message;
139 
140     g_return_if_fail(object != NULL);
141     g_return_if_fail(LIBBALSA_IS_MESSAGE(object));
142 
143     message = LIBBALSA_MESSAGE(object);
144 
145     libbalsa_message_headers_destroy(message->headers);
146     message->headers = NULL;
147 
148     if (message->sender) {
149 	g_object_unref(message->sender);
150 	message->sender = NULL;
151     }
152 
153 #if MESSAGE_COPY_CONTENT
154     g_free(message->subj);
155     message->subj = NULL;
156 #endif
157     g_list_foreach(message->references, (GFunc) g_free, NULL);
158     g_list_free(message->references);
159     message->references = NULL;
160 
161     g_list_foreach(message->in_reply_to, (GFunc) g_free, NULL);
162     g_list_free(message->in_reply_to);
163     message->in_reply_to = NULL;
164 
165     g_free(message->message_id);
166     message->message_id = NULL;
167 
168     g_free(message->subtype);
169     message->subtype = NULL;
170 
171     g_list_foreach(message->parameters, (GFunc) g_strfreev, NULL);
172     g_list_free(message->parameters);
173     message->parameters = NULL;
174 
175 
176     libbalsa_message_body_free(message->body_list);
177     message->body_list = NULL;
178 
179     if (message->mime_msg) {
180 	g_object_unref(message->mime_msg);
181 	message->mime_msg = NULL;
182     }
183 
184 #ifdef HAVE_GPGME
185     g_free(message->force_key_id);
186 #endif
187 
188     if (message->tempdir) {
189         if (rmdir(message->tempdir))
190             g_warning("Could not remove %s", message->tempdir);
191         g_free(message->tempdir);
192         message->tempdir = NULL;
193     }
194 
195     G_OBJECT_CLASS(parent_class)->finalize(object);
196 }
197 
198 static void
lb_message_headers_extra_destroy(LibBalsaMessageHeaders * headers)199 lb_message_headers_extra_destroy(LibBalsaMessageHeaders * headers)
200 {
201     if (!headers)
202 	return;
203 
204     FREE_HEADER_LIST(headers->user_hdrs);
205     headers->user_hdrs = NULL;
206 
207     g_free(headers->fcc_url);
208     headers->fcc_url = NULL;
209 }
210 
211 void
libbalsa_message_headers_destroy(LibBalsaMessageHeaders * headers)212 libbalsa_message_headers_destroy(LibBalsaMessageHeaders * headers)
213 {
214     if (!headers)
215 	return;
216 
217     g_free(headers->subject);
218     headers->subject = NULL;
219 
220     if (headers->from) {
221 	g_object_unref(headers->from);
222 	headers->from = NULL;
223     }
224 
225     if (headers->to_list) {
226 	g_object_unref(headers->to_list);
227 	headers->to_list = NULL;
228     }
229 
230     if (headers->content_type) {
231 	g_object_unref(headers->content_type);
232 	headers->content_type = NULL;
233     }
234 
235     if (headers->cc_list) {
236 	g_object_unref(headers->cc_list);
237 	headers->cc_list = NULL;
238     }
239 
240     if (headers->bcc_list) {
241 	g_object_unref(headers->bcc_list);
242 	headers->bcc_list = NULL;
243     }
244 
245     if (headers->reply_to) {
246 	g_object_unref(headers->reply_to);
247 	headers->reply_to = NULL;
248     }
249 
250     if(headers->dispnotify_to) {
251 	g_object_unref(headers->dispnotify_to);
252 	headers->dispnotify_to = NULL;
253     }
254 
255     lb_message_headers_extra_destroy(headers);
256 
257     g_free(headers);
258 }
259 
260 const gchar *
libbalsa_message_body_charset(LibBalsaMessageBody * body)261 libbalsa_message_body_charset(LibBalsaMessageBody * body)
262 {
263     const gchar *charset;
264 
265     if (!body)
266 	return NULL;
267 
268     if (body->charset) /* This overrides all! Important for non
269                         * us-ascii messages over IMAP. */
270         return body->charset;
271 
272     if (GMIME_IS_PART(body->mime_part)) {
273         GMimeContentType *type;
274 
275         type = g_mime_object_get_content_type(body->mime_part);
276         return g_mime_content_type_get_parameter(type, "charset");
277     }
278 
279     charset = libbalsa_message_body_charset(body->parts);
280     if (charset)
281         return charset;
282 
283     return libbalsa_message_body_charset(body->next);
284 }
285 
286 /* UTF-8-aware header cleaning by Albrecht */
287 static void
canonize_header_value(gchar * value)288 canonize_header_value(gchar *value)
289 {
290     gchar *dptr = value;
291 
292     while (*value) {
293         if (g_unichar_isspace(g_utf8_get_char(value))) {
294             do {
295                 value = g_utf8_next_char(value);
296             } while (g_unichar_isspace(g_utf8_get_char(value)));
297             *dptr++ = ' ';
298         } else {
299             gint bytes = g_utf8_next_char(value) - value;
300 
301             do {
302                 *dptr++ = *value++;
303             } while (--bytes > 0);
304         }
305     }
306 
307     *dptr = '\0';
308 }
309 
310 /* message_user_hdrs:
311    returns allocated GList containing (header=>value) ALL headers pairs
312    as generated by g_strsplit.
313    The list has to be freed by the following chunk of code:
314     FREE_HEADER_LIST(list);
315 */
316 static gchar **
libbalsa_create_hdr_pair(const gchar * name,gchar * value)317 libbalsa_create_hdr_pair(const gchar * name, gchar * value)
318 {
319     gchar **item = g_new(gchar *, 3);
320 
321     canonize_header_value(value);
322     item[0] = g_strdup(name);
323     item[1] = value;
324     item[2] = NULL;
325     return item;
326 }
327 
328 static GList*
libbalsa_message_header_get_helper(LibBalsaMessageHeaders * headers,const gchar * find)329 libbalsa_message_header_get_helper(LibBalsaMessageHeaders* headers,
330                                    const gchar *find)
331 {
332     GList *list;
333     for (list = headers->user_hdrs; list; list = list->next) {
334         const gchar * const *tmp = list->data;
335 
336         if (g_ascii_strcasecmp(tmp[0], find) == 0)
337             return list;
338     }
339     return NULL;
340 }
341 
342 /** libbalsa_message_find_user_hdr:
343     returns.... list element matching given header.
344 */
345 static GList *
libbalsa_message_find_user_hdr(LibBalsaMessage * message,const gchar * find)346 libbalsa_message_find_user_hdr(LibBalsaMessage * message, const gchar * find)
347 {
348     LibBalsaMessageHeaders *headers = message->headers;
349 
350     g_return_val_if_fail(headers, NULL);
351     if (!headers->user_hdrs && message->mailbox)
352         libbalsa_mailbox_set_msg_headers(message->mailbox, message);
353 
354     return libbalsa_message_header_get_helper(headers, find);
355 }
356 
357 /*
358  * Public user header methods
359  */
360 const gchar*
libbalsa_message_header_get_one(LibBalsaMessageHeaders * headers,const gchar * find)361 libbalsa_message_header_get_one(LibBalsaMessageHeaders* headers,
362                                 const gchar *find)
363 {
364     GList *header;
365     const gchar *const *pair;
366 
367     if (!(header = libbalsa_message_header_get_helper(headers, find)))
368         return NULL;
369 
370     pair = header->data;
371     return pair[1];
372 }
373 
374 GList*
libbalsa_message_header_get_all(LibBalsaMessageHeaders * headers,const gchar * find)375 libbalsa_message_header_get_all(LibBalsaMessageHeaders* headers,
376                                 const gchar *find)
377 {
378     GList *header;
379     const gchar *const *pair;
380     GList *res = NULL;
381 
382     if (!(header = libbalsa_message_header_get_helper(headers, find)))
383         return NULL;
384     pair = header->data;
385     for(pair++; *pair; pair++)
386         res = g_list_append(res, g_strdup(*pair));
387 
388     return res;
389 }
390 
391 const gchar *
libbalsa_message_get_user_header(LibBalsaMessage * message,const gchar * name)392 libbalsa_message_get_user_header(LibBalsaMessage * message,
393                                  const gchar * name)
394 {
395     GList *header;
396     const gchar *const *pair;
397 
398     g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), NULL);
399     g_return_val_if_fail(name != NULL, NULL);
400 
401     if (!(header = libbalsa_message_find_user_hdr(message, name)))
402         return NULL;
403 
404     pair = header->data;
405     return pair[1];
406 }
407 
408 void
libbalsa_message_set_user_header(LibBalsaMessage * message,const gchar * name,const gchar * value)409 libbalsa_message_set_user_header(LibBalsaMessage * message,
410                                  const gchar * name, const gchar * value)
411 {
412     LibBalsaMessageHeaders *headers;
413     GList *header;
414 
415     g_return_if_fail(LIBBALSA_IS_MESSAGE(message));
416     g_return_if_fail(name != NULL);
417 
418     headers = message->headers;
419     g_return_if_fail(headers != NULL);
420 
421     if ((header = libbalsa_message_find_user_hdr(message, name))) {
422         headers->user_hdrs =
423             g_list_remove_link(headers->user_hdrs, header);
424         FREE_HEADER_LIST(header);
425     }
426 
427     if (value && *value)
428         headers->user_hdrs =
429             g_list_prepend(headers->user_hdrs,
430                            libbalsa_create_hdr_pair(name,
431                                                     g_strdup(value)));
432 }
433 
434 static void
prepend_header_misc(const char * name,const char * value,gpointer user_data)435 prepend_header_misc(const char *name, const char *value,
436                     gpointer user_data)
437 {
438     char lcname[28]; /* one byte longer than the longest ignored header */
439     static const char ignored_headers[] =
440         "subject date from to cc bcc "
441         "message-id references in-reply-to status lines"
442         "disposition-notification-to";
443     unsigned i;
444     GList *res = *(GList **)user_data;
445     if (!*value)
446 	/* Empty header */
447 	return;
448     /* Standard Headers*/
449     for(i=0; i<sizeof(lcname)-1 && name[i]; i++)
450         lcname[i] = tolower(name[i]);
451     if (i < sizeof(lcname)) {
452 	/* short enough to be on the ignored-headers list */
453         lcname[i] = '\0';
454         if(strstr(ignored_headers, lcname))
455             return;
456     }
457 
458     res = g_list_prepend(res, libbalsa_create_hdr_pair(name, g_strdup(value)));
459     *(GList **)user_data = res;
460 }
461 
462 /*
463  * libbalsa_message_user_hdrs_from_gmime:
464  *
465  * returns allocated GList containing (header=>value) ALL headers
466  * pairs as generated by g_strsplit. The list has to be freed by the
467  * following chunk of code (or something functionally similar):
468  *
469  * g_list_foreach(list, (GFunc) g_strfreev, NULL);
470  * g_list_free(list);
471 */
472 
473 
474 GList *
libbalsa_message_user_hdrs_from_gmime(GMimeMessage * message)475 libbalsa_message_user_hdrs_from_gmime(GMimeMessage * message)
476 {
477     GMimeHeaderList *hdrlist;
478     GMimeHeaderIter iter;
479     GList *res = NULL;
480     const char *value;
481 
482     g_return_val_if_fail(message != NULL, NULL);
483 
484     value = g_mime_message_get_message_id(message);
485     if (value)
486 	res = g_list_prepend(res, libbalsa_create_hdr_pair("Message-ID",
487 					  g_strdup_printf("<%s>", value)));
488 
489     /* FIXME: This duplicates References headers since they are
490        already present in LibBalsaMessage::references field.  FWIW,
491        mailbox driver does not copy references to user_headers.
492     */
493     value = g_mime_object_get_header(GMIME_OBJECT(message), "References");
494     if (value) {
495 #if BALSA_NEEDS_SEPARATE_USER_HEADERS
496 	GMimeReferences *references, *reference;
497 	reference = references = g_mime_references_decode(value);
498 	while (reference) {
499 	    res =
500 		g_list_prepend(res,
501 			       libbalsa_create_hdr_pair("References",
502 							g_strdup_printf
503 							("<%s>",
504 							 reference->
505 							 msgid)));
506 	    reference = reference->next;
507 	}
508 	g_mime_references_clear(&references);
509 #else
510         res = g_list_prepend(res,
511                              libbalsa_create_hdr_pair("References",
512                                                       g_strdup(value)));
513 #endif
514     }
515 
516     value = g_mime_object_get_header(GMIME_OBJECT(message), "In-Reply-To");
517     if (value) {
518         res =
519             g_list_prepend(res,
520                            libbalsa_create_hdr_pair
521                            ("In-Reply-To",
522                             g_mime_utils_header_decode_text(value)));
523     }
524 
525     hdrlist = g_mime_object_get_header_list (GMIME_OBJECT(message));
526     if (g_mime_header_list_get_iter (hdrlist, &iter)) {
527 	do {
528 	    prepend_header_misc (g_mime_header_iter_get_name (&iter),
529 				 g_mime_header_iter_get_value (&iter),
530 				 &res);
531 	} while (g_mime_header_iter_next (&iter));
532     }
533 
534     return g_list_reverse(res);
535 }
536 
537 /* libbalsa_message_get_part_by_id:
538    return a message part identified by Content-ID=id
539    message must be referenced. (FIXME?)
540 */
541 LibBalsaMessageBody *
libbalsa_message_get_part_by_id(LibBalsaMessage * msg,const gchar * id)542 libbalsa_message_get_part_by_id(LibBalsaMessage* msg, const gchar* id)
543 {
544     return libbalsa_message_body_get_by_id(msg->body_list, id);
545 }
546 
547 /* libbalsa_message_save:
548    return TRUE on success and FALSE on failure.
549 */
550 gboolean
libbalsa_message_save(LibBalsaMessage * message,const gchar * filename)551 libbalsa_message_save(LibBalsaMessage * message, const gchar *filename)
552 {
553     FILE *outfile;
554     int res;
555     GMimeStream *msg_stream;
556     GMimeStream *out_stream;
557 
558     g_return_val_if_fail(message->mailbox, FALSE);
559 
560     if( (outfile = fopen(filename, "w")) == NULL) return FALSE;
561     g_return_val_if_fail(outfile, FALSE);
562 
563     msg_stream = libbalsa_message_stream(message);
564     if (msg_stream == NULL)
565 	return FALSE;
566     out_stream = g_mime_stream_file_new(outfile);
567     libbalsa_mailbox_lock_store(message->mailbox);
568     res = g_mime_stream_write_to_stream(msg_stream, out_stream);
569     libbalsa_mailbox_unlock_store(message->mailbox);
570 
571     g_object_unref(msg_stream);
572     g_object_unref(out_stream);
573 
574     return res >= 0;
575 }
576 
577 LibBalsaMessageAttach
libbalsa_message_get_attach_icon(LibBalsaMessage * message)578 libbalsa_message_get_attach_icon(LibBalsaMessage * message)
579 {
580 #ifdef HAVE_GPGME
581     if (libbalsa_message_is_pgp_encrypted(message))
582 	return LIBBALSA_MESSAGE_ATTACH_ENCR;
583     else if (message->prot_state != LIBBALSA_MSG_PROTECT_NONE ||
584 	libbalsa_message_is_pgp_signed(message)) {
585 	switch (message->prot_state) {
586 	case LIBBALSA_MSG_PROTECT_SIGN_GOOD:
587 	    return LIBBALSA_MESSAGE_ATTACH_GOOD;
588 	case LIBBALSA_MSG_PROTECT_SIGN_NOTRUST:
589 	    return LIBBALSA_MESSAGE_ATTACH_NOTRUST;
590 	case LIBBALSA_MSG_PROTECT_SIGN_BAD:
591 	    return LIBBALSA_MESSAGE_ATTACH_BAD;
592 	case LIBBALSA_MSG_PROTECT_CRYPT:
593 	    return LIBBALSA_MESSAGE_ATTACH_ENCR;
594 	default:
595 	    return LIBBALSA_MESSAGE_ATTACH_SIGN;
596 	}
597     } else
598 #endif
599     if (libbalsa_message_has_attachment(message))
600 	return LIBBALSA_MESSAGE_ATTACH_ATTACH;
601     else
602 	return LIBBALSA_MESSAGE_ATTACH_ICONS_NUM;
603 }
604 
605 /* Tell the mailbox driver to change flags. */
606 void
libbalsa_message_change_flags(LibBalsaMessage * message,LibBalsaMessageFlag set,LibBalsaMessageFlag clear)607 libbalsa_message_change_flags(LibBalsaMessage * message,
608                               LibBalsaMessageFlag set,
609                               LibBalsaMessageFlag clear)
610 {
611     g_return_if_fail(LIBBALSA_IS_MESSAGE(message));
612     g_return_if_fail(LIBBALSA_IS_MAILBOX(message->mailbox));
613     g_return_if_fail(message->msgno > 0);
614 
615     if (message->mailbox->readonly) {
616         libbalsa_information(LIBBALSA_INFORMATION_WARNING,
617                              _("Mailbox (%s) is readonly: "
618                                "cannot change flags."),
619                              message->mailbox->name);
620         return;
621     }
622 
623     libbalsa_mailbox_msgno_change_flags(message->mailbox, message->msgno,
624                                         set, clear);
625 }
626 
627 void
libbalsa_message_reply(LibBalsaMessage * message)628 libbalsa_message_reply(LibBalsaMessage * message)
629 {
630     g_return_if_fail(message->mailbox);
631     libbalsa_lock_mailbox(message->mailbox);
632     libbalsa_message_change_flags(message, LIBBALSA_MESSAGE_FLAG_REPLIED, 0);
633     libbalsa_unlock_mailbox(message->mailbox);
634 }
635 
636 
637 /* libbalsa_message_body_ref:
638    references the structure of given message possibly fetching also all
639    headers.
640    message parts can be fetched later on.
641 */
642 gboolean
libbalsa_message_body_ref(LibBalsaMessage * message,gboolean read,gboolean fetch_all_headers)643 libbalsa_message_body_ref(LibBalsaMessage * message, gboolean read,
644                           gboolean fetch_all_headers)
645 {
646     LibBalsaFetchFlag flags = 0;
647     gboolean retval = TRUE;
648 
649     g_return_val_if_fail(message, FALSE);
650     if (!message->mailbox) return FALSE;
651     g_return_val_if_fail(MAILBOX_OPEN(message->mailbox), FALSE);
652 
653     libbalsa_lock_mailbox(message->mailbox);
654 
655     if(fetch_all_headers && !message->has_all_headers)
656         flags |=  LB_FETCH_RFC822_HEADERS;
657 
658     if (message->body_ref == 0 && !message->body_list)
659         /* not fetched yet */
660         flags |= LB_FETCH_STRUCTURE;
661 
662     if (flags)
663         retval =
664             libbalsa_mailbox_fetch_message_structure(message->mailbox,
665                                                      message, flags);
666     if (retval)
667 	message->body_ref++;
668     libbalsa_unlock_mailbox(message->mailbox);
669 
670     return retval;
671 }
672 
673 
674 void
libbalsa_message_body_unref(LibBalsaMessage * message)675 libbalsa_message_body_unref(LibBalsaMessage * message)
676 {
677     g_return_if_fail(LIBBALSA_IS_MESSAGE(message));
678 
679     if (message->body_ref == 0)
680 	return;
681 
682    if(message->mailbox) { libbalsa_lock_mailbox(message->mailbox); }
683    if (--message->body_ref == 0) {
684 	libbalsa_message_body_free(message->body_list);
685 	message->body_list = NULL;
686 	if (message->mailbox)
687 	    libbalsa_mailbox_release_message(message->mailbox, message);
688 
689 	/* Free headers that we no longer need. */
690 	lb_message_headers_extra_destroy(message->headers);
691 	message->has_all_headers = 0;
692    }
693    if(message->mailbox) { libbalsa_unlock_mailbox(message->mailbox); }
694 }
695 
696 gboolean
libbalsa_message_is_multipart(LibBalsaMessage * message)697 libbalsa_message_is_multipart(LibBalsaMessage * message)
698 {
699     g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), FALSE);
700 
701     return message->headers->content_type ?
702 	g_mime_content_type_is_type(message->headers->content_type,
703 				    "multipart", "*") : FALSE;
704 }
705 
706 gboolean
libbalsa_message_is_partial(LibBalsaMessage * message,gchar ** id)707 libbalsa_message_is_partial(LibBalsaMessage * message, gchar ** id)
708 {
709     GMimeContentType *content_type;
710 
711     g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), FALSE);
712 
713     content_type = message->headers->content_type;
714     if (!content_type
715 	|| !g_mime_content_type_is_type(content_type,
716 					"message", "partial"))
717 	return FALSE;
718 
719     if (id)
720 	*id = g_strdup(g_mime_content_type_get_parameter(content_type,
721 							 "id"));
722 
723     return TRUE;
724 }
725 
726 /* Go through all parts and try to figure out whether it is a message
727    with attachments or not. It still yields insatsfactory
728    results... */
729 static gboolean
has_attached_part(LibBalsaMessageBody * body)730 has_attached_part(LibBalsaMessageBody *body)
731 {
732     LibBalsaMessageBody *lbbody;
733     /* the condition matches the one used in add_multipart_mixed() */
734     for(lbbody=body; lbbody; lbbody = lbbody->next) {
735         /* printf("part %s has disposition %s\n",
736                lbbody->content_type, lbbody->content_dsp); */
737         if(!libbalsa_message_body_is_multipart(lbbody) &&
738            !libbalsa_message_body_is_inline(lbbody) ) {
739             /* puts("Attachment found!"); */
740             return TRUE;
741         }
742         if(lbbody->parts && has_attached_part(lbbody->parts))
743             return TRUE;
744     }
745     /* no part was an  attachment */
746     return FALSE;
747 }
748 
749 gboolean
libbalsa_message_has_attachment(LibBalsaMessage * message)750 libbalsa_message_has_attachment(LibBalsaMessage * message)
751 {
752     g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), FALSE);
753 
754     /* A message has attachments if main message or one of the parts
755        has Content-type: multipart/mixed AND members with
756        Content-disposition: attachment. Unfortunately, part list may
757        not be available at this stage. */
758     if(!message->body_list) {
759         return message->headers->content_type &&
760             g_mime_content_type_is_type(message->headers->content_type,
761                                         "multipart", "mixed");
762     } else {
763         /* use "exact" algorithm */
764         return (has_attached_part(message->body_list->next) ||
765 		has_attached_part(message->body_list->parts));
766     }
767  }
768 
769 #ifdef HAVE_GPGME
770 gboolean
libbalsa_message_is_pgp_signed(LibBalsaMessage * message)771 libbalsa_message_is_pgp_signed(LibBalsaMessage * message)
772 {
773     g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), FALSE);
774 
775     return message->headers->content_type ?
776 	g_mime_content_type_is_type(message->headers->content_type,
777 				    "multipart", "signed") : FALSE;
778 }
779 
780 gboolean
libbalsa_message_is_pgp_encrypted(LibBalsaMessage * message)781 libbalsa_message_is_pgp_encrypted(LibBalsaMessage * message)
782 {
783     g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), FALSE);
784 
785     return message->headers->content_type ?
786 	g_mime_content_type_is_type(message->headers->content_type,
787 				    "multipart", "encrypted") : FALSE;
788 }
789 #endif
790 
791 void
libbalsa_message_append_part(LibBalsaMessage * message,LibBalsaMessageBody * body)792 libbalsa_message_append_part(LibBalsaMessage * message,
793 			     LibBalsaMessageBody * body)
794 {
795     LibBalsaMessageBody *part;
796 
797     g_return_if_fail(message != NULL);
798     g_return_if_fail(LIBBALSA_IS_MESSAGE(message));
799 
800     if (message->body_list == NULL) {
801 	message->body_list = body;
802     } else {
803 	part = message->body_list;
804 	while (part->next != NULL)
805 	    part = part->next;
806 	part->next = body;
807     }
808 }
809 
810 /* libbalsa_message_set_dispnotify:
811    sets a disposition notify to a given address
812    address can be NULL.
813 */
814 void
libbalsa_message_set_dispnotify(LibBalsaMessage * message,InternetAddress * ia)815 libbalsa_message_set_dispnotify(LibBalsaMessage * message,
816                                 InternetAddress * ia)
817 {
818     g_return_if_fail(message);
819 
820     g_object_unref(message->headers->dispnotify_to);
821     if (ia) {
822 	message->headers->dispnotify_to = internet_address_list_new ();
823 	internet_address_list_add (message->headers->dispnotify_to, ia);
824     } else {
825 	message->headers->dispnotify_to = NULL;
826     }
827 }
828 
829 #ifndef MESSAGE_COPY_CONTENT
830 /* libbalsa_message_get_subject:
831    get constant pointer to the subject of the message;
832 */
833 const gchar *
libbalsa_message_get_subject(LibBalsaMessage * msg)834 libbalsa_message_get_subject(LibBalsaMessage* msg)
835 {
836     const gchar *ret;
837     if(!msg->subj &&
838        msg->mime_msg && msg->mailbox) { /* a message in a mailbox... */
839         g_return_val_if_fail(MAILBOX_OPEN(msg->mailbox), NULL);
840         ret = g_mime_message_get_subject(msg->mime_msg);
841         libbalsa_message_set_subject_from_header(msg, ret);
842     } else
843 	ret = msg->subj;
844 
845     return ret ? ret : _("(No subject)");
846 }
847 
848 
849 guint
libbalsa_message_get_lines(LibBalsaMessage * msg)850 libbalsa_message_get_lines(LibBalsaMessage* msg)
851 {
852     /* set the line count */
853     const char *value;
854     if (!msg->mime_msg)
855 	return 0;
856     value = g_mime_object_get_header(msg->mime_msg, "Lines");
857     if (!value)
858 	return 0;
859     return atoi(value);
860 }
861 glong
libbalsa_message_get_length(LibBalsaMessage * msg)862 libbalsa_message_get_length(LibBalsaMessage* msg)
863 {
864     /* set the length */
865     const char *value;
866     if (!msg->mime_msg)
867 	return 0;
868     value = g_mime_object_get_header(msg->mime_msg, "Content-Length");
869     if (!value)
870 	return 0;
871     return atoi(value);
872 }
873 
874 glong
libbalsa_message_get_no(LibBalsaMessage * msg)875 libbalsa_message_get_no(LibBalsaMessage* msg)
876 {
877     return msg->msgno;
878 }
879 
880 
881 #endif
882 
883 /* Populate headers from mime_msg, but only the members that are needed
884  * all the time. */
885 static InternetAddressList *
lb_message_recipients(GMimeMessage * message,GMimeRecipientType type)886 lb_message_recipients(GMimeMessage * message, GMimeRecipientType type)
887 {
888     const InternetAddressList *list;
889     InternetAddressList *copy = NULL;
890 
891     if ((list = g_mime_message_get_recipients (message, type))) {
892 	copy = internet_address_list_new ();
893 	internet_address_list_append (copy, (InternetAddressList *) list);
894     }
895 
896     return copy;
897 }
898 
899 static void
lb_message_headers_basic_from_gmime(LibBalsaMessageHeaders * headers,GMimeMessage * mime_msg)900 lb_message_headers_basic_from_gmime(LibBalsaMessageHeaders *headers,
901 				    GMimeMessage *mime_msg)
902 {
903     g_return_if_fail(headers);
904     g_return_if_fail(mime_msg != NULL);
905 
906     if (!headers->from)
907         headers->from = internet_address_list_parse_string(mime_msg->from);
908 
909     if (!headers->date)
910 	g_mime_message_get_date(mime_msg, &headers->date, NULL);
911 
912     if (!headers->to_list)
913         headers->to_list =
914             lb_message_recipients(mime_msg, GMIME_RECIPIENT_TYPE_TO);
915 
916     if (!headers->content_type) {
917 	/* If we could:
918 	 * headers->content_type =
919 	 *     g_mime_content_type_copy
920 	 *         (g_mime_object_get_content_type(mime_msg->mime_part));
921 	 */
922 	GMimeContentType *content_type;
923 	gchar *str;
924 	g_return_if_fail(headers->content_type == NULL);
925 	content_type = g_mime_object_get_content_type(mime_msg->mime_part);
926 	str = g_mime_content_type_to_string(content_type);
927 	headers->content_type = g_mime_content_type_new_from_string(str);
928 	g_free(str);
929     }
930 }
931 
932 /* Populate headers from mime_msg, but only the members not handled in
933  * lb_message_headers_basic_from_gmime. */
934 static void
lb_message_headers_extra_from_gmime(LibBalsaMessageHeaders * headers,GMimeMessage * mime_msg)935 lb_message_headers_extra_from_gmime(LibBalsaMessageHeaders *headers,
936 				    GMimeMessage *mime_msg)
937 {
938     g_return_if_fail(headers);
939     g_return_if_fail(mime_msg != NULL);
940 
941     if (!headers->reply_to)
942         headers->reply_to =
943 	    internet_address_list_parse_string(mime_msg->reply_to);
944 
945     if (!headers->dispnotify_to)
946         headers->dispnotify_to =
947             internet_address_list_parse_string(g_mime_object_get_header
948                                           (GMIME_OBJECT(mime_msg),
949                                            "Disposition-Notification-To"));
950 
951     if (!headers->cc_list)
952         headers->cc_list =
953             lb_message_recipients(mime_msg, GMIME_RECIPIENT_TYPE_CC);
954 
955     if (!headers->bcc_list)
956         headers->bcc_list =
957             lb_message_recipients(mime_msg, GMIME_RECIPIENT_TYPE_BCC);
958 
959     /* Get fcc from message */
960     if (!headers->fcc_url)
961 	headers->fcc_url =
962 	    g_strdup(g_mime_object_get_header(GMIME_OBJECT(mime_msg), "X-Balsa-Fcc"));
963 }
964 
965 /* Populate headers from the info in mime_msg. */
966 void
libbalsa_message_headers_from_gmime(LibBalsaMessageHeaders * headers,GMimeMessage * mime_msg)967 libbalsa_message_headers_from_gmime(LibBalsaMessageHeaders *headers,
968 				    GMimeMessage *mime_msg)
969 {
970     lb_message_headers_basic_from_gmime(headers, mime_msg);
971     lb_message_headers_extra_from_gmime(headers, mime_msg);
972 }
973 
974 /* Populate message and message->headers from the info in mime_msg,
975  * but only the members that are needed all the time. */
976 void
libbalsa_message_init_from_gmime(LibBalsaMessage * message,GMimeMessage * mime_msg)977 libbalsa_message_init_from_gmime(LibBalsaMessage * message,
978 				 GMimeMessage *mime_msg)
979 {
980     const gchar *header;
981 
982     g_return_if_fail(LIBBALSA_IS_MESSAGE(message));
983     g_return_if_fail(GMIME_IS_MESSAGE(mime_msg));
984 
985 #ifdef MESSAGE_COPY_CONTENT
986     header = g_mime_message_get_subject(mime_msg);
987     libbalsa_message_set_subject_from_header(message, header);
988 
989     header = g_mime_object_get_header(GMIME_OBJECT(mime_msg), "Content-Length");
990     if (header)
991 	message->length = atoi(header);
992 #endif
993     header = g_mime_message_get_message_id(mime_msg);
994     if (header)
995 	message->message_id = g_strdup(header);
996 
997     header = g_mime_object_get_header(GMIME_OBJECT(mime_msg), "References");
998     if (header)
999 	libbalsa_message_set_references_from_string(message, header);
1000 
1001     header = g_mime_object_get_header(GMIME_OBJECT(mime_msg), "In-Reply-To");
1002     if (header)
1003 	libbalsa_message_set_in_reply_to_from_string(message, header);
1004 
1005     lb_message_headers_basic_from_gmime(message->headers, mime_msg);
1006 }
1007 
1008 /* Create a newly allocated list of references for threading.
1009  * This is a deep copy, with its own strings: deallocate with
1010  * g_free and g_list_free. */
1011 GList *
libbalsa_message_refs_for_threading(LibBalsaMessage * message)1012 libbalsa_message_refs_for_threading(LibBalsaMessage * message)
1013 {
1014     GList *tmp;
1015     GList *foo;
1016 
1017     g_return_val_if_fail(message != NULL, NULL);
1018 
1019     if (message->in_reply_to && message->in_reply_to->next)
1020 	return NULL;
1021 
1022     tmp = g_list_copy(message->references);
1023 
1024     if (message->in_reply_to) {
1025 	/* some mailers provide in_reply_to but no references, and
1026 	 * some apparently provide both but with the references in
1027 	 * the wrong order; we'll just make sure it's the last item
1028 	 * of this list */
1029 	foo = g_list_find_custom(tmp, message->in_reply_to->data,
1030 				 (GCompareFunc) strcmp);
1031 
1032 	if (foo) {
1033 	    tmp = g_list_remove_link(tmp, foo);
1034 	    g_list_free_1(foo);
1035 	}
1036 	tmp = g_list_append(tmp, message->in_reply_to->data);
1037     }
1038 
1039     for (foo = tmp; foo; foo = foo->next)
1040 	foo->data = g_strdup((gchar *) foo->data);
1041 
1042     return tmp;
1043 }
1044 
1045 static GList *
references_decode(const gchar * str)1046 references_decode(const gchar * str)
1047 {
1048     GMimeReferences *references, *reference;
1049     GList *list = NULL;
1050 
1051     reference = references = g_mime_references_decode(str);
1052     while (reference) {
1053 	list = g_list_prepend(list, g_strdup(reference->msgid));
1054 	reference = reference->next;
1055     }
1056     g_mime_references_clear(&references);
1057 
1058     return g_list_reverse(list);
1059 }
1060 
1061 void
libbalsa_message_set_references_from_string(LibBalsaMessage * message,const gchar * str)1062 libbalsa_message_set_references_from_string(LibBalsaMessage * message,
1063 					    const gchar *str)
1064 {
1065  /* Empty references are acceptable but require no action. Similarly,
1066     if references were set already, there is not reason to set them
1067     again - they are immutable anyway. */
1068     if(!message->references && str)
1069         message->references = references_decode(str);
1070 }
1071 
1072 void
libbalsa_message_set_in_reply_to_from_string(LibBalsaMessage * message,const gchar * str)1073 libbalsa_message_set_in_reply_to_from_string(LibBalsaMessage * message,
1074 					     const gchar * str)
1075 {
1076     if (!message->in_reply_to && str) {
1077 	/* FIXME for Balsa's old non-compliant header */
1078 	gchar *p = strrchr(str, ';');
1079 	p = p ? g_strndup(str, p - str) : g_strdup(str);
1080 	message->in_reply_to = references_decode(p);
1081 	g_free(p);
1082     }
1083 }
1084 
1085 /* set a header, if (all) or if it's needed all the time:
1086  *   headers->from
1087  *   headers->date
1088  *   headers->to_list
1089  *   headers->content_type
1090  *   subj
1091  *   length
1092  *
1093  * needed for threading local mailboxes:
1094  *   message_id
1095  *   references
1096  *   in_reply_to
1097  */
1098 static gboolean
lbmsg_set_header(LibBalsaMessage * message,const gchar * name,const gchar * value,gboolean all)1099 lbmsg_set_header(LibBalsaMessage *message, const gchar *name,
1100                  const gchar* value, gboolean all)
1101 {
1102     gchar *val = NULL;
1103 
1104     if (libbalsa_text_attr_string(value)) {
1105         /* Broken header: force it to utf8 using Balsa's fallback
1106          * charset, then rfc2047-encode it for passing to the
1107          * appropriate GMime decoder. */
1108         gchar *tmp = g_strdup(value);
1109         libbalsa_utf8_sanitize(&tmp, TRUE, NULL);
1110         val = g_mime_utils_header_encode_text(tmp);
1111         g_free(tmp);
1112 #ifdef DEBUG
1113         g_print("%s: non-ascii \"%s\" header \"%s\" encoded as \"%s\"\n",
1114                 __func__, name, value, val);
1115 #endif /* DEBUG */
1116         value = val;
1117     }
1118 
1119     if (g_ascii_strcasecmp(name, "Subject") == 0) {
1120 	if (!strcmp(value,
1121                     "DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA")) {
1122             g_free(val);
1123 	    return FALSE;
1124         }
1125 #if MESSAGE_COPY_CONTENT
1126         libbalsa_message_set_subject_from_header(message, value);
1127 #endif
1128     } else
1129     if (g_ascii_strcasecmp(name, "Date") == 0) {
1130 	message->headers->date = g_mime_utils_header_decode_date(value, NULL);
1131     } else
1132     if (message->headers->from == NULL &&
1133 	g_ascii_strcasecmp(name, "From") == 0) {
1134         message->headers->from = internet_address_list_parse_string(value);
1135     } else
1136     if (message->headers->to_list == NULL &&
1137         g_ascii_strcasecmp(name, "To") == 0) {
1138 	message->headers->to_list = internet_address_list_parse_string(value);
1139     } else
1140     if (g_ascii_strcasecmp(name, "In-Reply-To") == 0) {
1141 	libbalsa_message_set_in_reply_to_from_string(message, value);
1142     } else
1143     if (message->message_id == NULL &&
1144         g_ascii_strcasecmp(name, "Message-ID") == 0) {
1145 	message->message_id = g_mime_utils_decode_message_id(value);
1146     } else
1147     if (g_ascii_strcasecmp(name, "References") == 0) {
1148 	libbalsa_message_set_references_from_string(message, value);
1149     } else
1150     if (message->headers->content_type == NULL &&
1151 	g_ascii_strcasecmp(name, "Content-Type") == 0) {
1152 	message->headers->content_type =
1153 	    g_mime_content_type_new_from_string(value);
1154     } else
1155     if (message->headers->dispnotify_to == NULL &&
1156 	g_ascii_strcasecmp(name, "Disposition-Notification-To") == 0) {
1157 	message->headers->dispnotify_to =
1158 	    internet_address_list_parse_string(value);
1159     } else
1160 #ifdef MESSAGE_COPY_CONTENT
1161     if (g_ascii_strcasecmp(name, "Content-Length") == 0) {
1162 	    message->length = atoi(value);
1163     } else
1164 #endif
1165     if (all)
1166         message->headers->user_hdrs =
1167             g_list_prepend(message->headers->user_hdrs,
1168                            libbalsa_create_hdr_pair(name,
1169                                                     g_strdup(value)));
1170 
1171     g_free(val);
1172 
1173     return TRUE;
1174 }
1175 
1176 static gboolean
lb_message_set_headers_from_string(LibBalsaMessage * message,const gchar * lines,gboolean all)1177 lb_message_set_headers_from_string(LibBalsaMessage *message,
1178 				   const gchar *lines, gboolean all)
1179 {
1180     gchar *header, *value;
1181     const gchar *val, *eoh;
1182     do {
1183         for(val = lines; *val && *val >32 && *val<127 && *val != ':'; val++)
1184             ;
1185         if(*val != ':') /* parsing error */
1186             return FALSE;
1187         for(eoh = val+1; *eoh && (eoh[0] != '\n' || isspace(eoh[1])); eoh++)
1188             ;
1189         header = g_strndup(lines, val-lines);
1190         lines = eoh;
1191         for(val=val+1; *val && isspace(*val); val++)
1192             ;                           /* strip spaces at front... */
1193         while(eoh>val && isspace(*eoh)) eoh--; /* .. and at the end */
1194         value  = g_strndup(val, eoh-val+1);
1195 
1196         lbmsg_set_header(message, header, value, all);
1197         g_free(header); g_free(value);
1198         if(!*lines || !*++lines) break;
1199     } while(1);
1200     return TRUE;
1201 }
1202 
1203 gboolean
libbalsa_message_set_headers_from_string(LibBalsaMessage * message,const gchar * lines)1204 libbalsa_message_set_headers_from_string(LibBalsaMessage *message,
1205                                          const gchar *lines)
1206 {
1207     return lb_message_set_headers_from_string(message, lines, TRUE);
1208 }
1209 
1210 void
libbalsa_message_load_envelope_from_stream(LibBalsaMessage * message,GMimeStream * gmime_stream)1211 libbalsa_message_load_envelope_from_stream(LibBalsaMessage * message,
1212                                            GMimeStream *gmime_stream)
1213 {
1214     GMimeStream *gmime_stream_filter;
1215     GMimeFilter *gmime_filter_crlf;
1216     GMimeStream *gmime_stream_buffer;
1217     GByteArray *line;
1218     guchar lookahead;
1219 
1220     libbalsa_mime_stream_shared_lock(gmime_stream);
1221 
1222     /* CRLF-filter the message stream; we do not want '\r' in header
1223      * fields, and finding the empty line that separates the body from
1224      * the header is simpler if it has no '\r' in it. */
1225     gmime_stream_filter =
1226         g_mime_stream_filter_new(gmime_stream);
1227 
1228     gmime_filter_crlf =
1229         g_mime_filter_crlf_new(FALSE,
1230                                FALSE);
1231     g_mime_stream_filter_add(GMIME_STREAM_FILTER(gmime_stream_filter),
1232                                                  gmime_filter_crlf);
1233     g_object_unref(gmime_filter_crlf);
1234 
1235     /* Buffer the message stream, so we can read it line by line. */
1236     gmime_stream_buffer =
1237         g_mime_stream_buffer_new(gmime_stream_filter,
1238                                  GMIME_STREAM_BUFFER_BLOCK_READ);
1239     g_object_unref(gmime_stream_filter);
1240 
1241     /* Read header fields until either:
1242      * - we find an empty line, or
1243      * - end of file.
1244      */
1245     line = g_byte_array_new();
1246     do {
1247 	g_mime_stream_buffer_readln(gmime_stream_buffer, line);
1248 	while (g_mime_stream_read(gmime_stream_buffer,
1249 		                  (char *) &lookahead, 1) == 1
1250                && (lookahead == ' ' || lookahead == '\t')) {
1251             g_byte_array_append(line, &lookahead, 1);
1252             g_mime_stream_buffer_readln(gmime_stream_buffer, line);
1253 	}
1254 	if (line->len == 0 || line->data[line->len-1]!='\n') {
1255 	    /* EOF or read error; in either case, message has no body. */
1256 	    break;
1257 	}
1258 	line->data[line->len-1]='\0'; /* terminate line by overwriting '\n' */
1259 	if (!lb_message_set_headers_from_string(message,
1260 				                (gchar *) line->data,
1261 						FALSE)) {
1262 	    /* Ignore error return caused by malformed header. */
1263 	}
1264 	if (lookahead == '\n') {/* end of header */
1265             /* Message looks valid--set its length. */
1266             message->length = g_mime_stream_length(gmime_stream);
1267 	    break;
1268 	}
1269 	line->len = 0;
1270 	g_byte_array_append(line, &lookahead, 1);
1271     } while (TRUE);
1272     g_byte_array_free(line, TRUE);
1273 
1274     g_object_unref(gmime_stream_buffer);
1275     g_mime_stream_reset(gmime_stream);
1276     libbalsa_mime_stream_shared_unlock(gmime_stream);
1277 }
1278 
1279 void
libbalsa_message_load_envelope(LibBalsaMessage * message)1280 libbalsa_message_load_envelope(LibBalsaMessage *message)
1281 {
1282     GMimeStream *gmime_stream;
1283 
1284     gmime_stream = libbalsa_message_stream(message);
1285     if (!gmime_stream)
1286 	return;
1287 
1288     libbalsa_message_load_envelope_from_stream(message, gmime_stream);
1289     g_object_unref(gmime_stream);
1290 }
1291 
1292 GMimeStream *
libbalsa_message_stream(LibBalsaMessage * message)1293 libbalsa_message_stream(LibBalsaMessage * message)
1294 {
1295     LibBalsaMailbox *mailbox;
1296     GMimeStream *mime_stream;
1297 
1298     g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), NULL);
1299     mailbox = message->mailbox;
1300     g_return_val_if_fail(mailbox != NULL || message->mime_msg != NULL,
1301                          NULL);
1302 
1303     if (mailbox)
1304         return libbalsa_mailbox_get_message_stream(mailbox,
1305                                                    message->msgno, FALSE);
1306 
1307     mime_stream = g_mime_stream_mem_new();
1308     g_mime_object_write_to_stream(GMIME_OBJECT(message->mime_msg),
1309                                   mime_stream);
1310     g_mime_stream_reset(mime_stream);
1311 
1312     return mime_stream;
1313 }
1314 
1315 gboolean
libbalsa_message_copy(LibBalsaMessage * message,LibBalsaMailbox * dest,GError ** err)1316 libbalsa_message_copy(LibBalsaMessage * message, LibBalsaMailbox * dest,
1317                       GError ** err)
1318 {
1319     LibBalsaMailbox *mailbox;
1320     gboolean retval;
1321 
1322     g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), FALSE);
1323     g_return_val_if_fail(LIBBALSA_IS_MAILBOX(dest), FALSE);
1324     mailbox = message->mailbox;
1325     g_return_val_if_fail(mailbox != NULL || message->mime_msg != NULL,
1326                          FALSE);
1327 
1328     if (mailbox) {
1329         GArray *msgnos = g_array_sized_new(FALSE, FALSE, sizeof(guint), 1);
1330         g_array_append_val(msgnos, message->msgno);
1331         retval =
1332             libbalsa_mailbox_messages_copy(mailbox, msgnos, dest, err);
1333         g_array_free(msgnos, TRUE);
1334     } else {
1335         GMimeStream *mime_stream = libbalsa_message_stream(message);
1336         retval = libbalsa_mailbox_add_message(dest, mime_stream,
1337                                               message->flags, err);
1338         g_object_unref(mime_stream);
1339     }
1340 
1341     return retval;
1342 }
1343 
1344 void
libbalsa_message_set_subject(LibBalsaMessage * message,const gchar * subject)1345 libbalsa_message_set_subject(LibBalsaMessage * message,
1346                              const gchar * subject)
1347 {
1348     g_free(message->subj);
1349     message->subj = g_strdup(subject);
1350     libbalsa_utf8_sanitize(&message->subj, TRUE, NULL);
1351     canonize_header_value(message->subj);
1352 }
1353 
1354 void
libbalsa_message_set_subject_from_header(LibBalsaMessage * message,const gchar * header)1355 libbalsa_message_set_subject_from_header(LibBalsaMessage * message,
1356                                          const gchar * header)
1357 {
1358     if (header) {
1359         gchar *subject =
1360             g_mime_utils_header_decode_text(header);
1361         libbalsa_message_set_subject(message, subject);
1362         g_free(subject);
1363     }
1364 }
1365 
1366 const gchar *
libbalsa_message_get_tempdir(LibBalsaMessage * message)1367 libbalsa_message_get_tempdir(LibBalsaMessage * message)
1368 {
1369     if (!message->tempdir) {
1370         if (!libbalsa_mktempdir(&message->tempdir))
1371             g_warning("Could not make tempdir");
1372     }
1373 
1374     return message->tempdir;
1375 }
1376