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