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