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