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