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