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