2005-03-31 [colin] 1.9.6cvs17
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 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 <glib/gi18n.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27
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 #include "alertpanel.h"
41 #include "news.h"
42 #include "hooks.h"
43 #include "msgcache.h"
44 #include "partial_download.h"
45
46 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
47 {
48         GHashTable *msg_table;
49
50         if (mlist == NULL) return NULL;
51
52         msg_table = g_hash_table_new(NULL, g_direct_equal);
53         procmsg_msg_hash_table_append(msg_table, mlist);
54
55         return msg_table;
56 }
57
58 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
59 {
60         GSList *cur;
61         MsgInfo *msginfo;
62
63         if (msg_table == NULL || mlist == NULL) return;
64
65         for (cur = mlist; cur != NULL; cur = cur->next) {
66                 msginfo = (MsgInfo *)cur->data;
67
68                 g_hash_table_insert(msg_table,
69                                     GUINT_TO_POINTER(msginfo->msgnum),
70                                     msginfo);
71         }
72 }
73
74 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
75 {
76         GHashTable *msg_table;
77         GSList *cur;
78         MsgInfo *msginfo;
79
80         if (mlist == NULL) return NULL;
81
82         msg_table = g_hash_table_new(NULL, g_direct_equal);
83
84         for (cur = mlist; cur != NULL; cur = cur->next) {
85                 msginfo = (MsgInfo *)cur->data;
86                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
87         }
88
89         return msg_table;
90 }
91
92 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
93 {
94         GSList *cur;
95         MsgInfo *msginfo;
96         gint last = 0;
97
98         for (cur = mlist; cur != NULL; cur = cur->next) {
99                 msginfo = (MsgInfo *)cur->data;
100                 if (msginfo && msginfo->msgnum > last)
101                         last = msginfo->msgnum;
102         }
103
104         return last;
105 }
106
107 void procmsg_msg_list_free(GSList *mlist)
108 {
109         GSList *cur;
110         MsgInfo *msginfo;
111
112         for (cur = mlist; cur != NULL; cur = cur->next) {
113                 msginfo = (MsgInfo *)cur->data;
114                 procmsg_msginfo_free(msginfo);
115         }
116         g_slist_free(mlist);
117 }
118
119 struct MarkSum {
120         gint *new_msgs;
121         gint *unread_msgs;
122         gint *total_msgs;
123         gint *min;
124         gint *max;
125         gint first;
126 };
127
128 /* CLAWS subject threading:
129   
130   in the first round it inserts subject lines in a 
131   relation (subject <-> node)
132
133   the second round finishes the threads by attaching
134   matching subject lines to the one found in the
135   relation. will use the oldest node with the same
136   subject that is not more then thread_by_subject_max_age
137   days old (see subject_relation_lookup)
138 */  
139
140 static void subject_relation_insert(GRelation *relation, GNode *node)
141 {
142         gchar *subject;
143         MsgInfo *msginfo;
144
145         g_return_if_fail(relation != NULL);
146         g_return_if_fail(node != NULL);
147         msginfo = (MsgInfo *) node->data;
148         g_return_if_fail(msginfo != NULL);
149
150         subject = msginfo->subject;
151         if (subject == NULL)
152                 return;
153         subject += subject_get_prefix_length(subject);
154
155         g_relation_insert(relation, subject, node);
156 }
157
158 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
159 {
160         gchar *subject;
161         GTuples *tuples;
162         GNode *node = NULL;
163         gint prefix_length;
164     
165         g_return_val_if_fail(relation != NULL, NULL);
166
167         subject = msginfo->subject;
168         if (subject == NULL)
169                 return NULL;
170         prefix_length = subject_get_prefix_length(subject);
171         if (prefix_length <= 0)
172                 return NULL;
173         subject += prefix_length;
174         
175         tuples = g_relation_select(relation, subject, 0);
176         if (tuples == NULL)
177                 return NULL;
178
179         if (tuples->len > 0) {
180                 int i;
181                 GNode *relation_node;
182                 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
183                 gboolean match;
184
185                 /* check all nodes with the same subject to find the best parent */
186                 for (i = 0; i < tuples->len; i++) {
187                         relation_node = (GNode *) g_tuples_index(tuples, i, 1);
188                         relation_msginfo = (MsgInfo *) relation_node->data;
189                         match = FALSE;
190
191                         /* best node should be the oldest in the found nodes */
192                         /* parent node must not be older then msginfo */
193                         if ((relation_msginfo->date_t < msginfo->date_t) &&
194                             ((best_msginfo == NULL) ||
195                              (best_msginfo->date_t > relation_msginfo->date_t)))
196                                 match = TRUE;
197
198                         /* parent node must not be more then thread_by_subject_max_age
199                            days older then msginfo */
200                         if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
201                             prefs_common.thread_by_subject_max_age * 3600 * 24)
202                                 match = FALSE;
203
204                         /* can add new tests for all matching
205                            nodes found by subject */
206
207                         if (match) {
208                                 node = relation_node;
209                                 best_msginfo = relation_msginfo;
210                         }
211                 }           
212         }
213
214         g_tuples_destroy(tuples);
215         return node;
216 }
217
218 /* return the reversed thread tree */
219 GNode *procmsg_get_thread_tree(GSList *mlist)
220 {
221         GNode *root, *parent, *node, *next;
222         GHashTable *msgid_table;
223         GRelation *subject_relation;
224         MsgInfo *msginfo;
225         const gchar *msgid;
226         GSList *reflist;
227
228         root = g_node_new(NULL);
229         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
230         subject_relation = g_relation_new(2);
231         g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
232
233         for (; mlist != NULL; mlist = mlist->next) {
234                 msginfo = (MsgInfo *)mlist->data;
235                 parent = root;
236
237                 if (msginfo->inreplyto) {
238                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
239                         if (parent == NULL) {
240                                 parent = root;
241                         }
242                 }
243                 node = g_node_insert_data_before
244                         (parent, parent == root ? parent->children : NULL,
245                          msginfo);
246                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
247                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
248
249                 /* CLAWS: add subject to relation (without prefix) */
250                 if (prefs_common.thread_by_subject) {
251                         subject_relation_insert(subject_relation, node);
252                 }
253         }
254
255         /* complete the unfinished threads */
256         for (node = root->children; node != NULL; ) {
257                 next = node->next;
258                 msginfo = (MsgInfo *)node->data;
259                 parent = NULL;
260                 
261                 if (msginfo->inreplyto)
262                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
263
264                 /* try looking for the indirect parent */
265                 if (!parent && msginfo->references) {
266                         for (reflist = msginfo->references;
267                              reflist != NULL; reflist = reflist->next)
268                                 if ((parent = g_hash_table_lookup
269                                         (msgid_table, reflist->data)) != NULL)
270                                         break;
271                 }                                        
272               
273                 /* node should not be the parent, and node should not
274                    be an ancestor of parent (circular reference) */
275                 if (parent && parent != node &&
276                     !g_node_is_ancestor(node, parent)) {
277                         g_node_unlink(node);
278                         g_node_insert_before
279                                 (parent, parent->children, node);
280                 }
281                
282                 node = next;
283         }
284
285         if (prefs_common.thread_by_subject) {
286                 for (node = root->children; node && node != NULL;) {
287                         next = node->next;
288                         msginfo = (MsgInfo *) node->data;
289                         
290                         parent = subject_relation_lookup(subject_relation, msginfo);
291                         
292                         /* the node may already be threaded by IN-REPLY-TO, so go up 
293                          * in the tree to 
294                            find the parent node */
295                         if (parent != NULL) {
296                                 if (g_node_is_ancestor(node, parent))
297                                         parent = NULL;
298                                 if (parent == node)
299                                         parent = NULL;
300                         }
301                         
302                         if (parent) {
303                                 g_node_unlink(node);
304                                 g_node_append(parent, node);
305                         }
306
307                         node = next;
308                 }       
309         }
310         
311         g_relation_destroy(subject_relation);
312         g_hash_table_destroy(msgid_table);
313
314         return root;
315 }
316
317 void procmsg_move_messages(GSList *mlist)
318 {
319         GSList *cur, *movelist = NULL;
320         MsgInfo *msginfo;
321         FolderItem *dest = NULL;
322
323         if (!mlist) return;
324
325         folder_item_update_freeze();
326
327         for (cur = mlist; cur != NULL; cur = cur->next) {
328                 msginfo = (MsgInfo *)cur->data;
329                 if (!dest) {
330                         dest = msginfo->to_folder;
331                         movelist = g_slist_append(movelist, msginfo);
332                 } else if (dest == msginfo->to_folder) {
333                         movelist = g_slist_append(movelist, msginfo);
334                 } else {
335                         folder_item_move_msgs(dest, movelist);
336                         g_slist_free(movelist);
337                         movelist = NULL;
338                         dest = msginfo->to_folder;
339                         movelist = g_slist_append(movelist, msginfo);
340                 }
341                 procmsg_msginfo_set_to_folder(msginfo, NULL);
342         }
343
344         if (movelist) {
345                 folder_item_move_msgs(dest, movelist);
346                 g_slist_free(movelist);
347         }
348
349         folder_item_update_thaw();
350 }
351
352 void procmsg_copy_messages(GSList *mlist)
353 {
354         GSList *cur, *copylist = NULL;
355         MsgInfo *msginfo;
356         FolderItem *dest = NULL;
357
358         if (!mlist) return;
359
360         folder_item_update_freeze();
361
362         for (cur = mlist; cur != NULL; cur = cur->next) {
363                 msginfo = (MsgInfo *)cur->data;
364                 if (!dest) {
365                         dest = msginfo->to_folder;
366                         copylist = g_slist_append(copylist, msginfo);
367                 } else if (dest == msginfo->to_folder) {
368                         copylist = g_slist_append(copylist, msginfo);
369                 } else {
370                         folder_item_copy_msgs(dest, copylist);
371                         g_slist_free(copylist);
372                         copylist = NULL;
373                         dest = msginfo->to_folder;
374                         copylist = g_slist_append(copylist, msginfo);
375                 }
376                 procmsg_msginfo_set_to_folder(msginfo, NULL);
377         }
378
379         if (copylist) {
380                 folder_item_copy_msgs(dest, copylist);
381                 g_slist_free(copylist);
382         }
383
384         folder_item_update_thaw();
385 }
386
387 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
388 {
389         gchar *file;
390
391         g_return_val_if_fail(msginfo != NULL, NULL);
392
393         if (msginfo->plaintext_file)
394                 file = g_strdup(msginfo->plaintext_file);
395         else {
396                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
397         }
398
399         return file;
400 }
401
402 gchar *procmsg_get_message_file(MsgInfo *msginfo)
403 {
404         gchar *filename = NULL;
405
406         g_return_val_if_fail(msginfo != NULL, NULL);
407
408         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
409         if (!filename)
410                 debug_print("can't fetch message %d\n", msginfo->msgnum);
411
412         return filename;
413 }
414
415 GSList *procmsg_get_message_file_list(GSList *mlist)
416 {
417         GSList *file_list = NULL;
418         MsgInfo *msginfo;
419         MsgFileInfo *fileinfo;
420         gchar *file;
421
422         while (mlist != NULL) {
423                 msginfo = (MsgInfo *)mlist->data;
424                 file = procmsg_get_message_file(msginfo);
425                 if (!file) {
426                         procmsg_message_file_list_free(file_list);
427                         return NULL;
428                 }
429                 fileinfo = g_new(MsgFileInfo, 1);
430                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
431                 fileinfo->file = file;
432                 fileinfo->flags = g_new(MsgFlags, 1);
433                 *fileinfo->flags = msginfo->flags;
434                 file_list = g_slist_prepend(file_list, fileinfo);
435                 mlist = mlist->next;
436         }
437
438         file_list = g_slist_reverse(file_list);
439
440         return file_list;
441 }
442
443 void procmsg_message_file_list_free(MsgInfoList *file_list)
444 {
445         GSList *cur;
446         MsgFileInfo *fileinfo;
447
448         for (cur = file_list; cur != NULL; cur = cur->next) {
449                 fileinfo = (MsgFileInfo *)cur->data;
450                 procmsg_msginfo_free(fileinfo->msginfo);
451                 g_free(fileinfo->file);
452                 g_free(fileinfo->flags);
453                 g_free(fileinfo);
454         }
455
456         g_slist_free(file_list);
457 }
458
459 FILE *procmsg_open_message(MsgInfo *msginfo)
460 {
461         FILE *fp;
462         gchar *file;
463
464         g_return_val_if_fail(msginfo != NULL, NULL);
465
466         file = procmsg_get_message_file_path(msginfo);
467         g_return_val_if_fail(file != NULL, NULL);
468
469         if (!is_file_exist(file)) {
470                 g_free(file);
471                 file = procmsg_get_message_file(msginfo);
472                 if (!file)
473                         return NULL;
474         }
475
476         if ((fp = fopen(file, "rb")) == NULL) {
477                 FILE_OP_ERROR(file, "fopen");
478                 g_free(file);
479                 return NULL;
480         }
481
482         g_free(file);
483
484         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
485                 gchar buf[BUFFSIZE];
486
487                 while (fgets(buf, sizeof(buf), fp) != NULL)
488                         if (buf[0] == '\r' || buf[0] == '\n') break;
489         }
490
491         return fp;
492 }
493
494 gboolean procmsg_msg_exist(MsgInfo *msginfo)
495 {
496         gchar *path;
497         gboolean ret;
498
499         if (!msginfo) return FALSE;
500
501         path = folder_item_get_path(msginfo->folder);
502         change_dir(path);
503         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
504         g_free(path);
505
506         return ret;
507 }
508
509 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
510                                 PrefsFilterType type)
511 {
512         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
513                                        {"X-ML-Name:",      NULL, TRUE},
514                                        {"X-List:",         NULL, TRUE},
515                                        {"X-Mailing-list:", NULL, TRUE},
516                                        {"List-Id:",        NULL, TRUE},
517                                        {"X-Sequence:",     NULL, TRUE},
518                                        {NULL,              NULL, FALSE}};
519         enum
520         {
521                 H_X_BEENTHERE    = 0,
522                 H_X_ML_NAME      = 1,
523                 H_X_LIST         = 2,
524                 H_X_MAILING_LIST = 3,
525                 H_LIST_ID        = 4,
526                 H_X_SEQUENCE     = 5
527         };
528
529         FILE *fp;
530
531         g_return_if_fail(msginfo != NULL);
532         g_return_if_fail(header != NULL);
533         g_return_if_fail(key != NULL);
534
535         *header = NULL;
536         *key = NULL;
537
538         switch (type) {
539         case FILTER_BY_NONE:
540                 return;
541         case FILTER_BY_AUTO:
542                 if ((fp = procmsg_open_message(msginfo)) == NULL)
543                         return;
544                 procheader_get_header_fields(fp, hentry);
545                 fclose(fp);
546
547 #define SET_FILTER_KEY(hstr, idx)       \
548 {                                       \
549         *header = g_strdup(hstr);       \
550         *key = hentry[idx].body;        \
551         hentry[idx].body = NULL;        \
552 }
553
554                 if (hentry[H_X_BEENTHERE].body != NULL) {
555                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
556                 } else if (hentry[H_X_ML_NAME].body != NULL) {
557                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
558                 } else if (hentry[H_X_LIST].body != NULL) {
559                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
560                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
561                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
562                 } else if (hentry[H_LIST_ID].body != NULL) {
563                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
564                         extract_list_id_str(*key);
565                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
566                         guchar *p;
567
568                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
569                         p = *key;
570                         while (*p != '\0') {
571                                 while (*p != '\0' && !isspace(*p)) p++;
572                                 while (isspace(*p)) p++;
573                                 if (isdigit(*p)) {
574                                         *p = '\0';
575                                         break;
576                                 }
577                         }
578                         g_strstrip(*key);
579                 } else if (msginfo->subject) {
580                         *header = g_strdup("subject");
581                         *key = g_strdup(msginfo->subject);
582                 }
583
584 #undef SET_FILTER_KEY
585
586                 g_free(hentry[H_X_BEENTHERE].body);
587                 hentry[H_X_BEENTHERE].body = NULL;
588                 g_free(hentry[H_X_ML_NAME].body);
589                 hentry[H_X_ML_NAME].body = NULL;
590                 g_free(hentry[H_X_LIST].body);
591                 hentry[H_X_LIST].body = NULL;
592                 g_free(hentry[H_X_MAILING_LIST].body);
593                 hentry[H_X_MAILING_LIST].body = NULL;
594                 g_free(hentry[H_LIST_ID].body);
595                 hentry[H_LIST_ID].body = NULL;
596
597                 break;
598         case FILTER_BY_FROM:
599                 *header = g_strdup("from");
600                 *key = g_strdup(msginfo->from);
601                 break;
602         case FILTER_BY_TO:
603                 *header = g_strdup("to");
604                 *key = g_strdup(msginfo->to);
605                 break;
606         case FILTER_BY_SUBJECT:
607                 *header = g_strdup("subject");
608                 *key = g_strdup(msginfo->subject);
609                 break;
610         default:
611                 break;
612         }
613 }
614
615 void procmsg_empty_trash(FolderItem *trash)
616 {
617         if (trash && trash->total_msgs > 0) {
618                 GSList *mlist = folder_item_get_msg_list(trash);
619                 GSList *cur;
620                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
621                         MsgInfo * msginfo = (MsgInfo *) cur->data;
622                         partial_mark_for_delete(msginfo);
623                         procmsg_msginfo_free(msginfo);
624                 }
625
626                 folder_item_remove_all_msg(trash);
627         }
628 }
629
630 void procmsg_empty_all_trash(void)
631 {
632         FolderItem *trash;
633         GList *cur;
634
635         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
636                 trash = FOLDER(cur->data)->trash;
637                 procmsg_empty_trash(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 sent = 0, err = 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                                         err++;
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                                         sent++; 
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 (err != 0 ? -err : sent);
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, *tmp_msginfo;
728         MsgFlags flag = {0, 0};
729
730         debug_print("saving sent message...\n");
731
732         if (!outbox)
733                 outbox = folder_get_default_outbox();
734         g_return_val_if_fail(outbox != NULL, -1);
735
736         /* remove queueing headers */
737         if (is_queued) {
738                 gchar tmp[MAXPATHLEN + 1];
739
740                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
741                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
742                 
743                 if (procmsg_remove_special_headers(file, tmp) !=0)
744                         return -1;
745
746                 folder_item_scan(outbox);
747                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
748                         g_warning("can't save message\n");
749                         unlink(tmp);
750                         return -1;
751                 }
752         } else {
753                 folder_item_scan(outbox);
754                 if ((num = folder_item_add_msg
755                         (outbox, file, &flag, FALSE)) < 0) {
756                         g_warning("can't save message\n");
757                         return -1;
758                 }
759                 return -1;
760         }
761         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
762         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
763         if (msginfo != NULL) {
764                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
765                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
766                 /* tmp_msginfo == msginfo */
767                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
768                     msginfo->returnreceiptto)) {
769                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
770                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
771                 }       
772         }
773
774         return 0;
775 }
776
777 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
778 {
779         static const gchar *def_cmd = "lpr %s";
780         static guint id = 0;
781         gchar *prtmp;
782         FILE *tmpfp, *prfp;
783         gchar buf[1024];
784         gchar *p;
785
786         g_return_if_fail(msginfo);
787
788         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
789                 g_warning("Can't get text part\n");
790                 return;
791         }
792
793         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
794                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
795
796         if ((prfp = fopen(prtmp, "wb")) == NULL) {
797                 FILE_OP_ERROR(prtmp, "fopen");
798                 g_free(prtmp);
799                 fclose(tmpfp);
800                 return;
801         }
802
803         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
804         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
805         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
806         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
807         if (msginfo->newsgroups)
808                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
809         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
810         fputc('\n', prfp);
811
812         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
813                 fputs(buf, prfp);
814
815         fclose(prfp);
816         fclose(tmpfp);
817
818         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
819             !strchr(p + 2, '%'))
820                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
821         else {
822                 if (cmdline)
823                         g_warning("Print command line is invalid: `%s'\n",
824                                   cmdline);
825                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
826         }
827
828         g_free(prtmp);
829
830         g_strchomp(buf);
831         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
832         system(buf);
833 }
834
835 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
836 {
837         msginfo->refcnt++;
838         
839         return msginfo;
840 }
841
842 MsgInfo *procmsg_msginfo_new(void)
843 {
844         MsgInfo *newmsginfo;
845
846         newmsginfo = g_new0(MsgInfo, 1);
847         newmsginfo->refcnt = 1;
848         
849         return newmsginfo;
850 }
851
852 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
853 {
854         MsgInfo *newmsginfo;
855         GSList *refs;
856
857         if (msginfo == NULL) return NULL;
858
859         newmsginfo = g_new0(MsgInfo, 1);
860
861         newmsginfo->refcnt = 1;
862
863 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
864 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
865                         g_strdup(msginfo->mmb) : NULL
866
867         MEMBCOPY(msgnum);
868         MEMBCOPY(size);
869         MEMBCOPY(mtime);
870         MEMBCOPY(date_t);
871
872         MEMBCOPY(flags);
873
874         MEMBDUP(fromname);
875
876         MEMBDUP(date);
877         MEMBDUP(from);
878         MEMBDUP(to);
879         MEMBDUP(cc);
880         MEMBDUP(newsgroups);
881         MEMBDUP(subject);
882         MEMBDUP(msgid);
883         MEMBDUP(inreplyto);
884         MEMBDUP(xref);
885
886         MEMBCOPY(folder);
887         MEMBCOPY(to_folder);
888
889         MEMBDUP(xface);
890         MEMBDUP(dispositionnotificationto);
891         MEMBDUP(returnreceiptto);
892
893         refs = msginfo->references;
894         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
895                 newmsginfo->references = g_slist_prepend
896                         (newmsginfo->references, g_strdup(refs->data)); 
897         }
898         newmsginfo->references = g_slist_reverse(newmsginfo->references);
899
900         MEMBCOPY(score);
901         MEMBCOPY(threadscore);
902         MEMBDUP(plaintext_file);
903
904         return newmsginfo;
905 }
906
907 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
908 {
909         MsgInfo *full_msginfo;
910         gchar *file;
911
912         if (msginfo == NULL) return NULL;
913
914         file = procmsg_get_message_file_path(msginfo);
915         if (!file || !is_file_exist(file)) {
916                 g_free(file);
917                 file = procmsg_get_message_file(msginfo);
918         }
919         if (!file || !is_file_exist(file)) {
920                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
921                 return NULL;
922         }
923
924         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
925         g_free(file);
926         if (!full_msginfo) return NULL;
927
928         /* CLAWS: make sure we add the missing members; see: 
929          * procheader.c::procheader_get_headernames() */
930         if (!msginfo->xface)
931                 msginfo->xface = g_strdup(full_msginfo->xface);
932         if (!msginfo->dispositionnotificationto)
933                 msginfo->dispositionnotificationto = 
934                         g_strdup(full_msginfo->dispositionnotificationto);
935         if (!msginfo->returnreceiptto)
936                 msginfo->returnreceiptto = g_strdup
937                         (full_msginfo->returnreceiptto);
938         if (!msginfo->partial_recv && full_msginfo->partial_recv)
939                 msginfo->partial_recv = g_strdup
940                         (full_msginfo->partial_recv);
941         msginfo->total_size = full_msginfo->total_size;
942         if (!msginfo->account_server && full_msginfo->account_server)
943                 msginfo->account_server = g_strdup
944                         (full_msginfo->account_server);
945         if (!msginfo->account_login && full_msginfo->account_login)
946                 msginfo->account_login = g_strdup
947                         (full_msginfo->account_login);
948         msginfo->planned_download = full_msginfo->planned_download;
949         procmsg_msginfo_free(full_msginfo);
950
951         return procmsg_msginfo_new_ref(msginfo);
952 }
953
954 void procmsg_msginfo_free(MsgInfo *msginfo)
955 {
956         if (msginfo == NULL) return;
957
958         msginfo->refcnt--;
959         if (msginfo->refcnt > 0)
960                 return;
961
962         if (msginfo->to_folder) {
963                 msginfo->to_folder->op_count--;
964                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
965         }
966
967         g_free(msginfo->fromspace);
968         g_free(msginfo->returnreceiptto);
969         g_free(msginfo->dispositionnotificationto);
970         g_free(msginfo->xface);
971
972         g_free(msginfo->fromname);
973
974         g_free(msginfo->date);
975         g_free(msginfo->from);
976         g_free(msginfo->to);
977         g_free(msginfo->cc);
978         g_free(msginfo->newsgroups);
979         g_free(msginfo->subject);
980         g_free(msginfo->msgid);
981         g_free(msginfo->inreplyto);
982         g_free(msginfo->xref);
983
984         g_free(msginfo->partial_recv);
985         g_free(msginfo->account_server);
986         g_free(msginfo->account_login);
987         
988         slist_free_strings(msginfo->references);
989         g_slist_free(msginfo->references);
990
991         g_free(msginfo->plaintext_file);
992
993         g_free(msginfo);
994 }
995
996 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
997 {
998         guint memusage = 0;
999         
1000         memusage += sizeof(MsgInfo);
1001         if (msginfo->fromname)
1002                 memusage += strlen(msginfo->fromname);
1003         if (msginfo->date)
1004                 memusage += strlen(msginfo->date);
1005         if (msginfo->from)
1006                 memusage += strlen(msginfo->from);
1007         if (msginfo->to)
1008                 memusage += strlen(msginfo->to);
1009         if (msginfo->cc)
1010                 memusage += strlen(msginfo->cc);
1011         if (msginfo->newsgroups)
1012                 memusage += strlen(msginfo->newsgroups);
1013         if (msginfo->subject)
1014                 memusage += strlen(msginfo->subject);
1015         if (msginfo->msgid)
1016                 memusage += strlen(msginfo->msgid);
1017         if (msginfo->inreplyto)
1018                 memusage += strlen(msginfo->inreplyto);
1019         if (msginfo->xface)
1020                 memusage += strlen(msginfo->xface);
1021         if (msginfo->dispositionnotificationto)
1022                 memusage += strlen(msginfo->dispositionnotificationto);
1023         if (msginfo->returnreceiptto)
1024                 memusage += strlen(msginfo->returnreceiptto);
1025 #warning FIXME: Calculate size of references list                
1026 #if 0
1027         if (msginfo->references)
1028                 memusage += strlen(msginfo->references);
1029 #endif                
1030         if (msginfo->fromspace)
1031                 memusage += strlen(msginfo->fromspace);
1032
1033         return memusage;
1034 }
1035
1036 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1037 {
1038         const MsgInfo *msginfo1 = a;
1039         const MsgInfo *msginfo2 = b;
1040
1041         if (!msginfo1)
1042                 return -1;
1043         if (!msginfo2)
1044                 return -1;
1045
1046         return msginfo1->msgnum - msginfo2->msgnum;
1047 }
1048
1049 enum
1050 {
1051         Q_SENDER           = 0,
1052         Q_SMTPSERVER       = 1,
1053         Q_RECIPIENTS       = 2,
1054         Q_NEWSGROUPS       = 3,
1055         Q_MAIL_ACCOUNT_ID  = 4,
1056         Q_NEWS_ACCOUNT_ID  = 5,
1057         Q_SAVE_COPY_FOLDER = 6,
1058         Q_REPLY_MESSAGE_ID = 7,
1059         Q_FWD_MESSAGE_ID   = 8,
1060         Q_PRIVACY_SYSTEM   = 9,
1061         Q_ENCRYPT          = 10,
1062         Q_ENCRYPT_DATA     = 11,
1063 };
1064
1065 gint procmsg_send_message_queue(const gchar *file)
1066 {
1067         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1068                                        {"SSV:",  NULL, FALSE},
1069                                        {"R:",    NULL, FALSE},
1070                                        {"NG:",   NULL, FALSE},
1071                                        {"MAID:", NULL, FALSE},
1072                                        {"NAID:", NULL, FALSE},
1073                                        {"SCF:",  NULL, FALSE},
1074                                        {"RMID:", NULL, FALSE},
1075                                        {"FMID:", NULL, FALSE},
1076                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1077                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1078                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1079                                        {NULL,    NULL, FALSE}};
1080         FILE *fp;
1081         gint filepos;
1082         gint mailval = 0, newsval = 0;
1083         gchar *from = NULL;
1084         gchar *smtpserver = NULL;
1085         GSList *to_list = NULL;
1086         GSList *newsgroup_list = NULL;
1087         gchar *savecopyfolder = NULL;
1088         gchar *replymessageid = NULL;
1089         gchar *fwdmessageid = NULL;
1090         gchar *privacy_system = NULL;
1091         gboolean encrypt = FALSE;
1092         gchar *encrypt_data = NULL;
1093         gchar buf[BUFFSIZE];
1094         gint hnum;
1095         PrefsAccount *mailac = NULL, *newsac = NULL;
1096         gboolean save_clear_text = TRUE;
1097         gchar *tmp_enc_file = NULL;
1098
1099         int local = 0;
1100
1101         g_return_val_if_fail(file != NULL, -1);
1102
1103         if ((fp = fopen(file, "rb")) == NULL) {
1104                 FILE_OP_ERROR(file, "fopen");
1105                 return -1;
1106         }
1107
1108         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1109                != -1) {
1110                 gchar *p = buf + strlen(qentry[hnum].name);
1111
1112                 switch (hnum) {
1113                 case Q_SENDER:
1114                         if (from == NULL) 
1115                                 from = g_strdup(p);
1116                         break;
1117                 case Q_SMTPSERVER:
1118                         if (smtpserver == NULL) 
1119                                 smtpserver = g_strdup(p);
1120                         break;
1121                 case Q_RECIPIENTS:
1122                         to_list = address_list_append(to_list, p);
1123                         break;
1124                 case Q_NEWSGROUPS:
1125                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1126                         break;
1127                 case Q_MAIL_ACCOUNT_ID:
1128                         mailac = account_find_from_id(atoi(p));
1129                         break;
1130                 case Q_NEWS_ACCOUNT_ID:
1131                         newsac = account_find_from_id(atoi(p));
1132                         break;
1133                 case Q_SAVE_COPY_FOLDER:
1134                         if (savecopyfolder == NULL) 
1135                                 savecopyfolder = g_strdup(p);
1136                         break;
1137                 case Q_REPLY_MESSAGE_ID:
1138                         if (replymessageid == NULL) 
1139                                 replymessageid = g_strdup(p);
1140                         break;
1141                 case Q_FWD_MESSAGE_ID:
1142                         if (fwdmessageid == NULL) 
1143                                 fwdmessageid = g_strdup(p);
1144                         break;
1145                 case Q_PRIVACY_SYSTEM:
1146                         if (privacy_system == NULL) 
1147                                 privacy_system = g_strdup(p);
1148                         break;
1149                 case Q_ENCRYPT:
1150                         if (p[0] == '1') 
1151                                 encrypt = TRUE;
1152                         break;
1153                 case Q_ENCRYPT_DATA:
1154                         if (encrypt_data == NULL) 
1155                                 encrypt_data = g_strdup(p);
1156                         break;
1157                 }
1158         }
1159         filepos = ftell(fp);
1160
1161         if (encrypt) {
1162                 MimeInfo *mimeinfo;
1163
1164                 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1165
1166                 fclose(fp);
1167                 fp = NULL;
1168
1169                 mimeinfo = procmime_scan_queue_file(file);
1170                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1171                 || (fp = my_tmpfile()) == NULL
1172                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1173                         if (fp)
1174                                 fclose(fp);
1175                         procmime_mimeinfo_free_all(mimeinfo);
1176                         g_free(from);
1177                         g_free(smtpserver);
1178                         slist_free_strings(to_list);
1179                         g_slist_free(to_list);
1180                         slist_free_strings(newsgroup_list);
1181                         g_slist_free(newsgroup_list);
1182                         g_free(savecopyfolder);
1183                         g_free(replymessageid);
1184                         g_free(fwdmessageid);
1185                         g_free(privacy_system);
1186                         g_free(encrypt_data);
1187                         return -1;
1188                 }
1189                 
1190                 rewind(fp);
1191                 if (!save_clear_text) {
1192                         gchar *content = NULL;
1193                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1194                         if (tmpfp) {
1195                                 fclose(tmpfp);
1196
1197                                 content = file_read_stream_to_str(fp);
1198                                 rewind(fp);
1199
1200                                 str_write_to_file(content, tmp_enc_file);
1201                                 g_free(content);
1202                         } else {
1203                                 g_warning("couldn't get tempfile\n");
1204                         }
1205                 } 
1206                 
1207                 procmime_mimeinfo_free_all(mimeinfo);
1208                 
1209                 filepos = 0;
1210         }
1211
1212         if (to_list) {
1213                 debug_print("Sending message by mail\n");
1214                 if (!from) {
1215                         g_warning("Queued message header is broken.\n");
1216                         mailval = -1;
1217                 } else if (mailac && mailac->use_mail_command &&
1218                            mailac->mail_command && (* mailac->mail_command)) {
1219                         mailval = send_message_local(mailac->mail_command, fp);
1220                         local = 1;
1221                 } else {
1222                         if (!mailac) {
1223                                 mailac = account_find_from_smtp_server(from, smtpserver);
1224                                 if (!mailac) {
1225                                         g_warning("Account not found. "
1226                                                     "Using current account...\n");
1227                                         mailac = cur_account;
1228                                 }
1229                         }
1230
1231                         if (mailac)
1232                                 mailval = send_message_smtp(mailac, to_list, fp);
1233                         else {
1234                                 PrefsAccount tmp_ac;
1235
1236                                 g_warning("Account not found.\n");
1237
1238                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1239                                 tmp_ac.address = from;
1240                                 tmp_ac.smtp_server = smtpserver;
1241                                 tmp_ac.smtpport = SMTP_PORT;
1242                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1243                         }
1244                 }
1245         }
1246
1247         fseek(fp, filepos, SEEK_SET);
1248         if (newsgroup_list && (mailval == 0)) {
1249                 Folder *folder;
1250                 gchar *tmp = NULL;
1251                 FILE *tmpfp;
1252
1253                 /* write to temporary file */
1254                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1255                             G_DIR_SEPARATOR, (gint)file);
1256                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1257                         FILE_OP_ERROR(tmp, "fopen");
1258                         newsval = -1;
1259                         alertpanel_error(_("Could not create temporary file for news sending."));
1260                 } else {
1261                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1262                                 FILE_OP_ERROR(tmp, "chmod");
1263                                 g_warning("can't change file mode\n");
1264                         }
1265
1266                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1267                                 if (fputs(buf, tmpfp) == EOF) {
1268                                         FILE_OP_ERROR(tmp, "fputs");
1269                                         newsval = -1;
1270                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1271                                 }
1272                         }
1273                         fclose(tmpfp);
1274
1275                         if (newsval == 0) {
1276                                 debug_print("Sending message by news\n");
1277
1278                                 folder = FOLDER(newsac->folder);
1279
1280                                 newsval = news_post(folder, tmp);
1281                                 if (newsval < 0) {
1282                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1283                                                  newsac->nntp_server);
1284                                 }
1285                         }
1286                         unlink(tmp);
1287                 }
1288                 g_free(tmp);
1289         }
1290
1291         fclose(fp);
1292
1293         /* save message to outbox */
1294         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1295                 FolderItem *outbox;
1296
1297                 debug_print("saving sent message...\n");
1298
1299                 outbox = folder_find_item_from_identifier(savecopyfolder);
1300                 if (!outbox)
1301                         outbox = folder_get_default_outbox();
1302                         
1303                 if (save_clear_text || tmp_enc_file == NULL) {
1304                         procmsg_save_to_outbox(outbox, file, TRUE);
1305                 } else {
1306                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1307                 }
1308         }
1309
1310         if (tmp_enc_file != NULL) {
1311                 unlink(tmp_enc_file);
1312                 free(tmp_enc_file);
1313                 tmp_enc_file = NULL;
1314         }
1315
1316         if (replymessageid != NULL || fwdmessageid != NULL) {
1317                 gchar **tokens;
1318                 FolderItem *item;
1319                 
1320                 if (replymessageid != NULL)
1321                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1322                 else
1323                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1324                 item = folder_find_item_from_identifier(tokens[0]);
1325
1326                 /* check if queued message has valid folder and message id */
1327                 if (item != NULL && tokens[2] != NULL) {
1328                         MsgInfo *msginfo;
1329                         
1330                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1331                 
1332                         /* check if referring message exists and has a message id */
1333                         if ((msginfo != NULL) && 
1334                             (msginfo->msgid != NULL) &&
1335                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1336                                 procmsg_msginfo_free(msginfo);
1337                                 msginfo = NULL;
1338                         }
1339                         
1340                         if (msginfo == NULL) {
1341                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1342                         }
1343                         
1344                         if (msginfo != NULL) {
1345                                 if (replymessageid != NULL) {
1346                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1347                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1348                                 }  else {
1349                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1350                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1351                                 }
1352                                 procmsg_msginfo_free(msginfo);
1353                         }
1354                 }
1355                 g_strfreev(tokens);
1356         }
1357
1358         g_free(from);
1359         g_free(smtpserver);
1360         slist_free_strings(to_list);
1361         g_slist_free(to_list);
1362         slist_free_strings(newsgroup_list);
1363         g_slist_free(newsgroup_list);
1364         g_free(savecopyfolder);
1365         g_free(replymessageid);
1366         g_free(fwdmessageid);
1367         g_free(privacy_system);
1368         g_free(encrypt_data);
1369
1370         return (newsval != 0 ? newsval : mailval);
1371 }
1372
1373 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1374 {
1375         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1376
1377         /* NEW flag */
1378         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1379                 item->new_msgs++;
1380         }
1381
1382         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1383                 item->new_msgs--;
1384         }
1385
1386         /* UNREAD flag */
1387         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1388                 item->unread_msgs++;
1389                 if (procmsg_msg_has_marked_parent(msginfo))
1390                         item->unreadmarked_msgs++;
1391         }
1392
1393         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1394                 item->unread_msgs--;
1395                 if (procmsg_msg_has_marked_parent(msginfo))
1396                         item->unreadmarked_msgs--;
1397         }
1398         
1399         /* MARK flag */
1400         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1401                 procmsg_update_unread_children(msginfo, TRUE);
1402         }
1403
1404         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1405                 procmsg_update_unread_children(msginfo, FALSE);
1406         }
1407 }
1408
1409 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1410 {
1411         FolderItem *item;
1412         MsgInfoUpdate msginfo_update;
1413         MsgPermFlags perm_flags_new, perm_flags_old;
1414         MsgTmpFlags tmp_flags_old;
1415
1416         g_return_if_fail(msginfo != NULL);
1417         item = msginfo->folder;
1418         g_return_if_fail(item != NULL);
1419         
1420         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1421
1422         /* Perm Flags handling */
1423         perm_flags_old = msginfo->flags.perm_flags;
1424         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1425         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1426                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1427         }
1428
1429         if (perm_flags_old != perm_flags_new) {
1430                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1431
1432                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1433
1434         }
1435
1436         /* Tmp flags handling */
1437         tmp_flags_old = msginfo->flags.tmp_flags;
1438         msginfo->flags.tmp_flags |= tmp_flags;
1439
1440         /* update notification */
1441         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1442                 msginfo_update.msginfo = msginfo;
1443                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1444                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1445                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1446         }
1447 }
1448
1449 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1450 {
1451         FolderItem *item;
1452         MsgInfoUpdate msginfo_update;
1453         MsgPermFlags perm_flags_new, perm_flags_old;
1454         MsgTmpFlags tmp_flags_old;
1455
1456         g_return_if_fail(msginfo != NULL);
1457         item = msginfo->folder;
1458         g_return_if_fail(item != NULL);
1459         
1460         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1461
1462         /* Perm Flags handling */
1463         perm_flags_old = msginfo->flags.perm_flags;
1464         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1465         
1466         if (perm_flags_old != perm_flags_new) {
1467                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1468
1469                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1470
1471                 msginfo_update.msginfo = msginfo;
1472                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1473                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1474                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1475         }
1476
1477         /* Tmp flags hanlding */
1478         tmp_flags_old = msginfo->flags.tmp_flags;
1479         msginfo->flags.tmp_flags &= ~tmp_flags;
1480
1481         /* update notification */
1482         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1483                 msginfo_update.msginfo = msginfo;
1484                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1485                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1486                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1487         }
1488 }
1489
1490 /*!
1491  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1492  *
1493  *\param        info Current message
1494  *\param        perm_flags Flags to be checked
1495  *\param        parentmsgs Hash of prior msgs to avoid loops
1496  *
1497  *\return       gboolean TRUE if perm_flags are found
1498  */
1499 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1500                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1501 {
1502         MsgInfo *tmp;
1503
1504         g_return_val_if_fail(info != NULL, FALSE);
1505
1506         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1507                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1508                                 info->inreplyto);
1509                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1510                         procmsg_msginfo_free(tmp);
1511                         return TRUE;
1512                 } else if (tmp != NULL) {
1513                         gboolean result;
1514
1515                         if (g_hash_table_lookup(parentmsgs, info)) {
1516                                 debug_print("loop detected: %s%c%d\n",
1517                                         folder_item_get_path(info->folder),
1518                                         G_DIR_SEPARATOR, info->msgnum);
1519                                 result = FALSE;
1520                         } else {
1521                                 g_hash_table_insert(parentmsgs, info, "1");
1522                                 result = procmsg_msg_has_flagged_parent_real(
1523                                     tmp, perm_flags, parentmsgs);
1524                         }
1525                         procmsg_msginfo_free(tmp);
1526                         return result;
1527                 } else {
1528                         return FALSE;
1529                 }
1530         } else
1531                 return FALSE;
1532 }
1533
1534 /*!
1535  *\brief        Callback for cleaning up hash of parentmsgs
1536  */
1537 gboolean parentmsgs_hash_remove(gpointer key,
1538                             gpointer value,
1539                             gpointer user_data)
1540 {
1541         return TRUE;
1542 }
1543
1544 /*!
1545  *\brief        Set up list of parentmsgs
1546  *              See procmsg_msg_has_flagged_parent_real()
1547  */
1548 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1549 {
1550         gboolean result;
1551         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1552
1553         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1554         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1555         g_hash_table_destroy(parentmsgs);
1556         return result;
1557 }
1558
1559 /*!
1560  *\brief        Check if msgs prior in thread are marked
1561  *              See procmsg_msg_has_flagged_parent_real()
1562  */
1563 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1564 {
1565         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1566 }
1567
1568
1569 GSList *procmsg_find_children_func(MsgInfo *info, 
1570                                    GSList *children, GSList *all)
1571 {
1572         GSList *cur;
1573
1574         g_return_val_if_fail(info!=NULL, children);
1575         if (info->msgid == NULL)
1576                 return children;
1577
1578         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1579                 MsgInfo *tmp = (MsgInfo *)cur->data;
1580                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1581                         /* Check if message is already in the list */
1582                         if ((children == NULL) || 
1583                             (g_slist_index(children, tmp) == -1)) {
1584                                 children = g_slist_prepend(children,
1585                                                 procmsg_msginfo_new_ref(tmp));
1586                                 children = procmsg_find_children_func(tmp, 
1587                                                         children, 
1588                                                         all);
1589                         }
1590                 }
1591         }
1592         return children;
1593 }
1594
1595 GSList *procmsg_find_children (MsgInfo *info)
1596 {
1597         GSList *children;
1598         GSList *all, *cur;
1599
1600         g_return_val_if_fail(info!=NULL, NULL);
1601         all = folder_item_get_msg_list(info->folder);
1602         children = procmsg_find_children_func(info, NULL, all);
1603         if (children != NULL) {
1604                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1605                         /* this will not free the used pointers
1606                            created with procmsg_msginfo_new_ref */
1607                         procmsg_msginfo_free((MsgInfo *)cur->data);
1608                 }
1609         }
1610         g_slist_free(all);
1611
1612         return children;
1613 }
1614
1615 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1616 {
1617         GSList *children = procmsg_find_children(info);
1618         GSList *cur;
1619         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1620                 MsgInfo *tmp = (MsgInfo *)cur->data;
1621                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1622                         if(newly_marked) 
1623                                 info->folder->unreadmarked_msgs++;
1624                         else
1625                                 info->folder->unreadmarked_msgs--;
1626                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1627                 }
1628                 procmsg_msginfo_free(tmp);
1629         }
1630         g_slist_free(children);
1631 }
1632
1633 /**
1634  * Set the destination folder for a copy or move operation
1635  *
1636  * \param msginfo The message which's destination folder is changed
1637  * \param to_folder The destination folder for the operation
1638  */
1639 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1640 {
1641         if(msginfo->to_folder != NULL) {
1642                 msginfo->to_folder->op_count--;
1643                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1644         }
1645         msginfo->to_folder = to_folder;
1646         if(to_folder != NULL) {
1647                 to_folder->op_count++;
1648                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1649         }
1650 }
1651
1652 /**
1653  * Apply filtering actions to the msginfo
1654  *
1655  * \param msginfo The MsgInfo describing the message that should be filtered
1656  * \return TRUE if the message was moved and MsgInfo is now invalid,
1657  *         FALSE otherwise
1658  */
1659 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1660 {
1661         MailFilteringData mail_filtering_data;
1662                         
1663         mail_filtering_data.msginfo = msginfo;                  
1664         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1665                 return TRUE;
1666
1667         /* filter if enabled in prefs or move to inbox if not */
1668         if((filtering_rules != NULL) &&
1669            filter_message_by_msginfo(filtering_rules, msginfo))
1670                 return TRUE;
1671
1672         return FALSE;
1673 }
1674
1675 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
1676 {
1677         MsgInfo *tmp_msginfo = NULL;
1678         MsgFlags flags = {0, 0};
1679         
1680         
1681         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
1682             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
1683                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
1684                 return NULL;
1685         }
1686                     
1687         if (mimeinfo->content == MIMECONTENT_MEM) {
1688                 gchar *tmpfile = get_tmp_file();
1689                 str_write_to_file(mimeinfo->data.mem, tmpfile);
1690                 g_free(mimeinfo->data.mem);
1691                 mimeinfo->content == MIMECONTENT_FILE;
1692                 mimeinfo->data.filename = g_strdup(tmpfile);
1693                 g_free(tmpfile);
1694         }
1695
1696         tmp_msginfo = procheader_parse_file(mimeinfo->data.filename,
1697                                 flags, TRUE, FALSE);
1698
1699         if (tmp_msginfo != NULL) {
1700                 tmp_msginfo->folder = src_msginfo->folder;
1701                 tmp_msginfo->plaintext_file = g_strdup(mimeinfo->data.filename);
1702         } else {
1703                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
1704         }
1705         
1706         return tmp_msginfo;
1707 }