2dd93f192bad13abccb2fd4121873a67f9016be5
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 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, gboolean *queued_removed);
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                                 gboolean queued_removed = FALSE;
954                                 if (procmsg_send_message_queue_full(file, 
955                                                 !procmsg_is_last_for_account(queue, msginfo, elem),
956                                                 errstr, queue, msginfo->msgnum, &queued_removed) < 0) {
957                                         g_warning("Sending queued message %d failed.\n", 
958                                                   msginfo->msgnum);
959                                         err++;
960                                 } else {
961                                         sent++; 
962                                         if (!queued_removed)
963                                                 folder_item_remove_msg(queue, msginfo->msgnum);
964                                 }
965                                 g_free(file);
966                         }
967                 }
968                 /* FIXME: supposedly if only one message is locked, and queue
969                  * is being flushed, the following free says something like 
970                  * "freeing msg ## in folder (nil)". */
971                 procmsg_msginfo_free(msginfo);
972         }
973
974         g_slist_free(sorted_list);
975         folder_item_scan(queue);
976
977         if (queue->node && queue->node->children) {
978                 node = queue->node->children;
979                 while (node != NULL) {
980                         int res = 0;
981                         next = node->next;
982                         res = procmsg_send_queue(FOLDER_ITEM(node->data), save_msgs, errstr);
983                         if (res < 0) 
984                                 err = -res;
985                         else
986                                 sent += res;
987                         node = next;
988                 }
989         }
990         send_queue_lock = FALSE;
991         inc_unlock();
992         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
993
994         return (err != 0 ? -err : sent);
995 }
996
997 gboolean procmsg_is_sending(void)
998 {
999         return send_queue_lock;
1000 }
1001
1002 /*!
1003  *\brief        Determine if a queue folder is empty
1004  *
1005  *\param        queue Queue folder to process
1006  *
1007  *\return       TRUE if the queue folder is empty, otherwise return FALSE
1008  */
1009 gboolean procmsg_queue_is_empty(FolderItem *queue)
1010 {
1011         GSList *list;
1012         gboolean res = FALSE;
1013         if (!queue)
1014                 queue = folder_get_default_queue();
1015         g_return_val_if_fail(queue != NULL, TRUE);
1016
1017         folder_item_scan(queue);
1018         list = folder_item_get_msg_list(queue);
1019         res = (list == NULL);
1020         procmsg_msg_list_free(list);
1021
1022         if (res == TRUE) {
1023                 GNode *node, *next;
1024                 if (queue->node && queue->node->children) {
1025                         node = queue->node->children;
1026                         while (node != NULL) {
1027                                 next = node->next;
1028                                 if (!procmsg_queue_is_empty(FOLDER_ITEM(node->data)))
1029                                         return FALSE;
1030                                 node = next;
1031                         }
1032                 }
1033         }
1034         return res;
1035 }
1036
1037 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
1038 {
1039         FILE *fp, *outfp;
1040         gchar buf[BUFFSIZE];
1041         
1042         if ((fp = g_fopen(in, "rb")) == NULL) {
1043                 FILE_OP_ERROR(in, "fopen");
1044                 return -1;
1045         }
1046         if ((outfp = g_fopen(out, "wb")) == NULL) {
1047                 FILE_OP_ERROR(out, "fopen");
1048                 fclose(fp);
1049                 return -1;
1050         }
1051         while (fgets(buf, sizeof(buf), fp) != NULL) {
1052                 /* new way */
1053                 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
1054                         strlen("X-Claws-End-Special-Headers:"))) ||
1055                     (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
1056                         strlen("X-Sylpheed-End-Special-Headers:"))))
1057                         break;
1058                 /* old way */
1059                 if (buf[0] == '\r' || buf[0] == '\n') break;
1060                 /* from other mailers */
1061                 if (!strncmp(buf, "Date: ", 6)
1062                 ||  !strncmp(buf, "To: ", 4)
1063                 ||  !strncmp(buf, "From: ", 6)
1064                 ||  !strncmp(buf, "Subject: ", 9)) {
1065                         rewind(fp);
1066                         break;
1067                 }
1068         }
1069         while (fgets(buf, sizeof(buf), fp) != NULL)
1070                 fputs(buf, outfp);
1071         fclose(outfp);
1072         fclose(fp);
1073         return 0;
1074 }
1075
1076 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
1077                             gboolean is_queued)
1078 {
1079         gint num;
1080         MsgInfo *msginfo, *tmp_msginfo;
1081         MsgFlags flag = {0, 0};
1082
1083         debug_print("saving sent message...\n");
1084
1085         if (!outbox)
1086                 outbox = folder_get_default_outbox();
1087         g_return_val_if_fail(outbox != NULL, -1);
1088
1089         /* remove queueing headers */
1090         if (is_queued) {
1091                 gchar tmp[MAXPATHLEN + 1];
1092
1093                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
1094                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
1095                 
1096                 if (procmsg_remove_special_headers(file, tmp) !=0)
1097                         return -1;
1098
1099                 folder_item_scan(outbox);
1100                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
1101                         g_warning("can't save message\n");
1102                         g_unlink(tmp);
1103                         return -1;
1104                 }
1105         } else {
1106                 folder_item_scan(outbox);
1107                 if ((num = folder_item_add_msg
1108                         (outbox, file, &flag, FALSE)) < 0) {
1109                         g_warning("can't save message\n");
1110                         return -1;
1111                 }
1112         }
1113         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
1114         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
1115         if (msginfo != NULL) {
1116                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1117                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
1118                 /* tmp_msginfo == msginfo */
1119                 if (tmp_msginfo && msginfo->extradata && 
1120                     (msginfo->extradata->dispositionnotificationto || 
1121                      msginfo->extradata->returnreceiptto)) {
1122                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
1123                 }       
1124                 procmsg_msginfo_free(tmp_msginfo);              /* refcnt-- */
1125         }
1126
1127         return 0;
1128 }
1129
1130 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
1131 {
1132         static const gchar *def_cmd = "lpr %s";
1133         static guint id = 0;
1134         gchar *prtmp;
1135         FILE *tmpfp, *prfp;
1136         gchar buf[1024];
1137         gchar *p;
1138
1139         g_return_if_fail(msginfo);
1140
1141         if (procmime_msginfo_is_encrypted(msginfo))
1142                 tmpfp = procmime_get_first_encrypted_text_content(msginfo);
1143         else
1144                 tmpfp = procmime_get_first_text_content(msginfo);
1145         if (tmpfp == NULL) {
1146                 g_warning("Can't get text part\n");
1147                 return;
1148         }
1149
1150         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
1151                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
1152
1153         if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
1154                 FILE_OP_ERROR(prtmp, "fopen");
1155                 g_free(prtmp);
1156                 fclose(tmpfp);
1157                 return;
1158         }
1159
1160         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
1161         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
1162         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
1163         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
1164         if (msginfo->newsgroups)
1165                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
1166         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
1167         fputc('\n', prfp);
1168
1169         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1170                 fputs(buf, prfp);
1171
1172         fclose(prfp);
1173         fclose(tmpfp);
1174
1175         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
1176             !strchr(p + 2, '%'))
1177                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
1178         else {
1179                 if (cmdline)
1180                         g_warning("Print command line is invalid: '%s'\n",
1181                                   cmdline);
1182                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
1183         }
1184
1185         g_free(prtmp);
1186
1187         g_strchomp(buf);
1188         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
1189         system(buf);
1190 }
1191
1192 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1193 {
1194         msginfo->refcnt++;
1195         
1196         return msginfo;
1197 }
1198
1199 MsgInfo *procmsg_msginfo_new(void)
1200 {
1201         MsgInfo *newmsginfo;
1202
1203         newmsginfo = g_new0(MsgInfo, 1);
1204         newmsginfo->refcnt = 1;
1205         
1206         return newmsginfo;
1207 }
1208
1209 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1210 {
1211         MsgInfo *newmsginfo;
1212         GSList *refs;
1213
1214         if (msginfo == NULL) return NULL;
1215
1216         newmsginfo = g_new0(MsgInfo, 1);
1217
1218         newmsginfo->refcnt = 1;
1219
1220 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1221 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1222                         g_strdup(msginfo->mmb) : NULL
1223
1224         MEMBCOPY(msgnum);
1225         MEMBCOPY(size);
1226         MEMBCOPY(mtime);
1227         MEMBCOPY(date_t);
1228
1229         MEMBCOPY(flags);
1230
1231         MEMBDUP(fromname);
1232
1233         MEMBDUP(date);
1234         MEMBDUP(from);
1235         MEMBDUP(to);
1236         MEMBDUP(cc);
1237         MEMBDUP(newsgroups);
1238         MEMBDUP(subject);
1239         MEMBDUP(msgid);
1240         MEMBDUP(inreplyto);
1241         MEMBDUP(xref);
1242
1243         MEMBCOPY(folder);
1244         MEMBCOPY(to_folder);
1245
1246         if (msginfo->extradata) {
1247                 newmsginfo->extradata = g_new0(MsgInfoExtraData, 1);
1248                 MEMBDUP(extradata->face);
1249                 MEMBDUP(extradata->xface);
1250                 MEMBDUP(extradata->dispositionnotificationto);
1251                 MEMBDUP(extradata->returnreceiptto);
1252                 MEMBDUP(extradata->partial_recv);
1253                 MEMBDUP(extradata->account_server);
1254                 MEMBDUP(extradata->account_login);
1255                 MEMBDUP(extradata->list_post);
1256                 MEMBDUP(extradata->list_subscribe);
1257                 MEMBDUP(extradata->list_unsubscribe);
1258                 MEMBDUP(extradata->list_help);
1259                 MEMBDUP(extradata->list_archive);
1260                 MEMBDUP(extradata->list_owner);
1261         }
1262
1263         refs = msginfo->references;
1264         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1265                 newmsginfo->references = g_slist_prepend
1266                         (newmsginfo->references, g_strdup(refs->data)); 
1267         }
1268         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1269
1270         MEMBCOPY(score);
1271         MEMBDUP(plaintext_file);
1272
1273         return newmsginfo;
1274 }
1275
1276 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1277 {
1278         MsgInfo *full_msginfo;
1279         gchar *file;
1280
1281         if (msginfo == NULL) return NULL;
1282
1283         file = procmsg_get_message_file_path(msginfo);
1284         if (!file || !is_file_exist(file)) {
1285                 g_free(file);
1286                 file = procmsg_get_message_file(msginfo);
1287         }
1288         if (!file || !is_file_exist(file)) {
1289                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
1290                 return NULL;
1291         }
1292
1293         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1294         g_free(file);
1295         if (!full_msginfo) return NULL;
1296
1297         msginfo->total_size = full_msginfo->total_size;
1298         msginfo->planned_download = full_msginfo->planned_download;
1299
1300         if (full_msginfo->extradata) {
1301                 if (!msginfo->extradata)
1302                         msginfo->extradata = g_new0(MsgInfoExtraData, 1);
1303                 if (!msginfo->extradata->list_post)
1304                         msginfo->extradata->list_post = g_strdup(full_msginfo->extradata->list_post);
1305                 if (!msginfo->extradata->list_subscribe)
1306                         msginfo->extradata->list_subscribe = g_strdup(full_msginfo->extradata->list_subscribe);
1307                 if (!msginfo->extradata->list_unsubscribe)
1308                         msginfo->extradata->list_unsubscribe = g_strdup(full_msginfo->extradata->list_unsubscribe);
1309                 if (!msginfo->extradata->list_help)
1310                         msginfo->extradata->list_help = g_strdup(full_msginfo->extradata->list_help);
1311                 if (!msginfo->extradata->list_archive)
1312                         msginfo->extradata->list_archive= g_strdup(full_msginfo->extradata->list_archive);
1313                 if (!msginfo->extradata->list_owner)
1314                         msginfo->extradata->list_owner = g_strdup(full_msginfo->extradata->list_owner);
1315                 if (!msginfo->extradata->xface)
1316                         msginfo->extradata->xface = g_strdup(full_msginfo->extradata->xface);
1317                 if (!msginfo->extradata->face)
1318                         msginfo->extradata->face = g_strdup(full_msginfo->extradata->face);
1319                 if (!msginfo->extradata->dispositionnotificationto)
1320                         msginfo->extradata->dispositionnotificationto = 
1321                                 g_strdup(full_msginfo->extradata->dispositionnotificationto);
1322                 if (!msginfo->extradata->returnreceiptto)
1323                         msginfo->extradata->returnreceiptto = g_strdup
1324                                 (full_msginfo->extradata->returnreceiptto);
1325                 if (!msginfo->extradata->partial_recv && full_msginfo->extradata->partial_recv)
1326                         msginfo->extradata->partial_recv = g_strdup
1327                                 (full_msginfo->extradata->partial_recv);
1328                 if (!msginfo->extradata->account_server && full_msginfo->extradata->account_server)
1329                         msginfo->extradata->account_server = g_strdup
1330                                 (full_msginfo->extradata->account_server);
1331                 if (!msginfo->extradata->account_login && full_msginfo->extradata->account_login)
1332                         msginfo->extradata->account_login = g_strdup
1333                                 (full_msginfo->extradata->account_login);
1334         }
1335         procmsg_msginfo_free(full_msginfo);
1336
1337         return procmsg_msginfo_new_ref(msginfo);
1338 }
1339
1340 void procmsg_msginfo_free(MsgInfo *msginfo)
1341 {
1342         if (msginfo == NULL) return;
1343
1344         msginfo->refcnt--;
1345         if (msginfo->refcnt > 0)
1346                 return;
1347
1348         if (msginfo->to_folder) {
1349                 msginfo->to_folder->op_count--;
1350                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1351         }
1352
1353         g_free(msginfo->fromspace);
1354
1355         g_free(msginfo->fromname);
1356
1357         g_free(msginfo->date);
1358         g_free(msginfo->from);
1359         g_free(msginfo->to);
1360         g_free(msginfo->cc);
1361         g_free(msginfo->newsgroups);
1362         g_free(msginfo->subject);
1363         g_free(msginfo->msgid);
1364         g_free(msginfo->inreplyto);
1365         g_free(msginfo->xref);
1366
1367         if (msginfo->extradata) {
1368                 g_free(msginfo->extradata->returnreceiptto);
1369                 g_free(msginfo->extradata->dispositionnotificationto);
1370                 g_free(msginfo->extradata->xface);
1371                 g_free(msginfo->extradata->face);
1372                 g_free(msginfo->extradata->list_post);
1373                 g_free(msginfo->extradata->list_subscribe);
1374                 g_free(msginfo->extradata->list_unsubscribe);
1375                 g_free(msginfo->extradata->list_help);
1376                 g_free(msginfo->extradata->list_archive);
1377                 g_free(msginfo->extradata->list_owner);
1378                 g_free(msginfo->extradata->partial_recv);
1379                 g_free(msginfo->extradata->account_server);
1380                 g_free(msginfo->extradata->account_login);
1381                 g_free(msginfo->extradata);
1382         }
1383         slist_free_strings(msginfo->references);
1384         g_slist_free(msginfo->references);
1385
1386         g_free(msginfo->plaintext_file);
1387
1388         g_free(msginfo);
1389 }
1390
1391 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1392 {
1393         guint memusage = 0;
1394         GSList *refs;
1395         
1396         memusage += sizeof(MsgInfo);
1397         if (msginfo->fromname)
1398                 memusage += strlen(msginfo->fromname);
1399         if (msginfo->date)
1400                 memusage += strlen(msginfo->date);
1401         if (msginfo->from)
1402                 memusage += strlen(msginfo->from);
1403         if (msginfo->to)
1404                 memusage += strlen(msginfo->to);
1405         if (msginfo->cc)
1406                 memusage += strlen(msginfo->cc);
1407         if (msginfo->newsgroups)
1408                 memusage += strlen(msginfo->newsgroups);
1409         if (msginfo->subject)
1410                 memusage += strlen(msginfo->subject);
1411         if (msginfo->msgid)
1412                 memusage += strlen(msginfo->msgid);
1413         if (msginfo->inreplyto)
1414                 memusage += strlen(msginfo->inreplyto);
1415         for (refs = msginfo->references; refs; refs=refs->next) {
1416                 gchar *r = (gchar *)refs->data;
1417                 memusage += r?strlen(r):0;
1418         }
1419         if (msginfo->fromspace)
1420                 memusage += strlen(msginfo->fromspace);
1421
1422         if (msginfo->extradata) {
1423                 memusage += sizeof(MsgInfoExtraData);
1424                 if (msginfo->extradata->xface)
1425                         memusage += strlen(msginfo->extradata->xface);
1426                 if (msginfo->extradata->face)
1427                         memusage += strlen(msginfo->extradata->face);
1428                 if (msginfo->extradata->dispositionnotificationto)
1429                         memusage += strlen(msginfo->extradata->dispositionnotificationto);
1430                 if (msginfo->extradata->returnreceiptto)
1431                         memusage += strlen(msginfo->extradata->returnreceiptto);
1432
1433                 if (msginfo->extradata->partial_recv)
1434                         memusage += strlen(msginfo->extradata->partial_recv);
1435                 if (msginfo->extradata->account_server)
1436                         memusage += strlen(msginfo->extradata->account_server);
1437                 if (msginfo->extradata->account_login)
1438                         memusage += strlen(msginfo->extradata->account_login);
1439
1440                 if (msginfo->extradata->list_post)
1441                         memusage += strlen(msginfo->extradata->list_post);
1442                 if (msginfo->extradata->list_subscribe)
1443                         memusage += strlen(msginfo->extradata->list_subscribe);
1444                 if (msginfo->extradata->list_unsubscribe)
1445                         memusage += strlen(msginfo->extradata->list_unsubscribe);
1446                 if (msginfo->extradata->list_help)
1447                         memusage += strlen(msginfo->extradata->list_help);
1448                 if (msginfo->extradata->list_archive)
1449                         memusage += strlen(msginfo->extradata->list_archive);
1450                 if (msginfo->extradata->list_owner)
1451                         memusage += strlen(msginfo->extradata->list_owner);
1452         }
1453         return memusage;
1454 }
1455
1456 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1457 {
1458         const MsgInfo *msginfo1 = a;
1459         const MsgInfo *msginfo2 = b;
1460
1461         if (!msginfo1)
1462                 return -1;
1463         if (!msginfo2)
1464                 return -1;
1465
1466         return msginfo1->msgnum - msginfo2->msgnum;
1467 }
1468
1469 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr,
1470                                             FolderItem *queue, gint msgnum, gboolean *queued_removed)
1471 {
1472         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1473                                        {"SSV:",  NULL, FALSE},
1474                                        {"R:",    NULL, FALSE},
1475                                        {"NG:",   NULL, FALSE},
1476                                        {"MAID:", NULL, FALSE},
1477                                        {"NAID:", NULL, FALSE},
1478                                        {"SCF:",  NULL, FALSE},
1479                                        {"RMID:", NULL, FALSE},
1480                                        {"FMID:", NULL, FALSE},
1481                                        {"X-Claws-Privacy-System:", NULL, FALSE},
1482                                        {"X-Claws-Encrypt:", NULL, FALSE},
1483                                        {"X-Claws-Encrypt-Data:", NULL, FALSE},
1484                                        {"X-Claws-End-Special-Headers:", NULL, FALSE},
1485                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1486                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1487                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1488                                        {"X-Sylpheed-End-Special-Headers:", NULL, FALSE},
1489                                        {NULL,    NULL, FALSE}};
1490         FILE *fp;
1491         gint filepos;
1492         gint mailval = 0, newsval = 0;
1493         gchar *from = NULL;
1494         gchar *smtpserver = NULL;
1495         GSList *to_list = NULL;
1496         GSList *newsgroup_list = NULL;
1497         gchar *savecopyfolder = NULL;
1498         gchar *replymessageid = NULL;
1499         gchar *fwdmessageid = NULL;
1500         gchar *privacy_system = NULL;
1501         gboolean encrypt = FALSE;
1502         gchar *encrypt_data = NULL;
1503         gchar buf[BUFFSIZE];
1504         gint hnum;
1505         PrefsAccount *mailac = NULL, *newsac = NULL;
1506         gboolean save_clear_text = TRUE;
1507         gchar *tmp_enc_file = NULL;
1508
1509         int local = 0;
1510
1511         g_return_val_if_fail(file != NULL, -1);
1512
1513         if ((fp = g_fopen(file, "rb")) == NULL) {
1514                 FILE_OP_ERROR(file, "fopen");
1515                 if (errstr) {
1516                         if (*errstr) g_free(*errstr);
1517                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1518                 }
1519                 return -1;
1520         }
1521
1522         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1523                != -1) {
1524                 gchar *p = buf + strlen(qentry[hnum].name);
1525
1526                 switch (hnum) {
1527                 case Q_SENDER:
1528                         if (from == NULL) 
1529                                 from = g_strdup(p);
1530                         break;
1531                 case Q_SMTPSERVER:
1532                         if (smtpserver == NULL) 
1533                                 smtpserver = g_strdup(p);
1534                         break;
1535                 case Q_RECIPIENTS:
1536                         to_list = address_list_append(to_list, p);
1537                         break;
1538                 case Q_NEWSGROUPS:
1539                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1540                         break;
1541                 case Q_MAIL_ACCOUNT_ID:
1542                         mailac = account_find_from_id(atoi(p));
1543                         break;
1544                 case Q_NEWS_ACCOUNT_ID:
1545                         newsac = account_find_from_id(atoi(p));
1546                         break;
1547                 case Q_SAVE_COPY_FOLDER:
1548                         if (savecopyfolder == NULL) 
1549                                 savecopyfolder = g_strdup(p);
1550                         break;
1551                 case Q_REPLY_MESSAGE_ID:
1552                         if (replymessageid == NULL) 
1553                                 replymessageid = g_strdup(p);
1554                         break;
1555                 case Q_FWD_MESSAGE_ID:
1556                         if (fwdmessageid == NULL) 
1557                                 fwdmessageid = g_strdup(p);
1558                         break;
1559                 case Q_PRIVACY_SYSTEM:
1560                 case Q_PRIVACY_SYSTEM_OLD:
1561                         if (privacy_system == NULL) 
1562                                 privacy_system = g_strdup(p);
1563                         break;
1564                 case Q_ENCRYPT:
1565                 case Q_ENCRYPT_OLD:
1566                         if (p[0] == '1') 
1567                                 encrypt = TRUE;
1568                         break;
1569                 case Q_ENCRYPT_DATA:
1570                 case Q_ENCRYPT_DATA_OLD:
1571                         if (encrypt_data == NULL) 
1572                                 encrypt_data = g_strdup(p);
1573                         break;
1574                 case Q_CLAWS_HDRS:
1575                 case Q_CLAWS_HDRS_OLD:
1576                         /* end of special headers reached */
1577                         goto send_mail; /* can't "break;break;" */
1578                 }
1579         }
1580 send_mail:
1581         filepos = ftell(fp);
1582
1583         if (encrypt) {
1584                 MimeInfo *mimeinfo;
1585
1586                 if (mailac && mailac->save_encrypted_as_clear_text 
1587                 &&  !mailac->encrypt_to_self)
1588                         save_clear_text = TRUE;
1589                 else
1590                         save_clear_text = FALSE;
1591
1592                 fclose(fp);
1593                 fp = NULL;
1594
1595                 mimeinfo = procmime_scan_queue_file(file);
1596                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1597                 || (fp = my_tmpfile()) == NULL
1598                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1599                         if (fp)
1600                                 fclose(fp);
1601                         procmime_mimeinfo_free_all(mimeinfo);
1602                         g_free(from);
1603                         g_free(smtpserver);
1604                         slist_free_strings(to_list);
1605                         g_slist_free(to_list);
1606                         slist_free_strings(newsgroup_list);
1607                         g_slist_free(newsgroup_list);
1608                         g_free(savecopyfolder);
1609                         g_free(replymessageid);
1610                         g_free(fwdmessageid);
1611                         g_free(privacy_system);
1612                         g_free(encrypt_data);
1613                         if (errstr) {
1614                                 if (*errstr) g_free(*errstr);
1615                                 *errstr = g_strdup_printf(_("Couldn't encrypt the email: %s"),
1616                                                 privacy_get_error());
1617                         }
1618                         return -1;
1619                 }
1620                 
1621                 rewind(fp);
1622                 if (!save_clear_text) {
1623                         gchar *content = NULL;
1624                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1625                         if (tmpfp) {
1626                                 fclose(tmpfp);
1627
1628                                 content = file_read_stream_to_str(fp);
1629                                 rewind(fp);
1630
1631                                 str_write_to_file(content, tmp_enc_file);
1632                                 g_free(content);
1633                         } else {
1634                                 g_warning("couldn't get tempfile\n");
1635                         }
1636                 } 
1637                 
1638                 procmime_mimeinfo_free_all(mimeinfo);
1639                 
1640                 filepos = 0;
1641         }
1642
1643         if (to_list) {
1644                 debug_print("Sending message by mail\n");
1645                 if (!from) {
1646                         if (errstr) {
1647                                 if (*errstr) g_free(*errstr);
1648                                 *errstr = g_strdup_printf(_("Queued message header is broken."));
1649                         }
1650                         mailval = -1;
1651                 } else if (mailac && mailac->use_mail_command &&
1652                            mailac->mail_command && (* mailac->mail_command)) {
1653                         mailval = send_message_local(mailac->mail_command, fp);
1654                         local = 1;
1655                 } else {
1656                         if (!mailac) {
1657                                 mailac = account_find_from_smtp_server(from, smtpserver);
1658                                 if (!mailac) {
1659                                         g_warning("Account not found. "
1660                                                     "Using current account...\n");
1661                                         mailac = cur_account;
1662                                 }
1663                         }
1664
1665                         if (mailac) {
1666                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1667                                 if (mailval == -1 && errstr) {
1668                                         if (*errstr) g_free(*errstr);
1669                                         *errstr = g_strdup_printf(_("An error happened during SMTP session."));
1670                                 }
1671                         } else {
1672                                 PrefsAccount tmp_ac;
1673
1674                                 g_warning("Account not found.\n");
1675
1676                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1677                                 tmp_ac.address = from;
1678                                 tmp_ac.smtp_server = smtpserver;
1679                                 tmp_ac.smtpport = SMTP_PORT;
1680                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1681                                 if (mailval == -1 && errstr) {
1682                                         if (*errstr) g_free(*errstr);
1683                                         *errstr = g_strdup_printf(_("No specific account has been found to "
1684                                                         "send, and an error happened during SMTP session."));
1685                                 }
1686                         }
1687                 }
1688         } else if (!to_list && !newsgroup_list) {
1689                 if (errstr) {
1690                         if (*errstr) g_free(*errstr);
1691                         *errstr = g_strdup(_("Couldn't determine sending informations. "
1692                                 "Maybe the email hasn't been generated by Claws Mail."));
1693                 }
1694                 mailval = -1;
1695         }
1696
1697         fseek(fp, filepos, SEEK_SET);
1698         if (newsgroup_list && (mailval == 0)) {
1699                 Folder *folder;
1700                 gchar *tmp = NULL;
1701                 FILE *tmpfp;
1702
1703                 /* write to temporary file */
1704                 tmp = g_strdup_printf("%s%ctmp%p", g_get_tmp_dir(),
1705                             G_DIR_SEPARATOR, file);
1706                 if ((tmpfp = g_fopen(tmp, "wb")) == NULL) {
1707                         FILE_OP_ERROR(tmp, "fopen");
1708                         newsval = -1;
1709                         alertpanel_error(_("Couldn't create temporary file for news sending."));
1710                 } else {
1711                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1712                                 FILE_OP_ERROR(tmp, "chmod");
1713                                 g_warning("can't change file mode\n");
1714                         }
1715
1716                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1717                                 if (fputs(buf, tmpfp) == EOF) {
1718                                         FILE_OP_ERROR(tmp, "fputs");
1719                                         newsval = -1;
1720                                         if (errstr) {
1721                                                 if (*errstr) g_free(*errstr);
1722                                                 *errstr = g_strdup_printf(_("Error when writing temporary file for news sending."));
1723                                         }
1724                                 }
1725                         }
1726                         fclose(tmpfp);
1727
1728                         if (newsval == 0) {
1729                                 debug_print("Sending message by news\n");
1730
1731                                 folder = FOLDER(newsac->folder);
1732
1733                                 newsval = news_post(folder, tmp);
1734                                 if (newsval < 0 && errstr)  {
1735                                         if (*errstr) g_free(*errstr);
1736                                         *errstr = g_strdup_printf(_("Error occurred while posting the message to %s."),
1737                                          newsac->nntp_server);
1738                                 }
1739                         }
1740                         g_unlink(tmp);
1741                 }
1742                 g_free(tmp);
1743         }
1744
1745         fclose(fp);
1746
1747         /* save message to outbox */
1748         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1749                 FolderItem *outbox;
1750
1751                 debug_print("saving sent message...\n");
1752
1753                 outbox = folder_find_item_from_identifier(savecopyfolder);
1754                 if (!outbox)
1755                         outbox = folder_get_default_outbox();
1756                         
1757                 if (save_clear_text || tmp_enc_file == NULL) {
1758                         gboolean saved = FALSE;
1759                         *queued_removed = FALSE;
1760                         if (queue && msgnum > 0) {
1761                                 MsgInfo *queued_mail = folder_item_get_msginfo(queue, msgnum);
1762                                 if (folder_item_move_msg(outbox, queued_mail) >= 0) {
1763                                         debug_print("moved queued mail %d to sent folder\n", msgnum);
1764                                         saved = TRUE;
1765                                         *queued_removed = TRUE;
1766                                 } else if (folder_item_copy_msg(outbox, queued_mail) >= 0) {
1767                                         debug_print("copied queued mail %d to sent folder\n", msgnum);
1768                                         saved = TRUE;
1769                                 }
1770                                 procmsg_msginfo_free(queued_mail);
1771                         }
1772                         if (!saved) {
1773                                 debug_print("resaving clear text queued mail to sent folder\n");
1774                                 procmsg_save_to_outbox(outbox, file, TRUE);
1775                         }
1776                 } else {
1777                         debug_print("saving encrpyted queued mail to sent folder\n");
1778                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1779                 }
1780         }
1781
1782         if (tmp_enc_file != NULL) {
1783                 g_unlink(tmp_enc_file);
1784                 free(tmp_enc_file);
1785                 tmp_enc_file = NULL;
1786         }
1787
1788         if (replymessageid != NULL || fwdmessageid != NULL) {
1789                 gchar **tokens;
1790                 FolderItem *item;
1791                 
1792                 if (replymessageid != NULL)
1793                         tokens = g_strsplit(replymessageid, "\t", 0);
1794                 else
1795                         tokens = g_strsplit(fwdmessageid, "\t", 0);
1796                 item = folder_find_item_from_identifier(tokens[0]);
1797
1798                 /* check if queued message has valid folder and message id */
1799                 if (item != NULL && tokens[2] != NULL) {
1800                         MsgInfo *msginfo;
1801                         
1802                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1803                 
1804                         /* check if referring message exists and has a message id */
1805                         if ((msginfo != NULL) && 
1806                             (msginfo->msgid != NULL) &&
1807                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1808                                 procmsg_msginfo_free(msginfo);
1809                                 msginfo = NULL;
1810                         }
1811                         
1812                         if (msginfo == NULL) {
1813                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1814                         }
1815                         
1816                         if (msginfo != NULL) {
1817                                 if (replymessageid != NULL) {
1818                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1819                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1820                                 }  else {
1821                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1822                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1823                                 }
1824                                 procmsg_msginfo_free(msginfo);
1825                         }
1826                 }
1827                 g_strfreev(tokens);
1828         }
1829
1830         g_free(from);
1831         g_free(smtpserver);
1832         slist_free_strings(to_list);
1833         g_slist_free(to_list);
1834         slist_free_strings(newsgroup_list);
1835         g_slist_free(newsgroup_list);
1836         g_free(savecopyfolder);
1837         g_free(replymessageid);
1838         g_free(fwdmessageid);
1839         g_free(privacy_system);
1840         g_free(encrypt_data);
1841
1842         return (newsval != 0 ? newsval : mailval);
1843 }
1844
1845 gint procmsg_send_message_queue(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1846 {
1847         gint result = procmsg_send_message_queue_full(file, FALSE, errstr, queue, msgnum, queued_removed);
1848         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1849         return result;
1850 }
1851
1852 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1853 {
1854         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1855
1856         /* NEW flag */
1857         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1858                 item->new_msgs++;
1859         }
1860
1861         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1862                 item->new_msgs--;
1863         }
1864
1865         /* UNREAD flag */
1866         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1867                 item->unread_msgs++;
1868                 if (procmsg_msg_has_marked_parent(msginfo))
1869                         item->unreadmarked_msgs++;
1870         }
1871
1872         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1873                 item->unread_msgs--;
1874                 if (procmsg_msg_has_marked_parent(msginfo))
1875                         item->unreadmarked_msgs--;
1876         }
1877         
1878         /* MARK flag */
1879         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1880                 procmsg_update_unread_children(msginfo, TRUE);
1881                 item->marked_msgs++;
1882         }
1883
1884         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1885                 procmsg_update_unread_children(msginfo, FALSE);
1886                 item->marked_msgs--;
1887         }
1888 }
1889
1890 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1891 {
1892         FolderItem *item;
1893         MsgInfoUpdate msginfo_update;
1894         MsgPermFlags perm_flags_new, perm_flags_old;
1895         MsgTmpFlags tmp_flags_old;
1896
1897         g_return_if_fail(msginfo != NULL);
1898         item = msginfo->folder;
1899         g_return_if_fail(item != NULL);
1900         
1901         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1902
1903         /* Perm Flags handling */
1904         perm_flags_old = msginfo->flags.perm_flags;
1905         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1906         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1907                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1908         }
1909
1910         if (perm_flags_old != perm_flags_new) {
1911                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1912
1913                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1914
1915         }
1916
1917         /* Tmp flags handling */
1918         tmp_flags_old = msginfo->flags.tmp_flags;
1919         msginfo->flags.tmp_flags |= tmp_flags;
1920
1921         /* update notification */
1922         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1923                 msginfo_update.msginfo = msginfo;
1924                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1925                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1926                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1927         }
1928 }
1929
1930 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1931 {
1932         FolderItem *item;
1933         MsgInfoUpdate msginfo_update;
1934         MsgPermFlags perm_flags_new, perm_flags_old;
1935         MsgTmpFlags tmp_flags_old;
1936
1937         g_return_if_fail(msginfo != NULL);
1938         item = msginfo->folder;
1939         g_return_if_fail(item != NULL);
1940         
1941         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1942
1943         /* Perm Flags handling */
1944         perm_flags_old = msginfo->flags.perm_flags;
1945         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1946         
1947         if (perm_flags_old != perm_flags_new) {
1948                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1949
1950                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1951         }
1952
1953         /* Tmp flags hanlding */
1954         tmp_flags_old = msginfo->flags.tmp_flags;
1955         msginfo->flags.tmp_flags &= ~tmp_flags;
1956
1957         /* update notification */
1958         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1959                 msginfo_update.msginfo = msginfo;
1960                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1961                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1962                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1963         }
1964 }
1965
1966 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1967                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1968                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1969 {
1970         FolderItem *item;
1971         MsgInfoUpdate msginfo_update;
1972         MsgPermFlags perm_flags_new, perm_flags_old;
1973         MsgTmpFlags tmp_flags_old;
1974
1975         g_return_if_fail(msginfo != NULL);
1976         item = msginfo->folder;
1977         g_return_if_fail(item != NULL);
1978         
1979         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1980
1981         /* Perm Flags handling */
1982         perm_flags_old = msginfo->flags.perm_flags;
1983         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1984         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1985                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1986         }
1987
1988         if (perm_flags_old != perm_flags_new) {
1989                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1990
1991                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1992
1993         }
1994
1995         /* Tmp flags handling */
1996         tmp_flags_old = msginfo->flags.tmp_flags;
1997         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1998         msginfo->flags.tmp_flags |= add_tmp_flags;
1999
2000         /* update notification */
2001         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
2002                 msginfo_update.msginfo = msginfo;
2003                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
2004                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2005                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
2006         }
2007 }
2008
2009 /*!
2010  *\brief        check for flags (e.g. mark) in prior msgs of current thread
2011  *
2012  *\param        info Current message
2013  *\param        perm_flags Flags to be checked
2014  *\param        parentmsgs Hash of prior msgs to avoid loops
2015  *
2016  *\return       gboolean TRUE if perm_flags are found
2017  */
2018 static gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
2019                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
2020 {
2021         MsgInfo *tmp;
2022
2023         g_return_val_if_fail(info != NULL, FALSE);
2024
2025         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
2026                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
2027                                 info->inreplyto);
2028                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
2029                         procmsg_msginfo_free(tmp);
2030                         return TRUE;
2031                 } else if (tmp != NULL) {
2032                         gboolean result;
2033
2034                         if (g_hash_table_lookup(parentmsgs, info)) {
2035                                 debug_print("loop detected: %d\n",
2036                                         info->msgnum);
2037                                 result = FALSE;
2038                         } else {
2039                                 g_hash_table_insert(parentmsgs, info, "1");
2040                                 result = procmsg_msg_has_flagged_parent_real(
2041                                     tmp, perm_flags, parentmsgs);
2042                         }
2043                         procmsg_msginfo_free(tmp);
2044                         return result;
2045                 } else {
2046                         return FALSE;
2047                 }
2048         } else
2049                 return FALSE;
2050 }
2051
2052 /*!
2053  *\brief        Callback for cleaning up hash of parentmsgs
2054  */
2055 static gboolean parentmsgs_hash_remove(gpointer key,
2056                             gpointer value,
2057                             gpointer user_data)
2058 {
2059         return TRUE;
2060 }
2061
2062 /*!
2063  *\brief        Set up list of parentmsgs
2064  *              See procmsg_msg_has_flagged_parent_real()
2065  */
2066 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
2067 {
2068         gboolean result;
2069         static GHashTable *parentmsgs = NULL;
2070         
2071         if (parentmsgs == NULL)
2072                 parentmsgs = g_hash_table_new(NULL, NULL); 
2073
2074         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2075         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2076
2077         return result;
2078 }
2079
2080 /*!
2081  *\brief        Check if msgs prior in thread are marked
2082  *              See procmsg_msg_has_flagged_parent_real()
2083  */
2084 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2085 {
2086         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2087 }
2088
2089
2090 static GSList *procmsg_find_children_func(MsgInfo *info, 
2091                                    GSList *children, GSList *all)
2092 {
2093         GSList *cur;
2094
2095         g_return_val_if_fail(info!=NULL, children);
2096         if (info->msgid == NULL)
2097                 return children;
2098
2099         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2100                 MsgInfo *tmp = (MsgInfo *)cur->data;
2101                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2102                         /* Check if message is already in the list */
2103                         if ((children == NULL) || 
2104                             (g_slist_index(children, tmp) == -1)) {
2105                                 children = g_slist_prepend(children,
2106                                                 procmsg_msginfo_new_ref(tmp));
2107                                 children = procmsg_find_children_func(tmp, 
2108                                                         children, 
2109                                                         all);
2110                         }
2111                 }
2112         }
2113         return children;
2114 }
2115
2116 GSList *procmsg_find_children (MsgInfo *info)
2117 {
2118         GSList *children;
2119         GSList *all, *cur;
2120
2121         g_return_val_if_fail(info!=NULL, NULL);
2122         all = folder_item_get_msg_list(info->folder);
2123         children = procmsg_find_children_func(info, NULL, all);
2124         if (children != NULL) {
2125                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2126                         /* this will not free the used pointers
2127                            created with procmsg_msginfo_new_ref */
2128                         procmsg_msginfo_free((MsgInfo *)cur->data);
2129                 }
2130         }
2131         g_slist_free(all);
2132
2133         return children;
2134 }
2135
2136 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2137 {
2138         GSList *children = procmsg_find_children(info);
2139         GSList *cur;
2140         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2141                 MsgInfo *tmp = (MsgInfo *)cur->data;
2142                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2143                         if(newly_marked) 
2144                                 info->folder->unreadmarked_msgs++;
2145                         else
2146                                 info->folder->unreadmarked_msgs--;
2147                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2148                 }
2149                 procmsg_msginfo_free(tmp);
2150         }
2151         g_slist_free(children);
2152 }
2153
2154 /**
2155  * Set the destination folder for a copy or move operation
2156  *
2157  * \param msginfo The message which's destination folder is changed
2158  * \param to_folder The destination folder for the operation
2159  */
2160 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2161 {
2162         if(msginfo->to_folder != NULL) {
2163                 msginfo->to_folder->op_count--;
2164                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2165         }
2166         msginfo->to_folder = to_folder;
2167         if(to_folder != NULL) {
2168                 to_folder->op_count++;
2169                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2170         }
2171 }
2172
2173 /**
2174  * Apply filtering actions to the msginfo
2175  *
2176  * \param msginfo The MsgInfo describing the message that should be filtered
2177  * \return TRUE if the message was moved and MsgInfo is now invalid,
2178  *         FALSE otherwise
2179  */
2180 gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2181 {
2182         MailFilteringData mail_filtering_data;
2183                         
2184         mail_filtering_data.msginfo = msginfo;                  
2185         mail_filtering_data.msglist = NULL;                     
2186         mail_filtering_data.filtered = NULL;                    
2187         mail_filtering_data.unfiltered = NULL;                  
2188         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data)) {
2189                 return TRUE;
2190         }
2191
2192         /* filter if enabled in prefs or move to inbox if not */
2193         if((filtering_rules != NULL) &&
2194            filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs)) {
2195                 return TRUE;
2196         }
2197                 
2198         return FALSE;
2199 }
2200
2201 void procmsg_msglist_filter(GSList *list, PrefsAccount *ac, 
2202                             GSList **filtered, GSList **unfiltered,
2203                             gboolean do_filter)
2204 {
2205         GSList *cur, *to_do = NULL;
2206         gint total = 0, curnum = 0;
2207         MailFilteringData mail_filtering_data;
2208                         
2209         g_return_if_fail(filtered != NULL);
2210         g_return_if_fail(unfiltered != NULL);
2211
2212         *filtered = NULL;
2213         *unfiltered = NULL;
2214         
2215         if (list == NULL)
2216                 return;
2217
2218         total = g_slist_length(list);
2219
2220         if (!do_filter) {
2221                 *filtered = NULL;
2222                 *unfiltered = g_slist_copy(list);
2223                 return;
2224         }
2225
2226         statusbar_print_all(_("Filtering messages...\n"));
2227
2228         mail_filtering_data.msginfo = NULL;                     
2229         mail_filtering_data.msglist = list;                     
2230         mail_filtering_data.filtered = NULL;                    
2231         mail_filtering_data.unfiltered = NULL;  
2232                         
2233         hooks_invoke(MAIL_LISTFILTERING_HOOKLIST, &mail_filtering_data);
2234         
2235         if (mail_filtering_data.filtered == NULL &&
2236             mail_filtering_data.unfiltered == NULL) {
2237                 /* nothing happened */
2238                 debug_print(MAIL_LISTFILTERING_HOOKLIST " did nothing. filtering whole list normally.\n");
2239                 to_do = list;
2240         } 
2241         if (mail_filtering_data.filtered != NULL) {
2242                 /* keep track of what's been filtered by the hooks */
2243                 debug_print(MAIL_LISTFILTERING_HOOKLIST " filtered some stuff. total %d filtered %d unfilt %d.\n",
2244                         g_slist_length(list),
2245                         g_slist_length(mail_filtering_data.filtered),
2246                         g_slist_length(mail_filtering_data.unfiltered));
2247
2248                 *filtered = g_slist_copy(mail_filtering_data.filtered);
2249         }
2250         if (mail_filtering_data.unfiltered != NULL) {
2251                 /* what the hooks didn't handle will go in filtered or 
2252                  * unfiltered in the next loop */
2253                 debug_print(MAIL_LISTFILTERING_HOOKLIST " left unfiltered stuff. total %d filtered %d unfilt %d.\n",
2254                         g_slist_length(list),
2255                         g_slist_length(mail_filtering_data.filtered),
2256                         g_slist_length(mail_filtering_data.unfiltered));
2257                 to_do = mail_filtering_data.unfiltered;
2258         } 
2259
2260         for (cur = to_do; cur; cur = cur->next) {
2261                 MsgInfo *info = (MsgInfo *)cur->data;
2262                 if (procmsg_msginfo_filter(info, ac))
2263                         *filtered = g_slist_prepend(*filtered, info);
2264                 else
2265                         *unfiltered = g_slist_prepend(*unfiltered, info);
2266                 statusbar_progress_all(curnum++, total, prefs_common.statusbar_update_step);
2267         }
2268
2269         g_slist_free(mail_filtering_data.filtered);
2270         g_slist_free(mail_filtering_data.unfiltered);
2271         
2272         *filtered = g_slist_reverse(*filtered);
2273         *unfiltered = g_slist_reverse(*unfiltered);
2274
2275         statusbar_progress_all(0,0,0);
2276         statusbar_pop_all();
2277 }
2278
2279 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2280 {
2281         MsgInfo *tmp_msginfo = NULL;
2282         MsgFlags flags = {0, 0};
2283         gchar *tmpfile = get_tmp_file();
2284         FILE *fp = g_fopen(tmpfile, "wb");
2285         
2286         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2287             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2288                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2289                 if (fp) 
2290                         fclose(fp);
2291                 g_free(tmpfile);
2292                 return NULL;
2293         }
2294         
2295         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2296                 fclose(fp);
2297                 fp = NULL;
2298                 tmp_msginfo = procheader_parse_file(
2299                         tmpfile, flags, 
2300                         TRUE, FALSE);
2301         }
2302         if (fp)
2303                 fclose(fp);
2304
2305         if (tmp_msginfo != NULL) {
2306                 if (src_msginfo)
2307                         tmp_msginfo->folder = src_msginfo->folder;
2308                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2309         } else {
2310                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2311         }
2312
2313         g_free(tmpfile);
2314
2315         return tmp_msginfo;
2316 }
2317
2318 static GSList *spam_learners = NULL;
2319
2320 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2321 {
2322         if (!g_slist_find(spam_learners, learn_func))
2323                 spam_learners = g_slist_append(spam_learners, learn_func);
2324         if (mainwindow_get_mainwindow()) {
2325                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2326                 summary_set_menu_sensitive(
2327                         mainwindow_get_mainwindow()->summaryview);
2328                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2329         }
2330 }
2331
2332 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2333 {
2334         spam_learners = g_slist_remove(spam_learners, learn_func);
2335         if (mainwindow_get_mainwindow()) {
2336                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2337                 summary_set_menu_sensitive(
2338                         mainwindow_get_mainwindow()->summaryview);
2339                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2340         }
2341 }
2342
2343 gboolean procmsg_spam_can_learn(void)
2344 {
2345         return g_slist_length(spam_learners) > 0;
2346 }
2347
2348 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2349 {
2350         GSList *cur = spam_learners;
2351         int ret = 0;
2352         for (; cur; cur = cur->next) {
2353                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2354                 ret |= func(info, list, spam);
2355         }
2356         return ret;
2357 }
2358
2359 static gchar *spam_folder_item = NULL;
2360 void procmsg_spam_set_folder (const char *item_identifier)
2361 {
2362         g_free(spam_folder_item);
2363         if (item_identifier)
2364                 spam_folder_item = g_strdup(item_identifier);
2365         else
2366                 spam_folder_item = NULL;
2367 }
2368
2369 FolderItem *procmsg_spam_get_folder (void)
2370 {
2371         FolderItem *item = spam_folder_item ? folder_find_item_from_identifier(spam_folder_item) : NULL;
2372         return item ? item : folder_get_default_trash();
2373 }
2374
2375 static void item_has_queued_mails(FolderItem *item, gpointer data)
2376 {
2377         gboolean *result = (gboolean *)data;
2378         if (*result == TRUE)
2379                 return;
2380         if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0)
2381                 *result = TRUE;
2382 }
2383
2384 gboolean procmsg_have_queued_mails_fast (void)
2385 {
2386         gboolean result = FALSE;
2387         folder_func_to_all_folders(item_has_queued_mails, &result);
2388         return result;
2389 }