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