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