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