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