2007-03-21 [wwp] 2.8.1cvs30
[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                         res = procmsg_send_queue(FOLDER_ITEM(node->data), save_msgs, errstr);
925                         if (res < 0) 
926                                 err = -res;
927                         else
928                                 sent += res;
929                         node = next;
930                 }
931         }
932         send_queue_lock = FALSE;
933         inc_unlock();
934         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
935
936         return (err != 0 ? -err : sent);
937 }
938
939 gboolean procmsg_is_sending(void)
940 {
941         return send_queue_lock;
942 }
943
944 /*!
945  *\brief        Determine if a queue folder is empty
946  *
947  *\param        queue Queue folder to process
948  *
949  *\return       TRUE if the queue folder is empty, otherwise return FALSE
950  */
951 gboolean procmsg_queue_is_empty(FolderItem *queue)
952 {
953         GSList *list;
954         gboolean res = FALSE;
955         if (!queue)
956                 queue = folder_get_default_queue();
957         g_return_val_if_fail(queue != NULL, TRUE);
958
959         folder_item_scan(queue);
960         list = folder_item_get_msg_list(queue);
961         res = (list == NULL);
962         procmsg_msg_list_free(list);
963
964         if (res == TRUE) {
965                 GNode *node, *next;
966                 if (queue->node && queue->node->children) {
967                         node = queue->node->children;
968                         while (node != NULL) {
969                                 next = node->next;
970                                 if (!procmsg_queue_is_empty(FOLDER_ITEM(node->data)))
971                                         return FALSE;
972                                 node = next;
973                         }
974                 }
975         }
976         return res;
977 }
978
979 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
980 {
981         FILE *fp, *outfp;
982         gchar buf[BUFFSIZE];
983         
984         if ((fp = g_fopen(in, "rb")) == NULL) {
985                 FILE_OP_ERROR(in, "fopen");
986                 return -1;
987         }
988         if ((outfp = g_fopen(out, "wb")) == NULL) {
989                 FILE_OP_ERROR(out, "fopen");
990                 fclose(fp);
991                 return -1;
992         }
993         while (fgets(buf, sizeof(buf), fp) != NULL) {
994                 /* new way */
995                 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
996                         strlen("X-Claws-End-Special-Headers:"))) ||
997                     (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
998                         strlen("X-Sylpheed-End-Special-Headers:"))))
999                         break;
1000                 /* old way */
1001                 if (buf[0] == '\r' || buf[0] == '\n') break;
1002                 /* from other mailers */
1003                 if (!strncmp(buf, "Date: ", 6)
1004                 ||  !strncmp(buf, "To: ", 4)
1005                 ||  !strncmp(buf, "From: ", 6)
1006                 ||  !strncmp(buf, "Subject: ", 9)) {
1007                         rewind(fp);
1008                         break;
1009                 }
1010         }
1011         while (fgets(buf, sizeof(buf), fp) != NULL)
1012                 fputs(buf, outfp);
1013         fclose(outfp);
1014         fclose(fp);
1015         return 0;
1016 }
1017
1018 static gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
1019                             gboolean is_queued)
1020 {
1021         gint num;
1022         MsgInfo *msginfo, *tmp_msginfo;
1023         MsgFlags flag = {0, 0};
1024
1025         debug_print("saving sent message...\n");
1026
1027         if (!outbox)
1028                 outbox = folder_get_default_outbox();
1029         g_return_val_if_fail(outbox != NULL, -1);
1030
1031         /* remove queueing headers */
1032         if (is_queued) {
1033                 gchar tmp[MAXPATHLEN + 1];
1034
1035                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
1036                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
1037                 
1038                 if (procmsg_remove_special_headers(file, tmp) !=0)
1039                         return -1;
1040
1041                 folder_item_scan(outbox);
1042                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
1043                         g_warning("can't save message\n");
1044                         g_unlink(tmp);
1045                         return -1;
1046                 }
1047         } else {
1048                 folder_item_scan(outbox);
1049                 if ((num = folder_item_add_msg
1050                         (outbox, file, &flag, FALSE)) < 0) {
1051                         g_warning("can't save message\n");
1052                         return -1;
1053                 }
1054         }
1055         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
1056         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
1057         if (msginfo != NULL) {
1058                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1059                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
1060                 /* tmp_msginfo == msginfo */
1061                 if (tmp_msginfo && msginfo->extradata && 
1062                     (msginfo->extradata->dispositionnotificationto || 
1063                      msginfo->extradata->returnreceiptto)) {
1064                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
1065                 }       
1066                 procmsg_msginfo_free(tmp_msginfo);              /* refcnt-- */
1067         }
1068
1069         return 0;
1070 }
1071
1072 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
1073 {
1074         static const gchar *def_cmd = "lpr %s";
1075         static guint id = 0;
1076         gchar *prtmp;
1077         FILE *tmpfp, *prfp;
1078         gchar buf[1024];
1079         gchar *p;
1080
1081         g_return_if_fail(msginfo);
1082
1083         if (procmime_msginfo_is_encrypted(msginfo))
1084                 tmpfp = procmime_get_first_encrypted_text_content(msginfo);
1085         else
1086                 tmpfp = procmime_get_first_text_content(msginfo);
1087         if (tmpfp == NULL) {
1088                 g_warning("Can't get text part\n");
1089                 return;
1090         }
1091
1092         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
1093                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
1094
1095         if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
1096                 FILE_OP_ERROR(prtmp, "fopen");
1097                 g_free(prtmp);
1098                 fclose(tmpfp);
1099                 return;
1100         }
1101
1102         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
1103         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
1104         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
1105         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
1106         if (msginfo->newsgroups)
1107                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
1108         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
1109         fputc('\n', prfp);
1110
1111         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1112                 fputs(buf, prfp);
1113
1114         fclose(prfp);
1115         fclose(tmpfp);
1116
1117         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
1118             !strchr(p + 2, '%'))
1119                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
1120         else {
1121                 if (cmdline)
1122                         g_warning("Print command line is invalid: '%s'\n",
1123                                   cmdline);
1124                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
1125         }
1126
1127         g_free(prtmp);
1128
1129         g_strchomp(buf);
1130         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
1131         system(buf);
1132 }
1133
1134 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1135 {
1136         msginfo->refcnt++;
1137         
1138         return msginfo;
1139 }
1140
1141 MsgInfo *procmsg_msginfo_new(void)
1142 {
1143         MsgInfo *newmsginfo;
1144
1145         newmsginfo = g_new0(MsgInfo, 1);
1146         newmsginfo->refcnt = 1;
1147         
1148         return newmsginfo;
1149 }
1150
1151 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1152 {
1153         MsgInfo *newmsginfo;
1154         GSList *refs;
1155
1156         if (msginfo == NULL) return NULL;
1157
1158         newmsginfo = g_new0(MsgInfo, 1);
1159
1160         newmsginfo->refcnt = 1;
1161
1162 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1163 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1164                         g_strdup(msginfo->mmb) : NULL
1165
1166         MEMBCOPY(msgnum);
1167         MEMBCOPY(size);
1168         MEMBCOPY(mtime);
1169         MEMBCOPY(date_t);
1170
1171         MEMBCOPY(flags);
1172
1173         MEMBDUP(fromname);
1174
1175         MEMBDUP(date);
1176         MEMBDUP(from);
1177         MEMBDUP(to);
1178         MEMBDUP(cc);
1179         MEMBDUP(newsgroups);
1180         MEMBDUP(subject);
1181         MEMBDUP(msgid);
1182         MEMBDUP(inreplyto);
1183         MEMBDUP(xref);
1184
1185         MEMBCOPY(folder);
1186         MEMBCOPY(to_folder);
1187
1188         if (msginfo->extradata) {
1189                 newmsginfo->extradata = g_new0(MsgInfoExtraData, 1);
1190                 MEMBDUP(extradata->face);
1191                 MEMBDUP(extradata->xface);
1192                 MEMBDUP(extradata->dispositionnotificationto);
1193                 MEMBDUP(extradata->returnreceiptto);
1194                 MEMBDUP(extradata->partial_recv);
1195                 MEMBDUP(extradata->account_server);
1196                 MEMBDUP(extradata->account_login);
1197                 MEMBDUP(extradata->list_post);
1198                 MEMBDUP(extradata->list_subscribe);
1199                 MEMBDUP(extradata->list_unsubscribe);
1200                 MEMBDUP(extradata->list_help);
1201                 MEMBDUP(extradata->list_archive);
1202                 MEMBDUP(extradata->list_owner);
1203         }
1204
1205         refs = msginfo->references;
1206         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1207                 newmsginfo->references = g_slist_prepend
1208                         (newmsginfo->references, g_strdup(refs->data)); 
1209         }
1210         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1211
1212         MEMBCOPY(score);
1213         MEMBDUP(plaintext_file);
1214
1215         return newmsginfo;
1216 }
1217
1218 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1219 {
1220         MsgInfo *full_msginfo;
1221         gchar *file;
1222
1223         if (msginfo == NULL) return NULL;
1224
1225         file = procmsg_get_message_file_path(msginfo);
1226         if (!file || !is_file_exist(file)) {
1227                 g_free(file);
1228                 file = procmsg_get_message_file(msginfo);
1229         }
1230         if (!file || !is_file_exist(file)) {
1231                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
1232                 return NULL;
1233         }
1234
1235         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1236         g_free(file);
1237         if (!full_msginfo) return NULL;
1238
1239         msginfo->total_size = full_msginfo->total_size;
1240         msginfo->planned_download = full_msginfo->planned_download;
1241
1242         if (full_msginfo->extradata) {
1243                 if (!msginfo->extradata)
1244                         msginfo->extradata = g_new0(MsgInfoExtraData, 1);
1245                 if (!msginfo->extradata->list_post)
1246                         msginfo->extradata->list_post = g_strdup(full_msginfo->extradata->list_post);
1247                 if (!msginfo->extradata->list_subscribe)
1248                         msginfo->extradata->list_subscribe = g_strdup(full_msginfo->extradata->list_subscribe);
1249                 if (!msginfo->extradata->list_unsubscribe)
1250                         msginfo->extradata->list_unsubscribe = g_strdup(full_msginfo->extradata->list_unsubscribe);
1251                 if (!msginfo->extradata->list_help)
1252                         msginfo->extradata->list_help = g_strdup(full_msginfo->extradata->list_help);
1253                 if (!msginfo->extradata->list_archive)
1254                         msginfo->extradata->list_archive= g_strdup(full_msginfo->extradata->list_archive);
1255                 if (!msginfo->extradata->list_owner)
1256                         msginfo->extradata->list_owner = g_strdup(full_msginfo->extradata->list_owner);
1257                 if (!msginfo->extradata->xface)
1258                         msginfo->extradata->xface = g_strdup(full_msginfo->extradata->xface);
1259                 if (!msginfo->extradata->face)
1260                         msginfo->extradata->face = g_strdup(full_msginfo->extradata->face);
1261                 if (!msginfo->extradata->dispositionnotificationto)
1262                         msginfo->extradata->dispositionnotificationto = 
1263                                 g_strdup(full_msginfo->extradata->dispositionnotificationto);
1264                 if (!msginfo->extradata->returnreceiptto)
1265                         msginfo->extradata->returnreceiptto = g_strdup
1266                                 (full_msginfo->extradata->returnreceiptto);
1267                 if (!msginfo->extradata->partial_recv && full_msginfo->extradata->partial_recv)
1268                         msginfo->extradata->partial_recv = g_strdup
1269                                 (full_msginfo->extradata->partial_recv);
1270                 if (!msginfo->extradata->account_server && full_msginfo->extradata->account_server)
1271                         msginfo->extradata->account_server = g_strdup
1272                                 (full_msginfo->extradata->account_server);
1273                 if (!msginfo->extradata->account_login && full_msginfo->extradata->account_login)
1274                         msginfo->extradata->account_login = g_strdup
1275                                 (full_msginfo->extradata->account_login);
1276         }
1277         procmsg_msginfo_free(full_msginfo);
1278
1279         return procmsg_msginfo_new_ref(msginfo);
1280 }
1281
1282 void procmsg_msginfo_free(MsgInfo *msginfo)
1283 {
1284         if (msginfo == NULL) return;
1285
1286         msginfo->refcnt--;
1287         if (msginfo->refcnt > 0)
1288                 return;
1289
1290         if (msginfo->to_folder) {
1291                 msginfo->to_folder->op_count--;
1292                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1293         }
1294
1295         g_free(msginfo->fromspace);
1296
1297         g_free(msginfo->fromname);
1298
1299         g_free(msginfo->date);
1300         g_free(msginfo->from);
1301         g_free(msginfo->to);
1302         g_free(msginfo->cc);
1303         g_free(msginfo->newsgroups);
1304         g_free(msginfo->subject);
1305         g_free(msginfo->msgid);
1306         g_free(msginfo->inreplyto);
1307         g_free(msginfo->xref);
1308
1309         if (msginfo->extradata) {
1310                 g_free(msginfo->extradata->returnreceiptto);
1311                 g_free(msginfo->extradata->dispositionnotificationto);
1312                 g_free(msginfo->extradata->xface);
1313                 g_free(msginfo->extradata->face);
1314                 g_free(msginfo->extradata->list_post);
1315                 g_free(msginfo->extradata->list_subscribe);
1316                 g_free(msginfo->extradata->list_unsubscribe);
1317                 g_free(msginfo->extradata->list_help);
1318                 g_free(msginfo->extradata->list_archive);
1319                 g_free(msginfo->extradata->list_owner);
1320                 g_free(msginfo->extradata->partial_recv);
1321                 g_free(msginfo->extradata->account_server);
1322                 g_free(msginfo->extradata->account_login);
1323                 g_free(msginfo->extradata);
1324         }
1325         slist_free_strings(msginfo->references);
1326         g_slist_free(msginfo->references);
1327
1328         g_free(msginfo->plaintext_file);
1329
1330         g_free(msginfo);
1331 }
1332
1333 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1334 {
1335         guint memusage = 0;
1336         GSList *refs;
1337         
1338         memusage += sizeof(MsgInfo);
1339         if (msginfo->fromname)
1340                 memusage += strlen(msginfo->fromname);
1341         if (msginfo->date)
1342                 memusage += strlen(msginfo->date);
1343         if (msginfo->from)
1344                 memusage += strlen(msginfo->from);
1345         if (msginfo->to)
1346                 memusage += strlen(msginfo->to);
1347         if (msginfo->cc)
1348                 memusage += strlen(msginfo->cc);
1349         if (msginfo->newsgroups)
1350                 memusage += strlen(msginfo->newsgroups);
1351         if (msginfo->subject)
1352                 memusage += strlen(msginfo->subject);
1353         if (msginfo->msgid)
1354                 memusage += strlen(msginfo->msgid);
1355         if (msginfo->inreplyto)
1356                 memusage += strlen(msginfo->inreplyto);
1357         for (refs = msginfo->references; refs; refs=refs->next) {
1358                 gchar *r = (gchar *)refs->data;
1359                 memusage += r?strlen(r):0;
1360         }
1361         if (msginfo->fromspace)
1362                 memusage += strlen(msginfo->fromspace);
1363
1364         if (msginfo->extradata) {
1365                 memusage += sizeof(MsgInfoExtraData);
1366                 if (msginfo->extradata->xface)
1367                         memusage += strlen(msginfo->extradata->xface);
1368                 if (msginfo->extradata->face)
1369                         memusage += strlen(msginfo->extradata->face);
1370                 if (msginfo->extradata->dispositionnotificationto)
1371                         memusage += strlen(msginfo->extradata->dispositionnotificationto);
1372                 if (msginfo->extradata->returnreceiptto)
1373                         memusage += strlen(msginfo->extradata->returnreceiptto);
1374
1375                 if (msginfo->extradata->partial_recv)
1376                         memusage += strlen(msginfo->extradata->partial_recv);
1377                 if (msginfo->extradata->account_server)
1378                         memusage += strlen(msginfo->extradata->account_server);
1379                 if (msginfo->extradata->account_login)
1380                         memusage += strlen(msginfo->extradata->account_login);
1381
1382                 if (msginfo->extradata->list_post)
1383                         memusage += strlen(msginfo->extradata->list_post);
1384                 if (msginfo->extradata->list_subscribe)
1385                         memusage += strlen(msginfo->extradata->list_subscribe);
1386                 if (msginfo->extradata->list_unsubscribe)
1387                         memusage += strlen(msginfo->extradata->list_unsubscribe);
1388                 if (msginfo->extradata->list_help)
1389                         memusage += strlen(msginfo->extradata->list_help);
1390                 if (msginfo->extradata->list_archive)
1391                         memusage += strlen(msginfo->extradata->list_archive);
1392                 if (msginfo->extradata->list_owner)
1393                         memusage += strlen(msginfo->extradata->list_owner);
1394         }
1395         return memusage;
1396 }
1397
1398 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr,
1399                                             FolderItem *queue, gint msgnum, gboolean *queued_removed)
1400 {
1401         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1402                                        {"SSV:",  NULL, FALSE},
1403                                        {"R:",    NULL, FALSE},
1404                                        {"NG:",   NULL, FALSE},
1405                                        {"MAID:", NULL, FALSE},
1406                                        {"NAID:", NULL, FALSE},
1407                                        {"SCF:",  NULL, FALSE},
1408                                        {"RMID:", NULL, FALSE},
1409                                        {"FMID:", NULL, FALSE},
1410                                        {"X-Claws-Privacy-System:", NULL, FALSE},
1411                                        {"X-Claws-Encrypt:", NULL, FALSE},
1412                                        {"X-Claws-Encrypt-Data:", NULL, FALSE},
1413                                        {"X-Claws-End-Special-Headers:", NULL, FALSE},
1414                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1415                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1416                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1417                                        {"X-Sylpheed-End-Special-Headers:", NULL, FALSE},
1418                                        {NULL,    NULL, FALSE}};
1419         FILE *fp;
1420         gint filepos;
1421         gint mailval = 0, newsval = 0;
1422         gchar *from = NULL;
1423         gchar *smtpserver = NULL;
1424         GSList *to_list = NULL;
1425         GSList *newsgroup_list = NULL;
1426         gchar *savecopyfolder = NULL;
1427         gchar *replymessageid = NULL;
1428         gchar *fwdmessageid = NULL;
1429         gchar *privacy_system = NULL;
1430         gboolean encrypt = FALSE;
1431         gchar *encrypt_data = NULL;
1432         gchar buf[BUFFSIZE];
1433         gint hnum;
1434         PrefsAccount *mailac = NULL, *newsac = NULL;
1435         gboolean save_clear_text = TRUE;
1436         gchar *tmp_enc_file = NULL;
1437
1438         int local = 0;
1439
1440         g_return_val_if_fail(file != NULL, -1);
1441
1442         if ((fp = g_fopen(file, "rb")) == NULL) {
1443                 FILE_OP_ERROR(file, "fopen");
1444                 if (errstr) {
1445                         if (*errstr) g_free(*errstr);
1446                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1447                 }
1448                 return -1;
1449         }
1450
1451         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1452                != -1) {
1453                 gchar *p = buf + strlen(qentry[hnum].name);
1454
1455                 switch (hnum) {
1456                 case Q_SENDER:
1457                         if (from == NULL) 
1458                                 from = g_strdup(p);
1459                         break;
1460                 case Q_SMTPSERVER:
1461                         if (smtpserver == NULL) 
1462                                 smtpserver = g_strdup(p);
1463                         break;
1464                 case Q_RECIPIENTS:
1465                         to_list = address_list_append(to_list, p);
1466                         break;
1467                 case Q_NEWSGROUPS:
1468                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1469                         break;
1470                 case Q_MAIL_ACCOUNT_ID:
1471                         mailac = account_find_from_id(atoi(p));
1472                         break;
1473                 case Q_NEWS_ACCOUNT_ID:
1474                         newsac = account_find_from_id(atoi(p));
1475                         break;
1476                 case Q_SAVE_COPY_FOLDER:
1477                         if (savecopyfolder == NULL) 
1478                                 savecopyfolder = g_strdup(p);
1479                         break;
1480                 case Q_REPLY_MESSAGE_ID:
1481                         if (replymessageid == NULL) 
1482                                 replymessageid = g_strdup(p);
1483                         break;
1484                 case Q_FWD_MESSAGE_ID:
1485                         if (fwdmessageid == NULL) 
1486                                 fwdmessageid = g_strdup(p);
1487                         break;
1488                 case Q_PRIVACY_SYSTEM:
1489                 case Q_PRIVACY_SYSTEM_OLD:
1490                         if (privacy_system == NULL) 
1491                                 privacy_system = g_strdup(p);
1492                         break;
1493                 case Q_ENCRYPT:
1494                 case Q_ENCRYPT_OLD:
1495                         if (p[0] == '1') 
1496                                 encrypt = TRUE;
1497                         break;
1498                 case Q_ENCRYPT_DATA:
1499                 case Q_ENCRYPT_DATA_OLD:
1500                         if (encrypt_data == NULL) 
1501                                 encrypt_data = g_strdup(p);
1502                         break;
1503                 case Q_CLAWS_HDRS:
1504                 case Q_CLAWS_HDRS_OLD:
1505                         /* end of special headers reached */
1506                         goto send_mail; /* can't "break;break;" */
1507                 }
1508         }
1509 send_mail:
1510         filepos = ftell(fp);
1511
1512         if (encrypt) {
1513                 MimeInfo *mimeinfo;
1514
1515                 if (mailac && mailac->save_encrypted_as_clear_text 
1516                 &&  !mailac->encrypt_to_self)
1517                         save_clear_text = TRUE;
1518                 else
1519                         save_clear_text = FALSE;
1520
1521                 fclose(fp);
1522                 fp = NULL;
1523
1524                 mimeinfo = procmime_scan_queue_file(file);
1525                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1526                 || (fp = my_tmpfile()) == NULL
1527                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1528                         if (fp)
1529                                 fclose(fp);
1530                         procmime_mimeinfo_free_all(mimeinfo);
1531                         g_free(from);
1532                         g_free(smtpserver);
1533                         slist_free_strings(to_list);
1534                         g_slist_free(to_list);
1535                         slist_free_strings(newsgroup_list);
1536                         g_slist_free(newsgroup_list);
1537                         g_free(savecopyfolder);
1538                         g_free(replymessageid);
1539                         g_free(fwdmessageid);
1540                         g_free(privacy_system);
1541                         g_free(encrypt_data);
1542                         if (errstr) {
1543                                 if (*errstr) g_free(*errstr);
1544                                 *errstr = g_strdup_printf(_("Couldn't encrypt the email: %s"),
1545                                                 privacy_get_error());
1546                         }
1547                         return -1;
1548                 }
1549                 
1550                 rewind(fp);
1551                 if (!save_clear_text) {
1552                         gchar *content = NULL;
1553                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1554                         if (tmpfp) {
1555                                 fclose(tmpfp);
1556
1557                                 content = file_read_stream_to_str(fp);
1558                                 rewind(fp);
1559
1560                                 str_write_to_file(content, tmp_enc_file);
1561                                 g_free(content);
1562                         } else {
1563                                 g_warning("couldn't get tempfile\n");
1564                         }
1565                 } 
1566                 
1567                 procmime_mimeinfo_free_all(mimeinfo);
1568                 
1569                 filepos = 0;
1570         }
1571
1572         if (to_list) {
1573                 debug_print("Sending message by mail\n");
1574                 if (!from) {
1575                         if (errstr) {
1576                                 if (*errstr) g_free(*errstr);
1577                                 *errstr = g_strdup_printf(_("Queued message header is broken."));
1578                         }
1579                         mailval = -1;
1580                 } else if (mailac && mailac->use_mail_command &&
1581                            mailac->mail_command && (* mailac->mail_command)) {
1582                         mailval = send_message_local(mailac->mail_command, fp);
1583                         local = 1;
1584                 } else {
1585                         if (!mailac) {
1586                                 mailac = account_find_from_smtp_server(from, smtpserver);
1587                                 if (!mailac) {
1588                                         g_warning("Account not found. "
1589                                                     "Using current account...\n");
1590                                         mailac = cur_account;
1591                                 }
1592                         }
1593
1594                         if (mailac) {
1595                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1596                                 if (mailval == -1 && errstr) {
1597                                         if (*errstr) g_free(*errstr);
1598                                         *errstr = g_strdup_printf(_("An error happened during SMTP session."));
1599                                 }
1600                         } else {
1601                                 PrefsAccount tmp_ac;
1602
1603                                 g_warning("Account not found.\n");
1604
1605                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1606                                 tmp_ac.address = from;
1607                                 tmp_ac.smtp_server = smtpserver;
1608                                 tmp_ac.smtpport = SMTP_PORT;
1609                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1610                                 if (mailval == -1 && errstr) {
1611                                         if (*errstr) g_free(*errstr);
1612                                         *errstr = g_strdup_printf(_("No specific account has been found to "
1613                                                         "send, and an error happened during SMTP session."));
1614                                 }
1615                         }
1616                 }
1617         } else if (!to_list && !newsgroup_list) {
1618                 if (errstr) {
1619                         if (*errstr) g_free(*errstr);
1620                         *errstr = g_strdup(_("Couldn't determine sending informations. "
1621                                 "Maybe the email hasn't been generated by Claws Mail."));
1622                 }
1623                 mailval = -1;
1624         }
1625
1626         fseek(fp, filepos, SEEK_SET);
1627         if (newsgroup_list && (mailval == 0)) {
1628                 Folder *folder;
1629                 gchar *tmp = NULL;
1630                 FILE *tmpfp;
1631
1632                 /* write to temporary file */
1633                 tmp = g_strdup_printf("%s%ctmp%p", g_get_tmp_dir(),
1634                             G_DIR_SEPARATOR, file);
1635                 if ((tmpfp = g_fopen(tmp, "wb")) == NULL) {
1636                         FILE_OP_ERROR(tmp, "fopen");
1637                         newsval = -1;
1638                         alertpanel_error(_("Couldn't create temporary file for news sending."));
1639                 } else {
1640                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1641                                 FILE_OP_ERROR(tmp, "chmod");
1642                                 g_warning("can't change file mode\n");
1643                         }
1644
1645                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1646                                 if (fputs(buf, tmpfp) == EOF) {
1647                                         FILE_OP_ERROR(tmp, "fputs");
1648                                         newsval = -1;
1649                                         if (errstr) {
1650                                                 if (*errstr) g_free(*errstr);
1651                                                 *errstr = g_strdup_printf(_("Error when writing temporary file for news sending."));
1652                                         }
1653                                 }
1654                         }
1655                         fclose(tmpfp);
1656
1657                         if (newsval == 0) {
1658                                 debug_print("Sending message by news\n");
1659
1660                                 folder = FOLDER(newsac->folder);
1661
1662                                 newsval = news_post(folder, tmp);
1663                                 if (newsval < 0 && errstr)  {
1664                                         if (*errstr) g_free(*errstr);
1665                                         *errstr = g_strdup_printf(_("Error occurred while posting the message to %s."),
1666                                          newsac->nntp_server);
1667                                 }
1668                         }
1669                         g_unlink(tmp);
1670                 }
1671                 g_free(tmp);
1672         }
1673
1674         fclose(fp);
1675
1676         /* save message to outbox */
1677         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1678                 FolderItem *outbox;
1679
1680                 debug_print("saving sent message...\n");
1681
1682                 outbox = folder_find_item_from_identifier(savecopyfolder);
1683                 if (!outbox)
1684                         outbox = folder_get_default_outbox();
1685                         
1686                 if (save_clear_text || tmp_enc_file == NULL) {
1687                         gboolean saved = FALSE;
1688                         *queued_removed = FALSE;
1689                         if (queue && msgnum > 0) {
1690                                 MsgInfo *queued_mail = folder_item_get_msginfo(queue, msgnum);
1691                                 if (folder_item_move_msg(outbox, queued_mail) >= 0) {
1692                                         debug_print("moved queued mail %d to sent folder\n", msgnum);
1693                                         saved = TRUE;
1694                                         *queued_removed = TRUE;
1695                                 } else if (folder_item_copy_msg(outbox, queued_mail) >= 0) {
1696                                         debug_print("copied queued mail %d to sent folder\n", msgnum);
1697                                         saved = TRUE;
1698                                 }
1699                                 procmsg_msginfo_free(queued_mail);
1700                         }
1701                         if (!saved) {
1702                                 debug_print("resaving clear text queued mail to sent folder\n");
1703                                 procmsg_save_to_outbox(outbox, file, TRUE);
1704                         }
1705                 } else {
1706                         debug_print("saving encrpyted queued mail to sent folder\n");
1707                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1708                 }
1709         }
1710
1711         if (tmp_enc_file != NULL) {
1712                 g_unlink(tmp_enc_file);
1713                 free(tmp_enc_file);
1714                 tmp_enc_file = NULL;
1715         }
1716
1717         if (replymessageid != NULL || fwdmessageid != NULL) {
1718                 gchar **tokens;
1719                 FolderItem *item;
1720                 
1721                 if (replymessageid != NULL)
1722                         tokens = g_strsplit(replymessageid, "\t", 0);
1723                 else
1724                         tokens = g_strsplit(fwdmessageid, "\t", 0);
1725                 item = folder_find_item_from_identifier(tokens[0]);
1726
1727                 /* check if queued message has valid folder and message id */
1728                 if (item != NULL && tokens[2] != NULL) {
1729                         MsgInfo *msginfo;
1730                         
1731                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1732                 
1733                         /* check if referring message exists and has a message id */
1734                         if ((msginfo != NULL) && 
1735                             (msginfo->msgid != NULL) &&
1736                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1737                                 procmsg_msginfo_free(msginfo);
1738                                 msginfo = NULL;
1739                         }
1740                         
1741                         if (msginfo == NULL) {
1742                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1743                         }
1744                         
1745                         if (msginfo != NULL) {
1746                                 if (replymessageid != NULL) {
1747                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1748                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1749                                 }  else {
1750                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1751                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1752                                 }
1753                                 procmsg_msginfo_free(msginfo);
1754                         }
1755                 }
1756                 g_strfreev(tokens);
1757         }
1758
1759         g_free(from);
1760         g_free(smtpserver);
1761         slist_free_strings(to_list);
1762         g_slist_free(to_list);
1763         slist_free_strings(newsgroup_list);
1764         g_slist_free(newsgroup_list);
1765         g_free(savecopyfolder);
1766         g_free(replymessageid);
1767         g_free(fwdmessageid);
1768         g_free(privacy_system);
1769         g_free(encrypt_data);
1770
1771         return (newsval != 0 ? newsval : mailval);
1772 }
1773
1774 gint procmsg_send_message_queue(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1775 {
1776         gint result = procmsg_send_message_queue_full(file, FALSE, errstr, queue, msgnum, queued_removed);
1777         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1778         return result;
1779 }
1780
1781 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1782 {
1783         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1784
1785         /* NEW flag */
1786         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1787                 item->new_msgs++;
1788         }
1789
1790         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1791                 item->new_msgs--;
1792         }
1793
1794         /* UNREAD flag */
1795         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1796                 item->unread_msgs++;
1797                 if (procmsg_msg_has_marked_parent(msginfo))
1798                         item->unreadmarked_msgs++;
1799         }
1800
1801         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1802                 item->unread_msgs--;
1803                 if (procmsg_msg_has_marked_parent(msginfo))
1804                         item->unreadmarked_msgs--;
1805         }
1806         
1807         /* MARK flag */
1808         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1809                 procmsg_update_unread_children(msginfo, TRUE);
1810                 item->marked_msgs++;
1811         }
1812
1813         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1814                 procmsg_update_unread_children(msginfo, FALSE);
1815                 item->marked_msgs--;
1816         }
1817 }
1818
1819 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1820 {
1821         FolderItem *item;
1822         MsgInfoUpdate msginfo_update;
1823         MsgPermFlags perm_flags_new, perm_flags_old;
1824         MsgTmpFlags tmp_flags_old;
1825
1826         g_return_if_fail(msginfo != NULL);
1827         item = msginfo->folder;
1828         g_return_if_fail(item != NULL);
1829         
1830         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1831
1832         /* Perm Flags handling */
1833         perm_flags_old = msginfo->flags.perm_flags;
1834         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1835         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1836                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1837         }
1838
1839         if (perm_flags_old != perm_flags_new) {
1840                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1841
1842                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1843
1844         }
1845
1846         /* Tmp flags handling */
1847         tmp_flags_old = msginfo->flags.tmp_flags;
1848         msginfo->flags.tmp_flags |= tmp_flags;
1849
1850         /* update notification */
1851         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1852                 msginfo_update.msginfo = msginfo;
1853                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1854                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1855                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1856         }
1857 }
1858
1859 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1860 {
1861         FolderItem *item;
1862         MsgInfoUpdate msginfo_update;
1863         MsgPermFlags perm_flags_new, perm_flags_old;
1864         MsgTmpFlags tmp_flags_old;
1865
1866         g_return_if_fail(msginfo != NULL);
1867         item = msginfo->folder;
1868         g_return_if_fail(item != NULL);
1869         
1870         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1871
1872         /* Perm Flags handling */
1873         perm_flags_old = msginfo->flags.perm_flags;
1874         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1875         
1876         if (perm_flags_old != perm_flags_new) {
1877                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1878
1879                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1880         }
1881
1882         /* Tmp flags hanlding */
1883         tmp_flags_old = msginfo->flags.tmp_flags;
1884         msginfo->flags.tmp_flags &= ~tmp_flags;
1885
1886         /* update notification */
1887         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1888                 msginfo_update.msginfo = msginfo;
1889                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1890                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1891                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1892         }
1893 }
1894
1895 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1896                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1897                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1898 {
1899         FolderItem *item;
1900         MsgInfoUpdate msginfo_update;
1901         MsgPermFlags perm_flags_new, perm_flags_old;
1902         MsgTmpFlags tmp_flags_old;
1903
1904         g_return_if_fail(msginfo != NULL);
1905         item = msginfo->folder;
1906         g_return_if_fail(item != NULL);
1907         
1908         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1909
1910         /* Perm Flags handling */
1911         perm_flags_old = msginfo->flags.perm_flags;
1912         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1913         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1914                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1915         }
1916
1917         if (perm_flags_old != perm_flags_new) {
1918                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1919
1920                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1921
1922         }
1923
1924         /* Tmp flags handling */
1925         tmp_flags_old = msginfo->flags.tmp_flags;
1926         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1927         msginfo->flags.tmp_flags |= add_tmp_flags;
1928
1929         /* update notification */
1930         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1931                 msginfo_update.msginfo = msginfo;
1932                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1933                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1934                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1935         }
1936 }
1937
1938 /*!
1939  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1940  *
1941  *\param        info Current message
1942  *\param        perm_flags Flags to be checked
1943  *\param        parentmsgs Hash of prior msgs to avoid loops
1944  *
1945  *\return       gboolean TRUE if perm_flags are found
1946  */
1947 static gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1948                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1949 {
1950         MsgInfo *tmp;
1951
1952         g_return_val_if_fail(info != NULL, FALSE);
1953
1954         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1955                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1956                                 info->inreplyto);
1957                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1958                         procmsg_msginfo_free(tmp);
1959                         return TRUE;
1960                 } else if (tmp != NULL) {
1961                         gboolean result;
1962
1963                         if (g_hash_table_lookup(parentmsgs, info)) {
1964                                 debug_print("loop detected: %d\n",
1965                                         info->msgnum);
1966                                 result = FALSE;
1967                         } else {
1968                                 g_hash_table_insert(parentmsgs, info, "1");
1969                                 result = procmsg_msg_has_flagged_parent_real(
1970                                     tmp, perm_flags, parentmsgs);
1971                         }
1972                         procmsg_msginfo_free(tmp);
1973                         return result;
1974                 } else {
1975                         return FALSE;
1976                 }
1977         } else
1978                 return FALSE;
1979 }
1980
1981 /*!
1982  *\brief        Callback for cleaning up hash of parentmsgs
1983  */
1984 static gboolean parentmsgs_hash_remove(gpointer key,
1985                             gpointer value,
1986                             gpointer user_data)
1987 {
1988         return TRUE;
1989 }
1990
1991 /*!
1992  *\brief        Set up list of parentmsgs
1993  *              See procmsg_msg_has_flagged_parent_real()
1994  */
1995 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1996 {
1997         gboolean result;
1998         static GHashTable *parentmsgs = NULL;
1999         
2000         if (parentmsgs == NULL)
2001                 parentmsgs = g_hash_table_new(NULL, NULL); 
2002
2003         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2004         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2005
2006         return result;
2007 }
2008
2009 /*!
2010  *\brief        Check if msgs prior in thread are marked
2011  *              See procmsg_msg_has_flagged_parent_real()
2012  */
2013 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2014 {
2015         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2016 }
2017
2018
2019 static GSList *procmsg_find_children_func(MsgInfo *info, 
2020                                    GSList *children, GSList *all)
2021 {
2022         GSList *cur;
2023
2024         g_return_val_if_fail(info!=NULL, children);
2025         if (info->msgid == NULL)
2026                 return children;
2027
2028         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2029                 MsgInfo *tmp = (MsgInfo *)cur->data;
2030                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2031                         /* Check if message is already in the list */
2032                         if ((children == NULL) || 
2033                             (g_slist_index(children, tmp) == -1)) {
2034                                 children = g_slist_prepend(children,
2035                                                 procmsg_msginfo_new_ref(tmp));
2036                                 children = procmsg_find_children_func(tmp, 
2037                                                         children, 
2038                                                         all);
2039                         }
2040                 }
2041         }
2042         return children;
2043 }
2044
2045 static GSList *procmsg_find_children (MsgInfo *info)
2046 {
2047         GSList *children;
2048         GSList *all, *cur;
2049
2050         g_return_val_if_fail(info!=NULL, NULL);
2051         all = folder_item_get_msg_list(info->folder);
2052         children = procmsg_find_children_func(info, NULL, all);
2053         if (children != NULL) {
2054                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2055                         /* this will not free the used pointers
2056                            created with procmsg_msginfo_new_ref */
2057                         procmsg_msginfo_free((MsgInfo *)cur->data);
2058                 }
2059         }
2060         g_slist_free(all);
2061
2062         return children;
2063 }
2064
2065 static void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2066 {
2067         GSList *children = procmsg_find_children(info);
2068         GSList *cur;
2069         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2070                 MsgInfo *tmp = (MsgInfo *)cur->data;
2071                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2072                         if(newly_marked) 
2073                                 info->folder->unreadmarked_msgs++;
2074                         else
2075                                 info->folder->unreadmarked_msgs--;
2076                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2077                 }
2078                 procmsg_msginfo_free(tmp);
2079         }
2080         g_slist_free(children);
2081 }
2082
2083 /**
2084  * Set the destination folder for a copy or move operation
2085  *
2086  * \param msginfo The message which's destination folder is changed
2087  * \param to_folder The destination folder for the operation
2088  */
2089 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2090 {
2091         if(msginfo->to_folder != NULL) {
2092                 msginfo->to_folder->op_count--;
2093                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2094         }
2095         msginfo->to_folder = to_folder;
2096         if(to_folder != NULL) {
2097                 to_folder->op_count++;
2098                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2099         }
2100 }
2101
2102 /**
2103  * Apply filtering actions to the msginfo
2104  *
2105  * \param msginfo The MsgInfo describing the message that should be filtered
2106  * \return TRUE if the message was moved and MsgInfo is now invalid,
2107  *         FALSE otherwise
2108  */
2109 static gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2110 {
2111         MailFilteringData mail_filtering_data;
2112                         
2113         mail_filtering_data.msginfo = msginfo;                  
2114         mail_filtering_data.msglist = NULL;                     
2115         mail_filtering_data.filtered = NULL;                    
2116         mail_filtering_data.unfiltered = NULL;
2117         mail_filtering_data.account = ac_prefs; 
2118         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data)) {
2119                 return TRUE;
2120         }
2121
2122         /* filter if enabled in prefs or move to inbox if not */
2123         if((filtering_rules != NULL) &&
2124                 filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs,
2125                                 FILTERING_INCORPORATION, NULL)) {
2126                 return TRUE;
2127         }
2128                 
2129         return FALSE;
2130 }
2131
2132 void procmsg_msglist_filter(GSList *list, PrefsAccount *ac, 
2133                             GSList **filtered, GSList **unfiltered,
2134                             gboolean do_filter)
2135 {
2136         GSList *cur, *to_do = NULL;
2137         gint total = 0, curnum = 0;
2138         MailFilteringData mail_filtering_data;
2139                         
2140         g_return_if_fail(filtered != NULL);
2141         g_return_if_fail(unfiltered != NULL);
2142
2143         *filtered = NULL;
2144         *unfiltered = NULL;
2145         
2146         if (list == NULL)
2147                 return;
2148
2149         total = g_slist_length(list);
2150
2151         if (!do_filter) {
2152                 *filtered = NULL;
2153                 *unfiltered = g_slist_copy(list);
2154                 return;
2155         }
2156
2157         statusbar_print_all(_("Filtering messages...\n"));
2158
2159         mail_filtering_data.msginfo = NULL;                     
2160         mail_filtering_data.msglist = list;                     
2161         mail_filtering_data.filtered = NULL;                    
2162         mail_filtering_data.unfiltered = NULL;  
2163         mail_filtering_data.account = ac;       
2164                         
2165         hooks_invoke(MAIL_LISTFILTERING_HOOKLIST, &mail_filtering_data);
2166         
2167         if (mail_filtering_data.filtered == NULL &&
2168             mail_filtering_data.unfiltered == NULL) {
2169                 /* nothing happened */
2170                 debug_print(MAIL_LISTFILTERING_HOOKLIST " did nothing. filtering whole list normally.\n");
2171                 to_do = list;
2172         } 
2173         if (mail_filtering_data.filtered != NULL) {
2174                 /* keep track of what's been filtered by the hooks */
2175                 debug_print(MAIL_LISTFILTERING_HOOKLIST " filtered some stuff. total %d filtered %d unfilt %d.\n",
2176                         g_slist_length(list),
2177                         g_slist_length(mail_filtering_data.filtered),
2178                         g_slist_length(mail_filtering_data.unfiltered));
2179
2180                 *filtered = g_slist_copy(mail_filtering_data.filtered);
2181         }
2182         if (mail_filtering_data.unfiltered != NULL) {
2183                 /* what the hooks didn't handle will go in filtered or 
2184                  * unfiltered in the next loop */
2185                 debug_print(MAIL_LISTFILTERING_HOOKLIST " left unfiltered stuff. total %d filtered %d unfilt %d.\n",
2186                         g_slist_length(list),
2187                         g_slist_length(mail_filtering_data.filtered),
2188                         g_slist_length(mail_filtering_data.unfiltered));
2189                 to_do = mail_filtering_data.unfiltered;
2190         } 
2191
2192         for (cur = to_do; cur; cur = cur->next) {
2193                 MsgInfo *info = (MsgInfo *)cur->data;
2194                 if (procmsg_msginfo_filter(info, ac))
2195                         *filtered = g_slist_prepend(*filtered, info);
2196                 else
2197                         *unfiltered = g_slist_prepend(*unfiltered, info);
2198                 statusbar_progress_all(curnum++, total, prefs_common.statusbar_update_step);
2199         }
2200
2201         g_slist_free(mail_filtering_data.filtered);
2202         g_slist_free(mail_filtering_data.unfiltered);
2203         
2204         *filtered = g_slist_reverse(*filtered);
2205         *unfiltered = g_slist_reverse(*unfiltered);
2206
2207         statusbar_progress_all(0,0,0);
2208         statusbar_pop_all();
2209 }
2210
2211 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2212 {
2213         MsgInfo *tmp_msginfo = NULL;
2214         MsgFlags flags = {0, 0};
2215         gchar *tmpfile = get_tmp_file();
2216         FILE *fp = g_fopen(tmpfile, "wb");
2217         
2218         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2219             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2220                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2221                 if (fp) 
2222                         fclose(fp);
2223                 g_free(tmpfile);
2224                 return NULL;
2225         }
2226         
2227         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2228                 fclose(fp);
2229                 fp = NULL;
2230                 tmp_msginfo = procheader_parse_file(
2231                         tmpfile, flags, 
2232                         TRUE, FALSE);
2233         }
2234         if (fp)
2235                 fclose(fp);
2236
2237         if (tmp_msginfo != NULL) {
2238                 if (src_msginfo)
2239                         tmp_msginfo->folder = src_msginfo->folder;
2240                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2241         } else {
2242                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2243         }
2244
2245         g_free(tmpfile);
2246
2247         return tmp_msginfo;
2248 }
2249
2250 static GSList *spam_learners = NULL;
2251
2252 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2253 {
2254         if (!g_slist_find(spam_learners, learn_func))
2255                 spam_learners = g_slist_append(spam_learners, learn_func);
2256         if (mainwindow_get_mainwindow()) {
2257                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2258                 summary_set_menu_sensitive(
2259                         mainwindow_get_mainwindow()->summaryview);
2260                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2261         }
2262 }
2263
2264 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2265 {
2266         spam_learners = g_slist_remove(spam_learners, learn_func);
2267         if (mainwindow_get_mainwindow()) {
2268                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2269                 summary_set_menu_sensitive(
2270                         mainwindow_get_mainwindow()->summaryview);
2271                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2272         }
2273 }
2274
2275 gboolean procmsg_spam_can_learn(void)
2276 {
2277         return g_slist_length(spam_learners) > 0;
2278 }
2279
2280 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2281 {
2282         GSList *cur = spam_learners;
2283         int ret = 0;
2284         for (; cur; cur = cur->next) {
2285                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2286                 ret |= func(info, list, spam);
2287         }
2288         return ret;
2289 }
2290
2291 static gchar *spam_folder_item = NULL;
2292 static FolderItem * (*procmsg_spam_get_folder_func)(MsgInfo *msginfo) = NULL;
2293 void procmsg_spam_set_folder (const char *item_identifier, FolderItem *(*spam_get_folder_func)(MsgInfo *info))
2294 {
2295         g_free(spam_folder_item);
2296         if (item_identifier)
2297                 spam_folder_item = g_strdup(item_identifier);
2298         else
2299                 spam_folder_item = NULL;
2300         if (spam_get_folder_func != NULL)
2301                 procmsg_spam_get_folder_func = spam_get_folder_func;
2302         else
2303                 procmsg_spam_get_folder_func = NULL;
2304 }
2305
2306 FolderItem *procmsg_spam_get_folder (MsgInfo *msginfo)
2307 {
2308         FolderItem *item = NULL;
2309         
2310         if (procmsg_spam_get_folder_func) 
2311                 item = procmsg_spam_get_folder_func(msginfo);
2312         if (item == NULL && spam_folder_item)
2313                 item = folder_find_item_from_identifier(spam_folder_item);
2314         if (item == NULL)
2315                 item = folder_get_default_trash();
2316         return item;
2317 }
2318
2319 static void item_has_queued_mails(FolderItem *item, gpointer data)
2320 {
2321         gboolean *result = (gboolean *)data;
2322         if (*result == TRUE)
2323                 return;
2324         if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0)
2325                 *result = TRUE;
2326 }
2327
2328 gboolean procmsg_have_queued_mails_fast (void)
2329 {
2330         gboolean result = FALSE;
2331         folder_func_to_all_folders(item_has_queued_mails, &result);
2332         return result;
2333 }
2334
2335 static void item_has_trashed_mails(FolderItem *item, gpointer data)
2336 {
2337         gboolean *result = (gboolean *)data;
2338         if (*result == TRUE)
2339                 return;
2340         if (folder_has_parent_of_type(item, F_TRASH) && item->total_msgs > 0)
2341                 *result = TRUE;
2342 }
2343
2344 gboolean procmsg_have_trashed_mails_fast (void)
2345 {
2346         gboolean result = FALSE;
2347         folder_func_to_all_folders(item_has_trashed_mails, &result);
2348         return result;
2349 }