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