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