2007-06-04 [wwp] 2.9.2cvs33
[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                                 if (replymessageid != NULL) {
1749                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1750                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1751                                 }  else {
1752                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1753                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1754                                 }
1755                                 procmsg_msginfo_free(msginfo);
1756                         }
1757                 }
1758                 g_strfreev(tokens);
1759         }
1760
1761         g_free(from);
1762         g_free(smtpserver);
1763         slist_free_strings(to_list);
1764         g_slist_free(to_list);
1765         slist_free_strings(newsgroup_list);
1766         g_slist_free(newsgroup_list);
1767         g_free(savecopyfolder);
1768         g_free(replymessageid);
1769         g_free(fwdmessageid);
1770         g_free(privacy_system);
1771         g_free(encrypt_data);
1772
1773         return (newsval != 0 ? newsval : mailval);
1774 }
1775
1776 gint procmsg_send_message_queue(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1777 {
1778         gint result = procmsg_send_message_queue_full(file, FALSE, errstr, queue, msgnum, queued_removed);
1779         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1780         return result;
1781 }
1782
1783 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1784 {
1785         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1786
1787         /* NEW flag */
1788         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1789                 item->new_msgs++;
1790         }
1791
1792         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1793                 item->new_msgs--;
1794         }
1795
1796         /* UNREAD flag */
1797         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1798                 item->unread_msgs++;
1799                 if (procmsg_msg_has_marked_parent(msginfo))
1800                         item->unreadmarked_msgs++;
1801         }
1802
1803         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1804                 item->unread_msgs--;
1805                 if (procmsg_msg_has_marked_parent(msginfo))
1806                         item->unreadmarked_msgs--;
1807         }
1808         
1809         /* MARK flag */
1810         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1811                 procmsg_update_unread_children(msginfo, TRUE);
1812                 item->marked_msgs++;
1813         }
1814
1815         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1816                 procmsg_update_unread_children(msginfo, FALSE);
1817                 item->marked_msgs--;
1818         }
1819 }
1820
1821 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1822 {
1823         FolderItem *item;
1824         MsgInfoUpdate msginfo_update;
1825         MsgPermFlags perm_flags_new, perm_flags_old;
1826         MsgTmpFlags tmp_flags_old;
1827
1828         g_return_if_fail(msginfo != NULL);
1829         item = msginfo->folder;
1830         g_return_if_fail(item != NULL);
1831         
1832         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1833
1834         /* Perm Flags handling */
1835         perm_flags_old = msginfo->flags.perm_flags;
1836         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1837         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1838                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1839         }
1840
1841         if (perm_flags_old != perm_flags_new) {
1842                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1843
1844                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1845
1846         }
1847
1848         /* Tmp flags handling */
1849         tmp_flags_old = msginfo->flags.tmp_flags;
1850         msginfo->flags.tmp_flags |= tmp_flags;
1851
1852         /* update notification */
1853         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1854                 msginfo_update.msginfo = msginfo;
1855                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1856                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1857                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1858         }
1859 }
1860
1861 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1862 {
1863         FolderItem *item;
1864         MsgInfoUpdate msginfo_update;
1865         MsgPermFlags perm_flags_new, perm_flags_old;
1866         MsgTmpFlags tmp_flags_old;
1867
1868         g_return_if_fail(msginfo != NULL);
1869         item = msginfo->folder;
1870         g_return_if_fail(item != NULL);
1871         
1872         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1873
1874         /* Perm Flags handling */
1875         perm_flags_old = msginfo->flags.perm_flags;
1876         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1877         
1878         if (perm_flags_old != perm_flags_new) {
1879                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1880
1881                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1882         }
1883
1884         /* Tmp flags hanlding */
1885         tmp_flags_old = msginfo->flags.tmp_flags;
1886         msginfo->flags.tmp_flags &= ~tmp_flags;
1887
1888         /* update notification */
1889         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1890                 msginfo_update.msginfo = msginfo;
1891                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1892                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1893                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1894         }
1895 }
1896
1897 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1898                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1899                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1900 {
1901         FolderItem *item;
1902         MsgInfoUpdate msginfo_update;
1903         MsgPermFlags perm_flags_new, perm_flags_old;
1904         MsgTmpFlags tmp_flags_old;
1905
1906         g_return_if_fail(msginfo != NULL);
1907         item = msginfo->folder;
1908         g_return_if_fail(item != NULL);
1909         
1910         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1911
1912         /* Perm Flags handling */
1913         perm_flags_old = msginfo->flags.perm_flags;
1914         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1915         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1916                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1917         }
1918
1919         if (perm_flags_old != perm_flags_new) {
1920                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1921
1922                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1923
1924         }
1925
1926         /* Tmp flags handling */
1927         tmp_flags_old = msginfo->flags.tmp_flags;
1928         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1929         msginfo->flags.tmp_flags |= add_tmp_flags;
1930
1931         /* update notification */
1932         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1933                 msginfo_update.msginfo = msginfo;
1934                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1935                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1936                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1937         }
1938 }
1939
1940 /*!
1941  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1942  *
1943  *\param        info Current message
1944  *\param        perm_flags Flags to be checked
1945  *\param        parentmsgs Hash of prior msgs to avoid loops
1946  *
1947  *\return       gboolean TRUE if perm_flags are found
1948  */
1949 static gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1950                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1951 {
1952         MsgInfo *tmp;
1953
1954         g_return_val_if_fail(info != NULL, FALSE);
1955
1956         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1957                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1958                                 info->inreplyto);
1959                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1960                         procmsg_msginfo_free(tmp);
1961                         return TRUE;
1962                 } else if (tmp != NULL) {
1963                         gboolean result;
1964
1965                         if (g_hash_table_lookup(parentmsgs, info)) {
1966                                 debug_print("loop detected: %d\n",
1967                                         info->msgnum);
1968                                 result = FALSE;
1969                         } else {
1970                                 g_hash_table_insert(parentmsgs, info, "1");
1971                                 result = procmsg_msg_has_flagged_parent_real(
1972                                     tmp, perm_flags, parentmsgs);
1973                         }
1974                         procmsg_msginfo_free(tmp);
1975                         return result;
1976                 } else {
1977                         return FALSE;
1978                 }
1979         } else
1980                 return FALSE;
1981 }
1982
1983 /*!
1984  *\brief        Callback for cleaning up hash of parentmsgs
1985  */
1986 static gboolean parentmsgs_hash_remove(gpointer key,
1987                             gpointer value,
1988                             gpointer user_data)
1989 {
1990         return TRUE;
1991 }
1992
1993 /*!
1994  *\brief        Set up list of parentmsgs
1995  *              See procmsg_msg_has_flagged_parent_real()
1996  */
1997 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1998 {
1999         gboolean result;
2000         static GHashTable *parentmsgs = NULL;
2001         
2002         if (parentmsgs == NULL)
2003                 parentmsgs = g_hash_table_new(NULL, NULL); 
2004
2005         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2006         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2007
2008         return result;
2009 }
2010
2011 /*!
2012  *\brief        Check if msgs prior in thread are marked
2013  *              See procmsg_msg_has_flagged_parent_real()
2014  */
2015 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2016 {
2017         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2018 }
2019
2020
2021 static GSList *procmsg_find_children_func(MsgInfo *info, 
2022                                    GSList *children, GSList *all)
2023 {
2024         GSList *cur;
2025
2026         g_return_val_if_fail(info!=NULL, children);
2027         if (info->msgid == NULL)
2028                 return children;
2029
2030         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2031                 MsgInfo *tmp = (MsgInfo *)cur->data;
2032                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2033                         /* Check if message is already in the list */
2034                         if ((children == NULL) || 
2035                             (g_slist_index(children, tmp) == -1)) {
2036                                 children = g_slist_prepend(children,
2037                                                 procmsg_msginfo_new_ref(tmp));
2038                                 children = procmsg_find_children_func(tmp, 
2039                                                         children, 
2040                                                         all);
2041                         }
2042                 }
2043         }
2044         return children;
2045 }
2046
2047 static GSList *procmsg_find_children (MsgInfo *info)
2048 {
2049         GSList *children;
2050         GSList *all, *cur;
2051
2052         g_return_val_if_fail(info!=NULL, NULL);
2053         all = folder_item_get_msg_list(info->folder);
2054         children = procmsg_find_children_func(info, NULL, all);
2055         if (children != NULL) {
2056                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2057                         /* this will not free the used pointers
2058                            created with procmsg_msginfo_new_ref */
2059                         procmsg_msginfo_free((MsgInfo *)cur->data);
2060                 }
2061         }
2062         g_slist_free(all);
2063
2064         return children;
2065 }
2066
2067 static void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2068 {
2069         GSList *children = procmsg_find_children(info);
2070         GSList *cur;
2071         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2072                 MsgInfo *tmp = (MsgInfo *)cur->data;
2073                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2074                         if(newly_marked) 
2075                                 info->folder->unreadmarked_msgs++;
2076                         else
2077                                 info->folder->unreadmarked_msgs--;
2078                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2079                 }
2080                 procmsg_msginfo_free(tmp);
2081         }
2082         g_slist_free(children);
2083 }
2084
2085 /**
2086  * Set the destination folder for a copy or move operation
2087  *
2088  * \param msginfo The message which's destination folder is changed
2089  * \param to_folder The destination folder for the operation
2090  */
2091 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2092 {
2093         if(msginfo->to_folder != NULL) {
2094                 msginfo->to_folder->op_count--;
2095                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2096         }
2097         msginfo->to_folder = to_folder;
2098         if(to_folder != NULL) {
2099                 to_folder->op_count++;
2100                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2101         }
2102 }
2103
2104 /**
2105  * Apply filtering actions to the msginfo
2106  *
2107  * \param msginfo The MsgInfo describing the message that should be filtered
2108  * \return TRUE if the message was moved and MsgInfo is now invalid,
2109  *         FALSE otherwise
2110  */
2111 static gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2112 {
2113         MailFilteringData mail_filtering_data;
2114                         
2115         mail_filtering_data.msginfo = msginfo;                  
2116         mail_filtering_data.msglist = NULL;                     
2117         mail_filtering_data.filtered = NULL;                    
2118         mail_filtering_data.unfiltered = NULL;
2119         mail_filtering_data.account = ac_prefs; 
2120
2121         if (!ac_prefs || ac_prefs->filterhook_on_recv)
2122                 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
2123                 return TRUE;
2124
2125         /* filter if enabled in prefs or move to inbox if not */
2126         if((filtering_rules != NULL) &&
2127                 filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs,
2128                                 FILTERING_INCORPORATION, NULL)) {
2129                 return TRUE;
2130         }
2131                 
2132         return FALSE;
2133 }
2134
2135 void procmsg_msglist_filter(GSList *list, PrefsAccount *ac, 
2136                             GSList **filtered, GSList **unfiltered,
2137                             gboolean do_filter)
2138 {
2139         GSList *cur, *to_do = NULL;
2140         gint total = 0, curnum = 0;
2141         MailFilteringData mail_filtering_data;
2142                         
2143         g_return_if_fail(filtered != NULL);
2144         g_return_if_fail(unfiltered != NULL);
2145
2146         *filtered = NULL;
2147         *unfiltered = NULL;
2148         
2149         if (list == NULL)
2150                 return;
2151
2152         total = g_slist_length(list);
2153
2154         if (!do_filter) {
2155                 *filtered = NULL;
2156                 *unfiltered = g_slist_copy(list);
2157                 return;
2158         }
2159
2160         statusbar_print_all(_("Filtering messages...\n"));
2161
2162         mail_filtering_data.msginfo = NULL;                     
2163         mail_filtering_data.msglist = list;                     
2164         mail_filtering_data.filtered = NULL;                    
2165         mail_filtering_data.unfiltered = NULL;  
2166         mail_filtering_data.account = ac;       
2167                         
2168         if (!ac || ac->filterhook_on_recv)
2169         hooks_invoke(MAIL_LISTFILTERING_HOOKLIST, &mail_filtering_data);
2170         
2171         if (mail_filtering_data.filtered == NULL &&
2172             mail_filtering_data.unfiltered == NULL) {
2173                 /* nothing happened */
2174                 debug_print(MAIL_LISTFILTERING_HOOKLIST " did nothing. filtering whole list normally.\n");
2175                 to_do = list;
2176         } 
2177         if (mail_filtering_data.filtered != NULL) {
2178                 /* keep track of what's been filtered by the hooks */
2179                 debug_print(MAIL_LISTFILTERING_HOOKLIST " filtered some stuff. total %d filtered %d unfilt %d.\n",
2180                         g_slist_length(list),
2181                         g_slist_length(mail_filtering_data.filtered),
2182                         g_slist_length(mail_filtering_data.unfiltered));
2183
2184                 *filtered = g_slist_copy(mail_filtering_data.filtered);
2185         }
2186         if (mail_filtering_data.unfiltered != NULL) {
2187                 /* what the hooks didn't handle will go in filtered or 
2188                  * unfiltered in the next loop */
2189                 debug_print(MAIL_LISTFILTERING_HOOKLIST " left unfiltered stuff. total %d filtered %d unfilt %d.\n",
2190                         g_slist_length(list),
2191                         g_slist_length(mail_filtering_data.filtered),
2192                         g_slist_length(mail_filtering_data.unfiltered));
2193                 to_do = mail_filtering_data.unfiltered;
2194         } 
2195
2196         for (cur = to_do; cur; cur = cur->next) {
2197                 MsgInfo *info = (MsgInfo *)cur->data;
2198                 if (procmsg_msginfo_filter(info, ac))
2199                         *filtered = g_slist_prepend(*filtered, info);
2200                 else
2201                         *unfiltered = g_slist_prepend(*unfiltered, info);
2202                 statusbar_progress_all(curnum++, total, prefs_common.statusbar_update_step);
2203         }
2204
2205         g_slist_free(mail_filtering_data.filtered);
2206         g_slist_free(mail_filtering_data.unfiltered);
2207         
2208         *filtered = g_slist_reverse(*filtered);
2209         *unfiltered = g_slist_reverse(*unfiltered);
2210
2211         statusbar_progress_all(0,0,0);
2212         statusbar_pop_all();
2213 }
2214
2215 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2216 {
2217         MsgInfo *tmp_msginfo = NULL;
2218         MsgFlags flags = {0, 0};
2219         gchar *tmpfile = get_tmp_file();
2220         FILE *fp = g_fopen(tmpfile, "wb");
2221         
2222         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2223             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2224                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2225                 if (fp) 
2226                         fclose(fp);
2227                 g_free(tmpfile);
2228                 return NULL;
2229         }
2230         
2231         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2232                 fclose(fp);
2233                 fp = NULL;
2234                 tmp_msginfo = procheader_parse_file(
2235                         tmpfile, flags, 
2236                         TRUE, FALSE);
2237         }
2238         if (fp)
2239                 fclose(fp);
2240
2241         if (tmp_msginfo != NULL) {
2242                 if (src_msginfo)
2243                         tmp_msginfo->folder = src_msginfo->folder;
2244                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2245         } else {
2246                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2247         }
2248
2249         g_free(tmpfile);
2250
2251         return tmp_msginfo;
2252 }
2253
2254 static GSList *spam_learners = NULL;
2255
2256 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2257 {
2258         if (!g_slist_find(spam_learners, learn_func))
2259                 spam_learners = g_slist_append(spam_learners, learn_func);
2260         if (mainwindow_get_mainwindow()) {
2261                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2262                 summary_set_menu_sensitive(
2263                         mainwindow_get_mainwindow()->summaryview);
2264                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2265         }
2266 }
2267
2268 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2269 {
2270         spam_learners = g_slist_remove(spam_learners, learn_func);
2271         if (mainwindow_get_mainwindow()) {
2272                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2273                 summary_set_menu_sensitive(
2274                         mainwindow_get_mainwindow()->summaryview);
2275                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2276         }
2277 }
2278
2279 gboolean procmsg_spam_can_learn(void)
2280 {
2281         return g_slist_length(spam_learners) > 0;
2282 }
2283
2284 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2285 {
2286         GSList *cur = spam_learners;
2287         int ret = 0;
2288         for (; cur; cur = cur->next) {
2289                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2290                 ret |= func(info, list, spam);
2291         }
2292         return ret;
2293 }
2294
2295 static gchar *spam_folder_item = NULL;
2296 static FolderItem * (*procmsg_spam_get_folder_func)(MsgInfo *msginfo) = NULL;
2297 void procmsg_spam_set_folder (const char *item_identifier, FolderItem *(*spam_get_folder_func)(MsgInfo *info))
2298 {
2299         g_free(spam_folder_item);
2300         if (item_identifier)
2301                 spam_folder_item = g_strdup(item_identifier);
2302         else
2303                 spam_folder_item = NULL;
2304         if (spam_get_folder_func != NULL)
2305                 procmsg_spam_get_folder_func = spam_get_folder_func;
2306         else
2307                 procmsg_spam_get_folder_func = NULL;
2308 }
2309
2310 FolderItem *procmsg_spam_get_folder (MsgInfo *msginfo)
2311 {
2312         FolderItem *item = NULL;
2313         
2314         if (procmsg_spam_get_folder_func) 
2315                 item = procmsg_spam_get_folder_func(msginfo);
2316         if (item == NULL && spam_folder_item)
2317                 item = folder_find_item_from_identifier(spam_folder_item);
2318         if (item == NULL)
2319                 item = folder_get_default_trash();
2320         return item;
2321 }
2322
2323 static void item_has_queued_mails(FolderItem *item, gpointer data)
2324 {
2325         gboolean *result = (gboolean *)data;
2326         if (*result == TRUE)
2327                 return;
2328         if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0)
2329                 *result = TRUE;
2330 }
2331
2332 gboolean procmsg_have_queued_mails_fast (void)
2333 {
2334         gboolean result = FALSE;
2335         folder_func_to_all_folders(item_has_queued_mails, &result);
2336         return result;
2337 }
2338
2339 static void item_has_trashed_mails(FolderItem *item, gpointer data)
2340 {
2341         gboolean *result = (gboolean *)data;
2342         if (*result == TRUE)
2343                 return;
2344         if (folder_has_parent_of_type(item, F_TRASH) && item->total_msgs > 0)
2345                 *result = TRUE;
2346 }
2347
2348 gboolean procmsg_have_trashed_mails_fast (void)
2349 {
2350         gboolean result = FALSE;
2351         folder_func_to_all_folders(item_has_trashed_mails, &result);
2352         return result;
2353 }