cafe528db05c561b68408065c81ca73da47402b7
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "defs.h"
21
22 #include <glib.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26
27 #include "intl.h"
28 #include "main.h"
29 #include "utils.h"
30 #include "procmsg.h"
31 #include "procheader.h"
32 #include "send_message.h"
33 #include "procmime.h"
34 #include "statusbar.h"
35 #include "prefs_filtering.h"
36 #include "filtering.h"
37 #include "folder.h"
38 #include "prefs_common.h"
39 #include "account.h"
40 #include "alertpanel.h"
41 #include "news.h"
42 #include "hooks.h"
43 #include "msgcache.h"
44 #include "partial_download.h"
45
46 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
47 {
48         GHashTable *msg_table;
49
50         if (mlist == NULL) return NULL;
51
52         msg_table = g_hash_table_new(NULL, g_direct_equal);
53         procmsg_msg_hash_table_append(msg_table, mlist);
54
55         return msg_table;
56 }
57
58 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
59 {
60         GSList *cur;
61         MsgInfo *msginfo;
62
63         if (msg_table == NULL || mlist == NULL) return;
64
65         for (cur = mlist; cur != NULL; cur = cur->next) {
66                 msginfo = (MsgInfo *)cur->data;
67
68                 g_hash_table_insert(msg_table,
69                                     GUINT_TO_POINTER(msginfo->msgnum),
70                                     msginfo);
71         }
72 }
73
74 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
75 {
76         GHashTable *msg_table;
77         GSList *cur;
78         MsgInfo *msginfo;
79
80         if (mlist == NULL) return NULL;
81
82         msg_table = g_hash_table_new(NULL, g_direct_equal);
83
84         for (cur = mlist; cur != NULL; cur = cur->next) {
85                 msginfo = (MsgInfo *)cur->data;
86                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
87         }
88
89         return msg_table;
90 }
91
92 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
93 {
94         GSList *cur;
95         MsgInfo *msginfo;
96         gint last = 0;
97
98         for (cur = mlist; cur != NULL; cur = cur->next) {
99                 msginfo = (MsgInfo *)cur->data;
100                 if (msginfo && msginfo->msgnum > last)
101                         last = msginfo->msgnum;
102         }
103
104         return last;
105 }
106
107 void procmsg_msg_list_free(GSList *mlist)
108 {
109         GSList *cur;
110         MsgInfo *msginfo;
111
112         for (cur = mlist; cur != NULL; cur = cur->next) {
113                 msginfo = (MsgInfo *)cur->data;
114                 procmsg_msginfo_free(msginfo);
115         }
116         g_slist_free(mlist);
117 }
118
119 struct MarkSum {
120         gint *new_msgs;
121         gint *unread_msgs;
122         gint *total_msgs;
123         gint *min;
124         gint *max;
125         gint first;
126 };
127
128 /* CLAWS subject threading:
129   
130   in the first round it inserts subject lines in a 
131   relation (subject <-> node)
132
133   the second round finishes the threads by attaching
134   matching subject lines to the one found in the
135   relation. will use the oldest node with the same
136   subject that is not more then thread_by_subject_max_age
137   days old (see subject_relation_lookup)
138 */  
139
140 static void subject_relation_insert(GRelation *relation, GNode *node)
141 {
142         gchar *subject;
143         MsgInfo *msginfo;
144
145         g_return_if_fail(relation != NULL);
146         g_return_if_fail(node != NULL);
147         msginfo = (MsgInfo *) node->data;
148         g_return_if_fail(msginfo != NULL);
149
150         subject = msginfo->subject;
151         if (subject == NULL)
152                 return;
153         subject += subject_get_prefix_length(subject);
154
155         g_relation_insert(relation, subject, node);
156 }
157
158 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
159 {
160         gchar *subject;
161         GTuples *tuples;
162         GNode *node = NULL;
163         gint prefix_length;
164     
165         g_return_val_if_fail(relation != NULL, NULL);
166
167         subject = msginfo->subject;
168         if (subject == NULL)
169                 return NULL;
170         prefix_length = subject_get_prefix_length(subject);
171         if (prefix_length <= 0)
172                 return NULL;
173         subject += prefix_length;
174         
175         tuples = g_relation_select(relation, subject, 0);
176         if (tuples == NULL)
177                 return NULL;
178
179         if (tuples->len > 0) {
180                 int i;
181                 GNode *relation_node;
182                 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
183                 gboolean match;
184
185                 /* check all nodes with the same subject to find the best parent */
186                 for (i = 0; i < tuples->len; i++) {
187                         relation_node = (GNode *) g_tuples_index(tuples, i, 1);
188                         relation_msginfo = (MsgInfo *) relation_node->data;
189                         match = FALSE;
190
191                         /* best node should be the oldest in the found nodes */
192                         /* parent node must not be older then msginfo */
193                         if ((relation_msginfo->date_t < msginfo->date_t) &&
194                             ((best_msginfo == NULL) ||
195                              (best_msginfo->date_t > relation_msginfo->date_t)))
196                                 match = TRUE;
197
198                         /* parent node must not be more then thread_by_subject_max_age
199                            days older then msginfo */
200                         if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
201                             prefs_common.thread_by_subject_max_age * 3600 * 24)
202                                 match = FALSE;
203
204                         /* can add new tests for all matching
205                            nodes found by subject */
206
207                         if (match) {
208                                 node = relation_node;
209                                 best_msginfo = relation_msginfo;
210                         }
211                 }           
212         }
213
214         g_tuples_destroy(tuples);
215         return node;
216 }
217
218 /* return the reversed thread tree */
219 GNode *procmsg_get_thread_tree(GSList *mlist)
220 {
221         GNode *root, *parent, *node, *next;
222         GHashTable *msgid_table;
223         GRelation *subject_relation;
224         MsgInfo *msginfo;
225         const gchar *msgid;
226
227         root = g_node_new(NULL);
228         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
229         subject_relation = g_relation_new(2);
230         g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
231
232         for (; mlist != NULL; mlist = mlist->next) {
233                 msginfo = (MsgInfo *)mlist->data;
234                 parent = root;
235
236                 if (msginfo->inreplyto) {
237                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
238                         if (parent == NULL) {
239                                 parent = root;
240                         }
241                 }
242                 node = g_node_insert_data_before
243                         (parent, parent == root ? parent->children : NULL,
244                          msginfo);
245                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
246                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
247
248                 /* CLAWS: add subject to relation (without prefix) */
249                 if (prefs_common.thread_by_subject) {
250                         subject_relation_insert(subject_relation, node);
251                 }
252         }
253
254         /* complete the unfinished threads */
255         for (node = root->children; node != NULL; ) {
256                 parent = NULL;
257                 next = node->next;
258                 msginfo = (MsgInfo *)node->data;
259                 if (msginfo->inreplyto) { 
260                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
261                         /* node should not be the parent, and node should not 
262                            be an ancestor of parent (circular reference) */
263                         if (parent && parent != node && 
264                             !g_node_is_ancestor(node, parent)) {
265                                 g_node_unlink(node);
266                                 g_node_insert_before
267                                         (parent, parent->children, node);
268                         }                               
269                 }
270                 node = next;
271         }
272
273         if (prefs_common.thread_by_subject) {
274                 for (node = root->children; node && node != NULL;) {
275                         next = node->next;
276                         msginfo = (MsgInfo *) node->data;
277                         
278                         parent = subject_relation_lookup(subject_relation, msginfo);
279                         
280                         /* the node may already be threaded by IN-REPLY-TO, so go up 
281                          * in the tree to 
282                            find the parent node */
283                         if (parent != NULL) {
284                                 if (g_node_is_ancestor(node, parent))
285                                         parent = NULL;
286                                 if (parent == node)
287                                         parent = NULL;
288                         }
289                         
290                         if (parent) {
291                                 g_node_unlink(node);
292                                 g_node_append(parent, node);
293                         }
294
295                         node = next;
296                 }       
297         }
298         
299         g_relation_destroy(subject_relation);
300         g_hash_table_destroy(msgid_table);
301
302         return root;
303 }
304
305 void procmsg_move_messages(GSList *mlist)
306 {
307         GSList *cur, *movelist = NULL;
308         MsgInfo *msginfo;
309         FolderItem *dest = NULL;
310
311         if (!mlist) return;
312
313         folder_item_update_freeze();
314
315         for (cur = mlist; cur != NULL; cur = cur->next) {
316                 msginfo = (MsgInfo *)cur->data;
317                 if (!dest) {
318                         dest = msginfo->to_folder;
319                         movelist = g_slist_append(movelist, msginfo);
320                 } else if (dest == msginfo->to_folder) {
321                         movelist = g_slist_append(movelist, msginfo);
322                 } else {
323                         folder_item_move_msgs(dest, movelist);
324                         g_slist_free(movelist);
325                         movelist = NULL;
326                         dest = msginfo->to_folder;
327                         movelist = g_slist_append(movelist, msginfo);
328                 }
329                 procmsg_msginfo_set_to_folder(msginfo, NULL);
330         }
331
332         if (movelist) {
333                 folder_item_move_msgs(dest, movelist);
334                 g_slist_free(movelist);
335         }
336
337         folder_item_update_thaw();
338 }
339
340 void procmsg_copy_messages(GSList *mlist)
341 {
342         GSList *cur, *copylist = NULL;
343         MsgInfo *msginfo;
344         FolderItem *dest = NULL;
345
346         if (!mlist) return;
347
348         folder_item_update_freeze();
349
350         for (cur = mlist; cur != NULL; cur = cur->next) {
351                 msginfo = (MsgInfo *)cur->data;
352                 if (!dest) {
353                         dest = msginfo->to_folder;
354                         copylist = g_slist_append(copylist, msginfo);
355                 } else if (dest == msginfo->to_folder) {
356                         copylist = g_slist_append(copylist, msginfo);
357                 } else {
358                         folder_item_copy_msgs(dest, copylist);
359                         g_slist_free(copylist);
360                         copylist = NULL;
361                         dest = msginfo->to_folder;
362                         copylist = g_slist_append(copylist, msginfo);
363                 }
364                 procmsg_msginfo_set_to_folder(msginfo, NULL);
365         }
366
367         if (copylist) {
368                 folder_item_copy_msgs(dest, copylist);
369                 g_slist_free(copylist);
370         }
371
372         folder_item_update_thaw();
373 }
374
375 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
376 {
377         gchar *file;
378
379         g_return_val_if_fail(msginfo != NULL, NULL);
380
381         if (msginfo->plaintext_file)
382                 file = g_strdup(msginfo->plaintext_file);
383         else {
384                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
385         }
386
387         return file;
388 }
389
390 gchar *procmsg_get_message_file(MsgInfo *msginfo)
391 {
392         gchar *filename = NULL;
393
394         g_return_val_if_fail(msginfo != NULL, NULL);
395
396         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
397         if (!filename)
398                 debug_print("can't fetch message %d\n", msginfo->msgnum);
399
400         return filename;
401 }
402
403 GSList *procmsg_get_message_file_list(GSList *mlist)
404 {
405         GSList *file_list = NULL;
406         MsgInfo *msginfo;
407         MsgFileInfo *fileinfo;
408         gchar *file;
409
410         while (mlist != NULL) {
411                 msginfo = (MsgInfo *)mlist->data;
412                 file = procmsg_get_message_file(msginfo);
413                 if (!file) {
414                         procmsg_message_file_list_free(file_list);
415                         return NULL;
416                 }
417                 fileinfo = g_new(MsgFileInfo, 1);
418                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
419                 fileinfo->file = file;
420                 fileinfo->flags = g_new(MsgFlags, 1);
421                 *fileinfo->flags = msginfo->flags;
422                 file_list = g_slist_prepend(file_list, fileinfo);
423                 mlist = mlist->next;
424         }
425
426         file_list = g_slist_reverse(file_list);
427
428         return file_list;
429 }
430
431 void procmsg_message_file_list_free(MsgInfoList *file_list)
432 {
433         GSList *cur;
434         MsgFileInfo *fileinfo;
435
436         for (cur = file_list; cur != NULL; cur = cur->next) {
437                 fileinfo = (MsgFileInfo *)cur->data;
438                 procmsg_msginfo_free(fileinfo->msginfo);
439                 g_free(fileinfo->file);
440                 g_free(fileinfo->flags);
441                 g_free(fileinfo);
442         }
443
444         g_slist_free(file_list);
445 }
446
447 FILE *procmsg_open_message(MsgInfo *msginfo)
448 {
449         FILE *fp;
450         gchar *file;
451
452         g_return_val_if_fail(msginfo != NULL, NULL);
453
454         file = procmsg_get_message_file_path(msginfo);
455         g_return_val_if_fail(file != NULL, NULL);
456
457         if (!is_file_exist(file)) {
458                 g_free(file);
459                 file = procmsg_get_message_file(msginfo);
460                 if (!file)
461                         return NULL;
462         }
463
464         if ((fp = fopen(file, "rb")) == NULL) {
465                 FILE_OP_ERROR(file, "fopen");
466                 g_free(file);
467                 return NULL;
468         }
469
470         g_free(file);
471
472         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
473                 gchar buf[BUFFSIZE];
474
475                 while (fgets(buf, sizeof(buf), fp) != NULL)
476                         if (buf[0] == '\r' || buf[0] == '\n') break;
477         }
478
479         return fp;
480 }
481
482 gboolean procmsg_msg_exist(MsgInfo *msginfo)
483 {
484         gchar *path;
485         gboolean ret;
486
487         if (!msginfo) return FALSE;
488
489         path = folder_item_get_path(msginfo->folder);
490         change_dir(path);
491         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
492         g_free(path);
493
494         return ret;
495 }
496
497 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
498                                 PrefsFilterType type)
499 {
500         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
501                                        {"X-ML-Name:",      NULL, TRUE},
502                                        {"X-List:",         NULL, TRUE},
503                                        {"X-Mailing-list:", NULL, TRUE},
504                                        {"List-Id:",        NULL, TRUE},
505                                        {"X-Sequence:",     NULL, TRUE},
506                                        {NULL,              NULL, FALSE}};
507         enum
508         {
509                 H_X_BEENTHERE    = 0,
510                 H_X_ML_NAME      = 1,
511                 H_X_LIST         = 2,
512                 H_X_MAILING_LIST = 3,
513                 H_LIST_ID        = 4,
514                 H_X_SEQUENCE     = 5
515         };
516
517         FILE *fp;
518
519         g_return_if_fail(msginfo != NULL);
520         g_return_if_fail(header != NULL);
521         g_return_if_fail(key != NULL);
522
523         *header = NULL;
524         *key = NULL;
525
526         switch (type) {
527         case FILTER_BY_NONE:
528                 return;
529         case FILTER_BY_AUTO:
530                 if ((fp = procmsg_open_message(msginfo)) == NULL)
531                         return;
532                 procheader_get_header_fields(fp, hentry);
533                 fclose(fp);
534
535 #define SET_FILTER_KEY(hstr, idx)       \
536 {                                       \
537         *header = g_strdup(hstr);       \
538         *key = hentry[idx].body;        \
539         hentry[idx].body = NULL;        \
540 }
541
542                 if (hentry[H_X_BEENTHERE].body != NULL) {
543                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
544                 } else if (hentry[H_X_ML_NAME].body != NULL) {
545                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
546                 } else if (hentry[H_X_LIST].body != NULL) {
547                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
548                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
549                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
550                 } else if (hentry[H_LIST_ID].body != NULL) {
551                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
552                         extract_list_id_str(*key);
553                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
554                         guchar *p;
555
556                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
557                         p = *key;
558                         while (*p != '\0') {
559                                 while (*p != '\0' && !isspace(*p)) p++;
560                                 while (isspace(*p)) p++;
561                                 if (isdigit(*p)) {
562                                         *p = '\0';
563                                         break;
564                                 }
565                         }
566                         g_strstrip(*key);
567                 } else if (msginfo->subject) {
568                         *header = g_strdup("subject");
569                         *key = g_strdup(msginfo->subject);
570                 }
571
572 #undef SET_FILTER_KEY
573
574                 g_free(hentry[H_X_BEENTHERE].body);
575                 hentry[H_X_BEENTHERE].body = NULL;
576                 g_free(hentry[H_X_ML_NAME].body);
577                 hentry[H_X_ML_NAME].body = NULL;
578                 g_free(hentry[H_X_LIST].body);
579                 hentry[H_X_LIST].body = NULL;
580                 g_free(hentry[H_X_MAILING_LIST].body);
581                 hentry[H_X_MAILING_LIST].body = NULL;
582                 g_free(hentry[H_LIST_ID].body);
583                 hentry[H_LIST_ID].body = NULL;
584
585                 break;
586         case FILTER_BY_FROM:
587                 *header = g_strdup("from");
588                 *key = g_strdup(msginfo->from);
589                 break;
590         case FILTER_BY_TO:
591                 *header = g_strdup("to");
592                 *key = g_strdup(msginfo->to);
593                 break;
594         case FILTER_BY_SUBJECT:
595                 *header = g_strdup("subject");
596                 *key = g_strdup(msginfo->subject);
597                 break;
598         default:
599                 break;
600         }
601 }
602
603 void procmsg_empty_trash(FolderItem *trash)
604 {
605         FILE *fp;
606
607         if (trash && trash->total_msgs > 0) {
608                 GSList *mlist = folder_item_get_msg_list(trash);
609                 GSList *cur;
610                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
611                         MsgInfo * msginfo = (MsgInfo *) cur->data;
612                         partial_mark_for_delete(msginfo);
613                         procmsg_msginfo_free(msginfo);
614                 }
615
616                 folder_item_remove_all_msg(trash);
617         }
618 }
619
620 void procmsg_empty_all_trash(void)
621 {
622         FolderItem *trash;
623         GList *cur;
624
625         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
626                 trash = FOLDER(cur->data)->trash;
627                 procmsg_empty_trash(trash);
628         }
629 }
630
631 /*!
632  *\brief        Send messages in queue
633  *
634  *\param        queue Queue folder to process
635  *\param        save_msgs Unused
636  *
637  *\return       Number of messages sent, negative if an error occurred
638  *              positive if no error occurred
639  */
640 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
641 {
642         gint sent = 0, err = 0;
643         GSList *list, *elem;
644
645         if (!queue)
646                 queue = folder_get_default_queue();
647         g_return_val_if_fail(queue != NULL, -1);
648
649         folder_item_scan(queue);
650         list = folder_item_get_msg_list(queue);
651
652         for (elem = list; elem != NULL; elem = elem->next) {
653                 gchar *file;
654                 MsgInfo *msginfo;
655                 
656                 msginfo = (MsgInfo *)(elem->data);
657                 if (!MSG_IS_LOCKED(msginfo->flags)) {
658                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
659                         if (file) {
660                                 if (procmsg_send_message_queue(file) < 0) {
661                                         g_warning("Sending queued message %d failed.\n", 
662                                                   msginfo->msgnum);
663                                         err++;
664                                 } else {
665                                         /* CLAWS: 
666                                          * We save in procmsg_send_message_queue because
667                                          * we need the destination folder from the queue
668                                          * header
669                                                         
670                                         if (save_msgs)
671                                                 procmsg_save_to_outbox
672                                                         (queue->folder->outbox,
673                                                          file, TRUE);
674                                          */
675                                         sent++; 
676                                         folder_item_remove_msg(queue, msginfo->msgnum);
677                                 }
678                                 g_free(file);
679                         }
680                 }
681                 /* FIXME: supposedly if only one message is locked, and queue
682                  * is being flushed, the following free says something like 
683                  * "freeing msg ## in folder (nil)". */
684                 procmsg_msginfo_free(msginfo);
685         }
686
687         return (err != 0 ? -err : sent);
688 }
689
690 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
691 {
692         FILE *fp, *outfp;
693         gchar buf[BUFFSIZE];
694         
695         if ((fp = fopen(in, "rb")) == NULL) {
696                 FILE_OP_ERROR(in, "fopen");
697                 return -1;
698         }
699         if ((outfp = fopen(out, "wb")) == NULL) {
700                 FILE_OP_ERROR(out, "fopen");
701                 fclose(fp);
702                 return -1;
703         }
704         while (fgets(buf, sizeof(buf), fp) != NULL)
705                 if (buf[0] == '\r' || buf[0] == '\n') break;
706         while (fgets(buf, sizeof(buf), fp) != NULL)
707                 fputs(buf, outfp);
708         fclose(outfp);
709         fclose(fp);
710         return 0;
711
712 }
713 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
714                             gboolean is_queued)
715 {
716         gint num;
717         MsgInfo *msginfo, *tmp_msginfo;
718         MsgFlags flag = {0, 0};
719
720         debug_print("saving sent message...\n");
721
722         if (!outbox)
723                 outbox = folder_get_default_outbox();
724         g_return_val_if_fail(outbox != NULL, -1);
725
726         /* remove queueing headers */
727         if (is_queued) {
728                 gchar tmp[MAXPATHLEN + 1];
729
730                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
731                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
732                 
733                 if (procmsg_remove_special_headers(file, tmp) !=0)
734                         return -1;
735
736                 folder_item_scan(outbox);
737                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
738                         g_warning("can't save message\n");
739                         unlink(tmp);
740                         return -1;
741                 }
742         } else {
743                 folder_item_scan(outbox);
744                 if ((num = folder_item_add_msg
745                         (outbox, file, &flag, FALSE)) < 0) {
746                         g_warning("can't save message\n");
747                         return -1;
748                 }
749                 return -1;
750         }
751         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
752         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
753         if (msginfo != NULL) {
754                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
755                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
756                 /* tmp_msginfo == msginfo */
757                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
758                     msginfo->returnreceiptto)) {
759                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
760                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
761                 }       
762         }
763
764         return 0;
765 }
766
767 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
768 {
769         static const gchar *def_cmd = "lpr %s";
770         static guint id = 0;
771         gchar *prtmp;
772         FILE *tmpfp, *prfp;
773         gchar buf[1024];
774         gchar *p;
775
776         g_return_if_fail(msginfo);
777
778         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
779                 g_warning("Can't get text part\n");
780                 return;
781         }
782
783         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
784                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
785
786         if ((prfp = fopen(prtmp, "wb")) == NULL) {
787                 FILE_OP_ERROR(prtmp, "fopen");
788                 g_free(prtmp);
789                 fclose(tmpfp);
790                 return;
791         }
792
793         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
794         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
795         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
796         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
797         if (msginfo->newsgroups)
798                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
799         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
800         fputc('\n', prfp);
801
802         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
803                 fputs(buf, prfp);
804
805         fclose(prfp);
806         fclose(tmpfp);
807
808         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
809             !strchr(p + 2, '%'))
810                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
811         else {
812                 if (cmdline)
813                         g_warning("Print command line is invalid: `%s'\n",
814                                   cmdline);
815                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
816         }
817
818         g_free(prtmp);
819
820         g_strchomp(buf);
821         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
822         system(buf);
823 }
824
825 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
826 {
827         msginfo->refcnt++;
828         
829         return msginfo;
830 }
831
832 MsgInfo *procmsg_msginfo_new(void)
833 {
834         MsgInfo *newmsginfo;
835
836         newmsginfo = g_new0(MsgInfo, 1);
837         newmsginfo->refcnt = 1;
838         
839         return newmsginfo;
840 }
841
842 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
843 {
844         MsgInfo *newmsginfo;
845
846         if (msginfo == NULL) return NULL;
847
848         newmsginfo = g_new0(MsgInfo, 1);
849
850         newmsginfo->refcnt = 1;
851
852 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
853 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
854                         g_strdup(msginfo->mmb) : NULL
855
856         MEMBCOPY(msgnum);
857         MEMBCOPY(size);
858         MEMBCOPY(mtime);
859         MEMBCOPY(date_t);
860
861         MEMBCOPY(flags);
862
863         MEMBDUP(fromname);
864
865         MEMBDUP(date);
866         MEMBDUP(from);
867         MEMBDUP(to);
868         MEMBDUP(cc);
869         MEMBDUP(newsgroups);
870         MEMBDUP(subject);
871         MEMBDUP(msgid);
872         MEMBDUP(inreplyto);
873         MEMBDUP(xref);
874
875         MEMBCOPY(folder);
876         MEMBCOPY(to_folder);
877
878         MEMBDUP(xface);
879         MEMBDUP(dispositionnotificationto);
880         MEMBDUP(returnreceiptto);
881         MEMBDUP(references);
882
883         MEMBCOPY(score);
884         MEMBCOPY(threadscore);
885         MEMBDUP(plaintext_file);
886
887         return newmsginfo;
888 }
889
890 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
891 {
892         MsgInfo *full_msginfo;
893         gchar *file;
894
895         if (msginfo == NULL) return NULL;
896
897         file = procmsg_get_message_file_path(msginfo);
898         if (!file || !is_file_exist(file)) {
899                 g_free(file);
900                 file = procmsg_get_message_file(msginfo);
901         }
902         if (!file || !is_file_exist(file)) {
903                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
904                 return NULL;
905         }
906
907         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
908         g_free(file);
909         if (!full_msginfo) return NULL;
910
911         /* CLAWS: make sure we add the missing members; see: 
912          * procheader.c::procheader_get_headernames() */
913         if (!msginfo->xface)
914                 msginfo->xface = g_strdup(full_msginfo->xface);
915         if (!msginfo->dispositionnotificationto)
916                 msginfo->dispositionnotificationto = 
917                         g_strdup(full_msginfo->dispositionnotificationto);
918         if (!msginfo->returnreceiptto)
919                 msginfo->returnreceiptto = g_strdup
920                         (full_msginfo->returnreceiptto);
921         if (!msginfo->partial_recv && full_msginfo->partial_recv)
922                 msginfo->partial_recv = g_strdup
923                         (full_msginfo->partial_recv);
924         msginfo->total_size = full_msginfo->total_size;
925         if (!msginfo->account_server && full_msginfo->account_server)
926                 msginfo->account_server = g_strdup
927                         (full_msginfo->account_server);
928         if (!msginfo->account_login && full_msginfo->account_login)
929                 msginfo->account_login = g_strdup
930                         (full_msginfo->account_login);
931         msginfo->planned_download = full_msginfo->planned_download;
932         procmsg_msginfo_free(full_msginfo);
933
934         return procmsg_msginfo_new_ref(msginfo);
935 }
936
937 void procmsg_msginfo_free(MsgInfo *msginfo)
938 {
939         if (msginfo == NULL) return;
940
941         msginfo->refcnt--;
942         if (msginfo->refcnt > 0)
943                 return;
944
945         if (msginfo->to_folder) {
946                 msginfo->to_folder->op_count--;
947                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
948         }
949
950         g_free(msginfo->fromspace);
951         g_free(msginfo->references);
952         g_free(msginfo->returnreceiptto);
953         g_free(msginfo->dispositionnotificationto);
954         g_free(msginfo->xface);
955
956         g_free(msginfo->fromname);
957
958         g_free(msginfo->date);
959         g_free(msginfo->from);
960         g_free(msginfo->to);
961         g_free(msginfo->cc);
962         g_free(msginfo->newsgroups);
963         g_free(msginfo->subject);
964         g_free(msginfo->msgid);
965         g_free(msginfo->inreplyto);
966         g_free(msginfo->xref);
967
968         g_free(msginfo->partial_recv);
969         g_free(msginfo->account_server);
970         g_free(msginfo->account_login);
971         
972         g_free(msginfo->plaintext_file);
973
974         g_free(msginfo);
975 }
976
977 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
978 {
979         guint memusage = 0;
980         
981         memusage += sizeof(MsgInfo);
982         if (msginfo->fromname)
983                 memusage += strlen(msginfo->fromname);
984         if (msginfo->date)
985                 memusage += strlen(msginfo->date);
986         if (msginfo->from)
987                 memusage += strlen(msginfo->from);
988         if (msginfo->to)
989                 memusage += strlen(msginfo->to);
990         if (msginfo->cc)
991                 memusage += strlen(msginfo->cc);
992         if (msginfo->newsgroups)
993                 memusage += strlen(msginfo->newsgroups);
994         if (msginfo->subject)
995                 memusage += strlen(msginfo->subject);
996         if (msginfo->msgid)
997                 memusage += strlen(msginfo->msgid);
998         if (msginfo->inreplyto)
999                 memusage += strlen(msginfo->inreplyto);
1000         if (msginfo->xface)
1001                 memusage += strlen(msginfo->xface);
1002         if (msginfo->dispositionnotificationto)
1003                 memusage += strlen(msginfo->dispositionnotificationto);
1004         if (msginfo->returnreceiptto)
1005                 memusage += strlen(msginfo->returnreceiptto);
1006         if (msginfo->references)
1007                 memusage += strlen(msginfo->references);
1008         if (msginfo->fromspace)
1009                 memusage += strlen(msginfo->fromspace);
1010
1011         return memusage;
1012 }
1013
1014 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1015 {
1016         const MsgInfo *msginfo1 = a;
1017         const MsgInfo *msginfo2 = b;
1018
1019         if (!msginfo1)
1020                 return -1;
1021         if (!msginfo2)
1022                 return -1;
1023
1024         return msginfo1->msgnum - msginfo2->msgnum;
1025 }
1026
1027 enum
1028 {
1029         Q_SENDER           = 0,
1030         Q_SMTPSERVER       = 1,
1031         Q_RECIPIENTS       = 2,
1032         Q_NEWSGROUPS       = 3,
1033         Q_MAIL_ACCOUNT_ID  = 4,
1034         Q_NEWS_ACCOUNT_ID  = 5,
1035         Q_SAVE_COPY_FOLDER = 6,
1036         Q_REPLY_MESSAGE_ID = 7,
1037         Q_FWD_MESSAGE_ID   = 8,
1038         Q_PRIVACY_SYSTEM   = 9,
1039         Q_ENCRYPT          = 10,
1040         Q_ENCRYPT_DATA     = 11,
1041 };
1042
1043 gint procmsg_send_message_queue(const gchar *file)
1044 {
1045         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1046                                        {"SSV:",  NULL, FALSE},
1047                                        {"R:",    NULL, FALSE},
1048                                        {"NG:",   NULL, FALSE},
1049                                        {"MAID:", NULL, FALSE},
1050                                        {"NAID:", NULL, FALSE},
1051                                        {"SCF:",  NULL, FALSE},
1052                                        {"RMID:", NULL, FALSE},
1053                                        {"FMID:", NULL, FALSE},
1054                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1055                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1056                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1057                                        {NULL,    NULL, FALSE}};
1058         FILE *fp;
1059         gint filepos;
1060         gint mailval = 0, newsval = 0;
1061         gchar *from = NULL;
1062         gchar *smtpserver = NULL;
1063         GSList *to_list = NULL;
1064         GSList *newsgroup_list = NULL;
1065         gchar *savecopyfolder = NULL;
1066         gchar *replymessageid = NULL;
1067         gchar *fwdmessageid = NULL;
1068         gchar *privacy_system = NULL;
1069         gboolean encrypt = FALSE;
1070         gchar *encrypt_data = NULL;
1071         gchar buf[BUFFSIZE];
1072         gint hnum;
1073         PrefsAccount *mailac = NULL, *newsac = NULL;
1074         gboolean save_clear_text = TRUE;
1075         gchar *tmp_enc_file = NULL;
1076
1077         int local = 0;
1078
1079         g_return_val_if_fail(file != NULL, -1);
1080
1081         if ((fp = fopen(file, "rb")) == NULL) {
1082                 FILE_OP_ERROR(file, "fopen");
1083                 return -1;
1084         }
1085
1086         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1087                != -1) {
1088                 gchar *p = buf + strlen(qentry[hnum].name);
1089
1090                 switch (hnum) {
1091                 case Q_SENDER:
1092                         if (from == NULL) 
1093                                 from = g_strdup(p);
1094                         break;
1095                 case Q_SMTPSERVER:
1096                         if (smtpserver == NULL) 
1097                                 smtpserver = g_strdup(p);
1098                         break;
1099                 case Q_RECIPIENTS:
1100                         to_list = address_list_append(to_list, p);
1101                         break;
1102                 case Q_NEWSGROUPS:
1103                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1104                         break;
1105                 case Q_MAIL_ACCOUNT_ID:
1106                         mailac = account_find_from_id(atoi(p));
1107                         break;
1108                 case Q_NEWS_ACCOUNT_ID:
1109                         newsac = account_find_from_id(atoi(p));
1110                         break;
1111                 case Q_SAVE_COPY_FOLDER:
1112                         if (savecopyfolder == NULL) 
1113                                 savecopyfolder = g_strdup(p);
1114                         break;
1115                 case Q_REPLY_MESSAGE_ID:
1116                         if (replymessageid == NULL) 
1117                                 replymessageid = g_strdup(p);
1118                         break;
1119                 case Q_FWD_MESSAGE_ID:
1120                         if (fwdmessageid == NULL) 
1121                                 fwdmessageid = g_strdup(p);
1122                         break;
1123                 case Q_PRIVACY_SYSTEM:
1124                         if (privacy_system == NULL) 
1125                                 privacy_system = g_strdup(p);
1126                         break;
1127                 case Q_ENCRYPT:
1128                         if (p[0] == '1') 
1129                                 encrypt = TRUE;
1130                         break;
1131                 case Q_ENCRYPT_DATA:
1132                         if (encrypt_data == NULL) 
1133                                 encrypt_data = g_strdup(p);
1134                         break;
1135                 }
1136         }
1137         filepos = ftell(fp);
1138
1139         if (encrypt) {
1140                 MimeInfo *mimeinfo;
1141
1142                 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1143
1144                 fclose(fp);
1145                 fp = NULL;
1146
1147                 mimeinfo = procmime_scan_queue_file(file);
1148                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1149                 || (fp = my_tmpfile()) == NULL
1150                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1151                         if (fp)
1152                                 fclose(fp);
1153                         procmime_mimeinfo_free_all(mimeinfo);
1154                         g_free(from);
1155                         g_free(smtpserver);
1156                         slist_free_strings(to_list);
1157                         g_slist_free(to_list);
1158                         slist_free_strings(newsgroup_list);
1159                         g_slist_free(newsgroup_list);
1160                         g_free(savecopyfolder);
1161                         g_free(replymessageid);
1162                         g_free(fwdmessageid);
1163                         g_free(privacy_system);
1164                         g_free(encrypt_data);
1165                         return -1;
1166                 }
1167                 
1168                 rewind(fp);
1169                 if (!save_clear_text) {
1170                         gchar *content = NULL;
1171                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1172                         if (tmpfp) {
1173                                 fclose(tmpfp);
1174
1175                                 content = file_read_stream_to_str(fp);
1176                                 rewind(fp);
1177
1178                                 str_write_to_file(content, tmp_enc_file);
1179                                 g_free(content);
1180                         } else {
1181                                 g_warning("couldn't get tempfile\n");
1182                         }
1183                 } 
1184                 
1185                 procmime_mimeinfo_free_all(mimeinfo);
1186                 
1187                 filepos = 0;
1188         }
1189
1190         if (to_list) {
1191                 debug_print("Sending message by mail\n");
1192                 if (!from) {
1193                         g_warning("Queued message header is broken.\n");
1194                         mailval = -1;
1195                 } else if (mailac && mailac->use_mail_command &&
1196                            mailac->mail_command && (* mailac->mail_command)) {
1197                         mailval = send_message_local(mailac->mail_command, fp);
1198                         local = 1;
1199                 } else {
1200                         if (!mailac) {
1201                                 mailac = account_find_from_smtp_server(from, smtpserver);
1202                                 if (!mailac) {
1203                                         g_warning("Account not found. "
1204                                                     "Using current account...\n");
1205                                         mailac = cur_account;
1206                                 }
1207                         }
1208
1209                         if (mailac)
1210                                 mailval = send_message_smtp(mailac, to_list, fp);
1211                         else {
1212                                 PrefsAccount tmp_ac;
1213
1214                                 g_warning("Account not found.\n");
1215
1216                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1217                                 tmp_ac.address = from;
1218                                 tmp_ac.smtp_server = smtpserver;
1219                                 tmp_ac.smtpport = SMTP_PORT;
1220                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1221                         }
1222                 }
1223         }
1224
1225         fseek(fp, filepos, SEEK_SET);
1226         if (newsgroup_list && (mailval == 0)) {
1227                 Folder *folder;
1228                 gchar *tmp = NULL;
1229                 FILE *tmpfp;
1230
1231                 /* write to temporary file */
1232                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1233                             G_DIR_SEPARATOR, (gint)file);
1234                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1235                         FILE_OP_ERROR(tmp, "fopen");
1236                         newsval = -1;
1237                         alertpanel_error(_("Could not create temporary file for news sending."));
1238                 } else {
1239                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1240                                 FILE_OP_ERROR(tmp, "chmod");
1241                                 g_warning("can't change file mode\n");
1242                         }
1243
1244                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1245                                 if (fputs(buf, tmpfp) == EOF) {
1246                                         FILE_OP_ERROR(tmp, "fputs");
1247                                         newsval = -1;
1248                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1249                                 }
1250                         }
1251                         fclose(tmpfp);
1252
1253                         if (newsval == 0) {
1254                                 debug_print("Sending message by news\n");
1255
1256                                 folder = FOLDER(newsac->folder);
1257
1258                                 newsval = news_post(folder, tmp);
1259                                 if (newsval < 0) {
1260                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1261                                                  newsac->nntp_server);
1262                                 }
1263                         }
1264                         unlink(tmp);
1265                 }
1266                 g_free(tmp);
1267         }
1268
1269         fclose(fp);
1270
1271         /* save message to outbox */
1272         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1273                 FolderItem *outbox;
1274
1275                 debug_print("saving sent message...\n");
1276
1277                 outbox = folder_find_item_from_identifier(savecopyfolder);
1278                 if (!outbox)
1279                         outbox = folder_get_default_outbox();
1280                         
1281                 if (save_clear_text || tmp_enc_file == NULL) {
1282                         procmsg_save_to_outbox(outbox, file, TRUE);
1283                 } else {
1284                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1285                 }
1286         }
1287
1288         if (tmp_enc_file != NULL) {
1289                 unlink(tmp_enc_file);
1290                 free(tmp_enc_file);
1291                 tmp_enc_file = NULL;
1292         }
1293
1294         if (replymessageid != NULL || fwdmessageid != NULL) {
1295                 gchar **tokens;
1296                 FolderItem *item;
1297                 
1298                 if (replymessageid != NULL)
1299                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1300                 else
1301                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1302                 item = folder_find_item_from_identifier(tokens[0]);
1303
1304                 /* check if queued message has valid folder and message id */
1305                 if (item != NULL && tokens[2] != NULL) {
1306                         MsgInfo *msginfo;
1307                         
1308                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1309                 
1310                         /* check if referring message exists and has a message id */
1311                         if ((msginfo != NULL) && 
1312                             (msginfo->msgid != NULL) &&
1313                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1314                                 procmsg_msginfo_free(msginfo);
1315                                 msginfo = NULL;
1316                         }
1317                         
1318                         if (msginfo == NULL) {
1319                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1320                         }
1321                         
1322                         if (msginfo != NULL) {
1323                                 if (replymessageid != NULL) {
1324                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1325                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1326                                 }  else {
1327                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1328                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1329                                 }
1330                                 procmsg_msginfo_free(msginfo);
1331                         }
1332                 }
1333                 g_strfreev(tokens);
1334         }
1335
1336         g_free(from);
1337         g_free(smtpserver);
1338         slist_free_strings(to_list);
1339         g_slist_free(to_list);
1340         slist_free_strings(newsgroup_list);
1341         g_slist_free(newsgroup_list);
1342         g_free(savecopyfolder);
1343         g_free(replymessageid);
1344         g_free(fwdmessageid);
1345         g_free(privacy_system);
1346         g_free(encrypt_data);
1347
1348         return (newsval != 0 ? newsval : mailval);
1349 }
1350
1351 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1352 {
1353         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1354
1355         /* NEW flag */
1356         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1357                 item->new_msgs++;
1358         }
1359
1360         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1361                 item->new_msgs--;
1362         }
1363
1364         /* UNREAD flag */
1365         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1366                 item->unread_msgs++;
1367                 if (procmsg_msg_has_marked_parent(msginfo))
1368                         item->unreadmarked_msgs++;
1369         }
1370
1371         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1372                 item->unread_msgs--;
1373                 if (procmsg_msg_has_marked_parent(msginfo))
1374                         item->unreadmarked_msgs--;
1375         }
1376         
1377         /* MARK flag */
1378         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1379                 procmsg_update_unread_children(msginfo, TRUE);
1380         }
1381
1382         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1383                 procmsg_update_unread_children(msginfo, FALSE);
1384         }
1385 }
1386
1387 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1388 {
1389         FolderItem *item;
1390         MsgInfoUpdate msginfo_update;
1391         MsgPermFlags perm_flags_new, perm_flags_old;
1392         MsgTmpFlags tmp_flags_old;
1393
1394         g_return_if_fail(msginfo != NULL);
1395         item = msginfo->folder;
1396         g_return_if_fail(item != NULL);
1397         
1398         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1399
1400         /* Perm Flags handling */
1401         perm_flags_old = msginfo->flags.perm_flags;
1402         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1403         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1404                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1405         }
1406
1407         if (perm_flags_old != perm_flags_new) {
1408                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1409
1410                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1411
1412         }
1413
1414         /* Tmp flags handling */
1415         tmp_flags_old = msginfo->flags.tmp_flags;
1416         msginfo->flags.tmp_flags |= tmp_flags;
1417
1418         /* update notification */
1419         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1420                 msginfo_update.msginfo = msginfo;
1421                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1422                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1423                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1424         }
1425 }
1426
1427 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1428 {
1429         FolderItem *item;
1430         MsgInfoUpdate msginfo_update;
1431         MsgPermFlags perm_flags_new, perm_flags_old;
1432         MsgTmpFlags tmp_flags_old;
1433
1434         g_return_if_fail(msginfo != NULL);
1435         item = msginfo->folder;
1436         g_return_if_fail(item != NULL);
1437         
1438         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1439
1440         /* Perm Flags handling */
1441         perm_flags_old = msginfo->flags.perm_flags;
1442         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1443         
1444         if (perm_flags_old != perm_flags_new) {
1445                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1446
1447                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1448
1449                 msginfo_update.msginfo = msginfo;
1450                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1451                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1452                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1453         }
1454
1455         /* Tmp flags hanlding */
1456         tmp_flags_old = msginfo->flags.tmp_flags;
1457         msginfo->flags.tmp_flags &= ~tmp_flags;
1458
1459         /* update notification */
1460         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1461                 msginfo_update.msginfo = msginfo;
1462                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1463                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1464                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1465         }
1466 }
1467
1468 /*!
1469  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1470  *
1471  *\param        info Current message
1472  *\param        perm_flags Flags to be checked
1473  *\param        parentmsgs Hash of prior msgs to avoid loops
1474  *
1475  *\return       gboolean TRUE if perm_flags are found
1476  */
1477 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1478                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1479 {
1480         MsgInfo *tmp;
1481
1482         g_return_val_if_fail(info != NULL, FALSE);
1483
1484         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1485                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1486                                 info->inreplyto);
1487                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1488                         procmsg_msginfo_free(tmp);
1489                         return TRUE;
1490                 } else if (tmp != NULL) {
1491                         gboolean result;
1492
1493                         if (g_hash_table_lookup(parentmsgs, info)) {
1494                                 debug_print("loop detected: %s%c%d\n",
1495                                         folder_item_get_path(info->folder),
1496                                         G_DIR_SEPARATOR, info->msgnum);
1497                                 result = FALSE;
1498                         } else {
1499                                 g_hash_table_insert(parentmsgs, info, "1");
1500                                 result = procmsg_msg_has_flagged_parent_real(
1501                                     tmp, perm_flags, parentmsgs);
1502                         }
1503                         procmsg_msginfo_free(tmp);
1504                         return result;
1505                 } else {
1506                         return FALSE;
1507                 }
1508         } else
1509                 return FALSE;
1510 }
1511
1512 /*!
1513  *\brief        Callback for cleaning up hash of parentmsgs
1514  */
1515 gboolean parentmsgs_hash_remove(gpointer key,
1516                             gpointer value,
1517                             gpointer user_data)
1518 {
1519         return TRUE;
1520 }
1521
1522 /*!
1523  *\brief        Set up list of parentmsgs
1524  *              See procmsg_msg_has_flagged_parent_real()
1525  */
1526 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1527 {
1528         gboolean result;
1529         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1530
1531         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1532         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1533         g_hash_table_destroy(parentmsgs);
1534         return result;
1535 }
1536
1537 /*!
1538  *\brief        Check if msgs prior in thread are marked
1539  *              See procmsg_msg_has_flagged_parent_real()
1540  */
1541 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1542 {
1543         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1544 }
1545
1546
1547 GSList *procmsg_find_children_func(MsgInfo *info, 
1548                                    GSList *children, GSList *all)
1549 {
1550         GSList *cur;
1551
1552         g_return_val_if_fail(info!=NULL, children);
1553         if (info->msgid == NULL)
1554                 return children;
1555
1556         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1557                 MsgInfo *tmp = (MsgInfo *)cur->data;
1558                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1559                         /* Check if message is already in the list */
1560                         if ((children == NULL) || 
1561                             (g_slist_index(children, tmp) == -1)) {
1562                                 children = g_slist_prepend(children,
1563                                                 procmsg_msginfo_new_ref(tmp));
1564                                 children = procmsg_find_children_func(tmp, 
1565                                                         children, 
1566                                                         all);
1567                         }
1568                 }
1569         }
1570         return children;
1571 }
1572
1573 GSList *procmsg_find_children (MsgInfo *info)
1574 {
1575         GSList *children;
1576         GSList *all, *cur;
1577
1578         g_return_val_if_fail(info!=NULL, NULL);
1579         all = folder_item_get_msg_list(info->folder);
1580         children = procmsg_find_children_func(info, NULL, all);
1581         if (children != NULL) {
1582                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1583                         /* this will not free the used pointers
1584                            created with procmsg_msginfo_new_ref */
1585                         procmsg_msginfo_free((MsgInfo *)cur->data);
1586                 }
1587         }
1588         g_slist_free(all);
1589
1590         return children;
1591 }
1592
1593 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1594 {
1595         GSList *children = procmsg_find_children(info);
1596         GSList *cur;
1597         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1598                 MsgInfo *tmp = (MsgInfo *)cur->data;
1599                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1600                         if(newly_marked) 
1601                                 info->folder->unreadmarked_msgs++;
1602                         else
1603                                 info->folder->unreadmarked_msgs--;
1604                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1605                 }
1606                 procmsg_msginfo_free(tmp);
1607         }
1608         g_slist_free(children);
1609 }
1610
1611 /**
1612  * Set the destination folder for a copy or move operation
1613  *
1614  * \param msginfo The message which's destination folder is changed
1615  * \param to_folder The destination folder for the operation
1616  */
1617 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1618 {
1619         if(msginfo->to_folder != NULL) {
1620                 msginfo->to_folder->op_count--;
1621                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1622         }
1623         msginfo->to_folder = to_folder;
1624         if(to_folder != NULL) {
1625                 to_folder->op_count++;
1626                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1627         }
1628 }
1629
1630 /**
1631  * Apply filtering actions to the msginfo
1632  *
1633  * \param msginfo The MsgInfo describing the message that should be filtered
1634  * \return TRUE if the message was moved and MsgInfo is now invalid,
1635  *         FALSE otherwise
1636  */
1637 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1638 {
1639         MailFilteringData mail_filtering_data;
1640                         
1641         mail_filtering_data.msginfo = msginfo;                  
1642         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1643                 return TRUE;
1644
1645         /* filter if enabled in prefs or move to inbox if not */
1646         if((filtering_rules != NULL) &&
1647            filter_message_by_msginfo(filtering_rules, msginfo))
1648                 return TRUE;
1649
1650         return FALSE;
1651 }
1652
1653 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
1654 {
1655         MsgInfo *tmp_msginfo = NULL;
1656         MsgFlags flags = {0, 0};
1657         
1658         
1659         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
1660             g_strcasecmp(mimeinfo->subtype, "rfc822")) {
1661                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
1662                 return NULL;
1663         }
1664                     
1665         if (mimeinfo->content == MIMECONTENT_MEM) {
1666                 gchar *tmpfile = get_tmp_file();
1667                 str_write_to_file(mimeinfo->data.mem, tmpfile);
1668                 g_free(mimeinfo->data.mem);
1669                 mimeinfo->content == MIMECONTENT_FILE;
1670                 mimeinfo->data.filename = g_strdup(tmpfile);
1671                 g_free(tmpfile);
1672         }
1673
1674         tmp_msginfo = procheader_parse_file(mimeinfo->data.filename,
1675                                 flags, TRUE, FALSE);
1676
1677         if (tmp_msginfo != NULL) {
1678                 tmp_msginfo->folder = src_msginfo->folder;
1679                 tmp_msginfo->plaintext_file = g_strdup(mimeinfo->data.filename);
1680         } else {
1681                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
1682         }
1683         
1684         return tmp_msginfo;
1685 }