2007-07-19 [colin] 2.10.0cvs47
[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
1852         if (perm_flags_old != perm_flags_new) {
1853                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1854
1855                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1856
1857         }
1858
1859         /* Tmp flags handling */
1860         tmp_flags_old = msginfo->flags.tmp_flags;
1861         msginfo->flags.tmp_flags |= tmp_flags;
1862
1863         /* update notification */
1864         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1865                 msginfo_update.msginfo = msginfo;
1866                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1867                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1868                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1869         }
1870 }
1871
1872 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1873 {
1874         FolderItem *item;
1875         MsgInfoUpdate msginfo_update;
1876         MsgPermFlags perm_flags_new, perm_flags_old;
1877         MsgTmpFlags tmp_flags_old;
1878
1879         g_return_if_fail(msginfo != NULL);
1880         item = msginfo->folder;
1881         g_return_if_fail(item != NULL);
1882         
1883         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1884
1885         /* Perm Flags handling */
1886         perm_flags_old = msginfo->flags.perm_flags;
1887         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1888         
1889         if (perm_flags_old != perm_flags_new) {
1890                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1891
1892                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1893         }
1894
1895         /* Tmp flags hanlding */
1896         tmp_flags_old = msginfo->flags.tmp_flags;
1897         msginfo->flags.tmp_flags &= ~tmp_flags;
1898
1899         /* update notification */
1900         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1901                 msginfo_update.msginfo = msginfo;
1902                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1903                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1904                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1905         }
1906 }
1907
1908 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1909                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1910                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1911 {
1912         FolderItem *item;
1913         MsgInfoUpdate msginfo_update;
1914         MsgPermFlags perm_flags_new, perm_flags_old;
1915         MsgTmpFlags tmp_flags_old;
1916
1917         g_return_if_fail(msginfo != NULL);
1918         item = msginfo->folder;
1919         g_return_if_fail(item != NULL);
1920         
1921         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1922
1923         /* Perm Flags handling */
1924         perm_flags_old = msginfo->flags.perm_flags;
1925         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1926         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1927                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1928         }
1929
1930         if (perm_flags_old != perm_flags_new) {
1931                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1932
1933                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1934
1935         }
1936
1937         /* Tmp flags handling */
1938         tmp_flags_old = msginfo->flags.tmp_flags;
1939         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1940         msginfo->flags.tmp_flags |= add_tmp_flags;
1941
1942         /* update notification */
1943         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1944                 msginfo_update.msginfo = msginfo;
1945                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1946                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1947                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1948         }
1949 }
1950
1951 /*!
1952  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1953  *
1954  *\param        info Current message
1955  *\param        perm_flags Flags to be checked
1956  *\param        parentmsgs Hash of prior msgs to avoid loops
1957  *
1958  *\return       gboolean TRUE if perm_flags are found
1959  */
1960 static gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1961                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1962 {
1963         MsgInfo *tmp;
1964
1965         g_return_val_if_fail(info != NULL, FALSE);
1966
1967         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1968                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1969                                 info->inreplyto);
1970                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1971                         procmsg_msginfo_free(tmp);
1972                         return TRUE;
1973                 } else if (tmp != NULL) {
1974                         gboolean result;
1975
1976                         if (g_hash_table_lookup(parentmsgs, info)) {
1977                                 debug_print("loop detected: %d\n",
1978                                         info->msgnum);
1979                                 result = FALSE;
1980                         } else {
1981                                 g_hash_table_insert(parentmsgs, info, "1");
1982                                 result = procmsg_msg_has_flagged_parent_real(
1983                                     tmp, perm_flags, parentmsgs);
1984                         }
1985                         procmsg_msginfo_free(tmp);
1986                         return result;
1987                 } else {
1988                         return FALSE;
1989                 }
1990         } else
1991                 return FALSE;
1992 }
1993
1994 /*!
1995  *\brief        Callback for cleaning up hash of parentmsgs
1996  */
1997 static gboolean parentmsgs_hash_remove(gpointer key,
1998                             gpointer value,
1999                             gpointer user_data)
2000 {
2001         return TRUE;
2002 }
2003
2004 /*!
2005  *\brief        Set up list of parentmsgs
2006  *              See procmsg_msg_has_flagged_parent_real()
2007  */
2008 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
2009 {
2010         gboolean result;
2011         static GHashTable *parentmsgs = NULL;
2012         
2013         if (parentmsgs == NULL)
2014                 parentmsgs = g_hash_table_new(NULL, NULL); 
2015
2016         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2017         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2018
2019         return result;
2020 }
2021
2022 /*!
2023  *\brief        Check if msgs prior in thread are marked
2024  *              See procmsg_msg_has_flagged_parent_real()
2025  */
2026 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2027 {
2028         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2029 }
2030
2031
2032 static GSList *procmsg_find_children_func(MsgInfo *info, 
2033                                    GSList *children, GSList *all)
2034 {
2035         GSList *cur;
2036
2037         g_return_val_if_fail(info!=NULL, children);
2038         if (info->msgid == NULL)
2039                 return children;
2040
2041         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2042                 MsgInfo *tmp = (MsgInfo *)cur->data;
2043                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2044                         /* Check if message is already in the list */
2045                         if ((children == NULL) || 
2046                             (g_slist_index(children, tmp) == -1)) {
2047                                 children = g_slist_prepend(children,
2048                                                 procmsg_msginfo_new_ref(tmp));
2049                                 children = procmsg_find_children_func(tmp, 
2050                                                         children, 
2051                                                         all);
2052                         }
2053                 }
2054         }
2055         return children;
2056 }
2057
2058 static GSList *procmsg_find_children (MsgInfo *info)
2059 {
2060         GSList *children;
2061         GSList *all, *cur;
2062
2063         g_return_val_if_fail(info!=NULL, NULL);
2064         all = folder_item_get_msg_list(info->folder);
2065         children = procmsg_find_children_func(info, NULL, all);
2066         if (children != NULL) {
2067                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2068                         /* this will not free the used pointers
2069                            created with procmsg_msginfo_new_ref */
2070                         procmsg_msginfo_free((MsgInfo *)cur->data);
2071                 }
2072         }
2073         g_slist_free(all);
2074
2075         return children;
2076 }
2077
2078 static void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2079 {
2080         GSList *children = procmsg_find_children(info);
2081         GSList *cur;
2082         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2083                 MsgInfo *tmp = (MsgInfo *)cur->data;
2084                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2085                         if(newly_marked) 
2086                                 info->folder->unreadmarked_msgs++;
2087                         else
2088                                 info->folder->unreadmarked_msgs--;
2089                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2090                 }
2091                 procmsg_msginfo_free(tmp);
2092         }
2093         g_slist_free(children);
2094 }
2095
2096 /**
2097  * Set the destination folder for a copy or move operation
2098  *
2099  * \param msginfo The message which's destination folder is changed
2100  * \param to_folder The destination folder for the operation
2101  */
2102 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2103 {
2104         if(msginfo->to_folder != NULL) {
2105                 msginfo->to_folder->op_count--;
2106                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2107         }
2108         msginfo->to_folder = to_folder;
2109         if(to_folder != NULL) {
2110                 to_folder->op_count++;
2111                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2112         }
2113 }
2114
2115 /**
2116  * Apply filtering actions to the msginfo
2117  *
2118  * \param msginfo The MsgInfo describing the message that should be filtered
2119  * \return TRUE if the message was moved and MsgInfo is now invalid,
2120  *         FALSE otherwise
2121  */
2122 static gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2123 {
2124         MailFilteringData mail_filtering_data;
2125                         
2126         mail_filtering_data.msginfo = msginfo;                  
2127         mail_filtering_data.msglist = NULL;                     
2128         mail_filtering_data.filtered = NULL;                    
2129         mail_filtering_data.unfiltered = NULL;
2130         mail_filtering_data.account = ac_prefs; 
2131
2132         if (!ac_prefs || ac_prefs->filterhook_on_recv)
2133                 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
2134                 return TRUE;
2135
2136         /* filter if enabled in prefs or move to inbox if not */
2137         if((filtering_rules != NULL) &&
2138                 filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs,
2139                                 FILTERING_INCORPORATION, NULL)) {
2140                 return TRUE;
2141         }
2142                 
2143         return FALSE;
2144 }
2145
2146 void procmsg_msglist_filter(GSList *list, PrefsAccount *ac, 
2147                             GSList **filtered, GSList **unfiltered,
2148                             gboolean do_filter)
2149 {
2150         GSList *cur, *to_do = NULL;
2151         gint total = 0, curnum = 0;
2152         MailFilteringData mail_filtering_data;
2153                         
2154         g_return_if_fail(filtered != NULL);
2155         g_return_if_fail(unfiltered != NULL);
2156
2157         *filtered = NULL;
2158         *unfiltered = NULL;
2159         
2160         if (list == NULL)
2161                 return;
2162
2163         total = g_slist_length(list);
2164
2165         if (!do_filter) {
2166                 *filtered = NULL;
2167                 *unfiltered = g_slist_copy(list);
2168                 return;
2169         }
2170
2171         statusbar_print_all(_("Filtering messages...\n"));
2172
2173         mail_filtering_data.msginfo = NULL;                     
2174         mail_filtering_data.msglist = list;                     
2175         mail_filtering_data.filtered = NULL;                    
2176         mail_filtering_data.unfiltered = NULL;  
2177         mail_filtering_data.account = ac;       
2178                         
2179         if (!ac || ac->filterhook_on_recv)
2180         hooks_invoke(MAIL_LISTFILTERING_HOOKLIST, &mail_filtering_data);
2181         
2182         if (mail_filtering_data.filtered == NULL &&
2183             mail_filtering_data.unfiltered == NULL) {
2184                 /* nothing happened */
2185                 debug_print(MAIL_LISTFILTERING_HOOKLIST " did nothing. filtering whole list normally.\n");
2186                 to_do = list;
2187         } 
2188         if (mail_filtering_data.filtered != NULL) {
2189                 /* keep track of what's been filtered by the hooks */
2190                 debug_print(MAIL_LISTFILTERING_HOOKLIST " filtered some stuff. total %d filtered %d unfilt %d.\n",
2191                         g_slist_length(list),
2192                         g_slist_length(mail_filtering_data.filtered),
2193                         g_slist_length(mail_filtering_data.unfiltered));
2194
2195                 *filtered = g_slist_copy(mail_filtering_data.filtered);
2196         }
2197         if (mail_filtering_data.unfiltered != NULL) {
2198                 /* what the hooks didn't handle will go in filtered or 
2199                  * unfiltered in the next loop */
2200                 debug_print(MAIL_LISTFILTERING_HOOKLIST " left unfiltered stuff. total %d filtered %d unfilt %d.\n",
2201                         g_slist_length(list),
2202                         g_slist_length(mail_filtering_data.filtered),
2203                         g_slist_length(mail_filtering_data.unfiltered));
2204                 to_do = mail_filtering_data.unfiltered;
2205         } 
2206
2207         for (cur = to_do; cur; cur = cur->next) {
2208                 MsgInfo *info = (MsgInfo *)cur->data;
2209                 if (procmsg_msginfo_filter(info, ac))
2210                         *filtered = g_slist_prepend(*filtered, info);
2211                 else
2212                         *unfiltered = g_slist_prepend(*unfiltered, info);
2213                 statusbar_progress_all(curnum++, total, prefs_common.statusbar_update_step);
2214         }
2215
2216         g_slist_free(mail_filtering_data.filtered);
2217         g_slist_free(mail_filtering_data.unfiltered);
2218         
2219         *filtered = g_slist_reverse(*filtered);
2220         *unfiltered = g_slist_reverse(*unfiltered);
2221
2222         statusbar_progress_all(0,0,0);
2223         statusbar_pop_all();
2224 }
2225
2226 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2227 {
2228         MsgInfo *tmp_msginfo = NULL;
2229         MsgFlags flags = {0, 0};
2230         gchar *tmpfile = get_tmp_file();
2231         FILE *fp = g_fopen(tmpfile, "wb");
2232         
2233         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2234             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2235                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2236                 if (fp) 
2237                         fclose(fp);
2238                 g_free(tmpfile);
2239                 return NULL;
2240         }
2241         
2242         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2243                 fclose(fp);
2244                 fp = NULL;
2245                 tmp_msginfo = procheader_parse_file(
2246                         tmpfile, flags, 
2247                         TRUE, FALSE);
2248         }
2249         if (fp)
2250                 fclose(fp);
2251
2252         if (tmp_msginfo != NULL) {
2253                 if (src_msginfo)
2254                         tmp_msginfo->folder = src_msginfo->folder;
2255                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2256         } else {
2257                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2258         }
2259
2260         g_free(tmpfile);
2261
2262         return tmp_msginfo;
2263 }
2264
2265 static GSList *spam_learners = NULL;
2266
2267 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2268 {
2269         if (!g_slist_find(spam_learners, learn_func))
2270                 spam_learners = g_slist_append(spam_learners, learn_func);
2271         if (mainwindow_get_mainwindow()) {
2272                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2273                 summary_set_menu_sensitive(
2274                         mainwindow_get_mainwindow()->summaryview);
2275                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2276         }
2277 }
2278
2279 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2280 {
2281         spam_learners = g_slist_remove(spam_learners, learn_func);
2282         if (mainwindow_get_mainwindow()) {
2283                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2284                 summary_set_menu_sensitive(
2285                         mainwindow_get_mainwindow()->summaryview);
2286                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2287         }
2288 }
2289
2290 gboolean procmsg_spam_can_learn(void)
2291 {
2292         return g_slist_length(spam_learners) > 0;
2293 }
2294
2295 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2296 {
2297         GSList *cur = spam_learners;
2298         int ret = 0;
2299         for (; cur; cur = cur->next) {
2300                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2301                 ret |= func(info, list, spam);
2302         }
2303         return ret;
2304 }
2305
2306 static gchar *spam_folder_item = NULL;
2307 static FolderItem * (*procmsg_spam_get_folder_func)(MsgInfo *msginfo) = NULL;
2308 void procmsg_spam_set_folder (const char *item_identifier, FolderItem *(*spam_get_folder_func)(MsgInfo *info))
2309 {
2310         g_free(spam_folder_item);
2311         if (item_identifier)
2312                 spam_folder_item = g_strdup(item_identifier);
2313         else
2314                 spam_folder_item = NULL;
2315         if (spam_get_folder_func != NULL)
2316                 procmsg_spam_get_folder_func = spam_get_folder_func;
2317         else
2318                 procmsg_spam_get_folder_func = NULL;
2319 }
2320
2321 FolderItem *procmsg_spam_get_folder (MsgInfo *msginfo)
2322 {
2323         FolderItem *item = NULL;
2324         
2325         if (procmsg_spam_get_folder_func) 
2326                 item = procmsg_spam_get_folder_func(msginfo);
2327         if (item == NULL && spam_folder_item)
2328                 item = folder_find_item_from_identifier(spam_folder_item);
2329         if (item == NULL)
2330                 item = folder_get_default_trash();
2331         return item;
2332 }
2333
2334 static void item_has_queued_mails(FolderItem *item, gpointer data)
2335 {
2336         gboolean *result = (gboolean *)data;
2337         if (*result == TRUE)
2338                 return;
2339         if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0)
2340                 *result = TRUE;
2341 }
2342
2343 gboolean procmsg_have_queued_mails_fast (void)
2344 {
2345         gboolean result = FALSE;
2346         folder_func_to_all_folders(item_has_queued_mails, &result);
2347         return result;
2348 }
2349
2350 static void item_has_trashed_mails(FolderItem *item, gpointer data)
2351 {
2352         gboolean *result = (gboolean *)data;
2353         if (*result == TRUE)
2354                 return;
2355         if (folder_has_parent_of_type(item, F_TRASH) && item->total_msgs > 0)
2356                 *result = TRUE;
2357 }
2358
2359 gboolean procmsg_have_trashed_mails_fast (void)
2360 {
2361         gboolean result = FALSE;
2362         folder_func_to_all_folders(item_has_trashed_mails, &result);
2363         return result;
2364 }
2365
2366 gchar *procmsg_msginfo_get_tags_str(MsgInfo *msginfo)
2367 {
2368         GSList *cur = NULL;
2369         gchar *tags = NULL;
2370         
2371         if (!msginfo)
2372                 return NULL;
2373
2374         if (msginfo->tags == NULL)
2375                 return NULL;
2376         for (cur = msginfo->tags; cur; cur = cur->next) {
2377                 const gchar *tag = tags_get_tag(GPOINTER_TO_INT(cur->data));
2378                 if (!tag)
2379                         continue;
2380                 if (!tags)
2381                         tags = g_strdup(tag);
2382                 else {
2383                         int olen = strlen(tags);
2384                         int nlen = olen + strlen(tag) + 2 /* strlen(", ") */;
2385                         tags = g_realloc(tags, nlen+1);
2386                         if (!tags)
2387                                 return NULL;
2388                         strcpy(tags+olen, ", ");
2389                         strcpy(tags+olen+2, tag);
2390                         tags[nlen]='\0';
2391                 }
2392         }
2393         return tags;
2394 }
2395
2396 void procmsg_msginfo_update_tags(MsgInfo *msginfo, gboolean set, gint id)
2397 {
2398         if (!set) {
2399                 msginfo->tags = g_slist_remove(
2400                                         msginfo->tags,
2401                                         GINT_TO_POINTER(id));
2402         } else {
2403                 if (!g_slist_find(msginfo->tags, GINT_TO_POINTER(id)))
2404                         msginfo->tags = g_slist_append(
2405                                         msginfo->tags,
2406                                         GINT_TO_POINTER(id));
2407         }
2408 }
2409
2410 void procmsg_msginfo_clear_tags(MsgInfo *msginfo)
2411 {
2412         g_slist_free(msginfo->tags);
2413         msginfo->tags = NULL;
2414 }