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