0173070140aad82a42cd079036378f4c25ef2767
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #include "defs.h"
21
22 #include <glib.h>
23 #include <glib/gi18n.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27
28 #include "main.h"
29 #include "utils.h"
30 #include "procmsg.h"
31 #include "procheader.h"
32 #include "send_message.h"
33 #include "procmime.h"
34 #include "statusbar.h"
35 #include "prefs_filtering.h"
36 #include "filtering.h"
37 #include "folder.h"
38 #include "prefs_common.h"
39 #include "account.h"
40 #include "alertpanel.h"
41 #include "news.h"
42 #include "hooks.h"
43 #include "msgcache.h"
44 #include "partial_download.h"
45 #include "mainwindow.h"
46 #include "summaryview.h"
47 #include "log.h"
48 #include "tags.h"
49 #include "timing.h"
50 #include "inc.h"
51
52 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr,
53                                             FolderItem *queue, gint msgnum, gboolean *queued_removed);
54 static void procmsg_update_unread_children      (MsgInfo        *info,
55                                          gboolean        newly_marked);
56 enum
57 {
58         Q_SENDER           = 0,
59         Q_SMTPSERVER       = 1,
60         Q_RECIPIENTS       = 2,
61         Q_NEWSGROUPS       = 3,
62         Q_MAIL_ACCOUNT_ID  = 4,
63         Q_NEWS_ACCOUNT_ID  = 5,
64         Q_SAVE_COPY_FOLDER = 6,
65         Q_REPLY_MESSAGE_ID = 7,
66         Q_FWD_MESSAGE_ID   = 8,
67         Q_PRIVACY_SYSTEM   = 9,
68         Q_ENCRYPT          = 10,
69         Q_ENCRYPT_DATA     = 11,
70         Q_CLAWS_HDRS       = 12,
71         Q_PRIVACY_SYSTEM_OLD = 13,
72         Q_ENCRYPT_OLD        = 14,
73         Q_ENCRYPT_DATA_OLD   = 15,
74         Q_CLAWS_HDRS_OLD     = 16,
75 };
76
77 void procmsg_msg_list_free(GSList *mlist)
78 {
79         GSList *cur;
80         MsgInfo *msginfo;
81
82         for (cur = mlist; cur != NULL; cur = cur->next) {
83                 msginfo = (MsgInfo *)cur->data;
84                 procmsg_msginfo_free(msginfo);
85         }
86         g_slist_free(mlist);
87 }
88
89 struct MarkSum {
90         gint *new_msgs;
91         gint *unread_msgs;
92         gint *total_msgs;
93         gint *min;
94         gint *max;
95         gint first;
96 };
97
98 /* CLAWS subject threading:
99   
100   in the first round it inserts subject lines in a 
101   relation (subject <-> node)
102
103   the second round finishes the threads by attaching
104   matching subject lines to the one found in the
105   relation. will use the oldest node with the same
106   subject that is not more then thread_by_subject_max_age
107   days old (see subject_relation_lookup)
108 */  
109
110 static void subject_relation_insert(GRelation *relation, GNode *node)
111 {
112         gchar *subject;
113         MsgInfo *msginfo;
114
115         g_return_if_fail(relation != NULL);
116         g_return_if_fail(node != NULL);
117         msginfo = (MsgInfo *) node->data;
118         g_return_if_fail(msginfo != NULL);
119
120         subject = msginfo->subject;
121         if (subject == NULL)
122                 return;
123         subject += subject_get_prefix_length(subject);
124
125         g_relation_insert(relation, subject, node);
126 }
127
128 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
129 {
130         gchar *subject;
131         GTuples *tuples;
132         GNode *node = NULL;
133         gint prefix_length;
134     
135         g_return_val_if_fail(relation != NULL, NULL);
136
137         subject = msginfo->subject;
138         if (subject == NULL)
139                 return NULL;
140         prefix_length = subject_get_prefix_length(subject);
141         if (prefix_length <= 0)
142                 return NULL;
143         subject += prefix_length;
144         
145         tuples = g_relation_select(relation, subject, 0);
146         if (tuples == NULL)
147                 return NULL;
148
149         if (tuples->len > 0) {
150                 int i;
151                 GNode *relation_node;
152                 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
153                 gboolean match;
154
155                 /* check all nodes with the same subject to find the best parent */
156                 for (i = 0; i < tuples->len; i++) {
157                         relation_node = (GNode *) g_tuples_index(tuples, i, 1);
158                         relation_msginfo = (MsgInfo *) relation_node->data;
159                         match = FALSE;
160
161                         /* best node should be the oldest in the found nodes */
162                         /* parent node must not be older then msginfo */
163                         if ((relation_msginfo->date_t < msginfo->date_t) &&
164                             ((best_msginfo == NULL) ||
165                              (best_msginfo->date_t > relation_msginfo->date_t)))
166                                 match = TRUE;
167
168                         /* parent node must not be more then thread_by_subject_max_age
169                            days older then msginfo */
170                         if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
171                             prefs_common.thread_by_subject_max_age * 3600 * 24)
172                                 match = FALSE;
173
174                         /* can add new tests for all matching
175                            nodes found by subject */
176
177                         if (match) {
178                                 node = relation_node;
179                                 best_msginfo = relation_msginfo;
180                         }
181                 }           
182         }
183
184         g_tuples_destroy(tuples);
185         return node;
186 }
187
188 /* return the reversed thread tree */
189 GNode *procmsg_get_thread_tree(GSList *mlist)
190 {
191         GNode *root, *parent, *node, *next;
192         GHashTable *msgid_table;
193         GRelation *subject_relation = NULL;
194         MsgInfo *msginfo;
195         const gchar *msgid;
196         GSList *reflist;
197         START_TIMING("");
198         root = g_node_new(NULL);
199         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
200         
201         if (prefs_common.thread_by_subject) {
202                 subject_relation = g_relation_new(2);
203                 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
204         }
205
206         for (; mlist != NULL; mlist = mlist->next) {
207                 msginfo = (MsgInfo *)mlist->data;
208                 parent = root;
209
210                 if (msginfo->inreplyto) {
211                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
212                         if (parent == NULL) {
213                                 parent = root;
214                         }
215                 }
216                 node = g_node_insert_data_before
217                         (parent, parent == root ? parent->children : NULL,
218                          msginfo);
219                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
220                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
221
222                 /* CLAWS: add subject to relation (without prefix) */
223                 if (prefs_common.thread_by_subject) {
224                         subject_relation_insert(subject_relation, node);
225                 }
226         }
227
228         /* complete the unfinished threads */
229         for (node = root->children; node != NULL; ) {
230                 next = node->next;
231                 msginfo = (MsgInfo *)node->data;
232                 parent = NULL;
233                 
234                 if (msginfo->inreplyto)
235                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
236
237                 /* try looking for the indirect parent */
238                 if (!parent && msginfo->references) {
239                         for (reflist = msginfo->references;
240                              reflist != NULL; reflist = reflist->next)
241                                 if ((parent = g_hash_table_lookup
242                                         (msgid_table, reflist->data)) != NULL)
243                                         break;
244                 }                                        
245               
246                 /* node should not be the parent, and node should not
247                    be an ancestor of parent (circular reference) */
248                 if (parent && parent != node &&
249                     !g_node_is_ancestor(node, parent)) {
250                         g_node_unlink(node);
251                         g_node_insert_before
252                                 (parent, parent->children, node);
253                 }
254                
255                 node = next;
256         }
257
258         if (prefs_common.thread_by_subject) {
259                 START_TIMING("thread by subject");
260                 for (node = root->children; node && node != NULL;) {
261                         next = node->next;
262                         msginfo = (MsgInfo *) node->data;
263                         
264                         parent = subject_relation_lookup(subject_relation, msginfo);
265                         
266                         /* the node may already be threaded by IN-REPLY-TO, so go up 
267                          * in the tree to 
268                            find the parent node */
269                         if (parent != NULL) {
270                                 if (g_node_is_ancestor(node, parent))
271                                         parent = NULL;
272                                 if (parent == node)
273                                         parent = NULL;
274                         }
275                         
276                         if (parent) {
277                                 g_node_unlink(node);
278                                 g_node_append(parent, node);
279                         }
280
281                         node = next;
282                 }       
283                 END_TIMING();
284         }
285         
286         if (prefs_common.thread_by_subject)
287                 g_relation_destroy(subject_relation);
288
289         g_hash_table_destroy(msgid_table);
290         END_TIMING();
291         return root;
292 }
293
294 gint procmsg_move_messages(GSList *mlist)
295 {
296         GSList *cur, *movelist = NULL;
297         MsgInfo *msginfo;
298         FolderItem *dest = NULL;
299         gint retval = 0;
300         gboolean finished = TRUE;
301         if (!mlist) return 0;
302
303         folder_item_update_freeze();
304
305 next_folder:
306         for (cur = mlist; cur != NULL; cur = cur->next) {
307                 msginfo = (MsgInfo *)cur->data;
308                 if (!msginfo->to_folder) {
309                         continue;
310                 } else {
311                         finished = FALSE;
312                 }
313                 if (!dest) {
314                         dest = msginfo->to_folder;
315                         movelist = g_slist_prepend(movelist, msginfo);
316                 } else if (dest == msginfo->to_folder) {
317                         movelist = g_slist_prepend(movelist, msginfo);
318                 } else {
319                         continue;
320                 }
321                 procmsg_msginfo_set_to_folder(msginfo, NULL);
322         }
323         if (movelist) {
324                 movelist = g_slist_reverse(movelist);
325                 retval |= folder_item_move_msgs(dest, movelist);
326                 g_slist_free(movelist);
327                 movelist = NULL;
328         }
329         if (finished == FALSE) {
330                 finished = TRUE;
331                 dest = NULL;
332                 goto next_folder;
333         }
334
335         folder_item_update_thaw();
336         return retval;
337 }
338
339 void procmsg_copy_messages(GSList *mlist)
340 {
341         GSList *cur, *copylist = NULL;
342         MsgInfo *msginfo;
343         FolderItem *dest = NULL;
344         gboolean finished = TRUE;
345         if (!mlist) return;
346
347         folder_item_update_freeze();
348
349 next_folder:
350         for (cur = mlist; cur != NULL; cur = cur->next) {
351                 msginfo = (MsgInfo *)cur->data;
352                 if (!msginfo->to_folder) {
353                         continue;
354                 } else {
355                         finished = FALSE;
356                 }
357                 if (!dest) {
358                         dest = msginfo->to_folder;
359                         copylist = g_slist_prepend(copylist, msginfo);
360                 } else if (dest == msginfo->to_folder) {
361                         copylist = g_slist_prepend(copylist, msginfo);
362                 } else {
363                         continue;
364                 }
365                 procmsg_msginfo_set_to_folder(msginfo, NULL);
366         }
367         if (copylist) {
368                 copylist = g_slist_reverse(copylist);
369                 folder_item_copy_msgs(dest, copylist);
370                 g_slist_free(copylist);
371                 copylist = NULL;
372         }
373         if (finished == FALSE) {
374                 finished = TRUE;
375                 dest = NULL;
376                 goto next_folder;
377         }
378
379         folder_item_update_thaw();
380 }
381
382 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
383 {
384         gchar *file;
385
386         g_return_val_if_fail(msginfo != NULL, NULL);
387
388         if (msginfo->plaintext_file)
389                 file = g_strdup(msginfo->plaintext_file);
390         else {
391                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
392         }
393
394         return file;
395 }
396
397 gchar *procmsg_get_message_file(MsgInfo *msginfo)
398 {
399         gchar *filename = NULL;
400
401         g_return_val_if_fail(msginfo != NULL, NULL);
402
403         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
404         if (!filename)
405                 debug_print("can't fetch message %d\n", msginfo->msgnum);
406
407         return filename;
408 }
409
410 gchar *procmsg_get_message_file_full(MsgInfo *msginfo, gboolean headers, gboolean body)
411 {
412         gchar *filename = NULL;
413
414         g_return_val_if_fail(msginfo != NULL, NULL);
415
416         filename = folder_item_fetch_msg_full(msginfo->folder, msginfo->msgnum,
417                                                 headers, body);
418         if (!filename)
419                 debug_print("can't fetch message %d\n", msginfo->msgnum);
420
421         return filename;
422 }
423
424 GSList *procmsg_get_message_file_list(GSList *mlist)
425 {
426         GSList *file_list = NULL;
427         MsgInfo *msginfo;
428         MsgFileInfo *fileinfo;
429         gchar *file;
430
431         while (mlist != NULL) {
432                 msginfo = (MsgInfo *)mlist->data;
433                 file = procmsg_get_message_file(msginfo);
434                 if (!file) {
435                         procmsg_message_file_list_free(file_list);
436                         return NULL;
437                 }
438                 fileinfo = g_new(MsgFileInfo, 1);
439                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
440                 fileinfo->file = file;
441                 fileinfo->flags = g_new(MsgFlags, 1);
442                 *fileinfo->flags = msginfo->flags;
443                 file_list = g_slist_prepend(file_list, fileinfo);
444                 mlist = mlist->next;
445         }
446
447         file_list = g_slist_reverse(file_list);
448
449         return file_list;
450 }
451
452 void procmsg_message_file_list_free(MsgInfoList *file_list)
453 {
454         GSList *cur;
455         MsgFileInfo *fileinfo;
456
457         for (cur = file_list; cur != NULL; cur = cur->next) {
458                 fileinfo = (MsgFileInfo *)cur->data;
459                 procmsg_msginfo_free(fileinfo->msginfo);
460                 g_free(fileinfo->file);
461                 g_free(fileinfo->flags);
462                 g_free(fileinfo);
463         }
464
465         g_slist_free(file_list);
466 }
467
468 FILE *procmsg_open_message(MsgInfo *msginfo)
469 {
470         FILE *fp;
471         gchar *file;
472
473         g_return_val_if_fail(msginfo != NULL, NULL);
474
475         file = procmsg_get_message_file_path(msginfo);
476         g_return_val_if_fail(file != NULL, NULL);
477
478         if (!is_file_exist(file)) {
479                 g_free(file);
480                 file = procmsg_get_message_file(msginfo);
481                 if (!file)
482                         return NULL;
483         }
484
485         if ((fp = g_fopen(file, "rb")) == NULL) {
486                 FILE_OP_ERROR(file, "fopen");
487                 g_free(file);
488                 return NULL;
489         }
490
491         g_free(file);
492
493         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
494                 gchar buf[BUFFSIZE];
495
496                 while (fgets(buf, sizeof(buf), fp) != NULL) {
497                         /* new way */
498                         if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
499                                 strlen("X-Claws-End-Special-Headers:"))) ||
500                             (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
501                                 strlen("X-Sylpheed-End-Special-Headers:"))))
502                                 break;
503                         /* old way */
504                         if (buf[0] == '\r' || buf[0] == '\n') break;
505                         /* from other mailers */
506                         if (!strncmp(buf, "Date: ", 6)
507                         ||  !strncmp(buf, "To: ", 4)
508                         ||  !strncmp(buf, "From: ", 6)
509                         ||  !strncmp(buf, "Subject: ", 9)) {
510                                 rewind(fp);
511                                 break;
512                         }
513                 }
514         }
515
516         return fp;
517 }
518
519 gboolean procmsg_msg_exist(MsgInfo *msginfo)
520 {
521         gchar *path;
522         gboolean ret;
523
524         if (!msginfo) return FALSE;
525
526         path = folder_item_get_path(msginfo->folder);
527         change_dir(path);
528         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
529         g_free(path);
530
531         return ret;
532 }
533
534 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
535                                 PrefsFilterType type)
536 {
537         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
538                                        {"X-ML-Name:",      NULL, TRUE},
539                                        {"X-List:",         NULL, TRUE},
540                                        {"X-Mailing-list:", NULL, TRUE},
541                                        {"List-Id:",        NULL, TRUE},
542                                        {"X-Sequence:",     NULL, TRUE},
543                                        {"Sender:",         NULL, TRUE},
544                                        {"List-Post:",      NULL, TRUE},
545                                        {NULL,              NULL, FALSE}};
546         enum
547         {
548                 H_X_BEENTHERE    = 0,
549                 H_X_ML_NAME      = 1,
550                 H_X_LIST         = 2,
551                 H_X_MAILING_LIST = 3,
552                 H_LIST_ID        = 4,
553                 H_X_SEQUENCE     = 5,
554                 H_SENDER         = 6,
555                 H_LIST_POST      = 7
556         };
557
558         FILE *fp;
559
560         g_return_if_fail(msginfo != NULL);
561         g_return_if_fail(header != NULL);
562         g_return_if_fail(key != NULL);
563
564         *header = NULL;
565         *key = NULL;
566
567         switch (type) {
568         case FILTER_BY_NONE:
569                 return;
570         case FILTER_BY_AUTO:
571                 if ((fp = procmsg_open_message(msginfo)) == NULL)
572                         return;
573                 procheader_get_header_fields(fp, hentry);
574                 fclose(fp);
575
576 #define SET_FILTER_KEY(hstr, idx)       \
577 {                                       \
578         *header = g_strdup(hstr);       \
579         *key = hentry[idx].body;        \
580         hentry[idx].body = NULL;        \
581 }
582
583                 if (hentry[H_X_BEENTHERE].body != NULL) {
584                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
585                 } else if (hentry[H_X_ML_NAME].body != NULL) {
586                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
587                 } else if (hentry[H_X_LIST].body != NULL) {
588                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
589                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
590                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
591                 } else if (hentry[H_LIST_ID].body != NULL) {
592                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
593                         extract_list_id_str(*key);
594                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
595                         gchar *p;
596
597                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
598                         p = *key;
599                         while (*p != '\0') {
600                                 while (*p != '\0' && !g_ascii_isspace(*p)) p++;
601                                 while (g_ascii_isspace(*p)) p++;
602                                 if (g_ascii_isdigit(*p)) {
603                                         *p = '\0';
604                                         break;
605                                 }
606                         }
607                         g_strstrip(*key);
608                 } else if (hentry[H_SENDER].body != NULL) {
609                         SET_FILTER_KEY("header \"Sender\"", H_SENDER);
610                 } else if (hentry[H_LIST_POST].body != NULL) {
611                         SET_FILTER_KEY("header \"List-Post\"", H_LIST_POST);
612                 } else if (msginfo->to) {
613                         *header = g_strdup("to");
614                         *key = g_strdup(msginfo->to);
615                 } else if (msginfo->subject) {
616                         *header = g_strdup("subject");
617                         *key = g_strdup(msginfo->subject);
618                 }
619
620 #undef SET_FILTER_KEY
621
622                 g_free(hentry[H_X_BEENTHERE].body);
623                 hentry[H_X_BEENTHERE].body = NULL;
624                 g_free(hentry[H_X_ML_NAME].body);
625                 hentry[H_X_ML_NAME].body = NULL;
626                 g_free(hentry[H_X_LIST].body);
627                 hentry[H_X_LIST].body = NULL;
628                 g_free(hentry[H_X_MAILING_LIST].body);
629                 hentry[H_X_MAILING_LIST].body = NULL;
630                 g_free(hentry[H_LIST_ID].body);
631                 hentry[H_LIST_ID].body = NULL;
632                 g_free(hentry[H_SENDER].body);
633                 hentry[H_SENDER].body = NULL;
634                 g_free(hentry[H_LIST_POST].body);
635                 hentry[H_LIST_POST].body = NULL;
636
637                 break;
638         case FILTER_BY_FROM:
639                 *header = g_strdup("from");
640                 *key = g_strdup(msginfo->from);
641                 break;
642         case FILTER_BY_TO:
643                 *header = g_strdup("to");
644                 *key = g_strdup(msginfo->to);
645                 break;
646         case FILTER_BY_SUBJECT:
647                 *header = g_strdup("subject");
648                 *key = g_strdup(msginfo->subject);
649                 break;
650         default:
651                 break;
652         }
653 }
654
655 static void procmsg_empty_trash(FolderItem *trash)
656 {
657         GNode *node, *next;
658
659         if (!trash || 
660             (trash->stype != F_TRASH && 
661              !folder_has_parent_of_type(trash, F_TRASH)))
662                 return;
663
664         if (trash && trash->total_msgs > 0) {
665                 GSList *mlist = folder_item_get_msg_list(trash);
666                 GSList *cur;
667                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
668                         MsgInfo * msginfo = (MsgInfo *) cur->data;
669                         if (MSG_IS_LOCKED(msginfo->flags)) {
670                                 procmsg_msginfo_free(msginfo);
671                                 continue;
672                         }
673                         if (msginfo->total_size != 0 && 
674                             msginfo->size != (off_t)msginfo->total_size)
675                                 partial_mark_for_delete(msginfo);
676
677                         procmsg_msginfo_free(msginfo);
678                 }
679                 g_slist_free(mlist);
680                 folder_item_remove_all_msg(trash);
681         }
682
683         if (!trash->node || !trash->node->children)
684                 return;
685
686         node = trash->node->children;
687         while (node != NULL) {
688                 next = node->next;
689                 procmsg_empty_trash(FOLDER_ITEM(node->data));
690                 node = next;
691         }
692 }
693
694 void procmsg_empty_all_trash(void)
695 {
696         FolderItem *trash;
697         GList *cur;
698
699         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
700                 Folder *folder = FOLDER(cur->data);
701                 trash = folder->trash;
702                 procmsg_empty_trash(trash);
703                 if (folder->account && folder->account->set_trash_folder && 
704                     folder_find_item_from_identifier(folder->account->trash_folder))
705                         procmsg_empty_trash(
706                                 folder_find_item_from_identifier(folder->account->trash_folder));
707         }
708 }
709
710 static PrefsAccount *procmsg_get_account_from_file(const gchar *file)
711 {
712         PrefsAccount *mailac = NULL;
713         FILE *fp;
714         int hnum;
715         gchar buf[BUFFSIZE];
716         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
717                                        {"SSV:",  NULL, FALSE},
718                                        {"R:",    NULL, FALSE},
719                                        {"NG:",   NULL, FALSE},
720                                        {"MAID:", NULL, FALSE},
721                                        {"NAID:", NULL, FALSE},
722                                        {"SCF:",  NULL, FALSE},
723                                        {"RMID:", NULL, FALSE},
724                                        {"FMID:", NULL, FALSE},
725                                        {"X-Claws-Privacy-System:", NULL, FALSE},
726                                        {"X-Claws-Encrypt:", NULL, FALSE},
727                                        {"X-Claws-Encrypt-Data:", NULL, FALSE},
728                                        {"X-Claws-End-Special-Headers",    NULL, FALSE},
729                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
730                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
731                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
732                                        {NULL,    NULL, FALSE}};
733         
734         g_return_val_if_fail(file != NULL, NULL);
735
736         if ((fp = g_fopen(file, "rb")) == NULL) {
737                 FILE_OP_ERROR(file, "fopen");
738                 return NULL;
739         }
740
741         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
742                != -1) {
743                 gchar *p = buf + strlen(qentry[hnum].name);
744
745                 if (hnum == Q_MAIL_ACCOUNT_ID) {
746                         mailac = account_find_from_id(atoi(p));
747                         break;
748                 }
749         }
750         fclose(fp);
751         return mailac;
752 }
753
754 static GSList *procmsg_list_sort_by_account(FolderItem *queue, GSList *list)
755 {
756         GSList *result = NULL;
757         GSList *orig = NULL;
758         PrefsAccount *last_account = NULL;
759         MsgInfo *msg = NULL;
760         GSList *cur = NULL;
761         gboolean nothing_to_sort = TRUE;
762
763         if (!list)
764                 return NULL;
765
766         orig = g_slist_copy(list);
767         
768         msg = (MsgInfo *)orig->data;
769         
770         for (cur = orig; cur; cur = cur->next)
771                 debug_print("sort before %s\n", ((MsgInfo *)cur->data)->from);
772         
773         debug_print("\n");
774
775 parse_again:    
776         nothing_to_sort = TRUE;
777         cur = orig;
778         while (cur) {
779                 gchar *file = NULL;
780                 PrefsAccount *ac = NULL;
781                 msg = (MsgInfo *)cur->data;
782                 file = folder_item_fetch_msg(queue, msg->msgnum);
783                 ac = procmsg_get_account_from_file(file);
784                 g_free(file);
785
786                 if (last_account == NULL || (ac != NULL && ac == last_account)) {
787                         result = g_slist_append(result, msg);
788                         orig = g_slist_remove(orig, msg);
789                         last_account = ac;
790                         nothing_to_sort = FALSE;
791                         goto parse_again;
792                 }
793                 cur = cur->next;
794         }
795         
796         if (orig || g_slist_length(orig)) {
797                 if (!last_account && nothing_to_sort) {
798                         /* can't find an account for the rest of the list */
799                         cur = orig;
800                         while (cur) {
801                                 result = g_slist_append(result, cur->data);
802                                 cur = cur->next;
803                         }
804                 } else {
805                         last_account = NULL;
806                         goto parse_again;
807                 }
808         }
809         
810         g_slist_free(orig);
811         
812         for (cur = result; cur; cur = cur->next)
813                 debug_print("sort after %s\n", ((MsgInfo *)cur->data)->from);
814
815         debug_print("\n");
816
817         return result;
818 }
819
820 static gboolean procmsg_is_last_for_account(FolderItem *queue, MsgInfo *msginfo, GSList *elem)
821 {
822         gchar *file = folder_item_fetch_msg(queue, msginfo->msgnum);
823         PrefsAccount *ac = procmsg_get_account_from_file(file);
824         GSList *cur = elem;
825         g_free(file);
826         for (cur = elem; cur; cur = cur->next) {
827                 MsgInfo *cur_msginfo = (MsgInfo *)cur->data;
828                 file = folder_item_fetch_msg(queue, cur_msginfo->msgnum);
829                 
830                 if (cur_msginfo != msginfo && !MSG_IS_LOCKED(cur_msginfo->flags)) {
831                         if (procmsg_get_account_from_file(file) == ac) {
832                                 g_free(file);
833                                 return FALSE;
834                         }
835                 }
836                 
837                 g_free(file);
838         }
839         return TRUE;
840 }
841
842 static gboolean send_queue_lock = FALSE;
843 /*!
844  *\brief        Send messages in queue
845  *
846  *\param        queue Queue folder to process
847  *\param        save_msgs Unused
848  *
849  *\return       Number of messages sent, negative if an error occurred
850  *              positive if no error occurred
851  */
852 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs, gchar **errstr)
853 {
854         gint sent = 0, err = 0;
855         GSList *list, *elem;
856         GSList *sorted_list = NULL;
857         GNode *node, *next;
858         
859         if (send_queue_lock) {
860                 /* Avoid having to translate two similar strings */
861                 log_warning(LOG_PROTOCOL, "%s\n", _("Already trying to send."));
862                 if (errstr) {
863                         if (*errstr) g_free(*errstr);
864                         *errstr = g_strdup_printf(_("Already trying to send."));
865                 }
866                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
867                 return -1;
868         }
869         send_queue_lock = TRUE;
870         inc_lock();
871         if (!queue)
872                 queue = folder_get_default_queue();
873         
874         if (queue == NULL) {
875                 send_queue_lock = FALSE;
876                 inc_unlock();
877                 return -1;
878         }
879
880         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
881
882         folder_item_scan(queue);
883         list = folder_item_get_msg_list(queue);
884
885         /* sort the list per sender account; this helps reusing the same SMTP server */
886         sorted_list = procmsg_list_sort_by_account(queue, list);
887         
888         for (elem = sorted_list; elem != NULL; elem = elem->next) {
889                 gchar *file;
890                 MsgInfo *msginfo;
891                         
892                 msginfo = (MsgInfo *)(elem->data);
893                 if (!MSG_IS_LOCKED(msginfo->flags)) {
894                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
895                         if (file) {
896                                 gboolean queued_removed = FALSE;
897                                 if (procmsg_send_message_queue_full(file, 
898                                                 !procmsg_is_last_for_account(queue, msginfo, elem),
899                                                 errstr, queue, msginfo->msgnum, &queued_removed) < 0) {
900                                         g_warning("Sending queued message %d failed.\n", 
901                                                   msginfo->msgnum);
902                                         err++;
903                                 } else {
904                                         sent++; 
905                                         if (!queued_removed)
906                                                 folder_item_remove_msg(queue, msginfo->msgnum);
907                                 }
908                                 g_free(file);
909                         }
910                 }
911                 /* FIXME: supposedly if only one message is locked, and queue
912                  * is being flushed, the following free says something like 
913                  * "freeing msg ## in folder (nil)". */
914                 procmsg_msginfo_free(msginfo);
915         }
916
917         g_slist_free(sorted_list);
918         folder_item_scan(queue);
919
920         if (queue->node && queue->node->children) {
921                 node = queue->node->children;
922                 while (node != NULL) {
923                         int res = 0;
924                         next = node->next;
925                         send_queue_lock = FALSE;
926                         res = procmsg_send_queue(FOLDER_ITEM(node->data), save_msgs, errstr);
927                         send_queue_lock = TRUE;
928                         if (res < 0) 
929                                 err = -res;
930                         else
931                                 sent += res;
932                         node = next;
933                 }
934         }
935         send_queue_lock = FALSE;
936         inc_unlock();
937         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
938
939         return (err != 0 ? -err : sent);
940 }
941
942 gboolean procmsg_is_sending(void)
943 {
944         return send_queue_lock;
945 }
946
947 /*!
948  *\brief        Determine if a queue folder is empty
949  *
950  *\param        queue Queue folder to process
951  *
952  *\return       TRUE if the queue folder is empty, otherwise return FALSE
953  */
954 gboolean procmsg_queue_is_empty(FolderItem *queue)
955 {
956         GSList *list;
957         gboolean res = FALSE;
958         if (!queue)
959                 queue = folder_get_default_queue();
960         g_return_val_if_fail(queue != NULL, TRUE);
961
962         folder_item_scan(queue);
963         list = folder_item_get_msg_list(queue);
964         res = (list == NULL);
965         procmsg_msg_list_free(list);
966
967         if (res == TRUE) {
968                 GNode *node, *next;
969                 if (queue->node && queue->node->children) {
970                         node = queue->node->children;
971                         while (node != NULL) {
972                                 next = node->next;
973                                 if (!procmsg_queue_is_empty(FOLDER_ITEM(node->data)))
974                                         return FALSE;
975                                 node = next;
976                         }
977                 }
978         }
979         return res;
980 }
981
982 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
983 {
984         FILE *fp, *outfp;
985         gchar buf[BUFFSIZE];
986         
987         if ((fp = g_fopen(in, "rb")) == NULL) {
988                 FILE_OP_ERROR(in, "fopen");
989                 return -1;
990         }
991         if ((outfp = g_fopen(out, "wb")) == NULL) {
992                 FILE_OP_ERROR(out, "fopen");
993                 fclose(fp);
994                 return -1;
995         }
996         while (fgets(buf, sizeof(buf), fp) != NULL) {
997                 /* new way */
998                 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
999                         strlen("X-Claws-End-Special-Headers:"))) ||
1000                     (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
1001                         strlen("X-Sylpheed-End-Special-Headers:"))))
1002                         break;
1003                 /* old way */
1004                 if (buf[0] == '\r' || buf[0] == '\n') break;
1005                 /* from other mailers */
1006                 if (!strncmp(buf, "Date: ", 6)
1007                 ||  !strncmp(buf, "To: ", 4)
1008                 ||  !strncmp(buf, "From: ", 6)
1009                 ||  !strncmp(buf, "Subject: ", 9)) {
1010                         rewind(fp);
1011                         break;
1012                 }
1013         }
1014         while (fgets(buf, sizeof(buf), fp) != NULL)
1015                 fputs(buf, outfp);
1016         fclose(outfp);
1017         fclose(fp);
1018         return 0;
1019 }
1020
1021 static gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
1022                             gboolean is_queued)
1023 {
1024         gint num;
1025         MsgInfo *msginfo, *tmp_msginfo;
1026         MsgFlags flag = {0, 0};
1027
1028         debug_print("saving sent message...\n");
1029
1030         if (!outbox)
1031                 outbox = folder_get_default_outbox();
1032         g_return_val_if_fail(outbox != NULL, -1);
1033
1034         /* remove queueing headers */
1035         if (is_queued) {
1036                 gchar tmp[MAXPATHLEN + 1];
1037
1038                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
1039                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
1040                 
1041                 if (procmsg_remove_special_headers(file, tmp) !=0)
1042                         return -1;
1043
1044                 folder_item_scan(outbox);
1045                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
1046                         g_warning("can't save message\n");
1047                         g_unlink(tmp);
1048                         return -1;
1049                 }
1050         } else {
1051                 folder_item_scan(outbox);
1052                 if ((num = folder_item_add_msg
1053                         (outbox, file, &flag, FALSE)) < 0) {
1054                         g_warning("can't save message\n");
1055                         return -1;
1056                 }
1057         }
1058         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
1059         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
1060         if (msginfo != NULL) {
1061                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1062                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
1063                 /* tmp_msginfo == msginfo */
1064                 if (tmp_msginfo && msginfo->extradata && 
1065                     (msginfo->extradata->dispositionnotificationto || 
1066                      msginfo->extradata->returnreceiptto)) {
1067                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
1068                 }       
1069                 procmsg_msginfo_free(tmp_msginfo);              /* refcnt-- */
1070         }
1071
1072         return 0;
1073 }
1074
1075 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
1076 {
1077         static const gchar *def_cmd = "lpr %s";
1078         static guint id = 0;
1079         gchar *prtmp;
1080         FILE *tmpfp, *prfp;
1081         gchar buf[1024];
1082         gchar *p;
1083         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%ctmp%p", g_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                         g_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                 g_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
1847 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1848 {
1849         FolderItem *item;
1850         MsgInfoUpdate msginfo_update;
1851         MsgPermFlags perm_flags_new, perm_flags_old;
1852         MsgTmpFlags tmp_flags_old;
1853
1854         g_return_if_fail(msginfo != NULL);
1855         item = msginfo->folder;
1856         g_return_if_fail(item != NULL);
1857         
1858         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1859
1860         /* Perm Flags handling */
1861         perm_flags_old = msginfo->flags.perm_flags;
1862         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1863         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1864                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1865         }
1866         if ((perm_flags & MSG_WATCH_THREAD) || (perm_flags_old & MSG_WATCH_THREAD)) {
1867                 perm_flags_new &= ~(MSG_IGNORE_THREAD);
1868         }
1869
1870         if (perm_flags_old != perm_flags_new) {
1871                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1872
1873                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1874
1875         }
1876
1877         /* Tmp flags handling */
1878         tmp_flags_old = msginfo->flags.tmp_flags;
1879         msginfo->flags.tmp_flags |= tmp_flags;
1880
1881         /* update notification */
1882         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1883                 msginfo_update.msginfo = msginfo;
1884                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1885                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1886                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1887         }
1888 }
1889
1890 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1891 {
1892         FolderItem *item;
1893         MsgInfoUpdate msginfo_update;
1894         MsgPermFlags perm_flags_new, perm_flags_old;
1895         MsgTmpFlags tmp_flags_old;
1896
1897         g_return_if_fail(msginfo != NULL);
1898         item = msginfo->folder;
1899         g_return_if_fail(item != NULL);
1900         
1901         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1902
1903         /* Perm Flags handling */
1904         perm_flags_old = msginfo->flags.perm_flags;
1905         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1906         
1907         if (perm_flags_old != perm_flags_new) {
1908                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1909
1910                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1911         }
1912
1913         /* Tmp flags hanlding */
1914         tmp_flags_old = msginfo->flags.tmp_flags;
1915         msginfo->flags.tmp_flags &= ~tmp_flags;
1916
1917         /* update notification */
1918         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1919                 msginfo_update.msginfo = msginfo;
1920                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1921                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1922                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1923         }
1924 }
1925
1926 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1927                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1928                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1929 {
1930         FolderItem *item;
1931         MsgInfoUpdate msginfo_update;
1932         MsgPermFlags perm_flags_new, perm_flags_old;
1933         MsgTmpFlags tmp_flags_old;
1934
1935         g_return_if_fail(msginfo != NULL);
1936         item = msginfo->folder;
1937         g_return_if_fail(item != NULL);
1938         
1939         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1940
1941         /* Perm Flags handling */
1942         perm_flags_old = msginfo->flags.perm_flags;
1943         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1944         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1945                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1946         }
1947         if ((add_perm_flags & MSG_WATCH_THREAD) || (perm_flags_old & MSG_WATCH_THREAD)) {
1948                 perm_flags_new &= ~(MSG_IGNORE_THREAD);
1949         }
1950
1951         if (perm_flags_old != perm_flags_new) {
1952                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1953
1954                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1955
1956         }
1957
1958         /* Tmp flags handling */
1959         tmp_flags_old = msginfo->flags.tmp_flags;
1960         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1961         msginfo->flags.tmp_flags |= add_tmp_flags;
1962
1963         /* update notification */
1964         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1965                 msginfo_update.msginfo = msginfo;
1966                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1967                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1968                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1969         }
1970 }
1971
1972 /*!
1973  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1974  *
1975  *\param        info Current message
1976  *\param        perm_flags Flags to be checked
1977  *\param        parentmsgs Hash of prior msgs to avoid loops
1978  *
1979  *\return       gboolean TRUE if perm_flags are found
1980  */
1981 static gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1982                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1983 {
1984         MsgInfo *tmp;
1985
1986         g_return_val_if_fail(info != NULL, FALSE);
1987
1988         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1989                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1990                                 info->inreplyto);
1991                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1992                         procmsg_msginfo_free(tmp);
1993                         return TRUE;
1994                 } else if (tmp != NULL) {
1995                         gboolean result;
1996
1997                         if (g_hash_table_lookup(parentmsgs, info)) {
1998                                 debug_print("loop detected: %d\n",
1999                                         info->msgnum);
2000                                 result = FALSE;
2001                         } else {
2002                                 g_hash_table_insert(parentmsgs, info, "1");
2003                                 result = procmsg_msg_has_flagged_parent_real(
2004                                     tmp, perm_flags, parentmsgs);
2005                         }
2006                         procmsg_msginfo_free(tmp);
2007                         return result;
2008                 } else {
2009                         return FALSE;
2010                 }
2011         } else
2012                 return FALSE;
2013 }
2014
2015 /*!
2016  *\brief        Callback for cleaning up hash of parentmsgs
2017  */
2018 static gboolean parentmsgs_hash_remove(gpointer key,
2019                             gpointer value,
2020                             gpointer user_data)
2021 {
2022         return TRUE;
2023 }
2024
2025 /*!
2026  *\brief        Set up list of parentmsgs
2027  *              See procmsg_msg_has_flagged_parent_real()
2028  */
2029 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
2030 {
2031         gboolean result;
2032         static GHashTable *parentmsgs = NULL;
2033         
2034         if (parentmsgs == NULL)
2035                 parentmsgs = g_hash_table_new(NULL, NULL); 
2036
2037         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2038         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2039
2040         return result;
2041 }
2042
2043 /*!
2044  *\brief        Check if msgs prior in thread are marked
2045  *              See procmsg_msg_has_flagged_parent_real()
2046  */
2047 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2048 {
2049         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2050 }
2051
2052
2053 static GSList *procmsg_find_children_func(MsgInfo *info, 
2054                                    GSList *children, GSList *all)
2055 {
2056         GSList *cur;
2057
2058         g_return_val_if_fail(info!=NULL, children);
2059         if (info->msgid == NULL)
2060                 return children;
2061
2062         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2063                 MsgInfo *tmp = (MsgInfo *)cur->data;
2064                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2065                         /* Check if message is already in the list */
2066                         if ((children == NULL) || 
2067                             (g_slist_index(children, tmp) == -1)) {
2068                                 children = g_slist_prepend(children,
2069                                                 procmsg_msginfo_new_ref(tmp));
2070                                 children = procmsg_find_children_func(tmp, 
2071                                                         children, 
2072                                                         all);
2073                         }
2074                 }
2075         }
2076         return children;
2077 }
2078
2079 static GSList *procmsg_find_children (MsgInfo *info)
2080 {
2081         GSList *children;
2082         GSList *all, *cur;
2083
2084         g_return_val_if_fail(info!=NULL, NULL);
2085         all = folder_item_get_msg_list(info->folder);
2086         children = procmsg_find_children_func(info, NULL, all);
2087         if (children != NULL) {
2088                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2089                         /* this will not free the used pointers
2090                            created with procmsg_msginfo_new_ref */
2091                         procmsg_msginfo_free((MsgInfo *)cur->data);
2092                 }
2093         }
2094         g_slist_free(all);
2095
2096         return children;
2097 }
2098
2099 static void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2100 {
2101         GSList *children = procmsg_find_children(info);
2102         GSList *cur;
2103         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2104                 MsgInfo *tmp = (MsgInfo *)cur->data;
2105                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2106                         if(newly_marked) 
2107                                 info->folder->unreadmarked_msgs++;
2108                         else
2109                                 info->folder->unreadmarked_msgs--;
2110                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2111                 }
2112                 procmsg_msginfo_free(tmp);
2113         }
2114         g_slist_free(children);
2115 }
2116
2117 /**
2118  * Set the destination folder for a copy or move operation
2119  *
2120  * \param msginfo The message which's destination folder is changed
2121  * \param to_folder The destination folder for the operation
2122  */
2123 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2124 {
2125         if(msginfo->to_folder != NULL) {
2126                 msginfo->to_folder->op_count--;
2127                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2128         }
2129         msginfo->to_folder = to_folder;
2130         if(to_folder != NULL) {
2131                 to_folder->op_count++;
2132                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2133         }
2134 }
2135
2136 /**
2137  * Apply filtering actions to the msginfo
2138  *
2139  * \param msginfo The MsgInfo describing the message that should be filtered
2140  * \return TRUE if the message was moved and MsgInfo is now invalid,
2141  *         FALSE otherwise
2142  */
2143 static gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2144 {
2145         MailFilteringData mail_filtering_data;
2146                         
2147         mail_filtering_data.msginfo = msginfo;                  
2148         mail_filtering_data.msglist = NULL;                     
2149         mail_filtering_data.filtered = NULL;                    
2150         mail_filtering_data.unfiltered = NULL;
2151         mail_filtering_data.account = ac_prefs; 
2152
2153         if (!ac_prefs || ac_prefs->filterhook_on_recv)
2154                 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
2155                 return TRUE;
2156
2157         /* filter if enabled in prefs or move to inbox if not */
2158         if((filtering_rules != NULL) &&
2159                 filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs,
2160                                 FILTERING_INCORPORATION, NULL)) {
2161                 return TRUE;
2162         }
2163                 
2164         return FALSE;
2165 }
2166
2167 void procmsg_msglist_filter(GSList *list, PrefsAccount *ac, 
2168                             GSList **filtered, GSList **unfiltered,
2169                             gboolean do_filter)
2170 {
2171         GSList *cur, *to_do = NULL;
2172         gint total = 0, curnum = 0;
2173         MailFilteringData mail_filtering_data;
2174                         
2175         g_return_if_fail(filtered != NULL);
2176         g_return_if_fail(unfiltered != NULL);
2177
2178         *filtered = NULL;
2179         *unfiltered = NULL;
2180         
2181         if (list == NULL)
2182                 return;
2183
2184         total = g_slist_length(list);
2185
2186         if (!do_filter) {
2187                 *filtered = NULL;
2188                 *unfiltered = g_slist_copy(list);
2189                 return;
2190         }
2191
2192         statusbar_print_all(_("Filtering messages...\n"));
2193
2194         mail_filtering_data.msginfo = NULL;                     
2195         mail_filtering_data.msglist = list;                     
2196         mail_filtering_data.filtered = NULL;                    
2197         mail_filtering_data.unfiltered = NULL;  
2198         mail_filtering_data.account = ac;       
2199                         
2200         if (!ac || ac->filterhook_on_recv)
2201         hooks_invoke(MAIL_LISTFILTERING_HOOKLIST, &mail_filtering_data);
2202         
2203         if (mail_filtering_data.filtered == NULL &&
2204             mail_filtering_data.unfiltered == NULL) {
2205                 /* nothing happened */
2206                 debug_print(MAIL_LISTFILTERING_HOOKLIST " did nothing. filtering whole list normally.\n");
2207                 to_do = list;
2208         } 
2209         if (mail_filtering_data.filtered != NULL) {
2210                 /* keep track of what's been filtered by the hooks */
2211                 debug_print(MAIL_LISTFILTERING_HOOKLIST " filtered some stuff. total %d filtered %d unfilt %d.\n",
2212                         g_slist_length(list),
2213                         g_slist_length(mail_filtering_data.filtered),
2214                         g_slist_length(mail_filtering_data.unfiltered));
2215
2216                 *filtered = g_slist_copy(mail_filtering_data.filtered);
2217         }
2218         if (mail_filtering_data.unfiltered != NULL) {
2219                 /* what the hooks didn't handle will go in filtered or 
2220                  * unfiltered in the next loop */
2221                 debug_print(MAIL_LISTFILTERING_HOOKLIST " left unfiltered stuff. total %d filtered %d unfilt %d.\n",
2222                         g_slist_length(list),
2223                         g_slist_length(mail_filtering_data.filtered),
2224                         g_slist_length(mail_filtering_data.unfiltered));
2225                 to_do = mail_filtering_data.unfiltered;
2226         } 
2227
2228         for (cur = to_do; cur; cur = cur->next) {
2229                 MsgInfo *info = (MsgInfo *)cur->data;
2230                 if (procmsg_msginfo_filter(info, ac))
2231                         *filtered = g_slist_prepend(*filtered, info);
2232                 else
2233                         *unfiltered = g_slist_prepend(*unfiltered, info);
2234                 statusbar_progress_all(curnum++, total, prefs_common.statusbar_update_step);
2235         }
2236
2237         g_slist_free(mail_filtering_data.filtered);
2238         g_slist_free(mail_filtering_data.unfiltered);
2239         
2240         *filtered = g_slist_reverse(*filtered);
2241         *unfiltered = g_slist_reverse(*unfiltered);
2242
2243         statusbar_progress_all(0,0,0);
2244         statusbar_pop_all();
2245 }
2246
2247 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2248 {
2249         MsgInfo *tmp_msginfo = NULL;
2250         MsgFlags flags = {0, 0};
2251         gchar *tmpfile = get_tmp_file();
2252         FILE *fp = g_fopen(tmpfile, "wb");
2253         
2254         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2255             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2256                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2257                 if (fp) 
2258                         fclose(fp);
2259                 g_free(tmpfile);
2260                 return NULL;
2261         }
2262         
2263         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2264                 fclose(fp);
2265                 fp = NULL;
2266                 tmp_msginfo = procheader_parse_file(
2267                         tmpfile, flags, 
2268                         TRUE, FALSE);
2269         }
2270         if (fp)
2271                 fclose(fp);
2272
2273         if (tmp_msginfo != NULL) {
2274                 if (src_msginfo)
2275                         tmp_msginfo->folder = src_msginfo->folder;
2276                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2277         } else {
2278                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2279         }
2280
2281         g_free(tmpfile);
2282
2283         return tmp_msginfo;
2284 }
2285
2286 static GSList *spam_learners = NULL;
2287
2288 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2289 {
2290         if (!g_slist_find(spam_learners, learn_func))
2291                 spam_learners = g_slist_append(spam_learners, learn_func);
2292         if (mainwindow_get_mainwindow()) {
2293                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2294                 summary_set_menu_sensitive(
2295                         mainwindow_get_mainwindow()->summaryview);
2296                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2297         }
2298 }
2299
2300 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2301 {
2302         spam_learners = g_slist_remove(spam_learners, learn_func);
2303         if (mainwindow_get_mainwindow()) {
2304                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2305                 summary_set_menu_sensitive(
2306                         mainwindow_get_mainwindow()->summaryview);
2307                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2308         }
2309 }
2310
2311 gboolean procmsg_spam_can_learn(void)
2312 {
2313         return g_slist_length(spam_learners) > 0;
2314 }
2315
2316 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2317 {
2318         GSList *cur = spam_learners;
2319         int ret = 0;
2320         for (; cur; cur = cur->next) {
2321                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2322                 ret |= func(info, list, spam);
2323         }
2324         return ret;
2325 }
2326
2327 static gchar *spam_folder_item = NULL;
2328 static FolderItem * (*procmsg_spam_get_folder_func)(MsgInfo *msginfo) = NULL;
2329 void procmsg_spam_set_folder (const char *item_identifier, FolderItem *(*spam_get_folder_func)(MsgInfo *info))
2330 {
2331         g_free(spam_folder_item);
2332         if (item_identifier)
2333                 spam_folder_item = g_strdup(item_identifier);
2334         else
2335                 spam_folder_item = NULL;
2336         if (spam_get_folder_func != NULL)
2337                 procmsg_spam_get_folder_func = spam_get_folder_func;
2338         else
2339                 procmsg_spam_get_folder_func = NULL;
2340 }
2341
2342 FolderItem *procmsg_spam_get_folder (MsgInfo *msginfo)
2343 {
2344         FolderItem *item = NULL;
2345         
2346         if (procmsg_spam_get_folder_func) 
2347                 item = procmsg_spam_get_folder_func(msginfo);
2348         if (item == NULL && spam_folder_item)
2349                 item = folder_find_item_from_identifier(spam_folder_item);
2350         if (item == NULL)
2351                 item = folder_get_default_trash();
2352         return item;
2353 }
2354
2355 static void item_has_queued_mails(FolderItem *item, gpointer data)
2356 {
2357         gboolean *result = (gboolean *)data;
2358         if (*result == TRUE)
2359                 return;
2360         if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0)
2361                 *result = TRUE;
2362 }
2363
2364 gboolean procmsg_have_queued_mails_fast (void)
2365 {
2366         gboolean result = FALSE;
2367         folder_func_to_all_folders(item_has_queued_mails, &result);
2368         return result;
2369 }
2370
2371 static void item_has_trashed_mails(FolderItem *item, gpointer data)
2372 {
2373         gboolean *result = (gboolean *)data;
2374         if (*result == TRUE)
2375                 return;
2376         if (folder_has_parent_of_type(item, F_TRASH) && item->total_msgs > 0)
2377                 *result = TRUE;
2378 }
2379
2380 gboolean procmsg_have_trashed_mails_fast (void)
2381 {
2382         gboolean result = FALSE;
2383         folder_func_to_all_folders(item_has_trashed_mails, &result);
2384         return result;
2385 }
2386
2387 gchar *procmsg_msginfo_get_tags_str(MsgInfo *msginfo)
2388 {
2389         GSList *cur = NULL;
2390         gchar *tags = NULL;
2391         
2392         if (!msginfo)
2393                 return NULL;
2394
2395         if (msginfo->tags == NULL)
2396                 return NULL;
2397         for (cur = msginfo->tags; cur; cur = cur->next) {
2398                 const gchar *tag = tags_get_tag(GPOINTER_TO_INT(cur->data));
2399                 if (!tag)
2400                         continue;
2401                 if (!tags)
2402                         tags = g_strdup(tag);
2403                 else {
2404                         int olen = strlen(tags);
2405                         int nlen = olen + strlen(tag) + 2 /* strlen(", ") */;
2406                         tags = g_realloc(tags, nlen+1);
2407                         if (!tags)
2408                                 return NULL;
2409                         strcpy(tags+olen, ", ");
2410                         strcpy(tags+olen+2, tag);
2411                         tags[nlen]='\0';
2412                 }
2413         }
2414         return tags;
2415 }
2416
2417 void procmsg_msginfo_update_tags(MsgInfo *msginfo, gboolean set, gint id)
2418 {
2419         if (id == 0)
2420                 return;
2421
2422         if (!set) {
2423                 msginfo->tags = g_slist_remove(
2424                                         msginfo->tags,
2425                                         GINT_TO_POINTER(id));
2426         } else {
2427                 if (!g_slist_find(msginfo->tags, GINT_TO_POINTER(id)))
2428                         msginfo->tags = g_slist_append(
2429                                         msginfo->tags,
2430                                         GINT_TO_POINTER(id));
2431         }
2432 }
2433
2434 void procmsg_msginfo_clear_tags(MsgInfo *msginfo)
2435 {
2436         g_slist_free(msginfo->tags);
2437         msginfo->tags = NULL;
2438 }