ce066f08eb84ce82aece13d81eab5a900163ac1b
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 #include "mainwindow.h"
46 #include "summaryview.h"
47 #include "log.h"
48 #include "timing.h"
49 #include "inc.h"
50
51 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr);
52
53 enum
54 {
55         Q_SENDER           = 0,
56         Q_SMTPSERVER       = 1,
57         Q_RECIPIENTS       = 2,
58         Q_NEWSGROUPS       = 3,
59         Q_MAIL_ACCOUNT_ID  = 4,
60         Q_NEWS_ACCOUNT_ID  = 5,
61         Q_SAVE_COPY_FOLDER = 6,
62         Q_REPLY_MESSAGE_ID = 7,
63         Q_FWD_MESSAGE_ID   = 8,
64         Q_PRIVACY_SYSTEM   = 9,
65         Q_ENCRYPT          = 10,
66         Q_ENCRYPT_DATA     = 11,
67         Q_SYLPHEED_HDRS    = 12,
68 };
69
70 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
71 {
72         GHashTable *msg_table;
73
74         if (mlist == NULL) return NULL;
75
76         msg_table = g_hash_table_new(NULL, g_direct_equal);
77         procmsg_msg_hash_table_append(msg_table, mlist);
78
79         return msg_table;
80 }
81
82 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
83 {
84         GSList *cur;
85         MsgInfo *msginfo;
86
87         if (msg_table == NULL || mlist == NULL) return;
88
89         for (cur = mlist; cur != NULL; cur = cur->next) {
90                 msginfo = (MsgInfo *)cur->data;
91
92                 g_hash_table_insert(msg_table,
93                                     GUINT_TO_POINTER(msginfo->msgnum),
94                                     msginfo);
95         }
96 }
97
98 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
99 {
100         GHashTable *msg_table;
101         GSList *cur;
102         MsgInfo *msginfo;
103
104         if (mlist == NULL) return NULL;
105
106         msg_table = g_hash_table_new(NULL, g_direct_equal);
107
108         for (cur = mlist; cur != NULL; cur = cur->next) {
109                 msginfo = (MsgInfo *)cur->data;
110                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
111         }
112
113         return msg_table;
114 }
115
116 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
117 {
118         GSList *cur;
119         MsgInfo *msginfo;
120         gint last = 0;
121
122         for (cur = mlist; cur != NULL; cur = cur->next) {
123                 msginfo = (MsgInfo *)cur->data;
124                 if (msginfo && msginfo->msgnum > last)
125                         last = msginfo->msgnum;
126         }
127
128         return last;
129 }
130
131 void procmsg_msg_list_free(GSList *mlist)
132 {
133         GSList *cur;
134         MsgInfo *msginfo;
135
136         for (cur = mlist; cur != NULL; cur = cur->next) {
137                 msginfo = (MsgInfo *)cur->data;
138                 procmsg_msginfo_free(msginfo);
139         }
140         g_slist_free(mlist);
141 }
142
143 struct MarkSum {
144         gint *new_msgs;
145         gint *unread_msgs;
146         gint *total_msgs;
147         gint *min;
148         gint *max;
149         gint first;
150 };
151
152 /* CLAWS subject threading:
153   
154   in the first round it inserts subject lines in a 
155   relation (subject <-> node)
156
157   the second round finishes the threads by attaching
158   matching subject lines to the one found in the
159   relation. will use the oldest node with the same
160   subject that is not more then thread_by_subject_max_age
161   days old (see subject_relation_lookup)
162 */  
163
164 static void subject_relation_insert(GRelation *relation, GNode *node)
165 {
166         gchar *subject;
167         MsgInfo *msginfo;
168
169         g_return_if_fail(relation != NULL);
170         g_return_if_fail(node != NULL);
171         msginfo = (MsgInfo *) node->data;
172         g_return_if_fail(msginfo != NULL);
173
174         subject = msginfo->subject;
175         if (subject == NULL)
176                 return;
177         subject += subject_get_prefix_length(subject);
178
179         g_relation_insert(relation, subject, node);
180 }
181
182 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
183 {
184         gchar *subject;
185         GTuples *tuples;
186         GNode *node = NULL;
187         gint prefix_length;
188     
189         g_return_val_if_fail(relation != NULL, NULL);
190
191         subject = msginfo->subject;
192         if (subject == NULL)
193                 return NULL;
194         prefix_length = subject_get_prefix_length(subject);
195         if (prefix_length <= 0)
196                 return NULL;
197         subject += prefix_length;
198         
199         tuples = g_relation_select(relation, subject, 0);
200         if (tuples == NULL)
201                 return NULL;
202
203         if (tuples->len > 0) {
204                 int i;
205                 GNode *relation_node;
206                 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
207                 gboolean match;
208
209                 /* check all nodes with the same subject to find the best parent */
210                 for (i = 0; i < tuples->len; i++) {
211                         relation_node = (GNode *) g_tuples_index(tuples, i, 1);
212                         relation_msginfo = (MsgInfo *) relation_node->data;
213                         match = FALSE;
214
215                         /* best node should be the oldest in the found nodes */
216                         /* parent node must not be older then msginfo */
217                         if ((relation_msginfo->date_t < msginfo->date_t) &&
218                             ((best_msginfo == NULL) ||
219                              (best_msginfo->date_t > relation_msginfo->date_t)))
220                                 match = TRUE;
221
222                         /* parent node must not be more then thread_by_subject_max_age
223                            days older then msginfo */
224                         if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
225                             prefs_common.thread_by_subject_max_age * 3600 * 24)
226                                 match = FALSE;
227
228                         /* can add new tests for all matching
229                            nodes found by subject */
230
231                         if (match) {
232                                 node = relation_node;
233                                 best_msginfo = relation_msginfo;
234                         }
235                 }           
236         }
237
238         g_tuples_destroy(tuples);
239         return node;
240 }
241
242 /* return the reversed thread tree */
243 GNode *procmsg_get_thread_tree(GSList *mlist)
244 {
245         GNode *root, *parent, *node, *next;
246         GHashTable *msgid_table;
247         GRelation *subject_relation = NULL;
248         MsgInfo *msginfo;
249         const gchar *msgid;
250         GSList *reflist;
251         START_TIMING("procmsg_get_thread_tree");
252         root = g_node_new(NULL);
253         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
254         
255         if (prefs_common.thread_by_subject) {
256                 subject_relation = g_relation_new(2);
257                 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
258         }
259
260         for (; mlist != NULL; mlist = mlist->next) {
261                 msginfo = (MsgInfo *)mlist->data;
262                 parent = root;
263
264                 if (msginfo->inreplyto) {
265                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
266                         if (parent == NULL) {
267                                 parent = root;
268                         }
269                 }
270                 node = g_node_insert_data_before
271                         (parent, parent == root ? parent->children : NULL,
272                          msginfo);
273                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
274                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
275
276                 /* CLAWS: add subject to relation (without prefix) */
277                 if (prefs_common.thread_by_subject) {
278                         subject_relation_insert(subject_relation, node);
279                 }
280         }
281
282         /* complete the unfinished threads */
283         for (node = root->children; node != NULL; ) {
284                 next = node->next;
285                 msginfo = (MsgInfo *)node->data;
286                 parent = NULL;
287                 
288                 if (msginfo->inreplyto)
289                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
290
291                 /* try looking for the indirect parent */
292                 if (!parent && msginfo->references) {
293                         for (reflist = msginfo->references;
294                              reflist != NULL; reflist = reflist->next)
295                                 if ((parent = g_hash_table_lookup
296                                         (msgid_table, reflist->data)) != NULL)
297                                         break;
298                 }                                        
299               
300                 /* node should not be the parent, and node should not
301                    be an ancestor of parent (circular reference) */
302                 if (parent && parent != node &&
303                     !g_node_is_ancestor(node, parent)) {
304                         g_node_unlink(node);
305                         g_node_insert_before
306                                 (parent, parent->children, node);
307                 }
308                
309                 node = next;
310         }
311
312         if (prefs_common.thread_by_subject) {
313                 START_TIMING("procmsg_get_thread_tree(1)");
314                 for (node = root->children; node && node != NULL;) {
315                         next = node->next;
316                         msginfo = (MsgInfo *) node->data;
317                         
318                         parent = subject_relation_lookup(subject_relation, msginfo);
319                         
320                         /* the node may already be threaded by IN-REPLY-TO, so go up 
321                          * in the tree to 
322                            find the parent node */
323                         if (parent != NULL) {
324                                 if (g_node_is_ancestor(node, parent))
325                                         parent = NULL;
326                                 if (parent == node)
327                                         parent = NULL;
328                         }
329                         
330                         if (parent) {
331                                 g_node_unlink(node);
332                                 g_node_append(parent, node);
333                         }
334
335                         node = next;
336                 }       
337                 END_TIMING();
338         }
339         
340         if (prefs_common.thread_by_subject)
341                 g_relation_destroy(subject_relation);
342
343         g_hash_table_destroy(msgid_table);
344         END_TIMING();
345         return root;
346 }
347
348 gint procmsg_move_messages(GSList *mlist)
349 {
350         GSList *cur, *movelist = NULL;
351         MsgInfo *msginfo;
352         FolderItem *dest = NULL;
353         gint retval = 0;
354         gboolean finished = TRUE;
355         if (!mlist) return 0;
356
357         folder_item_update_freeze();
358
359 next_folder:
360         for (cur = mlist; cur != NULL; cur = cur->next) {
361                 msginfo = (MsgInfo *)cur->data;
362                 if (!msginfo->to_folder) {
363                         continue;
364                 } else {
365                         finished = FALSE;
366                 }
367                 if (!dest) {
368                         dest = msginfo->to_folder;
369                         movelist = g_slist_prepend(movelist, msginfo);
370                 } else if (dest == msginfo->to_folder) {
371                         movelist = g_slist_prepend(movelist, msginfo);
372                 } else {
373                         continue;
374                 }
375                 procmsg_msginfo_set_to_folder(msginfo, NULL);
376         }
377         if (movelist) {
378                 movelist = g_slist_reverse(movelist);
379                 retval |= folder_item_move_msgs(dest, movelist);
380                 g_slist_free(movelist);
381                 movelist = NULL;
382         }
383         if (finished == FALSE) {
384                 finished = TRUE;
385                 dest = NULL;
386                 goto next_folder;
387         }
388
389         folder_item_update_thaw();
390         return retval;
391 }
392
393 void procmsg_copy_messages(GSList *mlist)
394 {
395         GSList *cur, *copylist = NULL;
396         MsgInfo *msginfo;
397         FolderItem *dest = NULL;
398         gboolean finished = TRUE;
399         if (!mlist) return;
400
401         folder_item_update_freeze();
402
403 next_folder:
404         for (cur = mlist; cur != NULL; cur = cur->next) {
405                 msginfo = (MsgInfo *)cur->data;
406                 if (!msginfo->to_folder) {
407                         continue;
408                 } else {
409                         finished = FALSE;
410                 }
411                 if (!dest) {
412                         dest = msginfo->to_folder;
413                         copylist = g_slist_prepend(copylist, msginfo);
414                 } else if (dest == msginfo->to_folder) {
415                         copylist = g_slist_prepend(copylist, msginfo);
416                 } else {
417                         continue;
418                 }
419                 procmsg_msginfo_set_to_folder(msginfo, NULL);
420         }
421         if (copylist) {
422                 copylist = g_slist_reverse(copylist);
423                 folder_item_copy_msgs(dest, copylist);
424                 g_slist_free(copylist);
425                 copylist = NULL;
426         }
427         if (finished == FALSE) {
428                 finished = TRUE;
429                 dest = NULL;
430                 goto next_folder;
431         }
432
433         folder_item_update_thaw();
434 }
435
436 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
437 {
438         gchar *file;
439
440         g_return_val_if_fail(msginfo != NULL, NULL);
441
442         if (msginfo->plaintext_file)
443                 file = g_strdup(msginfo->plaintext_file);
444         else {
445                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
446         }
447
448         return file;
449 }
450
451 gchar *procmsg_get_message_file(MsgInfo *msginfo)
452 {
453         gchar *filename = NULL;
454
455         g_return_val_if_fail(msginfo != NULL, NULL);
456
457         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
458         if (!filename)
459                 debug_print("can't fetch message %d\n", msginfo->msgnum);
460
461         return filename;
462 }
463
464 gchar *procmsg_get_message_file_full(MsgInfo *msginfo, gboolean headers, gboolean body)
465 {
466         gchar *filename = NULL;
467
468         g_return_val_if_fail(msginfo != NULL, NULL);
469
470         filename = folder_item_fetch_msg_full(msginfo->folder, msginfo->msgnum,
471                                                 headers, body);
472         if (!filename)
473                 debug_print("can't fetch message %d\n", msginfo->msgnum);
474
475         return filename;
476 }
477
478 GSList *procmsg_get_message_file_list(GSList *mlist)
479 {
480         GSList *file_list = NULL;
481         MsgInfo *msginfo;
482         MsgFileInfo *fileinfo;
483         gchar *file;
484
485         while (mlist != NULL) {
486                 msginfo = (MsgInfo *)mlist->data;
487                 file = procmsg_get_message_file(msginfo);
488                 if (!file) {
489                         procmsg_message_file_list_free(file_list);
490                         return NULL;
491                 }
492                 fileinfo = g_new(MsgFileInfo, 1);
493                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
494                 fileinfo->file = file;
495                 fileinfo->flags = g_new(MsgFlags, 1);
496                 *fileinfo->flags = msginfo->flags;
497                 file_list = g_slist_prepend(file_list, fileinfo);
498                 mlist = mlist->next;
499         }
500
501         file_list = g_slist_reverse(file_list);
502
503         return file_list;
504 }
505
506 void procmsg_message_file_list_free(MsgInfoList *file_list)
507 {
508         GSList *cur;
509         MsgFileInfo *fileinfo;
510
511         for (cur = file_list; cur != NULL; cur = cur->next) {
512                 fileinfo = (MsgFileInfo *)cur->data;
513                 procmsg_msginfo_free(fileinfo->msginfo);
514                 g_free(fileinfo->file);
515                 g_free(fileinfo->flags);
516                 g_free(fileinfo);
517         }
518
519         g_slist_free(file_list);
520 }
521
522 FILE *procmsg_open_message(MsgInfo *msginfo)
523 {
524         FILE *fp;
525         gchar *file;
526
527         g_return_val_if_fail(msginfo != NULL, NULL);
528
529         file = procmsg_get_message_file_path(msginfo);
530         g_return_val_if_fail(file != NULL, NULL);
531
532         if (!is_file_exist(file)) {
533                 g_free(file);
534                 file = procmsg_get_message_file(msginfo);
535                 if (!file)
536                         return NULL;
537         }
538
539         if ((fp = g_fopen(file, "rb")) == NULL) {
540                 FILE_OP_ERROR(file, "fopen");
541                 g_free(file);
542                 return NULL;
543         }
544
545         g_free(file);
546
547         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
548                 gchar buf[BUFFSIZE];
549
550                 while (fgets(buf, sizeof(buf), fp) != NULL) {
551                         /* new way */
552                         if (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
553                                 strlen("X-Sylpheed-End-Special-Headers:")))
554                                 break;
555                         /* old way */
556                         if (buf[0] == '\r' || buf[0] == '\n') break;
557                         /* from other mailers */
558                         if (!strncmp(buf, "Date: ", 6)
559                         ||  !strncmp(buf, "To: ", 4)
560                         ||  !strncmp(buf, "From: ", 6)
561                         ||  !strncmp(buf, "Subject: ", 9)) {
562                                 rewind(fp);
563                                 break;
564                         }
565                 }
566         }
567
568         return fp;
569 }
570
571 gboolean procmsg_msg_exist(MsgInfo *msginfo)
572 {
573         gchar *path;
574         gboolean ret;
575
576         if (!msginfo) return FALSE;
577
578         path = folder_item_get_path(msginfo->folder);
579         change_dir(path);
580         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
581         g_free(path);
582
583         return ret;
584 }
585
586 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
587                                 PrefsFilterType type)
588 {
589         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
590                                        {"X-ML-Name:",      NULL, TRUE},
591                                        {"X-List:",         NULL, TRUE},
592                                        {"X-Mailing-list:", NULL, TRUE},
593                                        {"List-Id:",        NULL, TRUE},
594                                        {"X-Sequence:",     NULL, TRUE},
595                                        {"Sender:",         NULL, TRUE},
596                                        {"List-Post:",      NULL, TRUE},
597                                        {NULL,              NULL, FALSE}};
598         enum
599         {
600                 H_X_BEENTHERE    = 0,
601                 H_X_ML_NAME      = 1,
602                 H_X_LIST         = 2,
603                 H_X_MAILING_LIST = 3,
604                 H_LIST_ID        = 4,
605                 H_X_SEQUENCE     = 5,
606                 H_SENDER         = 6,
607                 H_LIST_POST      = 7
608         };
609
610         FILE *fp;
611
612         g_return_if_fail(msginfo != NULL);
613         g_return_if_fail(header != NULL);
614         g_return_if_fail(key != NULL);
615
616         *header = NULL;
617         *key = NULL;
618
619         switch (type) {
620         case FILTER_BY_NONE:
621                 return;
622         case FILTER_BY_AUTO:
623                 if ((fp = procmsg_open_message(msginfo)) == NULL)
624                         return;
625                 procheader_get_header_fields(fp, hentry);
626                 fclose(fp);
627
628 #define SET_FILTER_KEY(hstr, idx)       \
629 {                                       \
630         *header = g_strdup(hstr);       \
631         *key = hentry[idx].body;        \
632         hentry[idx].body = NULL;        \
633 }
634
635                 if (hentry[H_X_BEENTHERE].body != NULL) {
636                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
637                 } else if (hentry[H_X_ML_NAME].body != NULL) {
638                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
639                 } else if (hentry[H_X_LIST].body != NULL) {
640                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
641                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
642                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
643                 } else if (hentry[H_LIST_ID].body != NULL) {
644                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
645                         extract_list_id_str(*key);
646                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
647                         gchar *p;
648
649                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
650                         p = *key;
651                         while (*p != '\0') {
652                                 while (*p != '\0' && !g_ascii_isspace(*p)) p++;
653                                 while (g_ascii_isspace(*p)) p++;
654                                 if (g_ascii_isdigit(*p)) {
655                                         *p = '\0';
656                                         break;
657                                 }
658                         }
659                         g_strstrip(*key);
660                 } else if (hentry[H_SENDER].body != NULL) {
661                         SET_FILTER_KEY("header \"Sender\"", H_SENDER);
662                 } else if (hentry[H_LIST_POST].body != NULL) {
663                         SET_FILTER_KEY("header \"List-Post\"", H_LIST_POST);
664                 } else if (msginfo->to) {
665                         *header = g_strdup("to");
666                         *key = g_strdup(msginfo->to);
667                 } else if (msginfo->subject) {
668                         *header = g_strdup("subject");
669                         *key = g_strdup(msginfo->subject);
670                 }
671
672 #undef SET_FILTER_KEY
673
674                 g_free(hentry[H_X_BEENTHERE].body);
675                 hentry[H_X_BEENTHERE].body = NULL;
676                 g_free(hentry[H_X_ML_NAME].body);
677                 hentry[H_X_ML_NAME].body = NULL;
678                 g_free(hentry[H_X_LIST].body);
679                 hentry[H_X_LIST].body = NULL;
680                 g_free(hentry[H_X_MAILING_LIST].body);
681                 hentry[H_X_MAILING_LIST].body = NULL;
682                 g_free(hentry[H_LIST_ID].body);
683                 hentry[H_LIST_ID].body = NULL;
684                 g_free(hentry[H_SENDER].body);
685                 hentry[H_SENDER].body = NULL;
686                 g_free(hentry[H_LIST_POST].body);
687                 hentry[H_LIST_POST].body = NULL;
688
689                 break;
690         case FILTER_BY_FROM:
691                 *header = g_strdup("from");
692                 *key = g_strdup(msginfo->from);
693                 break;
694         case FILTER_BY_TO:
695                 *header = g_strdup("to");
696                 *key = g_strdup(msginfo->to);
697                 break;
698         case FILTER_BY_SUBJECT:
699                 *header = g_strdup("subject");
700                 *key = g_strdup(msginfo->subject);
701                 break;
702         default:
703                 break;
704         }
705 }
706
707 void procmsg_empty_trash(FolderItem *trash)
708 {
709         GNode *node, *next;
710
711         if (!trash || 
712             (trash->stype != F_TRASH && 
713              !folder_has_parent_of_type(trash, F_TRASH)))
714                 return;
715
716         if (trash && trash->total_msgs > 0) {
717                 GSList *mlist = folder_item_get_msg_list(trash);
718                 GSList *cur;
719                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
720                         MsgInfo * msginfo = (MsgInfo *) cur->data;
721                         if (MSG_IS_LOCKED(msginfo->flags))
722                                 continue;
723                         if (msginfo->total_size != 0 && 
724                             msginfo->size != (off_t)msginfo->total_size)
725                                 partial_mark_for_delete(msginfo);
726
727                         procmsg_msginfo_free(msginfo);
728                 }
729                 g_slist_free(mlist);
730                 folder_item_remove_all_msg(trash);
731         }
732
733         if (!trash->node || !trash->node->children)
734                 return;
735
736         node = trash->node->children;
737         while (node != NULL) {
738                 next = node->next;
739                 procmsg_empty_trash(FOLDER_ITEM(node->data));
740                 node = next;
741         }
742 }
743
744 void procmsg_empty_all_trash(void)
745 {
746         FolderItem *trash;
747         GList *cur;
748
749         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
750                 Folder *folder = FOLDER(cur->data);
751                 trash = folder->trash;
752                 procmsg_empty_trash(trash);
753                 if (folder->account && folder->account->set_trash_folder && 
754                     folder_find_item_from_identifier(folder->account->trash_folder))
755                         procmsg_empty_trash(
756                                 folder_find_item_from_identifier(folder->account->trash_folder));
757         }
758 }
759
760 static PrefsAccount *procmsg_get_account_from_file(const gchar *file)
761 {
762         PrefsAccount *mailac = NULL;
763         FILE *fp;
764         int hnum;
765         gchar buf[BUFFSIZE];
766         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
767                                        {"SSV:",  NULL, FALSE},
768                                        {"R:",    NULL, FALSE},
769                                        {"NG:",   NULL, FALSE},
770                                        {"MAID:", NULL, FALSE},
771                                        {"NAID:", NULL, FALSE},
772                                        {"SCF:",  NULL, FALSE},
773                                        {"RMID:", NULL, FALSE},
774                                        {"FMID:", NULL, FALSE},
775                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
776                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
777                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
778                                        {NULL,    NULL, FALSE}};
779         
780         g_return_val_if_fail(file != NULL, NULL);
781
782         if ((fp = g_fopen(file, "rb")) == NULL) {
783                 FILE_OP_ERROR(file, "fopen");
784                 return NULL;
785         }
786
787         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
788                != -1) {
789                 gchar *p = buf + strlen(qentry[hnum].name);
790
791                 if (hnum == Q_MAIL_ACCOUNT_ID) {
792                         mailac = account_find_from_id(atoi(p));
793                         break;
794                 }
795         }
796         fclose(fp);
797         return mailac;
798 }
799
800 static GSList *procmsg_list_sort_by_account(FolderItem *queue, GSList *list)
801 {
802         GSList *result = NULL;
803         GSList *orig = NULL;
804         PrefsAccount *last_account = NULL;
805         MsgInfo *msg = NULL;
806         GSList *cur = NULL;
807         gboolean nothing_to_sort = TRUE;
808
809         if (!list)
810                 return NULL;
811
812         orig = g_slist_copy(list);
813         
814         msg = (MsgInfo *)orig->data;
815         
816         for (cur = orig; cur; cur = cur->next)
817                 debug_print("sort before %s\n", ((MsgInfo *)cur->data)->from);
818         
819         debug_print("\n");
820
821 parse_again:    
822         nothing_to_sort = TRUE;
823         cur = orig;
824         while (cur) {
825                 gchar *file = NULL;
826                 PrefsAccount *ac = NULL;
827                 msg = (MsgInfo *)cur->data;
828                 file = folder_item_fetch_msg(queue, msg->msgnum);
829                 ac = procmsg_get_account_from_file(file);
830                 g_free(file);
831
832                 if (last_account == NULL || (ac != NULL && ac == last_account)) {
833                         result = g_slist_append(result, msg);
834                         orig = g_slist_remove(orig, msg);
835                         last_account = ac;
836                         nothing_to_sort = FALSE;
837                         goto parse_again;
838                 }
839                 cur = cur->next;
840         }
841         
842         if (orig || g_slist_length(orig)) {
843                 if (!last_account && nothing_to_sort) {
844                         /* can't find an account for the rest of the list */
845                         cur = orig;
846                         while (cur) {
847                                 result = g_slist_append(result, cur->data);
848                                 cur = cur->next;
849                         }
850                 } else {
851                         last_account = NULL;
852                         goto parse_again;
853                 }
854         }
855         
856         g_slist_free(orig);
857         
858         for (cur = result; cur; cur = cur->next)
859                 debug_print("sort after  %s\n", ((MsgInfo *)cur->data)->from);
860
861         debug_print("\n");
862
863         return result;
864 }
865
866 static gboolean procmsg_is_last_for_account(FolderItem *queue, MsgInfo *msginfo, GSList *elem)
867 {
868         gchar *file = folder_item_fetch_msg(queue, msginfo->msgnum);
869         PrefsAccount *ac = procmsg_get_account_from_file(file);
870         GSList *cur = elem;
871         g_free(file);
872         for (cur = elem; cur; cur = cur->next) {
873                 MsgInfo *cur_msginfo = (MsgInfo *)cur->data;
874                 file = folder_item_fetch_msg(queue, cur_msginfo->msgnum);
875                 
876                 if (cur_msginfo != msginfo && !MSG_IS_LOCKED(cur_msginfo->flags)) {
877                         if (procmsg_get_account_from_file(file) == ac) {
878                                 g_free(file);
879                                 return FALSE;
880                         }
881                 }
882                 
883                 g_free(file);
884         }
885         return TRUE;
886 }
887
888 static gboolean send_queue_lock = FALSE;
889 /*!
890  *\brief        Send messages in queue
891  *
892  *\param        queue Queue folder to process
893  *\param        save_msgs Unused
894  *
895  *\return       Number of messages sent, negative if an error occurred
896  *              positive if no error occurred
897  */
898 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs, gchar **errstr)
899 {
900         gint sent = 0, err = 0;
901         GSList *list, *elem;
902         GSList *sorted_list = NULL;
903         GNode *node, *next;
904         
905         if (send_queue_lock) {
906                 log_error(_("Already trying to send\n"));
907                 if (errstr) {
908                         if (*errstr) g_free(*errstr);
909                         *errstr = g_strdup_printf(_("Already trying to send."));
910                 }
911                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
912                 return -1;
913         }
914         send_queue_lock = TRUE;
915         inc_lock();
916         if (!queue)
917                 queue = folder_get_default_queue();
918         
919         if (queue == NULL) {
920                 send_queue_lock = FALSE;
921                 inc_unlock();
922                 return -1;
923         }
924
925         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
926
927         folder_item_scan(queue);
928         list = folder_item_get_msg_list(queue);
929
930         /* sort the list per sender account; this helps reusing the same SMTP server */
931         sorted_list = procmsg_list_sort_by_account(queue, list);
932         
933         for (elem = sorted_list; elem != NULL; elem = elem->next) {
934                 gchar *file;
935                 MsgInfo *msginfo;
936                         
937                 msginfo = (MsgInfo *)(elem->data);
938                 if (!MSG_IS_LOCKED(msginfo->flags)) {
939                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
940                         if (file) {
941                                 if (procmsg_send_message_queue_full(file, 
942                                                 !procmsg_is_last_for_account(queue, msginfo, elem),
943                                                 errstr) < 0) {
944                                         g_warning("Sending queued message %d failed.\n", 
945                                                   msginfo->msgnum);
946                                         err++;
947                                 } else {
948                                         sent++; 
949                                         folder_item_remove_msg(queue, msginfo->msgnum);
950                                 }
951                                 g_free(file);
952                         }
953                 }
954                 /* FIXME: supposedly if only one message is locked, and queue
955                  * is being flushed, the following free says something like 
956                  * "freeing msg ## in folder (nil)". */
957                 procmsg_msginfo_free(msginfo);
958         }
959
960         g_slist_free(sorted_list);
961         folder_item_scan(queue);
962
963         if (queue->node && queue->node->children) {
964                 node = queue->node->children;
965                 while (node != NULL) {
966                         int res = 0;
967                         next = node->next;
968                         res = procmsg_send_queue(FOLDER_ITEM(node->data), save_msgs, errstr);
969                         if (res < 0) 
970                                 err = -res;
971                         else
972                                 sent += res;
973                         node = next;
974                 }
975         }
976         send_queue_lock = FALSE;
977         inc_unlock();
978         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
979
980         return (err != 0 ? -err : sent);
981 }
982
983 gboolean procmsg_is_sending(void)
984 {
985         return send_queue_lock;
986 }
987
988 /*!
989  *\brief        Determine if a queue folder is empty
990  *
991  *\param        queue Queue folder to process
992  *
993  *\return       TRUE if the queue folder is empty, otherwise return FALSE
994  */
995 gboolean procmsg_queue_is_empty(FolderItem *queue)
996 {
997         GSList *list;
998         gboolean res = FALSE;
999         if (!queue)
1000                 queue = folder_get_default_queue();
1001         g_return_val_if_fail(queue != NULL, TRUE);
1002
1003         folder_item_scan(queue);
1004         list = folder_item_get_msg_list(queue);
1005         res = (list == NULL);
1006         procmsg_msg_list_free(list);
1007
1008         if (res == TRUE) {
1009                 GNode *node, *next;
1010                 if (queue->node && queue->node->children) {
1011                         node = queue->node->children;
1012                         while (node != NULL) {
1013                                 next = node->next;
1014                                 if (!procmsg_queue_is_empty(FOLDER_ITEM(node->data)))
1015                                         return FALSE;
1016                                 node = next;
1017                         }
1018                 }
1019         }
1020         return res;
1021 }
1022
1023 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
1024 {
1025         FILE *fp, *outfp;
1026         gchar buf[BUFFSIZE];
1027         
1028         if ((fp = g_fopen(in, "rb")) == NULL) {
1029                 FILE_OP_ERROR(in, "fopen");
1030                 return -1;
1031         }
1032         if ((outfp = g_fopen(out, "wb")) == NULL) {
1033                 FILE_OP_ERROR(out, "fopen");
1034                 fclose(fp);
1035                 return -1;
1036         }
1037         while (fgets(buf, sizeof(buf), fp) != NULL) {
1038                 /* new way */
1039                 if (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
1040                         strlen("X-Sylpheed-End-Special-Headers:")))
1041                         break;
1042                 /* old way */
1043                 if (buf[0] == '\r' || buf[0] == '\n') break;
1044                 /* from other mailers */
1045                 if (!strncmp(buf, "Date: ", 6)
1046                 ||  !strncmp(buf, "To: ", 4)
1047                 ||  !strncmp(buf, "From: ", 6)
1048                 ||  !strncmp(buf, "Subject: ", 9)) {
1049                         rewind(fp);
1050                         break;
1051                 }
1052         }
1053         while (fgets(buf, sizeof(buf), fp) != NULL)
1054                 fputs(buf, outfp);
1055         fclose(outfp);
1056         fclose(fp);
1057         return 0;
1058 }
1059
1060 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
1061                             gboolean is_queued)
1062 {
1063         gint num;
1064         MsgInfo *msginfo, *tmp_msginfo;
1065         MsgFlags flag = {0, 0};
1066
1067         debug_print("saving sent message...\n");
1068
1069         if (!outbox)
1070                 outbox = folder_get_default_outbox();
1071         g_return_val_if_fail(outbox != NULL, -1);
1072
1073         /* remove queueing headers */
1074         if (is_queued) {
1075                 gchar tmp[MAXPATHLEN + 1];
1076
1077                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
1078                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
1079                 
1080                 if (procmsg_remove_special_headers(file, tmp) !=0)
1081                         return -1;
1082
1083                 folder_item_scan(outbox);
1084                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
1085                         g_warning("can't save message\n");
1086                         g_unlink(tmp);
1087                         return -1;
1088                 }
1089         } else {
1090                 folder_item_scan(outbox);
1091                 if ((num = folder_item_add_msg
1092                         (outbox, file, &flag, FALSE)) < 0) {
1093                         g_warning("can't save message\n");
1094                         return -1;
1095                 }
1096         }
1097         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
1098         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
1099         if (msginfo != NULL) {
1100                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1101                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
1102                 /* tmp_msginfo == msginfo */
1103                 if (tmp_msginfo && msginfo->extradata && 
1104                     (msginfo->extradata->dispositionnotificationto || 
1105                      msginfo->extradata->returnreceiptto)) {
1106                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
1107                 }       
1108                 procmsg_msginfo_free(tmp_msginfo);              /* refcnt-- */
1109         }
1110
1111         return 0;
1112 }
1113
1114 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
1115 {
1116         static const gchar *def_cmd = "lpr %s";
1117         static guint id = 0;
1118         gchar *prtmp;
1119         FILE *tmpfp, *prfp;
1120         gchar buf[1024];
1121         gchar *p;
1122
1123         g_return_if_fail(msginfo);
1124
1125         if (procmime_msginfo_is_encrypted(msginfo))
1126                 tmpfp = procmime_get_first_encrypted_text_content(msginfo);
1127         else
1128                 tmpfp = procmime_get_first_text_content(msginfo);
1129         if (tmpfp == NULL) {
1130                 g_warning("Can't get text part\n");
1131                 return;
1132         }
1133
1134         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
1135                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
1136
1137         if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
1138                 FILE_OP_ERROR(prtmp, "fopen");
1139                 g_free(prtmp);
1140                 fclose(tmpfp);
1141                 return;
1142         }
1143
1144         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
1145         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
1146         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
1147         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
1148         if (msginfo->newsgroups)
1149                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
1150         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
1151         fputc('\n', prfp);
1152
1153         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1154                 fputs(buf, prfp);
1155
1156         fclose(prfp);
1157         fclose(tmpfp);
1158
1159         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
1160             !strchr(p + 2, '%'))
1161                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
1162         else {
1163                 if (cmdline)
1164                         g_warning("Print command line is invalid: '%s'\n",
1165                                   cmdline);
1166                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
1167         }
1168
1169         g_free(prtmp);
1170
1171         g_strchomp(buf);
1172         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
1173         system(buf);
1174 }
1175
1176 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1177 {
1178         msginfo->refcnt++;
1179         
1180         return msginfo;
1181 }
1182
1183 MsgInfo *procmsg_msginfo_new(void)
1184 {
1185         MsgInfo *newmsginfo;
1186
1187         newmsginfo = g_new0(MsgInfo, 1);
1188         newmsginfo->refcnt = 1;
1189         
1190         return newmsginfo;
1191 }
1192
1193 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1194 {
1195         MsgInfo *newmsginfo;
1196         GSList *refs;
1197
1198         if (msginfo == NULL) return NULL;
1199
1200         newmsginfo = g_new0(MsgInfo, 1);
1201
1202         newmsginfo->refcnt = 1;
1203
1204 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1205 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1206                         g_strdup(msginfo->mmb) : NULL
1207
1208         MEMBCOPY(msgnum);
1209         MEMBCOPY(size);
1210         MEMBCOPY(mtime);
1211         MEMBCOPY(date_t);
1212
1213         MEMBCOPY(flags);
1214
1215         MEMBDUP(fromname);
1216
1217         MEMBDUP(date);
1218         MEMBDUP(from);
1219         MEMBDUP(to);
1220         MEMBDUP(cc);
1221         MEMBDUP(newsgroups);
1222         MEMBDUP(subject);
1223         MEMBDUP(msgid);
1224         MEMBDUP(inreplyto);
1225         MEMBDUP(xref);
1226
1227         MEMBCOPY(folder);
1228         MEMBCOPY(to_folder);
1229
1230         if (msginfo->extradata) {
1231                 newmsginfo->extradata = g_new0(MsgInfoExtraData, 1);
1232                 MEMBDUP(extradata->face);
1233                 MEMBDUP(extradata->xface);
1234                 MEMBDUP(extradata->dispositionnotificationto);
1235                 MEMBDUP(extradata->returnreceiptto);
1236                 MEMBDUP(extradata->partial_recv);
1237                 MEMBDUP(extradata->account_server);
1238                 MEMBDUP(extradata->account_login);
1239                 MEMBDUP(extradata->list_post);
1240                 MEMBDUP(extradata->list_subscribe);
1241                 MEMBDUP(extradata->list_unsubscribe);
1242                 MEMBDUP(extradata->list_help);
1243                 MEMBDUP(extradata->list_archive);
1244                 MEMBDUP(extradata->list_owner);
1245         }
1246
1247         refs = msginfo->references;
1248         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1249                 newmsginfo->references = g_slist_prepend
1250                         (newmsginfo->references, g_strdup(refs->data)); 
1251         }
1252         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1253
1254         MEMBCOPY(score);
1255         MEMBDUP(plaintext_file);
1256
1257         return newmsginfo;
1258 }
1259
1260 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1261 {
1262         MsgInfo *full_msginfo;
1263         gchar *file;
1264
1265         if (msginfo == NULL) return NULL;
1266
1267         file = procmsg_get_message_file_path(msginfo);
1268         if (!file || !is_file_exist(file)) {
1269                 g_free(file);
1270                 file = procmsg_get_message_file(msginfo);
1271         }
1272         if (!file || !is_file_exist(file)) {
1273                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
1274                 return NULL;
1275         }
1276
1277         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1278         g_free(file);
1279         if (!full_msginfo) return NULL;
1280
1281         msginfo->total_size = full_msginfo->total_size;
1282         msginfo->planned_download = full_msginfo->planned_download;
1283
1284         if (full_msginfo->extradata) {
1285                 if (!msginfo->extradata)
1286                         msginfo->extradata = g_new0(MsgInfoExtraData, 1);
1287                 if (!msginfo->extradata->list_post)
1288                         msginfo->extradata->list_post = g_strdup(full_msginfo->extradata->list_post);
1289                 if (!msginfo->extradata->list_subscribe)
1290                         msginfo->extradata->list_subscribe = g_strdup(full_msginfo->extradata->list_subscribe);
1291                 if (!msginfo->extradata->list_unsubscribe)
1292                         msginfo->extradata->list_unsubscribe = g_strdup(full_msginfo->extradata->list_unsubscribe);
1293                 if (!msginfo->extradata->list_help)
1294                         msginfo->extradata->list_help = g_strdup(full_msginfo->extradata->list_help);
1295                 if (!msginfo->extradata->list_archive)
1296                         msginfo->extradata->list_archive= g_strdup(full_msginfo->extradata->list_archive);
1297                 if (!msginfo->extradata->list_owner)
1298                         msginfo->extradata->list_owner = g_strdup(full_msginfo->extradata->list_owner);
1299                 if (!msginfo->extradata->xface)
1300                         msginfo->extradata->xface = g_strdup(full_msginfo->extradata->xface);
1301                 if (!msginfo->extradata->face)
1302                         msginfo->extradata->face = g_strdup(full_msginfo->extradata->face);
1303                 if (!msginfo->extradata->dispositionnotificationto)
1304                         msginfo->extradata->dispositionnotificationto = 
1305                                 g_strdup(full_msginfo->extradata->dispositionnotificationto);
1306                 if (!msginfo->extradata->returnreceiptto)
1307                         msginfo->extradata->returnreceiptto = g_strdup
1308                                 (full_msginfo->extradata->returnreceiptto);
1309                 if (!msginfo->extradata->partial_recv && full_msginfo->extradata->partial_recv)
1310                         msginfo->extradata->partial_recv = g_strdup
1311                                 (full_msginfo->extradata->partial_recv);
1312                 if (!msginfo->extradata->account_server && full_msginfo->extradata->account_server)
1313                         msginfo->extradata->account_server = g_strdup
1314                                 (full_msginfo->extradata->account_server);
1315                 if (!msginfo->extradata->account_login && full_msginfo->extradata->account_login)
1316                         msginfo->extradata->account_login = g_strdup
1317                                 (full_msginfo->extradata->account_login);
1318         }
1319         procmsg_msginfo_free(full_msginfo);
1320
1321         return procmsg_msginfo_new_ref(msginfo);
1322 }
1323
1324 void procmsg_msginfo_free(MsgInfo *msginfo)
1325 {
1326         if (msginfo == NULL) return;
1327
1328         msginfo->refcnt--;
1329         if (msginfo->refcnt > 0)
1330                 return;
1331
1332         if (msginfo->to_folder) {
1333                 msginfo->to_folder->op_count--;
1334                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1335         }
1336
1337         g_free(msginfo->fromspace);
1338
1339         g_free(msginfo->fromname);
1340
1341         g_free(msginfo->date);
1342         g_free(msginfo->from);
1343         g_free(msginfo->to);
1344         g_free(msginfo->cc);
1345         g_free(msginfo->newsgroups);
1346         g_free(msginfo->subject);
1347         g_free(msginfo->msgid);
1348         g_free(msginfo->inreplyto);
1349         g_free(msginfo->xref);
1350
1351         if (msginfo->extradata) {
1352                 g_free(msginfo->extradata->returnreceiptto);
1353                 g_free(msginfo->extradata->dispositionnotificationto);
1354                 g_free(msginfo->extradata->xface);
1355                 g_free(msginfo->extradata->face);
1356                 g_free(msginfo->extradata->list_post);
1357                 g_free(msginfo->extradata->list_subscribe);
1358                 g_free(msginfo->extradata->list_unsubscribe);
1359                 g_free(msginfo->extradata->list_help);
1360                 g_free(msginfo->extradata->list_archive);
1361                 g_free(msginfo->extradata->list_owner);
1362                 g_free(msginfo->extradata->partial_recv);
1363                 g_free(msginfo->extradata->account_server);
1364                 g_free(msginfo->extradata->account_login);
1365                 g_free(msginfo->extradata);
1366         }
1367         slist_free_strings(msginfo->references);
1368         g_slist_free(msginfo->references);
1369
1370         g_free(msginfo->plaintext_file);
1371
1372         g_free(msginfo);
1373 }
1374
1375 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1376 {
1377         guint memusage = 0;
1378         GSList *refs;
1379         
1380         memusage += sizeof(MsgInfo);
1381         if (msginfo->fromname)
1382                 memusage += strlen(msginfo->fromname);
1383         if (msginfo->date)
1384                 memusage += strlen(msginfo->date);
1385         if (msginfo->from)
1386                 memusage += strlen(msginfo->from);
1387         if (msginfo->to)
1388                 memusage += strlen(msginfo->to);
1389         if (msginfo->cc)
1390                 memusage += strlen(msginfo->cc);
1391         if (msginfo->newsgroups)
1392                 memusage += strlen(msginfo->newsgroups);
1393         if (msginfo->subject)
1394                 memusage += strlen(msginfo->subject);
1395         if (msginfo->msgid)
1396                 memusage += strlen(msginfo->msgid);
1397         if (msginfo->inreplyto)
1398                 memusage += strlen(msginfo->inreplyto);
1399         for (refs = msginfo->references; refs; refs=refs->next) {
1400                 gchar *r = (gchar *)refs->data;
1401                 memusage += r?strlen(r):0;
1402         }
1403         if (msginfo->fromspace)
1404                 memusage += strlen(msginfo->fromspace);
1405
1406         if (msginfo->extradata) {
1407                 memusage += sizeof(MsgInfoExtraData);
1408                 if (msginfo->extradata->xface)
1409                         memusage += strlen(msginfo->extradata->xface);
1410                 if (msginfo->extradata->face)
1411                         memusage += strlen(msginfo->extradata->face);
1412                 if (msginfo->extradata->dispositionnotificationto)
1413                         memusage += strlen(msginfo->extradata->dispositionnotificationto);
1414                 if (msginfo->extradata->returnreceiptto)
1415                         memusage += strlen(msginfo->extradata->returnreceiptto);
1416
1417                 if (msginfo->extradata->partial_recv)
1418                         memusage += strlen(msginfo->extradata->partial_recv);
1419                 if (msginfo->extradata->account_server)
1420                         memusage += strlen(msginfo->extradata->account_server);
1421                 if (msginfo->extradata->account_login)
1422                         memusage += strlen(msginfo->extradata->account_login);
1423
1424                 if (msginfo->extradata->list_post)
1425                         memusage += strlen(msginfo->extradata->list_post);
1426                 if (msginfo->extradata->list_subscribe)
1427                         memusage += strlen(msginfo->extradata->list_subscribe);
1428                 if (msginfo->extradata->list_unsubscribe)
1429                         memusage += strlen(msginfo->extradata->list_unsubscribe);
1430                 if (msginfo->extradata->list_help)
1431                         memusage += strlen(msginfo->extradata->list_help);
1432                 if (msginfo->extradata->list_archive)
1433                         memusage += strlen(msginfo->extradata->list_archive);
1434                 if (msginfo->extradata->list_owner)
1435                         memusage += strlen(msginfo->extradata->list_owner);
1436         }
1437         return memusage;
1438 }
1439
1440 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1441 {
1442         const MsgInfo *msginfo1 = a;
1443         const MsgInfo *msginfo2 = b;
1444
1445         if (!msginfo1)
1446                 return -1;
1447         if (!msginfo2)
1448                 return -1;
1449
1450         return msginfo1->msgnum - msginfo2->msgnum;
1451 }
1452
1453 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr)
1454 {
1455         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1456                                        {"SSV:",  NULL, FALSE},
1457                                        {"R:",    NULL, FALSE},
1458                                        {"NG:",   NULL, FALSE},
1459                                        {"MAID:", NULL, FALSE},
1460                                        {"NAID:", NULL, FALSE},
1461                                        {"SCF:",  NULL, FALSE},
1462                                        {"RMID:", NULL, FALSE},
1463                                        {"FMID:", NULL, FALSE},
1464                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1465                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1466                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1467                                        {"X-Sylpheed-End-Special-Headers:", NULL, FALSE},
1468                                        {NULL,    NULL, FALSE}};
1469         FILE *fp;
1470         gint filepos;
1471         gint mailval = 0, newsval = 0;
1472         gchar *from = NULL;
1473         gchar *smtpserver = NULL;
1474         GSList *to_list = NULL;
1475         GSList *newsgroup_list = NULL;
1476         gchar *savecopyfolder = NULL;
1477         gchar *replymessageid = NULL;
1478         gchar *fwdmessageid = NULL;
1479         gchar *privacy_system = NULL;
1480         gboolean encrypt = FALSE;
1481         gchar *encrypt_data = NULL;
1482         gchar buf[BUFFSIZE];
1483         gint hnum;
1484         PrefsAccount *mailac = NULL, *newsac = NULL;
1485         gboolean save_clear_text = TRUE;
1486         gchar *tmp_enc_file = NULL;
1487
1488         int local = 0;
1489
1490         g_return_val_if_fail(file != NULL, -1);
1491
1492         if ((fp = g_fopen(file, "rb")) == NULL) {
1493                 FILE_OP_ERROR(file, "fopen");
1494                 if (errstr) {
1495                         if (*errstr) g_free(*errstr);
1496                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1497                 }
1498                 return -1;
1499         }
1500
1501         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1502                != -1) {
1503                 gchar *p = buf + strlen(qentry[hnum].name);
1504
1505                 switch (hnum) {
1506                 case Q_SENDER:
1507                         if (from == NULL) 
1508                                 from = g_strdup(p);
1509                         break;
1510                 case Q_SMTPSERVER:
1511                         if (smtpserver == NULL) 
1512                                 smtpserver = g_strdup(p);
1513                         break;
1514                 case Q_RECIPIENTS:
1515                         to_list = address_list_append(to_list, p);
1516                         break;
1517                 case Q_NEWSGROUPS:
1518                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1519                         break;
1520                 case Q_MAIL_ACCOUNT_ID:
1521                         mailac = account_find_from_id(atoi(p));
1522                         break;
1523                 case Q_NEWS_ACCOUNT_ID:
1524                         newsac = account_find_from_id(atoi(p));
1525                         break;
1526                 case Q_SAVE_COPY_FOLDER:
1527                         if (savecopyfolder == NULL) 
1528                                 savecopyfolder = g_strdup(p);
1529                         break;
1530                 case Q_REPLY_MESSAGE_ID:
1531                         if (replymessageid == NULL) 
1532                                 replymessageid = g_strdup(p);
1533                         break;
1534                 case Q_FWD_MESSAGE_ID:
1535                         if (fwdmessageid == NULL) 
1536                                 fwdmessageid = g_strdup(p);
1537                         break;
1538                 case Q_PRIVACY_SYSTEM:
1539                         if (privacy_system == NULL) 
1540                                 privacy_system = g_strdup(p);
1541                         break;
1542                 case Q_ENCRYPT:
1543                         if (p[0] == '1') 
1544                                 encrypt = TRUE;
1545                         break;
1546                 case Q_ENCRYPT_DATA:
1547                         if (encrypt_data == NULL) 
1548                                 encrypt_data = g_strdup(p);
1549                         break;
1550                 case Q_SYLPHEED_HDRS:
1551                         /* end of special headers reached */
1552                         goto send_mail; /* can't "break;break;" */
1553                 }
1554         }
1555 send_mail:
1556         filepos = ftell(fp);
1557
1558         if (encrypt) {
1559                 MimeInfo *mimeinfo;
1560
1561                 if (mailac && mailac->save_encrypted_as_clear_text 
1562                 &&  !mailac->encrypt_to_self)
1563                         save_clear_text = TRUE;
1564                 else
1565                         save_clear_text = FALSE;
1566
1567                 fclose(fp);
1568                 fp = NULL;
1569
1570                 mimeinfo = procmime_scan_queue_file(file);
1571                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1572                 || (fp = my_tmpfile()) == NULL
1573                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1574                         if (fp)
1575                                 fclose(fp);
1576                         procmime_mimeinfo_free_all(mimeinfo);
1577                         g_free(from);
1578                         g_free(smtpserver);
1579                         slist_free_strings(to_list);
1580                         g_slist_free(to_list);
1581                         slist_free_strings(newsgroup_list);
1582                         g_slist_free(newsgroup_list);
1583                         g_free(savecopyfolder);
1584                         g_free(replymessageid);
1585                         g_free(fwdmessageid);
1586                         g_free(privacy_system);
1587                         g_free(encrypt_data);
1588                         if (errstr) {
1589                                 if (*errstr) g_free(*errstr);
1590                                 *errstr = g_strdup_printf(_("Couldn't encrypt the email: %s"),
1591                                                 privacy_get_error());
1592                         }
1593                         return -1;
1594                 }
1595                 
1596                 rewind(fp);
1597                 if (!save_clear_text) {
1598                         gchar *content = NULL;
1599                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1600                         if (tmpfp) {
1601                                 fclose(tmpfp);
1602
1603                                 content = file_read_stream_to_str(fp);
1604                                 rewind(fp);
1605
1606                                 str_write_to_file(content, tmp_enc_file);
1607                                 g_free(content);
1608                         } else {
1609                                 g_warning("couldn't get tempfile\n");
1610                         }
1611                 } 
1612                 
1613                 procmime_mimeinfo_free_all(mimeinfo);
1614                 
1615                 filepos = 0;
1616         }
1617
1618         if (to_list) {
1619                 debug_print("Sending message by mail\n");
1620                 if (!from) {
1621                         if (errstr) {
1622                                 if (*errstr) g_free(*errstr);
1623                                 *errstr = g_strdup_printf(_("Queued message header is broken."));
1624                         }
1625                         mailval = -1;
1626                 } else if (mailac && mailac->use_mail_command &&
1627                            mailac->mail_command && (* mailac->mail_command)) {
1628                         mailval = send_message_local(mailac->mail_command, fp);
1629                         local = 1;
1630                 } else {
1631                         if (!mailac) {
1632                                 mailac = account_find_from_smtp_server(from, smtpserver);
1633                                 if (!mailac) {
1634                                         g_warning("Account not found. "
1635                                                     "Using current account...\n");
1636                                         mailac = cur_account;
1637                                 }
1638                         }
1639
1640                         if (mailac) {
1641                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1642                                 if (mailval == -1 && errstr) {
1643                                         if (*errstr) g_free(*errstr);
1644                                         *errstr = g_strdup_printf(_("An error happened during SMTP session."));
1645                                 }
1646                         } else {
1647                                 PrefsAccount tmp_ac;
1648
1649                                 g_warning("Account not found.\n");
1650
1651                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1652                                 tmp_ac.address = from;
1653                                 tmp_ac.smtp_server = smtpserver;
1654                                 tmp_ac.smtpport = SMTP_PORT;
1655                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1656                                 if (mailval == -1 && errstr) {
1657                                         if (*errstr) g_free(*errstr);
1658                                         *errstr = g_strdup_printf(_("No specific account has been found to "
1659                                                         "send, and an error happened during SMTP session."));
1660                                 }
1661                         }
1662                 }
1663         } else if (!to_list && !newsgroup_list) {
1664                 if (errstr) {
1665                         if (*errstr) g_free(*errstr);
1666                         *errstr = g_strdup(_("Couldn't determine sending informations. "
1667                                 "Maybe the email hasn't been generated by Sylpheed-Claws."));
1668                 }
1669                 mailval = -1;
1670         }
1671
1672         fseek(fp, filepos, SEEK_SET);
1673         if (newsgroup_list && (mailval == 0)) {
1674                 Folder *folder;
1675                 gchar *tmp = NULL;
1676                 FILE *tmpfp;
1677
1678                 /* write to temporary file */
1679                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1680                             G_DIR_SEPARATOR, (gint)file);
1681                 if ((tmpfp = g_fopen(tmp, "wb")) == NULL) {
1682                         FILE_OP_ERROR(tmp, "fopen");
1683                         newsval = -1;
1684                         alertpanel_error(_("Could not create temporary file for news sending."));
1685                 } else {
1686                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1687                                 FILE_OP_ERROR(tmp, "chmod");
1688                                 g_warning("can't change file mode\n");
1689                         }
1690
1691                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1692                                 if (fputs(buf, tmpfp) == EOF) {
1693                                         FILE_OP_ERROR(tmp, "fputs");
1694                                         newsval = -1;
1695                                         if (errstr) {
1696                                                 if (*errstr) g_free(*errstr);
1697                                                 *errstr = g_strdup_printf(_("Error when writing temporary file for news sending."));
1698                                         }
1699                                 }
1700                         }
1701                         fclose(tmpfp);
1702
1703                         if (newsval == 0) {
1704                                 debug_print("Sending message by news\n");
1705
1706                                 folder = FOLDER(newsac->folder);
1707
1708                                 newsval = news_post(folder, tmp);
1709                                 if (newsval < 0 && errstr)  {
1710                                         if (*errstr) g_free(*errstr);
1711                                         *errstr = g_strdup_printf(_("Error occurred while posting the message to %s."),
1712                                          newsac->nntp_server);
1713                                 }
1714                         }
1715                         g_unlink(tmp);
1716                 }
1717                 g_free(tmp);
1718         }
1719
1720         fclose(fp);
1721
1722         /* save message to outbox */
1723         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1724                 FolderItem *outbox;
1725
1726                 debug_print("saving sent message...\n");
1727
1728                 outbox = folder_find_item_from_identifier(savecopyfolder);
1729                 if (!outbox)
1730                         outbox = folder_get_default_outbox();
1731                         
1732                 if (save_clear_text || tmp_enc_file == NULL) {
1733                         procmsg_save_to_outbox(outbox, file, TRUE);
1734                 } else {
1735                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1736                 }
1737         }
1738
1739         if (tmp_enc_file != NULL) {
1740                 g_unlink(tmp_enc_file);
1741                 free(tmp_enc_file);
1742                 tmp_enc_file = NULL;
1743         }
1744
1745         if (replymessageid != NULL || fwdmessageid != NULL) {
1746                 gchar **tokens;
1747                 FolderItem *item;
1748                 
1749                 if (replymessageid != NULL)
1750                         tokens = g_strsplit(replymessageid, "\t", 0);
1751                 else
1752                         tokens = g_strsplit(fwdmessageid, "\t", 0);
1753                 item = folder_find_item_from_identifier(tokens[0]);
1754
1755                 /* check if queued message has valid folder and message id */
1756                 if (item != NULL && tokens[2] != NULL) {
1757                         MsgInfo *msginfo;
1758                         
1759                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1760                 
1761                         /* check if referring message exists and has a message id */
1762                         if ((msginfo != NULL) && 
1763                             (msginfo->msgid != NULL) &&
1764                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1765                                 procmsg_msginfo_free(msginfo);
1766                                 msginfo = NULL;
1767                         }
1768                         
1769                         if (msginfo == NULL) {
1770                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1771                         }
1772                         
1773                         if (msginfo != NULL) {
1774                                 if (replymessageid != NULL) {
1775                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1776                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1777                                 }  else {
1778                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1779                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1780                                 }
1781                                 procmsg_msginfo_free(msginfo);
1782                         }
1783                 }
1784                 g_strfreev(tokens);
1785         }
1786
1787         g_free(from);
1788         g_free(smtpserver);
1789         slist_free_strings(to_list);
1790         g_slist_free(to_list);
1791         slist_free_strings(newsgroup_list);
1792         g_slist_free(newsgroup_list);
1793         g_free(savecopyfolder);
1794         g_free(replymessageid);
1795         g_free(fwdmessageid);
1796         g_free(privacy_system);
1797         g_free(encrypt_data);
1798
1799         return (newsval != 0 ? newsval : mailval);
1800 }
1801
1802 gint procmsg_send_message_queue(const gchar *file, gchar **errstr)
1803 {
1804         gint result = procmsg_send_message_queue_full(file, FALSE, errstr);
1805         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1806         return result;
1807 }
1808
1809 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1810 {
1811         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1812
1813         /* NEW flag */
1814         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1815                 item->new_msgs++;
1816         }
1817
1818         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1819                 item->new_msgs--;
1820         }
1821
1822         /* UNREAD flag */
1823         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1824                 item->unread_msgs++;
1825                 if (procmsg_msg_has_marked_parent(msginfo))
1826                         item->unreadmarked_msgs++;
1827         }
1828
1829         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1830                 item->unread_msgs--;
1831                 if (procmsg_msg_has_marked_parent(msginfo))
1832                         item->unreadmarked_msgs--;
1833         }
1834         
1835         /* MARK flag */
1836         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1837                 procmsg_update_unread_children(msginfo, TRUE);
1838                 item->marked_msgs++;
1839         }
1840
1841         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1842                 procmsg_update_unread_children(msginfo, FALSE);
1843                 item->marked_msgs--;
1844         }
1845 }
1846
1847 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1848 {
1849         FolderItem *item;
1850         MsgInfoUpdate msginfo_update;
1851         MsgPermFlags perm_flags_new, perm_flags_old;
1852         MsgTmpFlags tmp_flags_old;
1853
1854         g_return_if_fail(msginfo != NULL);
1855         item = msginfo->folder;
1856         g_return_if_fail(item != NULL);
1857         
1858         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1859
1860         /* Perm Flags handling */
1861         perm_flags_old = msginfo->flags.perm_flags;
1862         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1863         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1864                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1865         }
1866
1867         if (perm_flags_old != perm_flags_new) {
1868                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1869
1870                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1871
1872         }
1873
1874         /* Tmp flags handling */
1875         tmp_flags_old = msginfo->flags.tmp_flags;
1876         msginfo->flags.tmp_flags |= tmp_flags;
1877
1878         /* update notification */
1879         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1880                 msginfo_update.msginfo = msginfo;
1881                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1882                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1883                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1884         }
1885 }
1886
1887 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1888 {
1889         FolderItem *item;
1890         MsgInfoUpdate msginfo_update;
1891         MsgPermFlags perm_flags_new, perm_flags_old;
1892         MsgTmpFlags tmp_flags_old;
1893
1894         g_return_if_fail(msginfo != NULL);
1895         item = msginfo->folder;
1896         g_return_if_fail(item != NULL);
1897         
1898         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1899
1900         /* Perm Flags handling */
1901         perm_flags_old = msginfo->flags.perm_flags;
1902         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1903         
1904         if (perm_flags_old != perm_flags_new) {
1905                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1906
1907                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1908         }
1909
1910         /* Tmp flags hanlding */
1911         tmp_flags_old = msginfo->flags.tmp_flags;
1912         msginfo->flags.tmp_flags &= ~tmp_flags;
1913
1914         /* update notification */
1915         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1916                 msginfo_update.msginfo = msginfo;
1917                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1918                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1919                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1920         }
1921 }
1922
1923 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1924                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1925                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1926 {
1927         FolderItem *item;
1928         MsgInfoUpdate msginfo_update;
1929         MsgPermFlags perm_flags_new, perm_flags_old;
1930         MsgTmpFlags tmp_flags_old;
1931
1932         g_return_if_fail(msginfo != NULL);
1933         item = msginfo->folder;
1934         g_return_if_fail(item != NULL);
1935         
1936         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1937
1938         /* Perm Flags handling */
1939         perm_flags_old = msginfo->flags.perm_flags;
1940         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1941         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1942                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1943         }
1944
1945         if (perm_flags_old != perm_flags_new) {
1946                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1947
1948                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1949
1950         }
1951
1952         /* Tmp flags handling */
1953         tmp_flags_old = msginfo->flags.tmp_flags;
1954         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1955         msginfo->flags.tmp_flags |= add_tmp_flags;
1956
1957         /* update notification */
1958         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1959                 msginfo_update.msginfo = msginfo;
1960                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1961                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1962                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1963         }
1964 }
1965
1966 /*!
1967  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1968  *
1969  *\param        info Current message
1970  *\param        perm_flags Flags to be checked
1971  *\param        parentmsgs Hash of prior msgs to avoid loops
1972  *
1973  *\return       gboolean TRUE if perm_flags are found
1974  */
1975 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1976                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1977 {
1978         MsgInfo *tmp;
1979
1980         g_return_val_if_fail(info != NULL, FALSE);
1981
1982         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1983                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1984                                 info->inreplyto);
1985                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1986                         procmsg_msginfo_free(tmp);
1987                         return TRUE;
1988                 } else if (tmp != NULL) {
1989                         gboolean result;
1990
1991                         if (g_hash_table_lookup(parentmsgs, info)) {
1992                                 debug_print("loop detected: %d\n",
1993                                         info->msgnum);
1994                                 result = FALSE;
1995                         } else {
1996                                 g_hash_table_insert(parentmsgs, info, "1");
1997                                 result = procmsg_msg_has_flagged_parent_real(
1998                                     tmp, perm_flags, parentmsgs);
1999                         }
2000                         procmsg_msginfo_free(tmp);
2001                         return result;
2002                 } else {
2003                         return FALSE;
2004                 }
2005         } else
2006                 return FALSE;
2007 }
2008
2009 /*!
2010  *\brief        Callback for cleaning up hash of parentmsgs
2011  */
2012 gboolean parentmsgs_hash_remove(gpointer key,
2013                             gpointer value,
2014                             gpointer user_data)
2015 {
2016         return TRUE;
2017 }
2018
2019 /*!
2020  *\brief        Set up list of parentmsgs
2021  *              See procmsg_msg_has_flagged_parent_real()
2022  */
2023 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
2024 {
2025         gboolean result;
2026         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
2027
2028         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2029         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2030         g_hash_table_destroy(parentmsgs);
2031         return result;
2032 }
2033
2034 /*!
2035  *\brief        Check if msgs prior in thread are marked
2036  *              See procmsg_msg_has_flagged_parent_real()
2037  */
2038 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2039 {
2040         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2041 }
2042
2043
2044 GSList *procmsg_find_children_func(MsgInfo *info, 
2045                                    GSList *children, GSList *all)
2046 {
2047         GSList *cur;
2048
2049         g_return_val_if_fail(info!=NULL, children);
2050         if (info->msgid == NULL)
2051                 return children;
2052
2053         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2054                 MsgInfo *tmp = (MsgInfo *)cur->data;
2055                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2056                         /* Check if message is already in the list */
2057                         if ((children == NULL) || 
2058                             (g_slist_index(children, tmp) == -1)) {
2059                                 children = g_slist_prepend(children,
2060                                                 procmsg_msginfo_new_ref(tmp));
2061                                 children = procmsg_find_children_func(tmp, 
2062                                                         children, 
2063                                                         all);
2064                         }
2065                 }
2066         }
2067         return children;
2068 }
2069
2070 GSList *procmsg_find_children (MsgInfo *info)
2071 {
2072         GSList *children;
2073         GSList *all, *cur;
2074
2075         g_return_val_if_fail(info!=NULL, NULL);
2076         all = folder_item_get_msg_list(info->folder);
2077         children = procmsg_find_children_func(info, NULL, all);
2078         if (children != NULL) {
2079                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2080                         /* this will not free the used pointers
2081                            created with procmsg_msginfo_new_ref */
2082                         procmsg_msginfo_free((MsgInfo *)cur->data);
2083                 }
2084         }
2085         g_slist_free(all);
2086
2087         return children;
2088 }
2089
2090 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2091 {
2092         GSList *children = procmsg_find_children(info);
2093         GSList *cur;
2094         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2095                 MsgInfo *tmp = (MsgInfo *)cur->data;
2096                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2097                         if(newly_marked) 
2098                                 info->folder->unreadmarked_msgs++;
2099                         else
2100                                 info->folder->unreadmarked_msgs--;
2101                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2102                 }
2103                 procmsg_msginfo_free(tmp);
2104         }
2105         g_slist_free(children);
2106 }
2107
2108 /**
2109  * Set the destination folder for a copy or move operation
2110  *
2111  * \param msginfo The message which's destination folder is changed
2112  * \param to_folder The destination folder for the operation
2113  */
2114 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2115 {
2116         if(msginfo->to_folder != NULL) {
2117                 msginfo->to_folder->op_count--;
2118                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2119         }
2120         msginfo->to_folder = to_folder;
2121         if(to_folder != NULL) {
2122                 to_folder->op_count++;
2123                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2124         }
2125 }
2126
2127 /**
2128  * Apply filtering actions to the msginfo
2129  *
2130  * \param msginfo The MsgInfo describing the message that should be filtered
2131  * \return TRUE if the message was moved and MsgInfo is now invalid,
2132  *         FALSE otherwise
2133  */
2134 gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2135 {
2136         MailFilteringData mail_filtering_data;
2137                         
2138         mail_filtering_data.msginfo = msginfo;                  
2139         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data)) {
2140                 return TRUE;
2141         }
2142
2143         /* filter if enabled in prefs or move to inbox if not */
2144         if((filtering_rules != NULL) &&
2145            filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs)) {
2146                 return TRUE;
2147         }
2148                 
2149         return FALSE;
2150 }
2151
2152 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2153 {
2154         MsgInfo *tmp_msginfo = NULL;
2155         MsgFlags flags = {0, 0};
2156         gchar *tmpfile = get_tmp_file();
2157         FILE *fp = g_fopen(tmpfile, "wb");
2158         
2159         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2160             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2161                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2162                 if (fp) 
2163                         fclose(fp);
2164                 g_free(tmpfile);
2165                 return NULL;
2166         }
2167         
2168         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2169                 fclose(fp);
2170                 fp = NULL;
2171                 tmp_msginfo = procheader_parse_file(
2172                         tmpfile, flags, 
2173                         TRUE, FALSE);
2174         }
2175         if (fp)
2176                 fclose(fp);
2177
2178         if (tmp_msginfo != NULL) {
2179                 if (src_msginfo)
2180                         tmp_msginfo->folder = src_msginfo->folder;
2181                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2182         } else {
2183                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2184         }
2185
2186         g_free(tmpfile);
2187
2188         return tmp_msginfo;
2189 }
2190
2191 static GSList *spam_learners = NULL;
2192
2193 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2194 {
2195         if (!g_slist_find(spam_learners, learn_func))
2196                 spam_learners = g_slist_append(spam_learners, learn_func);
2197         if (mainwindow_get_mainwindow()) {
2198                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2199                 summary_set_menu_sensitive(
2200                         mainwindow_get_mainwindow()->summaryview);
2201                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2202         }
2203 }
2204
2205 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2206 {
2207         spam_learners = g_slist_remove(spam_learners, learn_func);
2208         if (mainwindow_get_mainwindow()) {
2209                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2210                 summary_set_menu_sensitive(
2211                         mainwindow_get_mainwindow()->summaryview);
2212                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2213         }
2214 }
2215
2216 gboolean procmsg_spam_can_learn(void)
2217 {
2218         return g_slist_length(spam_learners) > 0;
2219 }
2220
2221 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2222 {
2223         GSList *cur = spam_learners;
2224         int ret = 0;
2225         for (; cur; cur = cur->next) {
2226                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2227                 ret |= func(info, list, spam);
2228         }
2229         return ret;
2230 }
2231
2232 static gchar *spam_folder_item = NULL;
2233 void procmsg_spam_set_folder (const char *item_identifier)
2234 {
2235         g_free(spam_folder_item);
2236         if (item_identifier)
2237                 spam_folder_item = g_strdup(item_identifier);
2238         else
2239                 spam_folder_item = NULL;
2240 }
2241
2242 FolderItem *procmsg_spam_get_folder (void)
2243 {
2244         FolderItem *item = spam_folder_item ? folder_find_item_from_identifier(spam_folder_item) : NULL;
2245         return item ? item : folder_get_default_trash();
2246 }
2247
2248 static void item_has_queued_mails(FolderItem *item, gpointer data)
2249 {
2250         gboolean *result = (gboolean *)data;
2251         if (*result == TRUE)
2252                 return;
2253         if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0)
2254                 *result = TRUE;
2255 }
2256
2257 gboolean procmsg_have_queued_mails_fast (void)
2258 {
2259         gboolean result = FALSE;
2260         folder_func_to_all_folders(item_has_queued_mails, &result);
2261         return result;
2262 }