0.9.3claws36
[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 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 #if 0
855         MsgInfo *full_msginfo;
856         gchar *file;
857 #endif
858
859         if (msginfo == NULL) return NULL;
860
861         /* 
862          * In Claws we simply return a new reference to the same msginfo.
863          * otherwise the new msginfo has wrong flags and causes incorrect
864          * msgcounts... TODO: fill in data from full_msginfo into msginfo,
865          * we can then keep the new data in the cache
866          */
867         return procmsg_msginfo_new_ref(msginfo);
868 #if 0
869         file = procmsg_get_message_file(msginfo);
870         if (!file) {
871                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
872                 return NULL;
873         }
874
875         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
876         g_free(file);
877         if (!full_msginfo) return NULL;
878
879         full_msginfo->msgnum = msginfo->msgnum;
880         full_msginfo->size = msginfo->size;
881         full_msginfo->mtime = msginfo->mtime;
882         full_msginfo->folder = msginfo->folder;
883 #if USE_GPGME
884         full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
885         full_msginfo->decryption_failed = msginfo->decryption_failed;
886 #endif
887         procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
888
889         return full_msginfo;
890 #endif
891 }
892
893 void procmsg_msginfo_free(MsgInfo *msginfo)
894 {
895         if (msginfo == NULL) return;
896
897         msginfo->refcnt--;
898         if (msginfo->refcnt > 0)
899                 return;
900
901         debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
902
903         if (msginfo->to_folder) {
904                 msginfo->to_folder->op_count--;
905                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
906         }
907
908         g_free(msginfo->fromspace);
909         g_free(msginfo->references);
910         g_free(msginfo->returnreceiptto);
911         g_free(msginfo->dispositionnotificationto);
912         g_free(msginfo->xface);
913
914         g_free(msginfo->fromname);
915
916         g_free(msginfo->date);
917         g_free(msginfo->from);
918         g_free(msginfo->to);
919         g_free(msginfo->cc);
920         g_free(msginfo->newsgroups);
921         g_free(msginfo->subject);
922         g_free(msginfo->msgid);
923         g_free(msginfo->inreplyto);
924         g_free(msginfo->xref);
925
926         g_free(msginfo);
927 }
928
929 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
930 {
931         guint memusage = 0;
932         
933         memusage += sizeof(MsgInfo);
934         if (msginfo->fromname)
935                 memusage += strlen(msginfo->fromname);
936         if (msginfo->date)
937                 memusage += strlen(msginfo->date);
938         if (msginfo->from)
939                 memusage += strlen(msginfo->from);
940         if (msginfo->to)
941                 memusage += strlen(msginfo->to);
942         if (msginfo->cc)
943                 memusage += strlen(msginfo->cc);
944         if (msginfo->newsgroups)
945                 memusage += strlen(msginfo->newsgroups);
946         if (msginfo->subject)
947                 memusage += strlen(msginfo->subject);
948         if (msginfo->msgid)
949                 memusage += strlen(msginfo->msgid);
950         if (msginfo->inreplyto)
951                 memusage += strlen(msginfo->inreplyto);
952         if (msginfo->xface)
953                 memusage += strlen(msginfo->xface);
954         if (msginfo->dispositionnotificationto)
955                 memusage += strlen(msginfo->dispositionnotificationto);
956         if (msginfo->returnreceiptto)
957                 memusage += strlen(msginfo->returnreceiptto);
958         if (msginfo->references)
959                 memusage += strlen(msginfo->references);
960         if (msginfo->fromspace)
961                 memusage += strlen(msginfo->fromspace);
962
963         return memusage;
964 }
965
966 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
967 {
968         const MsgInfo *msginfo1 = a;
969         const MsgInfo *msginfo2 = b;
970
971         if (!msginfo1)
972                 return -1;
973         if (!msginfo2)
974                 return -1;
975
976         return msginfo1->msgnum - msginfo2->msgnum;
977 }
978
979 enum
980 {
981         Q_SENDER           = 0,
982         Q_SMTPSERVER       = 1,
983         Q_RECIPIENTS       = 2,
984         Q_NEWSGROUPS       = 3,
985         Q_MAIL_ACCOUNT_ID  = 4,
986         Q_NEWS_ACCOUNT_ID  = 5,
987         Q_SAVE_COPY_FOLDER = 6,
988         Q_REPLY_MESSAGE_ID = 7,
989         Q_FWD_MESSAGE_ID   = 8
990 };
991
992 gint procmsg_send_message_queue(const gchar *file)
993 {
994         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
995                                        {"SSV:",  NULL, FALSE},
996                                        {"R:",    NULL, FALSE},
997                                        {"NG:",   NULL, FALSE},
998                                        {"MAID:", NULL, FALSE},
999                                        {"NAID:", NULL, FALSE},
1000                                        {"SCF:",  NULL, FALSE},
1001                                        {"RMID:", NULL, FALSE},
1002                                        {"FMID:", NULL, FALSE},
1003                                        {NULL,    NULL, FALSE}};
1004         FILE *fp;
1005         gint filepos;
1006         gint mailval = 0, newsval = 0;
1007         gchar *from = NULL;
1008         gchar *smtpserver = NULL;
1009         GSList *to_list = NULL;
1010         GSList *newsgroup_list = NULL;
1011         gchar *savecopyfolder = NULL;
1012         gchar *replymessageid = NULL;
1013         gchar *fwdmessageid = NULL;
1014         gchar buf[BUFFSIZE];
1015         gint hnum;
1016         PrefsAccount *mailac = NULL, *newsac = NULL;
1017         int local = 0;
1018
1019         g_return_val_if_fail(file != NULL, -1);
1020
1021         if ((fp = fopen(file, "rb")) == NULL) {
1022                 FILE_OP_ERROR(file, "fopen");
1023                 return -1;
1024         }
1025
1026         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1027                != -1) {
1028                 gchar *p = buf + strlen(qentry[hnum].name);
1029
1030                 switch (hnum) {
1031                 case Q_SENDER:
1032                         if (!from) from = g_strdup(p);
1033                         break;
1034                 case Q_SMTPSERVER:
1035                         if (!smtpserver) smtpserver = g_strdup(p);
1036                         break;
1037                 case Q_RECIPIENTS:
1038                         to_list = address_list_append(to_list, p);
1039                         break;
1040                 case Q_NEWSGROUPS:
1041                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1042                         break;
1043                 case Q_MAIL_ACCOUNT_ID:
1044                         mailac = account_find_from_id(atoi(p));
1045                         break;
1046                 case Q_NEWS_ACCOUNT_ID:
1047                         newsac = account_find_from_id(atoi(p));
1048                         break;
1049                 case Q_SAVE_COPY_FOLDER:
1050                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1051                         break;
1052                 case Q_REPLY_MESSAGE_ID:
1053                         if (!replymessageid) replymessageid = g_strdup(p);
1054                         break;
1055                 case Q_FWD_MESSAGE_ID:
1056                         if (!fwdmessageid) fwdmessageid = g_strdup(p);
1057                         break;
1058                 }
1059         }
1060         filepos = ftell(fp);
1061
1062         if (to_list) {
1063                 debug_print("Sending message by mail\n");
1064                 if (!from) {
1065                         g_warning("Queued message header is broken.\n");
1066                         mailval = -1;
1067                 } else if (mailac && mailac->use_mail_command &&
1068                            mailac->mail_command && (* mailac->mail_command)) {
1069                         mailval = send_message_local(mailac->mail_command, fp);
1070                         local = 1;
1071                 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1072                         mailval = send_message_local(prefs_common.extsend_cmd, fp);
1073                         local = 1;
1074                 } else {
1075                         if (!mailac) {
1076                                 mailac = account_find_from_smtp_server(from, smtpserver);
1077                                 if (!mailac) {
1078                                         g_warning("Account not found. "
1079                                                     "Using current account...\n");
1080                                         mailac = cur_account;
1081                                 }
1082                         }
1083
1084                         if (mailac)
1085                                 mailval = send_message_smtp(mailac, to_list, fp);
1086                         else {
1087                                 PrefsAccount tmp_ac;
1088
1089                                 g_warning("Account not found.\n");
1090
1091                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1092                                 tmp_ac.address = from;
1093                                 tmp_ac.smtp_server = smtpserver;
1094                                 tmp_ac.smtpport = SMTP_PORT;
1095                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1096                         }
1097                 }
1098         }
1099
1100         fseek(fp, filepos, SEEK_SET);
1101         if (newsgroup_list && (newsval == 0)) {
1102                 Folder *folder;
1103                 gchar *tmp = NULL;
1104                 FILE *tmpfp;
1105
1106                 /* write to temporary file */
1107                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1108                             G_DIR_SEPARATOR, (gint)file);
1109                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1110                         FILE_OP_ERROR(tmp, "fopen");
1111                         newsval = -1;
1112                         alertpanel_error(_("Could not create temporary file for news sending."));
1113                 } else {
1114                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1115                                 FILE_OP_ERROR(tmp, "chmod");
1116                                 g_warning("can't change file mode\n");
1117                         }
1118
1119                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1120                                 if (fputs(buf, tmpfp) == EOF) {
1121                                         FILE_OP_ERROR(tmp, "fputs");
1122                                         newsval = -1;
1123                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1124                                 }
1125                         }
1126                         fclose(tmpfp);
1127
1128                         if (newsval == 0) {
1129                                 debug_print("Sending message by news\n");
1130
1131                                 folder = FOLDER(newsac->folder);
1132
1133                                 newsval = news_post(folder, tmp);
1134                                 if (newsval < 0) {
1135                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1136                                                  newsac->nntp_server);
1137                                 }
1138                         }
1139                         unlink(tmp);
1140                 }
1141                 g_free(tmp);
1142         }
1143
1144         slist_free_strings(to_list);
1145         g_slist_free(to_list);
1146         slist_free_strings(newsgroup_list);
1147         g_slist_free(newsgroup_list);
1148         g_free(from);
1149         g_free(smtpserver);
1150         fclose(fp);
1151
1152         /* save message to outbox */
1153         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1154                 FolderItem *outbox;
1155
1156                 debug_print("saving sent message...\n");
1157
1158                 outbox = folder_find_item_from_identifier(savecopyfolder);
1159                 if (!outbox)
1160                         outbox = folder_get_default_outbox();
1161
1162                 procmsg_save_to_outbox(outbox, file, TRUE);
1163         }
1164
1165         if (replymessageid != NULL || fwdmessageid != NULL) {
1166                 gchar **tokens;
1167                 FolderItem *item;
1168                 
1169                 if (replymessageid != NULL)
1170                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1171                 else
1172                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1173                 item = folder_find_item_from_identifier(tokens[0]);
1174
1175                 /* check if queued message has valid folder and message id */
1176                 if (item != NULL && tokens[2] != NULL) {
1177                         MsgInfo *msginfo;
1178                         
1179                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1180                 
1181                         /* check if referring message exists and has a message id */
1182                         if ((msginfo != NULL) && 
1183                             (msginfo->msgid != NULL) &&
1184                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1185                                 procmsg_msginfo_free(msginfo);
1186                                 msginfo = NULL;
1187                         }
1188                         
1189                         if (msginfo == NULL) {
1190                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1191                         }
1192                         
1193                         if (msginfo != NULL) {
1194                                 if (replymessageid != NULL) {
1195                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1196                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1197                                 } 
1198                                 else {
1199                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1200                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1201                                 }
1202                                 procmsg_msginfo_free(msginfo);
1203                         }
1204                 }
1205                 g_strfreev(tokens);
1206         }
1207
1208         g_free(savecopyfolder);
1209         g_free(replymessageid);
1210         g_free(fwdmessageid);
1211         
1212         return (newsval != 0 ? newsval : mailval);
1213 }
1214
1215 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1216 {
1217         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1218
1219         /* NEW flag */
1220         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1221                 item->new_msgs++;
1222         }
1223
1224         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1225                 item->new_msgs--;
1226         }
1227
1228         /* UNREAD flag */
1229         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1230                 item->unread_msgs++;
1231                 if (procmsg_msg_has_marked_parent(msginfo))
1232                         item->unreadmarked_msgs++;
1233         }
1234
1235         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1236                 item->unread_msgs--;
1237                 if (procmsg_msg_has_marked_parent(msginfo))
1238                         item->unreadmarked_msgs--;
1239         }
1240         
1241         /* MARK flag */
1242         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1243                 procmsg_update_unread_children(msginfo, TRUE);
1244         }
1245
1246         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1247                 procmsg_update_unread_children(msginfo, FALSE);
1248         }
1249 }
1250
1251 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1252 {
1253         FolderItem *item;
1254         MsgInfoUpdate msginfo_update;
1255         MsgPermFlags perm_flags_new, perm_flags_old;
1256
1257         g_return_if_fail(msginfo != NULL);
1258         item = msginfo->folder;
1259         g_return_if_fail(item != NULL);
1260         
1261         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1262
1263         /* Perm Flags handling */
1264         perm_flags_old = msginfo->flags.perm_flags;
1265         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1266         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1267                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1268         }
1269
1270         if (perm_flags_old != perm_flags_new) {
1271                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1272
1273                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1274
1275                 msginfo_update.msginfo = msginfo;
1276                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1277                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1278         }
1279
1280         /* Tmp flags hanlding */
1281         msginfo->flags.tmp_flags |= tmp_flags;
1282 }
1283
1284 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1285 {
1286         FolderItem *item;
1287         MsgInfoUpdate msginfo_update;
1288         MsgPermFlags perm_flags_new, perm_flags_old;
1289
1290         g_return_if_fail(msginfo != NULL);
1291         item = msginfo->folder;
1292         g_return_if_fail(item != NULL);
1293         
1294         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1295
1296         /* Perm Flags handling */
1297         perm_flags_old = msginfo->flags.perm_flags;
1298         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1299         
1300         if (perm_flags_old != perm_flags_new) {
1301                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1302
1303                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1304
1305                 msginfo_update.msginfo = msginfo;
1306                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1307                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1308         }
1309
1310         /* Tmp flags hanlding */
1311         msginfo->flags.tmp_flags &= ~tmp_flags;
1312 }
1313
1314 /*!
1315  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1316  *
1317  *\param        info Current message
1318  *\param        perm_flags Flags to be checked
1319  *\param        parentmsgs Hash of prior msgs to avoid loops
1320  *
1321  *\return       gboolean TRUE if perm_flags are found
1322  */
1323 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1324                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1325 {
1326         MsgInfo *tmp;
1327
1328         g_return_val_if_fail(info != NULL, FALSE);
1329
1330         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1331                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1332                                 info->inreplyto);
1333                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1334                         procmsg_msginfo_free(tmp);
1335                         return TRUE;
1336                 } else if (tmp != NULL) {
1337                         gboolean result;
1338
1339                         if (g_hash_table_lookup(parentmsgs, info)) {
1340                                 debug_print("loop detected: %s%c%d\n",
1341                                         folder_item_get_path(info->folder),
1342                                         G_DIR_SEPARATOR, info->msgnum);
1343                                 result = FALSE;
1344                         } else {
1345                                 g_hash_table_insert(parentmsgs, info, "1");
1346                                 result = procmsg_msg_has_flagged_parent_real(
1347                                     tmp, perm_flags, parentmsgs);
1348                         }
1349                         procmsg_msginfo_free(tmp);
1350                         return result;
1351                 } else {
1352                         return FALSE;
1353                 }
1354         } else
1355                 return FALSE;
1356 }
1357
1358 /*!
1359  *\brief        Callback for cleaning up hash of parentmsgs
1360  */
1361 gboolean parentmsgs_hash_remove(gpointer key,
1362                             gpointer value,
1363                             gpointer user_data)
1364 {
1365         return TRUE;
1366 }
1367
1368 /*!
1369  *\brief        Set up list of parentmsgs
1370  *              See procmsg_msg_has_flagged_parent_real()
1371  */
1372 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1373 {
1374         gboolean result;
1375         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1376
1377         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1378         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1379         g_hash_table_destroy(parentmsgs);
1380         return result;
1381 }
1382
1383 /*!
1384  *\brief        Check if msgs prior in thread are marked
1385  *              See procmsg_msg_has_flagged_parent_real()
1386  */
1387 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1388 {
1389         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1390 }
1391
1392
1393 GSList *procmsg_find_children_func(MsgInfo *info, 
1394                                    GSList *children, GSList *all)
1395 {
1396         GSList *cur;
1397
1398         g_return_val_if_fail(info!=NULL, children);
1399         if (info->msgid == NULL)
1400                 return children;
1401
1402         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1403                 MsgInfo *tmp = (MsgInfo *)cur->data;
1404                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1405                         /* Check if message is already in the list */
1406                         if ((children == NULL) || 
1407                             (g_slist_index(children, tmp) == -1)) {
1408                                 children = g_slist_prepend(children,
1409                                                 procmsg_msginfo_new_ref(tmp));
1410                                 children = procmsg_find_children_func(tmp, 
1411                                                         children, 
1412                                                         all);
1413                         }
1414                 }
1415         }
1416         return children;
1417 }
1418
1419 GSList *procmsg_find_children (MsgInfo *info)
1420 {
1421         GSList *children;
1422         GSList *all, *cur;
1423
1424         g_return_val_if_fail(info!=NULL, NULL);
1425         all = folder_item_get_msg_list(info->folder);
1426         children = procmsg_find_children_func(info, NULL, all);
1427         if (children != NULL) {
1428                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1429                         /* this will not free the used pointers
1430                            created with procmsg_msginfo_new_ref */
1431                         procmsg_msginfo_free((MsgInfo *)cur->data);
1432                 }
1433         }
1434         g_slist_free(all);
1435
1436         return children;
1437 }
1438
1439 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1440 {
1441         GSList *children = procmsg_find_children(info);
1442         GSList *cur;
1443         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1444                 MsgInfo *tmp = (MsgInfo *)cur->data;
1445                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1446                         if(newly_marked) 
1447                                 info->folder->unreadmarked_msgs++;
1448                         else
1449                                 info->folder->unreadmarked_msgs--;
1450                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1451                 }
1452                 procmsg_msginfo_free(tmp);
1453         }
1454         g_slist_free(children);
1455 }
1456
1457 /**
1458  * Set the destination folder for a copy or move operation
1459  *
1460  * \param msginfo The message which's destination folder is changed
1461  * \param to_folder The destination folder for the operation
1462  */
1463 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1464 {
1465         if(msginfo->to_folder != NULL) {
1466                 msginfo->to_folder->op_count--;
1467                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1468         }
1469         msginfo->to_folder = to_folder;
1470         if(to_folder != NULL) {
1471                 to_folder->op_count++;
1472                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1473         }
1474 }
1475
1476 /**
1477  * Apply filtering actions to the msginfo
1478  *
1479  * \param msginfo The MsgInfo describing the message that should be filtered
1480  * \return TRUE if the message was moved and MsgInfo is now invalid,
1481  *         FALSE otherwise
1482  */
1483 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1484 {
1485         MailFilteringData mail_filtering_data;
1486                         
1487         mail_filtering_data.msginfo = msginfo;                  
1488         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1489                 return TRUE;
1490
1491         /* filter if enabled in prefs or move to inbox if not */
1492         if((global_processing != NULL) &&
1493            filter_message_by_msginfo(global_processing, msginfo))
1494                 return TRUE;
1495
1496         return FALSE;
1497 }