* src/procmsg.c
[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_reply_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_reply_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 FILE *procmsg_open_message(MsgInfo *msginfo)
386 {
387         FILE *fp;
388         gchar *file;
389
390         g_return_val_if_fail(msginfo != NULL, NULL);
391
392         file = procmsg_get_message_file_path(msginfo);
393         g_return_val_if_fail(file != NULL, NULL);
394
395         if (!is_file_exist(file)) {
396                 g_free(file);
397                 file = procmsg_get_message_file(msginfo);
398                 g_return_val_if_fail(file != NULL, NULL);
399         }
400
401         if ((fp = fopen(file, "rb")) == NULL) {
402                 FILE_OP_ERROR(file, "fopen");
403                 g_free(file);
404                 return NULL;
405         }
406
407         g_free(file);
408
409         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
410                 gchar buf[BUFFSIZE];
411
412                 while (fgets(buf, sizeof(buf), fp) != NULL)
413                         if (buf[0] == '\r' || buf[0] == '\n') break;
414         }
415
416         return fp;
417 }
418
419 #if USE_GPGME
420 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
421 {
422         FILE *fp;
423         MimeInfo *mimeinfo_;
424         glong fpos;
425
426         g_return_val_if_fail(msginfo != NULL, NULL);
427
428         if (mimeinfo) *mimeinfo = NULL;
429
430         if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
431
432         mimeinfo_ = procmime_scan_mime_header(fp);
433         if (!mimeinfo_) {
434                 fclose(fp);
435                 return NULL;
436         }
437
438         if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
439             rfc2015_is_encrypted(mimeinfo_)) {
440                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
441         }
442
443         if (MSG_IS_ENCRYPTED(msginfo->flags) &&
444             !msginfo->plaintext_file &&
445             !msginfo->decryption_failed) {
446                 fpos = ftell(fp);
447                 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
448                 if (msginfo->plaintext_file &&
449                     !msginfo->decryption_failed) {
450                         fclose(fp);
451                         procmime_mimeinfo_free_all(mimeinfo_);
452                         if ((fp = procmsg_open_message(msginfo)) == NULL)
453                                 return NULL;
454                         mimeinfo_ = procmime_scan_mime_header(fp);
455                         if (!mimeinfo_) {
456                                 fclose(fp);
457                                 return NULL;
458                         }
459                 } else {
460                         if (fseek(fp, fpos, SEEK_SET) < 0)
461                                 perror("fseek");
462                 }
463         }
464
465         if (mimeinfo) *mimeinfo = mimeinfo_;
466         return fp;
467 }
468 #endif
469
470 gboolean procmsg_msg_exist(MsgInfo *msginfo)
471 {
472         gchar *path;
473         gboolean ret;
474
475         if (!msginfo) return FALSE;
476
477         path = folder_item_get_path(msginfo->folder);
478         change_dir(path);
479         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
480         g_free(path);
481
482         return ret;
483 }
484
485 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
486                                 PrefsFilterType type)
487 {
488         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
489                                        {"X-ML-Name:",      NULL, TRUE},
490                                        {"X-List:",         NULL, TRUE},
491                                        {"X-Mailing-list:", NULL, TRUE},
492                                        {"List-Id:",        NULL, TRUE},
493                                        {"X-Sequence:",     NULL, TRUE},
494                                        {NULL,              NULL, FALSE}};
495         enum
496         {
497                 H_X_BEENTHERE    = 0,
498                 H_X_ML_NAME      = 1,
499                 H_X_LIST         = 2,
500                 H_X_MAILING_LIST = 3,
501                 H_LIST_ID        = 4,
502                 H_X_SEQUENCE     = 5
503         };
504
505         FILE *fp;
506
507         g_return_if_fail(msginfo != NULL);
508         g_return_if_fail(header != NULL);
509         g_return_if_fail(key != NULL);
510
511         *header = NULL;
512         *key = NULL;
513
514         switch (type) {
515         case FILTER_BY_NONE:
516                 return;
517         case FILTER_BY_AUTO:
518                 if ((fp = procmsg_open_message(msginfo)) == NULL)
519                         return;
520                 procheader_get_header_fields(fp, hentry);
521                 fclose(fp);
522
523 #define SET_FILTER_KEY(hstr, idx)       \
524 {                                       \
525         *header = g_strdup(hstr);       \
526         *key = hentry[idx].body;        \
527         hentry[idx].body = NULL;        \
528 }
529
530                 if (hentry[H_X_BEENTHERE].body != NULL) {
531                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
532                 } else if (hentry[H_X_ML_NAME].body != NULL) {
533                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
534                 } else if (hentry[H_X_LIST].body != NULL) {
535                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
536                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
537                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
538                 } else if (hentry[H_LIST_ID].body != NULL) {
539                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
540                         extract_list_id_str(*key);
541                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
542                         gchar *p;
543
544                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
545                         p = *key;
546                         while (*p != '\0') {
547                                 while (*p != '\0' && !isspace(*p)) p++;
548                                 while (isspace(*p)) p++;
549                                 if (isdigit(*p)) {
550                                         *p = '\0';
551                                         break;
552                                 }
553                         }
554                         g_strstrip(*key);
555                 } else if (msginfo->subject) {
556                         *header = g_strdup("subject");
557                         *key = g_strdup(msginfo->subject);
558                 }
559
560 #undef SET_FILTER_KEY
561
562                 g_free(hentry[H_X_BEENTHERE].body);
563                 hentry[H_X_BEENTHERE].body = NULL;
564                 g_free(hentry[H_X_ML_NAME].body);
565                 hentry[H_X_ML_NAME].body = NULL;
566                 g_free(hentry[H_X_LIST].body);
567                 hentry[H_X_LIST].body = NULL;
568                 g_free(hentry[H_X_MAILING_LIST].body);
569                 hentry[H_X_MAILING_LIST].body = NULL;
570                 g_free(hentry[H_LIST_ID].body);
571                 hentry[H_LIST_ID].body = NULL;
572
573                 break;
574         case FILTER_BY_FROM:
575                 *header = g_strdup("from");
576                 *key = g_strdup(msginfo->from);
577                 break;
578         case FILTER_BY_TO:
579                 *header = g_strdup("to");
580                 *key = g_strdup(msginfo->to);
581                 break;
582         case FILTER_BY_SUBJECT:
583                 *header = g_strdup("subject");
584                 *key = g_strdup(msginfo->subject);
585                 break;
586         default:
587                 break;
588         }
589 }
590
591 void procmsg_empty_trash(void)
592 {
593         FolderItem *trash;
594         GList *cur;
595
596         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
597                 trash = FOLDER(cur->data)->trash;
598                 if (trash && trash->total_msgs > 0)
599                         folder_item_remove_all_msg(trash);
600         }
601 }
602
603 /*!
604  *\brief        Send messages in queue
605  *
606  *\param        queue Queue folder to process
607  *\param        save_msgs Unused
608  *
609  *\return       Number of messages sent, negative if an error occurred
610  *              positive if no error occurred
611  */
612 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
613 {
614         gint ret = 1, count = 0;
615         GSList *list, *elem;
616
617         if (!queue)
618                 queue = folder_get_default_queue();
619         g_return_val_if_fail(queue != NULL, -1);
620
621         folder_item_scan(queue);
622         list = folder_item_get_msg_list(queue);
623
624         for (elem = list; elem != NULL; elem = elem->next) {
625                 gchar *file;
626                 MsgInfo *msginfo;
627                 
628                 msginfo = (MsgInfo *)(elem->data);
629                 if (!MSG_IS_LOCKED(msginfo->flags)) {
630                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
631                         if (file) {
632                                 if (procmsg_send_message_queue(file) < 0) {
633                                         g_warning("Sending queued message %d failed.\n", 
634                                                   msginfo->msgnum);
635                                         ret = -1;
636                                 } else {
637                                         /* CLAWS: 
638                                          * We save in procmsg_send_message_queue because
639                                          * we need the destination folder from the queue
640                                          * header
641                                                         
642                                         if (save_msgs)
643                                                 procmsg_save_to_outbox
644                                                         (queue->folder->outbox,
645                                                          file, TRUE);
646                                          */
647                                         count++; 
648                                         folder_item_remove_msg(queue, msginfo->msgnum);
649                                 }
650                                 g_free(file);
651                         }
652                 }
653                 /* FIXME: supposedly if only one message is locked, and queue
654                  * is being flushed, the following free says something like 
655                  * "freeing msg ## in folder (nil)". */
656                 procmsg_msginfo_free(msginfo);
657         }
658
659         return ret * count;
660 }
661
662 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
663 {
664         FILE *fp, *outfp;
665         gchar buf[BUFFSIZE];
666         
667         if ((fp = fopen(in, "rb")) == NULL) {
668                 FILE_OP_ERROR(in, "fopen");
669                 return -1;
670         }
671         if ((outfp = fopen(out, "wb")) == NULL) {
672                 FILE_OP_ERROR(out, "fopen");
673                 fclose(fp);
674                 return -1;
675         }
676         while (fgets(buf, sizeof(buf), fp) != NULL)
677                 if (buf[0] == '\r' || buf[0] == '\n') break;
678         while (fgets(buf, sizeof(buf), fp) != NULL)
679                 fputs(buf, outfp);
680         fclose(outfp);
681         fclose(fp);
682         return 0;
683
684 }
685 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
686                             gboolean is_queued)
687 {
688         gint num;
689         MsgInfo *msginfo;
690
691         debug_print("saving sent message...\n");
692
693         if (!outbox)
694                 outbox = folder_get_default_outbox();
695         g_return_val_if_fail(outbox != NULL, -1);
696
697         /* remove queueing headers */
698         if (is_queued) {
699                 gchar tmp[MAXPATHLEN + 1];
700
701                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
702                            get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
703                 
704                 if (procmsg_remove_special_headers(file, tmp) !=0)
705                         return -1;
706
707                 folder_item_scan(outbox);
708                 if ((num = folder_item_add_msg(outbox, tmp, TRUE)) < 0) {
709                         g_warning("can't save message\n");
710                         unlink(tmp);
711                         return -1;
712                 }
713         } else {
714                 folder_item_scan(outbox);
715                 if ((num = folder_item_add_msg(outbox, file, FALSE)) < 0) {
716                         g_warning("can't save message\n");
717                         return -1;
718                 }
719                 return -1;
720         }
721         msginfo = folder_item_get_msginfo(outbox, num);
722         if (msginfo != NULL) {
723             procmsg_msginfo_unset_flags(msginfo, ~0, 0);
724             procmsg_msginfo_free(msginfo);
725         }
726         folder_item_update(outbox, TRUE);
727
728         return 0;
729 }
730
731 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
732 {
733         static const gchar *def_cmd = "lpr %s";
734         static guint id = 0;
735         gchar *prtmp;
736         FILE *tmpfp, *prfp;
737         gchar buf[1024];
738         gchar *p;
739
740         g_return_if_fail(msginfo);
741
742         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
743                 g_warning("Can't get text part\n");
744                 return;
745         }
746
747         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
748                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
749
750         if ((prfp = fopen(prtmp, "wb")) == NULL) {
751                 FILE_OP_ERROR(prtmp, "fopen");
752                 g_free(prtmp);
753                 fclose(tmpfp);
754                 return;
755         }
756
757         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
758         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
759         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
760         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
761         if (msginfo->newsgroups)
762                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
763         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
764         fputc('\n', prfp);
765
766         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
767                 fputs(buf, prfp);
768
769         fclose(prfp);
770         fclose(tmpfp);
771
772         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
773             !strchr(p + 2, '%'))
774                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
775         else {
776                 if (cmdline)
777                         g_warning("Print command line is invalid: `%s'\n",
778                                   cmdline);
779                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
780         }
781
782         g_free(prtmp);
783
784         g_strchomp(buf);
785         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
786         system(buf);
787 }
788
789 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
790 {
791         msginfo->refcnt++;
792         
793         return msginfo;
794 }
795
796 MsgInfo *procmsg_msginfo_new(void)
797 {
798         MsgInfo *newmsginfo;
799
800         newmsginfo = g_new0(MsgInfo, 1);
801         newmsginfo->refcnt = 1;
802         
803         return newmsginfo;
804 }
805
806 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
807 {
808         MsgInfo *newmsginfo;
809
810         if (msginfo == NULL) return NULL;
811
812         newmsginfo = g_new0(MsgInfo, 1);
813
814         newmsginfo->refcnt = 1;
815
816 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
817 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
818                         g_strdup(msginfo->mmb) : NULL
819
820         MEMBCOPY(msgnum);
821         MEMBCOPY(size);
822         MEMBCOPY(mtime);
823         MEMBCOPY(date_t);
824         MEMBCOPY(flags);
825
826         MEMBDUP(fromname);
827
828         MEMBDUP(date);
829         MEMBDUP(from);
830         MEMBDUP(to);
831         MEMBDUP(cc);
832         MEMBDUP(newsgroups);
833         MEMBDUP(subject);
834         MEMBDUP(msgid);
835         MEMBDUP(inreplyto);
836         MEMBDUP(xref);
837
838         MEMBCOPY(folder);
839         MEMBCOPY(to_folder);
840
841         MEMBDUP(xface);
842         MEMBDUP(dispositionnotificationto);
843         MEMBDUP(returnreceiptto);
844         MEMBDUP(references);
845
846         MEMBCOPY(score);
847         MEMBCOPY(threadscore);
848
849         return newmsginfo;
850 }
851
852 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
853 {
854         MsgInfo *full_msginfo;
855         gchar *file;
856
857         if (msginfo == NULL) return NULL;
858
859         file = procmsg_get_message_file(msginfo);
860         if (!file) {
861                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
862                 return NULL;
863         }
864
865         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
866         g_free(file);
867         if (!full_msginfo) return NULL;
868
869         full_msginfo->msgnum = msginfo->msgnum;
870         full_msginfo->size = msginfo->size;
871         full_msginfo->mtime = msginfo->mtime;
872         full_msginfo->folder = msginfo->folder;
873 #if USE_GPGME
874         full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
875         full_msginfo->decryption_failed = msginfo->decryption_failed;
876 #endif
877         procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
878
879         return full_msginfo;
880 }
881
882 void procmsg_msginfo_free(MsgInfo *msginfo)
883 {
884         if (msginfo == NULL) return;
885
886         msginfo->refcnt--;
887         if (msginfo->refcnt > 0)
888                 return;
889
890         debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
891
892         if (msginfo->to_folder) {
893                 msginfo->to_folder->op_count--;
894                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
895         }
896
897         g_free(msginfo->fromspace);
898         g_free(msginfo->references);
899         g_free(msginfo->returnreceiptto);
900         g_free(msginfo->dispositionnotificationto);
901         g_free(msginfo->xface);
902
903         g_free(msginfo->fromname);
904
905         g_free(msginfo->date);
906         g_free(msginfo->from);
907         g_free(msginfo->to);
908         g_free(msginfo->cc);
909         g_free(msginfo->newsgroups);
910         g_free(msginfo->subject);
911         g_free(msginfo->msgid);
912         g_free(msginfo->inreplyto);
913         g_free(msginfo->xref);
914
915         g_free(msginfo);
916 }
917
918 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
919 {
920         guint memusage = 0;
921         
922         memusage += sizeof(MsgInfo);
923         if (msginfo->fromname)
924                 memusage += strlen(msginfo->fromname);
925         if (msginfo->date)
926                 memusage += strlen(msginfo->date);
927         if (msginfo->from)
928                 memusage += strlen(msginfo->from);
929         if (msginfo->to)
930                 memusage += strlen(msginfo->to);
931         if (msginfo->cc)
932                 memusage += strlen(msginfo->cc);
933         if (msginfo->newsgroups)
934                 memusage += strlen(msginfo->newsgroups);
935         if (msginfo->subject)
936                 memusage += strlen(msginfo->subject);
937         if (msginfo->msgid)
938                 memusage += strlen(msginfo->msgid);
939         if (msginfo->inreplyto)
940                 memusage += strlen(msginfo->inreplyto);
941         if (msginfo->xface)
942                 memusage += strlen(msginfo->xface);
943         if (msginfo->dispositionnotificationto)
944                 memusage += strlen(msginfo->dispositionnotificationto);
945         if (msginfo->returnreceiptto)
946                 memusage += strlen(msginfo->returnreceiptto);
947         if (msginfo->references)
948                 memusage += strlen(msginfo->references);
949         if (msginfo->fromspace)
950                 memusage += strlen(msginfo->fromspace);
951
952         return memusage;
953 }
954
955 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
956 {
957         const MsgInfo *msginfo1 = a;
958         const MsgInfo *msginfo2 = b;
959
960         if (!msginfo1)
961                 return -1;
962         if (!msginfo2)
963                 return -1;
964
965         return msginfo1->msgnum - msginfo2->msgnum;
966 }
967
968 enum
969 {
970         Q_SENDER           = 0,
971         Q_SMTPSERVER       = 1,
972         Q_RECIPIENTS       = 2,
973         Q_NEWSGROUPS       = 3,
974         Q_MAIL_ACCOUNT_ID  = 4,
975         Q_NEWS_ACCOUNT_ID  = 5,
976         Q_SAVE_COPY_FOLDER = 6,
977         Q_REPLY_MESSAGE_ID = 7,
978         Q_FWD_MESSAGE_ID   = 8
979 };
980
981 gint procmsg_send_message_queue(const gchar *file)
982 {
983         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
984                                        {"SSV:",  NULL, FALSE},
985                                        {"R:",    NULL, FALSE},
986                                        {"NG:",   NULL, FALSE},
987                                        {"MAID:", NULL, FALSE},
988                                        {"NAID:", NULL, FALSE},
989                                        {"SCF:",  NULL, FALSE},
990                                        {"RMID:", NULL, FALSE},
991                                        {"FMID:", NULL, FALSE},
992                                        {NULL,    NULL, FALSE}};
993         FILE *fp;
994         gint filepos;
995         gint mailval = 0, newsval = 0;
996         gchar *from = NULL;
997         gchar *smtpserver = NULL;
998         GSList *to_list = NULL;
999         GSList *newsgroup_list = NULL;
1000         gchar *savecopyfolder = NULL;
1001         gchar *replymessageid = NULL;
1002         gchar *fwdmessageid = NULL;
1003         gchar buf[BUFFSIZE];
1004         gint hnum;
1005         PrefsAccount *mailac = NULL, *newsac = NULL;
1006         int local = 0;
1007
1008         g_return_val_if_fail(file != NULL, -1);
1009
1010         if ((fp = fopen(file, "rb")) == NULL) {
1011                 FILE_OP_ERROR(file, "fopen");
1012                 return -1;
1013         }
1014
1015         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1016                != -1) {
1017                 gchar *p = buf + strlen(qentry[hnum].name);
1018
1019                 switch (hnum) {
1020                 case Q_SENDER:
1021                         if (!from) from = g_strdup(p);
1022                         break;
1023                 case Q_SMTPSERVER:
1024                         if (!smtpserver) smtpserver = g_strdup(p);
1025                         break;
1026                 case Q_RECIPIENTS:
1027                         to_list = address_list_append(to_list, p);
1028                         break;
1029                 case Q_NEWSGROUPS:
1030                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1031                         break;
1032                 case Q_MAIL_ACCOUNT_ID:
1033                         mailac = account_find_from_id(atoi(p));
1034                         break;
1035                 case Q_NEWS_ACCOUNT_ID:
1036                         newsac = account_find_from_id(atoi(p));
1037                         break;
1038                 case Q_SAVE_COPY_FOLDER:
1039                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1040                         break;
1041                 case Q_REPLY_MESSAGE_ID:
1042                         if (!replymessageid) replymessageid = g_strdup(p);
1043                         break;
1044                 case Q_FWD_MESSAGE_ID:
1045                         if (!fwdmessageid) fwdmessageid = g_strdup(p);
1046                         break;
1047                 }
1048         }
1049         filepos = ftell(fp);
1050
1051         if (to_list) {
1052                 debug_print("Sending message by mail\n");
1053                 if (!from) {
1054                         g_warning("Queued message header is broken.\n");
1055                         mailval = -1;
1056                 } else if (mailac && mailac->use_mail_command &&
1057                            mailac->mail_command && (* mailac->mail_command)) {
1058                         mailval = send_message_local(mailac->mail_command, fp);
1059                         local = 1;
1060                 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1061                         mailval = send_message_local(prefs_common.extsend_cmd, fp);
1062                         local = 1;
1063                 } else {
1064                         if (!mailac) {
1065                                 mailac = account_find_from_smtp_server(from, smtpserver);
1066                                 if (!mailac) {
1067                                         g_warning("Account not found. "
1068                                                     "Using current account...\n");
1069                                         mailac = cur_account;
1070                                 }
1071                         }
1072
1073                         if (mailac)
1074                                 mailval = send_message_smtp(mailac, to_list, fp);
1075                         else {
1076                                 PrefsAccount tmp_ac;
1077
1078                                 g_warning("Account not found.\n");
1079
1080                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1081                                 tmp_ac.address = from;
1082                                 tmp_ac.smtp_server = smtpserver;
1083                                 tmp_ac.smtpport = SMTP_PORT;
1084                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1085                         }
1086                 }
1087         }
1088
1089         fseek(fp, filepos, SEEK_SET);
1090         if (newsgroup_list && (newsval == 0)) {
1091                 Folder *folder;
1092                 gchar *tmp = NULL;
1093                 FILE *tmpfp;
1094
1095                 /* write to temporary file */
1096                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1097                             G_DIR_SEPARATOR, (gint)file);
1098                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1099                         FILE_OP_ERROR(tmp, "fopen");
1100                         newsval = -1;
1101                         alertpanel_error(_("Could not create temporary file for news sending."));
1102                 } else {
1103                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1104                                 FILE_OP_ERROR(tmp, "chmod");
1105                                 g_warning("can't change file mode\n");
1106                         }
1107
1108                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1109                                 if (fputs(buf, tmpfp) == EOF) {
1110                                         FILE_OP_ERROR(tmp, "fputs");
1111                                         newsval = -1;
1112                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1113                                 }
1114                         }
1115                         fclose(tmpfp);
1116
1117                         if (newsval == 0) {
1118                                 debug_print("Sending message by news\n");
1119
1120                                 folder = FOLDER(newsac->folder);
1121
1122                                 newsval = news_post(folder, tmp);
1123                                 if (newsval < 0) {
1124                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1125                                                  newsac->nntp_server);
1126                                 }
1127                         }
1128                         unlink(tmp);
1129                 }
1130                 g_free(tmp);
1131         }
1132
1133         slist_free_strings(to_list);
1134         g_slist_free(to_list);
1135         slist_free_strings(newsgroup_list);
1136         g_slist_free(newsgroup_list);
1137         g_free(from);
1138         g_free(smtpserver);
1139         fclose(fp);
1140
1141         /* save message to outbox */
1142         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1143                 FolderItem *outbox;
1144
1145                 debug_print("saving sent message...\n");
1146
1147                 outbox = folder_find_item_from_identifier(savecopyfolder);
1148                 if (!outbox)
1149                         outbox = folder_get_default_outbox();
1150
1151                 procmsg_save_to_outbox(outbox, file, TRUE);
1152         }
1153
1154         if (replymessageid != NULL || fwdmessageid != NULL) {
1155                 gchar **tokens;
1156                 FolderItem *item;
1157                 
1158                 if (replymessageid != NULL)
1159                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1160                 else
1161                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1162                 item = folder_find_item_from_identifier(tokens[0]);
1163
1164                 /* check if queued message has valid folder and message id */
1165                 if (item != NULL && tokens[2] != NULL) {
1166                         MsgInfo *msginfo;
1167                         
1168                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1169                 
1170                         /* check if referring message exists and has a message id */
1171                         if ((msginfo != NULL) && 
1172                             (msginfo->msgid != NULL) &&
1173                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1174                                 procmsg_msginfo_free(msginfo);
1175                                 msginfo = NULL;
1176                         }
1177                         
1178                         if (msginfo == NULL) {
1179                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1180                         }
1181                         
1182                         if (msginfo != NULL) {
1183                                 if (replymessageid != NULL) {
1184                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1185                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1186                                 } 
1187                                 else {
1188                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1189                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1190                                 }
1191                                 procmsg_msginfo_free(msginfo);
1192                         }
1193                 }
1194                 g_strfreev(tokens);
1195         }
1196
1197         g_free(savecopyfolder);
1198         g_free(replymessageid);
1199         g_free(fwdmessageid);
1200         
1201         return (newsval != 0 ? newsval : mailval);
1202 }
1203
1204 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1205 {
1206         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1207
1208         /* NEW flag */
1209         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1210                 item->new_msgs++;
1211         }
1212
1213         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1214                 item->new_msgs--;
1215         }
1216
1217         /* UNREAD flag */
1218         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1219                 item->unread_msgs++;
1220                 if (procmsg_msg_has_marked_parent(msginfo))
1221                         item->unreadmarked_msgs++;
1222         }
1223
1224         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1225                 item->unread_msgs--;
1226                 if (procmsg_msg_has_marked_parent(msginfo))
1227                         item->unreadmarked_msgs--;
1228         }
1229         
1230         /* MARK flag */
1231         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1232                 procmsg_update_unread_children(msginfo, TRUE);
1233         }
1234
1235         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1236                 procmsg_update_unread_children(msginfo, FALSE);
1237         }
1238 }
1239
1240 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1241 {
1242         FolderItem *item;
1243         MsgInfoUpdate msginfo_update;
1244         MsgPermFlags perm_flags_new, perm_flags_old;
1245
1246         g_return_if_fail(msginfo != NULL);
1247         item = msginfo->folder;
1248         g_return_if_fail(item != NULL);
1249         
1250         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1251
1252         /* Perm Flags handling */
1253         perm_flags_old = msginfo->flags.perm_flags;
1254         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1255         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1256                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1257         }
1258
1259         if (perm_flags_old != perm_flags_new) {
1260                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1261
1262                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1263
1264                 msginfo_update.msginfo = msginfo;
1265                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1266                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1267         }
1268
1269         /* Tmp flags hanlding */
1270         msginfo->flags.tmp_flags |= tmp_flags;
1271 }
1272
1273 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1274 {
1275         FolderItem *item;
1276         MsgInfoUpdate msginfo_update;
1277         MsgPermFlags perm_flags_new, perm_flags_old;
1278
1279         g_return_if_fail(msginfo != NULL);
1280         item = msginfo->folder;
1281         g_return_if_fail(item != NULL);
1282         
1283         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1284
1285         /* Perm Flags handling */
1286         perm_flags_old = msginfo->flags.perm_flags;
1287         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1288         
1289         if (perm_flags_old != perm_flags_new) {
1290                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1291
1292                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1293
1294                 msginfo_update.msginfo = msginfo;
1295                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1296                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1297         }
1298
1299         /* Tmp flags hanlding */
1300         msginfo->flags.tmp_flags &= ~tmp_flags;
1301 }
1302
1303 /*!
1304  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1305  *
1306  *\param        info Current message
1307  *\param        perm_flags Flags to be checked
1308  *\param        parentmsgs Hash of prior msgs to avoid loops
1309  *
1310  *\return       gboolean TRUE if perm_flags are found
1311  */
1312 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1313                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1314 {
1315         MsgInfo *tmp;
1316
1317         g_return_val_if_fail(info != NULL, FALSE);
1318
1319         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1320                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1321                                 info->inreplyto);
1322                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1323                         procmsg_msginfo_free(tmp);
1324                         return TRUE;
1325                 } else if (tmp != NULL) {
1326                         gboolean result;
1327
1328                         if (g_hash_table_lookup(parentmsgs, info)) {
1329                                 debug_print("loop detected: %s%c%d\n",
1330                                         folder_item_get_path(info->folder),
1331                                         G_DIR_SEPARATOR, info->msgnum);
1332                                 result = FALSE;
1333                         } else {
1334                                 g_hash_table_insert(parentmsgs, info, "1");
1335                                 result = procmsg_msg_has_flagged_parent_real(
1336                                     tmp, perm_flags, parentmsgs);
1337                         }
1338                         procmsg_msginfo_free(tmp);
1339                         return result;
1340                 } else {
1341                         return FALSE;
1342                 }
1343         } else
1344                 return FALSE;
1345 }
1346
1347 /*!
1348  *\brief        Callback for cleaning up hash of parentmsgs
1349  */
1350 gboolean parentmsgs_hash_remove(gpointer key,
1351                             gpointer value,
1352                             gpointer user_data)
1353 {
1354         return TRUE;
1355 }
1356
1357 /*!
1358  *\brief        Set up list of parentmsgs
1359  *              See procmsg_msg_has_flagged_parent_real()
1360  */
1361 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1362 {
1363         gboolean result;
1364         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1365
1366         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1367         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1368         g_hash_table_destroy(parentmsgs);
1369         return result;
1370 }
1371
1372 /*!
1373  *\brief        Check if msgs prior in thread are marked
1374  *              See procmsg_msg_has_flagged_parent_real()
1375  */
1376 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1377 {
1378         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1379 }
1380
1381
1382 GSList *procmsg_find_children_func(MsgInfo *info, 
1383                                    GSList *children, GSList *all)
1384 {
1385         GSList *cur;
1386
1387         g_return_val_if_fail(info!=NULL, children);
1388         if (info->msgid == NULL)
1389                 return children;
1390
1391         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1392                 MsgInfo *tmp = (MsgInfo *)cur->data;
1393                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1394                         /* Check if message is already in the list */
1395                         if ((children == NULL) || 
1396                             (g_slist_index(children, tmp) == -1)) {
1397                                 children = g_slist_prepend(children,
1398                                                 procmsg_msginfo_new_ref(tmp));
1399                                 children = procmsg_find_children_func(tmp, 
1400                                                         children, 
1401                                                         all);
1402                         }
1403                 }
1404         }
1405         return children;
1406 }
1407
1408 GSList *procmsg_find_children (MsgInfo *info)
1409 {
1410         GSList *children;
1411         GSList *all, *cur;
1412
1413         g_return_val_if_fail(info!=NULL, NULL);
1414         all = folder_item_get_msg_list(info->folder);
1415         children = procmsg_find_children_func(info, NULL, all);
1416         if (children != NULL) {
1417                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1418                         /* this will not free the used pointers
1419                            created with procmsg_msginfo_new_ref */
1420                         procmsg_msginfo_free((MsgInfo *)cur->data);
1421                 }
1422         }
1423         g_slist_free(all);
1424
1425         return children;
1426 }
1427
1428 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1429 {
1430         GSList *children = procmsg_find_children(info);
1431         GSList *cur;
1432         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1433                 MsgInfo *tmp = (MsgInfo *)cur->data;
1434                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1435                         if(newly_marked) 
1436                                 info->folder->unreadmarked_msgs++;
1437                         else
1438                                 info->folder->unreadmarked_msgs--;
1439                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1440                 }
1441                 procmsg_msginfo_free(tmp);
1442         }
1443         g_slist_free(children);
1444 }
1445
1446 /**
1447  * Set the destination folder for a copy or move operation
1448  *
1449  * \param msginfo The message which's destination folder is changed
1450  * \param to_folder The destination folder for the operation
1451  */
1452 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1453 {
1454         if(msginfo->to_folder != NULL) {
1455                 msginfo->to_folder->op_count--;
1456                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1457         }
1458         msginfo->to_folder = to_folder;
1459         if(to_folder != NULL) {
1460                 to_folder->op_count++;
1461                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1462         }
1463 }
1464
1465 /**
1466  * Apply filtering actions to the msginfo
1467  *
1468  * \param msginfo The MsgInfo describing the message that should be filtered
1469  * \return TRUE if the message was moved and MsgInfo is now invalid,
1470  *         FALSE otherwise
1471  */
1472 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1473 {
1474         MailFilteringData mail_filtering_data;
1475                         
1476         mail_filtering_data.msginfo = msginfo;                  
1477         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1478                 return TRUE;
1479
1480         /* filter if enabled in prefs or move to inbox if not */
1481         if((global_processing != NULL) &&
1482            filter_message_by_msginfo(global_processing, msginfo))
1483                 return TRUE;
1484
1485         return FALSE;
1486 }