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