379e9782fb5c22697926d828369efc2f9aa252d0
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 Hiroyuki Yamamoto
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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
46 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session);
47
48 enum
49 {
50         Q_SENDER           = 0,
51         Q_SMTPSERVER       = 1,
52         Q_RECIPIENTS       = 2,
53         Q_NEWSGROUPS       = 3,
54         Q_MAIL_ACCOUNT_ID  = 4,
55         Q_NEWS_ACCOUNT_ID  = 5,
56         Q_SAVE_COPY_FOLDER = 6,
57         Q_REPLY_MESSAGE_ID = 7,
58         Q_FWD_MESSAGE_ID   = 8,
59         Q_PRIVACY_SYSTEM   = 9,
60         Q_ENCRYPT          = 10,
61         Q_ENCRYPT_DATA     = 11,
62 };
63
64 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
65 {
66         GHashTable *msg_table;
67
68         if (mlist == NULL) return NULL;
69
70         msg_table = g_hash_table_new(NULL, g_direct_equal);
71         procmsg_msg_hash_table_append(msg_table, mlist);
72
73         return msg_table;
74 }
75
76 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
77 {
78         GSList *cur;
79         MsgInfo *msginfo;
80
81         if (msg_table == NULL || mlist == NULL) return;
82
83         for (cur = mlist; cur != NULL; cur = cur->next) {
84                 msginfo = (MsgInfo *)cur->data;
85
86                 g_hash_table_insert(msg_table,
87                                     GUINT_TO_POINTER(msginfo->msgnum),
88                                     msginfo);
89         }
90 }
91
92 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
93 {
94         GHashTable *msg_table;
95         GSList *cur;
96         MsgInfo *msginfo;
97
98         if (mlist == NULL) return NULL;
99
100         msg_table = g_hash_table_new(NULL, g_direct_equal);
101
102         for (cur = mlist; cur != NULL; cur = cur->next) {
103                 msginfo = (MsgInfo *)cur->data;
104                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
105         }
106
107         return msg_table;
108 }
109
110 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
111 {
112         GSList *cur;
113         MsgInfo *msginfo;
114         gint last = 0;
115
116         for (cur = mlist; cur != NULL; cur = cur->next) {
117                 msginfo = (MsgInfo *)cur->data;
118                 if (msginfo && msginfo->msgnum > last)
119                         last = msginfo->msgnum;
120         }
121
122         return last;
123 }
124
125 void procmsg_msg_list_free(GSList *mlist)
126 {
127         GSList *cur;
128         MsgInfo *msginfo;
129
130         for (cur = mlist; cur != NULL; cur = cur->next) {
131                 msginfo = (MsgInfo *)cur->data;
132                 procmsg_msginfo_free(msginfo);
133         }
134         g_slist_free(mlist);
135 }
136
137 struct MarkSum {
138         gint *new_msgs;
139         gint *unread_msgs;
140         gint *total_msgs;
141         gint *min;
142         gint *max;
143         gint first;
144 };
145
146 /* CLAWS subject threading:
147   
148   in the first round it inserts subject lines in a 
149   relation (subject <-> node)
150
151   the second round finishes the threads by attaching
152   matching subject lines to the one found in the
153   relation. will use the oldest node with the same
154   subject that is not more then thread_by_subject_max_age
155   days old (see subject_relation_lookup)
156 */  
157
158 static void subject_relation_insert(GRelation *relation, GNode *node)
159 {
160         gchar *subject;
161         MsgInfo *msginfo;
162
163         g_return_if_fail(relation != NULL);
164         g_return_if_fail(node != NULL);
165         msginfo = (MsgInfo *) node->data;
166         g_return_if_fail(msginfo != NULL);
167
168         subject = msginfo->subject;
169         if (subject == NULL)
170                 return;
171         subject += subject_get_prefix_length(subject);
172
173         g_relation_insert(relation, subject, node);
174 }
175
176 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
177 {
178         gchar *subject;
179         GTuples *tuples;
180         GNode *node = NULL;
181         gint prefix_length;
182     
183         g_return_val_if_fail(relation != NULL, NULL);
184
185         subject = msginfo->subject;
186         if (subject == NULL)
187                 return NULL;
188         prefix_length = subject_get_prefix_length(subject);
189         if (prefix_length <= 0)
190                 return NULL;
191         subject += prefix_length;
192         
193         tuples = g_relation_select(relation, subject, 0);
194         if (tuples == NULL)
195                 return NULL;
196
197         if (tuples->len > 0) {
198                 int i;
199                 GNode *relation_node;
200                 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
201                 gboolean match;
202
203                 /* check all nodes with the same subject to find the best parent */
204                 for (i = 0; i < tuples->len; i++) {
205                         relation_node = (GNode *) g_tuples_index(tuples, i, 1);
206                         relation_msginfo = (MsgInfo *) relation_node->data;
207                         match = FALSE;
208
209                         /* best node should be the oldest in the found nodes */
210                         /* parent node must not be older then msginfo */
211                         if ((relation_msginfo->date_t < msginfo->date_t) &&
212                             ((best_msginfo == NULL) ||
213                              (best_msginfo->date_t > relation_msginfo->date_t)))
214                                 match = TRUE;
215
216                         /* parent node must not be more then thread_by_subject_max_age
217                            days older then msginfo */
218                         if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
219                             prefs_common.thread_by_subject_max_age * 3600 * 24)
220                                 match = FALSE;
221
222                         /* can add new tests for all matching
223                            nodes found by subject */
224
225                         if (match) {
226                                 node = relation_node;
227                                 best_msginfo = relation_msginfo;
228                         }
229                 }           
230         }
231
232         g_tuples_destroy(tuples);
233         return node;
234 }
235
236 /* return the reversed thread tree */
237 GNode *procmsg_get_thread_tree(GSList *mlist)
238 {
239         GNode *root, *parent, *node, *next;
240         GHashTable *msgid_table;
241         GRelation *subject_relation;
242         MsgInfo *msginfo;
243         const gchar *msgid;
244         GSList *reflist;
245
246         root = g_node_new(NULL);
247         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
248         subject_relation = g_relation_new(2);
249         g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
250
251         for (; mlist != NULL; mlist = mlist->next) {
252                 msginfo = (MsgInfo *)mlist->data;
253                 parent = root;
254
255                 if (msginfo->inreplyto) {
256                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
257                         if (parent == NULL) {
258                                 parent = root;
259                         }
260                 }
261                 node = g_node_insert_data_before
262                         (parent, parent == root ? parent->children : NULL,
263                          msginfo);
264                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
265                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
266
267                 /* CLAWS: add subject to relation (without prefix) */
268                 if (prefs_common.thread_by_subject) {
269                         subject_relation_insert(subject_relation, node);
270                 }
271         }
272
273         /* complete the unfinished threads */
274         for (node = root->children; node != NULL; ) {
275                 next = node->next;
276                 msginfo = (MsgInfo *)node->data;
277                 parent = NULL;
278                 
279                 if (msginfo->inreplyto)
280                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
281
282                 /* try looking for the indirect parent */
283                 if (!parent && msginfo->references) {
284                         for (reflist = msginfo->references;
285                              reflist != NULL; reflist = reflist->next)
286                                 if ((parent = g_hash_table_lookup
287                                         (msgid_table, reflist->data)) != NULL)
288                                         break;
289                 }                                        
290               
291                 /* node should not be the parent, and node should not
292                    be an ancestor of parent (circular reference) */
293                 if (parent && parent != node &&
294                     !g_node_is_ancestor(node, parent)) {
295                         g_node_unlink(node);
296                         g_node_insert_before
297                                 (parent, parent->children, node);
298                 }
299                
300                 node = next;
301         }
302
303         if (prefs_common.thread_by_subject) {
304                 for (node = root->children; node && node != NULL;) {
305                         next = node->next;
306                         msginfo = (MsgInfo *) node->data;
307                         
308                         parent = subject_relation_lookup(subject_relation, msginfo);
309                         
310                         /* the node may already be threaded by IN-REPLY-TO, so go up 
311                          * in the tree to 
312                            find the parent node */
313                         if (parent != NULL) {
314                                 if (g_node_is_ancestor(node, parent))
315                                         parent = NULL;
316                                 if (parent == node)
317                                         parent = NULL;
318                         }
319                         
320                         if (parent) {
321                                 g_node_unlink(node);
322                                 g_node_append(parent, node);
323                         }
324
325                         node = next;
326                 }       
327         }
328         
329         g_relation_destroy(subject_relation);
330         g_hash_table_destroy(msgid_table);
331
332         return root;
333 }
334
335 gint procmsg_move_messages(GSList *mlist)
336 {
337         GSList *cur, *movelist = NULL;
338         MsgInfo *msginfo;
339         FolderItem *dest = NULL;
340         gint retval = 0;
341
342         if (!mlist) return 0;
343
344         folder_item_update_freeze();
345
346         for (cur = mlist; cur != NULL; cur = cur->next) {
347                 msginfo = (MsgInfo *)cur->data;
348                 if (!dest) {
349                         dest = msginfo->to_folder;
350                         movelist = g_slist_append(movelist, msginfo);
351                 } else if (dest == msginfo->to_folder) {
352                         movelist = g_slist_append(movelist, msginfo);
353                 } else {
354                         folder_item_move_msgs(dest, movelist);
355                         g_slist_free(movelist);
356                         movelist = NULL;
357                         dest = msginfo->to_folder;
358                         movelist = g_slist_append(movelist, msginfo);
359                 }
360                 procmsg_msginfo_set_to_folder(msginfo, NULL);
361         }
362
363         if (movelist) {
364                 retval = folder_item_move_msgs(dest, movelist);
365                 g_slist_free(movelist);
366         }
367
368         folder_item_update_thaw();
369         return retval;
370 }
371
372 void procmsg_copy_messages(GSList *mlist)
373 {
374         GSList *cur, *copylist = NULL;
375         MsgInfo *msginfo;
376         FolderItem *dest = NULL;
377
378         if (!mlist) return;
379
380         folder_item_update_freeze();
381
382         for (cur = mlist; cur != NULL; cur = cur->next) {
383                 msginfo = (MsgInfo *)cur->data;
384                 if (!dest) {
385                         dest = msginfo->to_folder;
386                         copylist = g_slist_append(copylist, msginfo);
387                 } else if (dest == msginfo->to_folder) {
388                         copylist = g_slist_append(copylist, msginfo);
389                 } else {
390                         folder_item_copy_msgs(dest, copylist);
391                         g_slist_free(copylist);
392                         copylist = NULL;
393                         dest = msginfo->to_folder;
394                         copylist = g_slist_append(copylist, msginfo);
395                 }
396                 procmsg_msginfo_set_to_folder(msginfo, NULL);
397         }
398
399         if (copylist) {
400                 folder_item_copy_msgs(dest, copylist);
401                 g_slist_free(copylist);
402         }
403
404         folder_item_update_thaw();
405 }
406
407 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
408 {
409         gchar *file;
410
411         g_return_val_if_fail(msginfo != NULL, NULL);
412
413         if (msginfo->plaintext_file)
414                 file = g_strdup(msginfo->plaintext_file);
415         else {
416                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
417         }
418
419         return file;
420 }
421
422 gchar *procmsg_get_message_file(MsgInfo *msginfo)
423 {
424         gchar *filename = NULL;
425
426         g_return_val_if_fail(msginfo != NULL, NULL);
427
428         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
429         if (!filename)
430                 debug_print("can't fetch message %d\n", msginfo->msgnum);
431
432         return filename;
433 }
434
435 gchar *procmsg_get_message_file_full(MsgInfo *msginfo, gboolean headers, gboolean body)
436 {
437         gchar *filename = NULL;
438
439         g_return_val_if_fail(msginfo != NULL, NULL);
440
441         filename = folder_item_fetch_msg_full(msginfo->folder, msginfo->msgnum,
442                                                 headers, body);
443         if (!filename)
444                 debug_print("can't fetch message %d\n", msginfo->msgnum);
445
446         return filename;
447 }
448
449 GSList *procmsg_get_message_file_list(GSList *mlist)
450 {
451         GSList *file_list = NULL;
452         MsgInfo *msginfo;
453         MsgFileInfo *fileinfo;
454         gchar *file;
455
456         while (mlist != NULL) {
457                 msginfo = (MsgInfo *)mlist->data;
458                 file = procmsg_get_message_file(msginfo);
459                 if (!file) {
460                         procmsg_message_file_list_free(file_list);
461                         return NULL;
462                 }
463                 fileinfo = g_new(MsgFileInfo, 1);
464                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
465                 fileinfo->file = file;
466                 fileinfo->flags = g_new(MsgFlags, 1);
467                 *fileinfo->flags = msginfo->flags;
468                 file_list = g_slist_prepend(file_list, fileinfo);
469                 mlist = mlist->next;
470         }
471
472         file_list = g_slist_reverse(file_list);
473
474         return file_list;
475 }
476
477 void procmsg_message_file_list_free(MsgInfoList *file_list)
478 {
479         GSList *cur;
480         MsgFileInfo *fileinfo;
481
482         for (cur = file_list; cur != NULL; cur = cur->next) {
483                 fileinfo = (MsgFileInfo *)cur->data;
484                 procmsg_msginfo_free(fileinfo->msginfo);
485                 g_free(fileinfo->file);
486                 g_free(fileinfo->flags);
487                 g_free(fileinfo);
488         }
489
490         g_slist_free(file_list);
491 }
492
493 FILE *procmsg_open_message(MsgInfo *msginfo)
494 {
495         FILE *fp;
496         gchar *file;
497
498         g_return_val_if_fail(msginfo != NULL, NULL);
499
500         file = procmsg_get_message_file_path(msginfo);
501         g_return_val_if_fail(file != NULL, NULL);
502
503         if (!is_file_exist(file)) {
504                 g_free(file);
505                 file = procmsg_get_message_file(msginfo);
506                 if (!file)
507                         return NULL;
508         }
509
510         if ((fp = g_fopen(file, "rb")) == NULL) {
511                 FILE_OP_ERROR(file, "fopen");
512                 g_free(file);
513                 return NULL;
514         }
515
516         g_free(file);
517
518         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
519                 gchar buf[BUFFSIZE];
520
521                 while (fgets(buf, sizeof(buf), fp) != NULL)
522                         if (buf[0] == '\r' || buf[0] == '\n') break;
523         }
524
525         return fp;
526 }
527
528 gboolean procmsg_msg_exist(MsgInfo *msginfo)
529 {
530         gchar *path;
531         gboolean ret;
532
533         if (!msginfo) return FALSE;
534
535         path = folder_item_get_path(msginfo->folder);
536         change_dir(path);
537         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
538         g_free(path);
539
540         return ret;
541 }
542
543 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
544                                 PrefsFilterType type)
545 {
546         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
547                                        {"X-ML-Name:",      NULL, TRUE},
548                                        {"X-List:",         NULL, TRUE},
549                                        {"X-Mailing-list:", NULL, TRUE},
550                                        {"List-Id:",        NULL, TRUE},
551                                        {"X-Sequence:",     NULL, TRUE},
552                                        {NULL,              NULL, FALSE}};
553         enum
554         {
555                 H_X_BEENTHERE    = 0,
556                 H_X_ML_NAME      = 1,
557                 H_X_LIST         = 2,
558                 H_X_MAILING_LIST = 3,
559                 H_LIST_ID        = 4,
560                 H_X_SEQUENCE     = 5
561         };
562
563         FILE *fp;
564
565         g_return_if_fail(msginfo != NULL);
566         g_return_if_fail(header != NULL);
567         g_return_if_fail(key != NULL);
568
569         *header = NULL;
570         *key = NULL;
571
572         switch (type) {
573         case FILTER_BY_NONE:
574                 return;
575         case FILTER_BY_AUTO:
576                 if ((fp = procmsg_open_message(msginfo)) == NULL)
577                         return;
578                 procheader_get_header_fields(fp, hentry);
579                 fclose(fp);
580
581 #define SET_FILTER_KEY(hstr, idx)       \
582 {                                       \
583         *header = g_strdup(hstr);       \
584         *key = hentry[idx].body;        \
585         hentry[idx].body = NULL;        \
586 }
587
588                 if (hentry[H_X_BEENTHERE].body != NULL) {
589                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
590                 } else if (hentry[H_X_ML_NAME].body != NULL) {
591                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
592                 } else if (hentry[H_X_LIST].body != NULL) {
593                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
594                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
595                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
596                 } else if (hentry[H_LIST_ID].body != NULL) {
597                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
598                         extract_list_id_str(*key);
599                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
600                         gchar *p;
601
602                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
603                         p = *key;
604                         while (*p != '\0') {
605                                 while (*p != '\0' && !g_ascii_isspace(*p)) p++;
606                                 while (g_ascii_isspace(*p)) p++;
607                                 if (g_ascii_isdigit(*p)) {
608                                         *p = '\0';
609                                         break;
610                                 }
611                         }
612                         g_strstrip(*key);
613                 } else if (msginfo->subject) {
614                         *header = g_strdup("subject");
615                         *key = g_strdup(msginfo->subject);
616                 }
617
618 #undef SET_FILTER_KEY
619
620                 g_free(hentry[H_X_BEENTHERE].body);
621                 hentry[H_X_BEENTHERE].body = NULL;
622                 g_free(hentry[H_X_ML_NAME].body);
623                 hentry[H_X_ML_NAME].body = NULL;
624                 g_free(hentry[H_X_LIST].body);
625                 hentry[H_X_LIST].body = NULL;
626                 g_free(hentry[H_X_MAILING_LIST].body);
627                 hentry[H_X_MAILING_LIST].body = NULL;
628                 g_free(hentry[H_LIST_ID].body);
629                 hentry[H_LIST_ID].body = NULL;
630
631                 break;
632         case FILTER_BY_FROM:
633                 *header = g_strdup("from");
634                 *key = g_strdup(msginfo->from);
635                 break;
636         case FILTER_BY_TO:
637                 *header = g_strdup("to");
638                 *key = g_strdup(msginfo->to);
639                 break;
640         case FILTER_BY_SUBJECT:
641                 *header = g_strdup("subject");
642                 *key = g_strdup(msginfo->subject);
643                 break;
644         default:
645                 break;
646         }
647 }
648
649 void procmsg_empty_trash(FolderItem *trash)
650 {
651         if (trash && trash->total_msgs > 0) {
652                 GSList *mlist = folder_item_get_msg_list(trash);
653                 GSList *cur;
654                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
655                         MsgInfo * msginfo = (MsgInfo *) cur->data;
656                         if (MSG_IS_LOCKED(msginfo->flags))
657                                 continue;
658                         if (msginfo->total_size != 0 && 
659                             msginfo->size != (off_t)msginfo->total_size)
660                                 partial_mark_for_delete(msginfo);
661
662                         procmsg_msginfo_free(msginfo);
663                 }
664                 g_slist_free(mlist);
665                 folder_item_remove_all_msg(trash);
666         }
667 }
668
669 void procmsg_empty_all_trash(void)
670 {
671         FolderItem *trash;
672         GList *cur;
673
674         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
675                 Folder *folder = FOLDER(cur->data);
676                 trash = folder->trash;
677                 procmsg_empty_trash(trash);
678                 if (folder->account && folder->account->set_trash_folder && 
679                     folder_find_item_from_identifier(folder->account->trash_folder))
680                         procmsg_empty_trash(
681                                 folder_find_item_from_identifier(folder->account->trash_folder));
682         }
683 }
684
685 static PrefsAccount *procmsg_get_account_from_file(const gchar *file)
686 {
687         PrefsAccount *mailac = NULL;
688         FILE *fp;
689         int hnum;
690         gchar buf[BUFFSIZE];
691         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
692                                        {"SSV:",  NULL, FALSE},
693                                        {"R:",    NULL, FALSE},
694                                        {"NG:",   NULL, FALSE},
695                                        {"MAID:", NULL, FALSE},
696                                        {"NAID:", NULL, FALSE},
697                                        {"SCF:",  NULL, FALSE},
698                                        {"RMID:", NULL, FALSE},
699                                        {"FMID:", NULL, FALSE},
700                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
701                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
702                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
703                                        {NULL,    NULL, FALSE}};
704         
705         g_return_val_if_fail(file != NULL, NULL);
706
707         if ((fp = g_fopen(file, "rb")) == NULL) {
708                 FILE_OP_ERROR(file, "fopen");
709                 return NULL;
710         }
711
712         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
713                != -1) {
714                 gchar *p = buf + strlen(qentry[hnum].name);
715
716                 if (hnum == Q_MAIL_ACCOUNT_ID) {
717                         mailac = account_find_from_id(atoi(p));
718                         break;
719                 }
720         }
721         fclose(fp);
722         return mailac;
723 }
724
725 static GSList *procmsg_list_sort_by_account(FolderItem *queue, GSList *list)
726 {
727         GSList *result = NULL;
728         GSList *orig = NULL;
729         PrefsAccount *last_account = NULL;
730         MsgInfo *msg = NULL;
731         GSList *cur = NULL;
732         gboolean nothing_to_sort = TRUE;
733
734         if (!list)
735                 return NULL;
736
737         orig = g_slist_copy(list);
738         
739         msg = (MsgInfo *)orig->data;
740         
741         for (cur = orig; cur; cur = cur->next)
742                 debug_print("sort before %s\n", ((MsgInfo *)cur->data)->from);
743         
744         debug_print("\n");
745
746 parse_again:    
747         nothing_to_sort = TRUE;
748         cur = orig;
749         while (cur) {
750                 gchar *file = NULL;
751                 PrefsAccount *ac = procmsg_get_account_from_file(file);
752                 msg = (MsgInfo *)cur->data;
753                 file = folder_item_fetch_msg(queue, msg->msgnum);
754                 g_free(file);
755
756                 if (last_account == NULL || (ac != NULL && ac == last_account)) {
757                         result = g_slist_append(result, msg);
758                         orig = g_slist_remove(orig, msg);
759                         last_account = ac;
760                         nothing_to_sort = FALSE;
761                         goto parse_again;
762                 }
763                 cur = cur->next;
764         }
765         
766         if (orig || g_slist_length(orig)) {
767                 if (!last_account && nothing_to_sort) {
768                         /* can't find an account for the rest of the list */
769                         cur = orig;
770                         while (cur) {
771                                 result = g_slist_append(result, cur->data);
772                                 cur = cur->next;
773                         }
774                 } else {
775                         last_account = NULL;
776                         goto parse_again;
777                 }
778         }
779         
780         g_slist_free(orig);
781         
782         for (cur = result; cur; cur = cur->next)
783                 debug_print("sort after  %s\n", ((MsgInfo *)cur->data)->from);
784
785         debug_print("\n");
786
787         return result;
788 }
789
790 static gboolean procmsg_is_last_for_account(FolderItem *queue, MsgInfo *msginfo, GSList *elem)
791 {
792         gchar *file = folder_item_fetch_msg(queue, msginfo->msgnum);
793         PrefsAccount *ac = procmsg_get_account_from_file(file);
794         GSList *cur = elem;
795         g_free(file);
796         for (cur = elem; cur; cur = cur->next) {
797                 MsgInfo *cur_msginfo = (MsgInfo *)cur->data;
798                 file = folder_item_fetch_msg(queue, cur_msginfo->msgnum);
799                 
800                 if (cur_msginfo != msginfo && !MSG_IS_LOCKED(cur_msginfo->flags)) {
801                         if (procmsg_get_account_from_file(file) == ac) {
802                                 g_free(file);
803                                 return FALSE;
804                         }
805                 }
806                 
807                 g_free(file);
808         }
809         return TRUE;
810 }
811
812 /*!
813  *\brief        Send messages in queue
814  *
815  *\param        queue Queue folder to process
816  *\param        save_msgs Unused
817  *
818  *\return       Number of messages sent, negative if an error occurred
819  *              positive if no error occurred
820  */
821 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
822 {
823         gint sent = 0, err = 0;
824         GSList *list, *elem;
825         GSList *sorted_list = NULL;
826
827         if (!queue)
828                 queue = folder_get_default_queue();
829         g_return_val_if_fail(queue != NULL, -1);
830
831         folder_item_scan(queue);
832         list = folder_item_get_msg_list(queue);
833
834         /* sort the list per sender account; this helps reusing the same SMTP server */
835         sorted_list = procmsg_list_sort_by_account(queue, list);
836         
837         for (elem = sorted_list; elem != NULL; elem = elem->next) {
838                 gchar *file;
839                 MsgInfo *msginfo;
840                         
841                 msginfo = (MsgInfo *)(elem->data);
842                 if (!MSG_IS_LOCKED(msginfo->flags)) {
843                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
844                         if (file) {
845                                 if (procmsg_send_message_queue_full(file, 
846                                                 !procmsg_is_last_for_account(queue, msginfo, elem)) < 0) {
847                                         g_warning("Sending queued message %d failed.\n", 
848                                                   msginfo->msgnum);
849                                         err++;
850                                 } else {
851                                         /* CLAWS: 
852                                          * We save in procmsg_send_message_queue because
853                                          * we need the destination folder from the queue
854                                          * header
855                                                         
856                                         if (save_msgs)
857                                                 procmsg_save_to_outbox
858                                                         (queue->folder->outbox,
859                                                          file, TRUE);
860                                          */
861                                         sent++; 
862                                         folder_item_remove_msg(queue, msginfo->msgnum);
863                                 }
864                                 g_free(file);
865                         }
866                 }
867                 /* FIXME: supposedly if only one message is locked, and queue
868                  * is being flushed, the following free says something like 
869                  * "freeing msg ## in folder (nil)". */
870                 procmsg_msginfo_free(msginfo);
871         }
872
873         g_slist_free(sorted_list);
874
875         return (err != 0 ? -err : sent);
876 }
877
878 /*!
879  *\brief        Determine if a queue folder is empty
880  *
881  *\param        queue Queue folder to process
882  *
883  *\return       TRUE if the queue folder is empty, otherwise return FALSE
884  */
885 gboolean procmsg_queue_is_empty(FolderItem *queue)
886 {
887         GSList *list;
888         gboolean res = FALSE;
889         if (!queue)
890                 queue = folder_get_default_queue();
891         g_return_val_if_fail(queue != NULL, TRUE);
892
893         folder_item_scan(queue);
894         list = folder_item_get_msg_list(queue);
895         res = (list == NULL);
896         procmsg_msg_list_free(list);
897         return res;
898 }
899
900 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
901 {
902         FILE *fp, *outfp;
903         gchar buf[BUFFSIZE];
904         
905         if ((fp = g_fopen(in, "rb")) == NULL) {
906                 FILE_OP_ERROR(in, "fopen");
907                 return -1;
908         }
909         if ((outfp = g_fopen(out, "wb")) == NULL) {
910                 FILE_OP_ERROR(out, "fopen");
911                 fclose(fp);
912                 return -1;
913         }
914         while (fgets(buf, sizeof(buf), fp) != NULL)
915                 if (buf[0] == '\r' || buf[0] == '\n') break;
916         while (fgets(buf, sizeof(buf), fp) != NULL)
917                 fputs(buf, outfp);
918         fclose(outfp);
919         fclose(fp);
920         return 0;
921
922 }
923 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
924                             gboolean is_queued)
925 {
926         gint num;
927         MsgInfo *msginfo, *tmp_msginfo;
928         MsgFlags flag = {0, 0};
929
930         debug_print("saving sent message...\n");
931
932         if (!outbox)
933                 outbox = folder_get_default_outbox();
934         g_return_val_if_fail(outbox != NULL, -1);
935
936         /* remove queueing headers */
937         if (is_queued) {
938                 gchar tmp[MAXPATHLEN + 1];
939
940                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
941                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
942                 
943                 if (procmsg_remove_special_headers(file, tmp) !=0)
944                         return -1;
945
946                 folder_item_scan(outbox);
947                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
948                         g_warning("can't save message\n");
949                         g_unlink(tmp);
950                         return -1;
951                 }
952         } else {
953                 folder_item_scan(outbox);
954                 if ((num = folder_item_add_msg
955                         (outbox, file, &flag, FALSE)) < 0) {
956                         g_warning("can't save message\n");
957                         return -1;
958                 }
959                 return -1;
960         }
961         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
962         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
963         if (msginfo != NULL) {
964                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
965                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
966                 /* tmp_msginfo == msginfo */
967                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
968                     msginfo->returnreceiptto)) {
969                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
970                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
971                 }       
972         }
973
974         return 0;
975 }
976
977 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
978 {
979         static const gchar *def_cmd = "lpr %s";
980         static guint id = 0;
981         gchar *prtmp;
982         FILE *tmpfp, *prfp;
983         gchar buf[1024];
984         gchar *p;
985
986         g_return_if_fail(msginfo);
987
988         if (procmime_msginfo_is_encrypted(msginfo))
989                 tmpfp = procmime_get_first_encrypted_text_content(msginfo);
990         else
991                 tmpfp = procmime_get_first_text_content(msginfo);
992         if (tmpfp == NULL) {
993                 g_warning("Can't get text part\n");
994                 return;
995         }
996
997         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
998                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
999
1000         if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
1001                 FILE_OP_ERROR(prtmp, "fopen");
1002                 g_free(prtmp);
1003                 fclose(tmpfp);
1004                 return;
1005         }
1006
1007         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
1008         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
1009         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
1010         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
1011         if (msginfo->newsgroups)
1012                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
1013         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
1014         fputc('\n', prfp);
1015
1016         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1017                 fputs(buf, prfp);
1018
1019         fclose(prfp);
1020         fclose(tmpfp);
1021
1022         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
1023             !strchr(p + 2, '%'))
1024                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
1025         else {
1026                 if (cmdline)
1027                         g_warning("Print command line is invalid: '%s'\n",
1028                                   cmdline);
1029                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
1030         }
1031
1032         g_free(prtmp);
1033
1034         g_strchomp(buf);
1035         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
1036         system(buf);
1037 }
1038
1039 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1040 {
1041         msginfo->refcnt++;
1042         
1043         return msginfo;
1044 }
1045
1046 MsgInfo *procmsg_msginfo_new(void)
1047 {
1048         MsgInfo *newmsginfo;
1049
1050         newmsginfo = g_new0(MsgInfo, 1);
1051         newmsginfo->refcnt = 1;
1052         
1053         return newmsginfo;
1054 }
1055
1056 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1057 {
1058         MsgInfo *newmsginfo;
1059         GSList *refs;
1060
1061         if (msginfo == NULL) return NULL;
1062
1063         newmsginfo = g_new0(MsgInfo, 1);
1064
1065         newmsginfo->refcnt = 1;
1066
1067 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1068 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1069                         g_strdup(msginfo->mmb) : NULL
1070
1071         MEMBCOPY(msgnum);
1072         MEMBCOPY(size);
1073         MEMBCOPY(mtime);
1074         MEMBCOPY(date_t);
1075
1076         MEMBCOPY(flags);
1077
1078         MEMBDUP(fromname);
1079
1080         MEMBDUP(date);
1081         MEMBDUP(from);
1082         MEMBDUP(to);
1083         MEMBDUP(cc);
1084         MEMBDUP(newsgroups);
1085         MEMBDUP(subject);
1086         MEMBDUP(msgid);
1087         MEMBDUP(inreplyto);
1088         MEMBDUP(xref);
1089
1090         MEMBCOPY(folder);
1091         MEMBCOPY(to_folder);
1092
1093         MEMBDUP(xface);
1094         MEMBDUP(dispositionnotificationto);
1095         MEMBDUP(returnreceiptto);
1096
1097         refs = msginfo->references;
1098         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1099                 newmsginfo->references = g_slist_prepend
1100                         (newmsginfo->references, g_strdup(refs->data)); 
1101         }
1102         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1103
1104         MEMBCOPY(score);
1105         MEMBCOPY(threadscore);
1106         MEMBDUP(plaintext_file);
1107
1108         return newmsginfo;
1109 }
1110
1111 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1112 {
1113         MsgInfo *full_msginfo;
1114         gchar *file;
1115
1116         if (msginfo == NULL) return NULL;
1117
1118         file = procmsg_get_message_file_path(msginfo);
1119         if (!file || !is_file_exist(file)) {
1120                 g_free(file);
1121                 file = procmsg_get_message_file(msginfo);
1122         }
1123         if (!file || !is_file_exist(file)) {
1124                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
1125                 return NULL;
1126         }
1127
1128         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1129         g_free(file);
1130         if (!full_msginfo) return NULL;
1131
1132         /* CLAWS: make sure we add the missing members; see: 
1133          * procheader.c::procheader_get_headernames() */
1134         if (!msginfo->xface)
1135                 msginfo->xface = g_strdup(full_msginfo->xface);
1136         if (!msginfo->dispositionnotificationto)
1137                 msginfo->dispositionnotificationto = 
1138                         g_strdup(full_msginfo->dispositionnotificationto);
1139         if (!msginfo->returnreceiptto)
1140                 msginfo->returnreceiptto = g_strdup
1141                         (full_msginfo->returnreceiptto);
1142         if (!msginfo->partial_recv && full_msginfo->partial_recv)
1143                 msginfo->partial_recv = g_strdup
1144                         (full_msginfo->partial_recv);
1145         msginfo->total_size = full_msginfo->total_size;
1146         if (!msginfo->account_server && full_msginfo->account_server)
1147                 msginfo->account_server = g_strdup
1148                         (full_msginfo->account_server);
1149         if (!msginfo->account_login && full_msginfo->account_login)
1150                 msginfo->account_login = g_strdup
1151                         (full_msginfo->account_login);
1152         msginfo->planned_download = full_msginfo->planned_download;
1153         procmsg_msginfo_free(full_msginfo);
1154
1155         return procmsg_msginfo_new_ref(msginfo);
1156 }
1157
1158 void procmsg_msginfo_free(MsgInfo *msginfo)
1159 {
1160         if (msginfo == NULL) return;
1161
1162         msginfo->refcnt--;
1163         if (msginfo->refcnt > 0)
1164                 return;
1165
1166         if (msginfo->to_folder) {
1167                 msginfo->to_folder->op_count--;
1168                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1169         }
1170
1171         g_free(msginfo->fromspace);
1172         g_free(msginfo->returnreceiptto);
1173         g_free(msginfo->dispositionnotificationto);
1174         g_free(msginfo->xface);
1175
1176         g_free(msginfo->fromname);
1177
1178         g_free(msginfo->date);
1179         g_free(msginfo->from);
1180         g_free(msginfo->to);
1181         g_free(msginfo->cc);
1182         g_free(msginfo->newsgroups);
1183         g_free(msginfo->subject);
1184         g_free(msginfo->msgid);
1185         g_free(msginfo->inreplyto);
1186         g_free(msginfo->xref);
1187
1188         g_free(msginfo->partial_recv);
1189         g_free(msginfo->account_server);
1190         g_free(msginfo->account_login);
1191         
1192         slist_free_strings(msginfo->references);
1193         g_slist_free(msginfo->references);
1194
1195         g_free(msginfo->plaintext_file);
1196
1197         g_free(msginfo);
1198 }
1199
1200 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1201 {
1202         guint memusage = 0;
1203         GSList *refs;
1204         
1205         memusage += sizeof(MsgInfo);
1206         if (msginfo->fromname)
1207                 memusage += strlen(msginfo->fromname);
1208         if (msginfo->date)
1209                 memusage += strlen(msginfo->date);
1210         if (msginfo->from)
1211                 memusage += strlen(msginfo->from);
1212         if (msginfo->to)
1213                 memusage += strlen(msginfo->to);
1214         if (msginfo->cc)
1215                 memusage += strlen(msginfo->cc);
1216         if (msginfo->newsgroups)
1217                 memusage += strlen(msginfo->newsgroups);
1218         if (msginfo->subject)
1219                 memusage += strlen(msginfo->subject);
1220         if (msginfo->msgid)
1221                 memusage += strlen(msginfo->msgid);
1222         if (msginfo->inreplyto)
1223                 memusage += strlen(msginfo->inreplyto);
1224         if (msginfo->xface)
1225                 memusage += strlen(msginfo->xface);
1226         if (msginfo->dispositionnotificationto)
1227                 memusage += strlen(msginfo->dispositionnotificationto);
1228         if (msginfo->returnreceiptto)
1229                 memusage += strlen(msginfo->returnreceiptto);
1230         for (refs = msginfo->references; refs; refs=refs->next) {
1231                 gchar *r = (gchar *)refs->data;
1232                 memusage += r?strlen(r):0;
1233         }
1234         if (msginfo->fromspace)
1235                 memusage += strlen(msginfo->fromspace);
1236
1237         return memusage;
1238 }
1239
1240 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1241 {
1242         const MsgInfo *msginfo1 = a;
1243         const MsgInfo *msginfo2 = b;
1244
1245         if (!msginfo1)
1246                 return -1;
1247         if (!msginfo2)
1248                 return -1;
1249
1250         return msginfo1->msgnum - msginfo2->msgnum;
1251 }
1252
1253 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session)
1254 {
1255         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1256                                        {"SSV:",  NULL, FALSE},
1257                                        {"R:",    NULL, FALSE},
1258                                        {"NG:",   NULL, FALSE},
1259                                        {"MAID:", NULL, FALSE},
1260                                        {"NAID:", NULL, FALSE},
1261                                        {"SCF:",  NULL, FALSE},
1262                                        {"RMID:", NULL, FALSE},
1263                                        {"FMID:", NULL, FALSE},
1264                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1265                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1266                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1267                                        {NULL,    NULL, FALSE}};
1268         FILE *fp;
1269         gint filepos;
1270         gint mailval = 0, newsval = 0;
1271         gchar *from = NULL;
1272         gchar *smtpserver = NULL;
1273         GSList *to_list = NULL;
1274         GSList *newsgroup_list = NULL;
1275         gchar *savecopyfolder = NULL;
1276         gchar *replymessageid = NULL;
1277         gchar *fwdmessageid = NULL;
1278         gchar *privacy_system = NULL;
1279         gboolean encrypt = FALSE;
1280         gchar *encrypt_data = NULL;
1281         gchar buf[BUFFSIZE];
1282         gint hnum;
1283         PrefsAccount *mailac = NULL, *newsac = NULL;
1284         gboolean save_clear_text = TRUE;
1285         gchar *tmp_enc_file = NULL;
1286
1287         int local = 0;
1288
1289         g_return_val_if_fail(file != NULL, -1);
1290
1291         if ((fp = g_fopen(file, "rb")) == NULL) {
1292                 FILE_OP_ERROR(file, "fopen");
1293                 return -1;
1294         }
1295
1296         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1297                != -1) {
1298                 gchar *p = buf + strlen(qentry[hnum].name);
1299
1300                 switch (hnum) {
1301                 case Q_SENDER:
1302                         if (from == NULL) 
1303                                 from = g_strdup(p);
1304                         break;
1305                 case Q_SMTPSERVER:
1306                         if (smtpserver == NULL) 
1307                                 smtpserver = g_strdup(p);
1308                         break;
1309                 case Q_RECIPIENTS:
1310                         to_list = address_list_append(to_list, p);
1311                         break;
1312                 case Q_NEWSGROUPS:
1313                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1314                         break;
1315                 case Q_MAIL_ACCOUNT_ID:
1316                         mailac = account_find_from_id(atoi(p));
1317                         break;
1318                 case Q_NEWS_ACCOUNT_ID:
1319                         newsac = account_find_from_id(atoi(p));
1320                         break;
1321                 case Q_SAVE_COPY_FOLDER:
1322                         if (savecopyfolder == NULL) 
1323                                 savecopyfolder = g_strdup(p);
1324                         break;
1325                 case Q_REPLY_MESSAGE_ID:
1326                         if (replymessageid == NULL) 
1327                                 replymessageid = g_strdup(p);
1328                         break;
1329                 case Q_FWD_MESSAGE_ID:
1330                         if (fwdmessageid == NULL) 
1331                                 fwdmessageid = g_strdup(p);
1332                         break;
1333                 case Q_PRIVACY_SYSTEM:
1334                         if (privacy_system == NULL) 
1335                                 privacy_system = g_strdup(p);
1336                         break;
1337                 case Q_ENCRYPT:
1338                         if (p[0] == '1') 
1339                                 encrypt = TRUE;
1340                         break;
1341                 case Q_ENCRYPT_DATA:
1342                         if (encrypt_data == NULL) 
1343                                 encrypt_data = g_strdup(p);
1344                         break;
1345                 }
1346         }
1347         filepos = ftell(fp);
1348
1349         if (encrypt) {
1350                 MimeInfo *mimeinfo;
1351
1352                 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1353
1354                 fclose(fp);
1355                 fp = NULL;
1356
1357                 mimeinfo = procmime_scan_queue_file(file);
1358                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1359                 || (fp = my_tmpfile()) == NULL
1360                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1361                         if (fp)
1362                                 fclose(fp);
1363                         procmime_mimeinfo_free_all(mimeinfo);
1364                         g_free(from);
1365                         g_free(smtpserver);
1366                         slist_free_strings(to_list);
1367                         g_slist_free(to_list);
1368                         slist_free_strings(newsgroup_list);
1369                         g_slist_free(newsgroup_list);
1370                         g_free(savecopyfolder);
1371                         g_free(replymessageid);
1372                         g_free(fwdmessageid);
1373                         g_free(privacy_system);
1374                         g_free(encrypt_data);
1375                         return -1;
1376                 }
1377                 
1378                 rewind(fp);
1379                 if (!save_clear_text) {
1380                         gchar *content = NULL;
1381                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1382                         if (tmpfp) {
1383                                 fclose(tmpfp);
1384
1385                                 content = file_read_stream_to_str(fp);
1386                                 rewind(fp);
1387
1388                                 str_write_to_file(content, tmp_enc_file);
1389                                 g_free(content);
1390                         } else {
1391                                 g_warning("couldn't get tempfile\n");
1392                         }
1393                 } 
1394                 
1395                 procmime_mimeinfo_free_all(mimeinfo);
1396                 
1397                 filepos = 0;
1398         }
1399
1400         if (to_list) {
1401                 debug_print("Sending message by mail\n");
1402                 if (!from) {
1403                         g_warning("Queued message header is broken.\n");
1404                         mailval = -1;
1405                 } else if (mailac && mailac->use_mail_command &&
1406                            mailac->mail_command && (* mailac->mail_command)) {
1407                         mailval = send_message_local(mailac->mail_command, fp);
1408                         local = 1;
1409                 } else {
1410                         if (!mailac) {
1411                                 mailac = account_find_from_smtp_server(from, smtpserver);
1412                                 if (!mailac) {
1413                                         g_warning("Account not found. "
1414                                                     "Using current account...\n");
1415                                         mailac = cur_account;
1416                                 }
1417                         }
1418
1419                         if (mailac)
1420                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1421                         else {
1422                                 PrefsAccount tmp_ac;
1423
1424                                 g_warning("Account not found.\n");
1425
1426                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1427                                 tmp_ac.address = from;
1428                                 tmp_ac.smtp_server = smtpserver;
1429                                 tmp_ac.smtpport = SMTP_PORT;
1430                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1431                         }
1432                 }
1433         }
1434
1435         fseek(fp, filepos, SEEK_SET);
1436         if (newsgroup_list && (mailval == 0)) {
1437                 Folder *folder;
1438                 gchar *tmp = NULL;
1439                 FILE *tmpfp;
1440
1441                 /* write to temporary file */
1442                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1443                             G_DIR_SEPARATOR, (gint)file);
1444                 if ((tmpfp = g_fopen(tmp, "wb")) == NULL) {
1445                         FILE_OP_ERROR(tmp, "fopen");
1446                         newsval = -1;
1447                         alertpanel_error(_("Could not create temporary file for news sending."));
1448                 } else {
1449                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1450                                 FILE_OP_ERROR(tmp, "chmod");
1451                                 g_warning("can't change file mode\n");
1452                         }
1453
1454                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1455                                 if (fputs(buf, tmpfp) == EOF) {
1456                                         FILE_OP_ERROR(tmp, "fputs");
1457                                         newsval = -1;
1458                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1459                                 }
1460                         }
1461                         fclose(tmpfp);
1462
1463                         if (newsval == 0) {
1464                                 debug_print("Sending message by news\n");
1465
1466                                 folder = FOLDER(newsac->folder);
1467
1468                                 newsval = news_post(folder, tmp);
1469                                 if (newsval < 0) {
1470                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1471                                                  newsac->nntp_server);
1472                                 }
1473                         }
1474                         g_unlink(tmp);
1475                 }
1476                 g_free(tmp);
1477         }
1478
1479         fclose(fp);
1480
1481         /* save message to outbox */
1482         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1483                 FolderItem *outbox;
1484
1485                 debug_print("saving sent message...\n");
1486
1487                 outbox = folder_find_item_from_identifier(savecopyfolder);
1488                 if (!outbox)
1489                         outbox = folder_get_default_outbox();
1490                         
1491                 if (save_clear_text || tmp_enc_file == NULL) {
1492                         procmsg_save_to_outbox(outbox, file, TRUE);
1493                 } else {
1494                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1495                 }
1496         }
1497
1498         if (tmp_enc_file != NULL) {
1499                 g_unlink(tmp_enc_file);
1500                 free(tmp_enc_file);
1501                 tmp_enc_file = NULL;
1502         }
1503
1504         if (replymessageid != NULL || fwdmessageid != NULL) {
1505                 gchar **tokens;
1506                 FolderItem *item;
1507                 
1508                 if (replymessageid != NULL)
1509                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1510                 else
1511                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1512                 item = folder_find_item_from_identifier(tokens[0]);
1513
1514                 /* check if queued message has valid folder and message id */
1515                 if (item != NULL && tokens[2] != NULL) {
1516                         MsgInfo *msginfo;
1517                         
1518                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1519                 
1520                         /* check if referring message exists and has a message id */
1521                         if ((msginfo != NULL) && 
1522                             (msginfo->msgid != NULL) &&
1523                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1524                                 procmsg_msginfo_free(msginfo);
1525                                 msginfo = NULL;
1526                         }
1527                         
1528                         if (msginfo == NULL) {
1529                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1530                         }
1531                         
1532                         if (msginfo != NULL) {
1533                                 if (replymessageid != NULL) {
1534                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1535                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1536                                 }  else {
1537                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1538                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1539                                 }
1540                                 procmsg_msginfo_free(msginfo);
1541                         }
1542                 }
1543                 g_strfreev(tokens);
1544         }
1545
1546         g_free(from);
1547         g_free(smtpserver);
1548         slist_free_strings(to_list);
1549         g_slist_free(to_list);
1550         slist_free_strings(newsgroup_list);
1551         g_slist_free(newsgroup_list);
1552         g_free(savecopyfolder);
1553         g_free(replymessageid);
1554         g_free(fwdmessageid);
1555         g_free(privacy_system);
1556         g_free(encrypt_data);
1557
1558         return (newsval != 0 ? newsval : mailval);
1559 }
1560
1561 gint procmsg_send_message_queue(const gchar *file)
1562 {
1563         return procmsg_send_message_queue_full(file, FALSE);
1564 }
1565
1566 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1567 {
1568         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1569
1570         /* NEW flag */
1571         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1572                 item->new_msgs++;
1573         }
1574
1575         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1576                 item->new_msgs--;
1577         }
1578
1579         /* UNREAD flag */
1580         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1581                 item->unread_msgs++;
1582                 if (procmsg_msg_has_marked_parent(msginfo))
1583                         item->unreadmarked_msgs++;
1584         }
1585
1586         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1587                 item->unread_msgs--;
1588                 if (procmsg_msg_has_marked_parent(msginfo))
1589                         item->unreadmarked_msgs--;
1590         }
1591         
1592         /* MARK flag */
1593         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1594                 procmsg_update_unread_children(msginfo, TRUE);
1595                 item->marked_msgs++;
1596         }
1597
1598         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1599                 procmsg_update_unread_children(msginfo, FALSE);
1600                 item->marked_msgs--;
1601         }
1602 }
1603
1604 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1605 {
1606         FolderItem *item;
1607         MsgInfoUpdate msginfo_update;
1608         MsgPermFlags perm_flags_new, perm_flags_old;
1609         MsgTmpFlags tmp_flags_old;
1610
1611         g_return_if_fail(msginfo != NULL);
1612         item = msginfo->folder;
1613         g_return_if_fail(item != NULL);
1614         
1615         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1616
1617         /* Perm Flags handling */
1618         perm_flags_old = msginfo->flags.perm_flags;
1619         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1620         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1621                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1622         }
1623
1624         if (perm_flags_old != perm_flags_new) {
1625                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1626
1627                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1628
1629         }
1630
1631         /* Tmp flags handling */
1632         tmp_flags_old = msginfo->flags.tmp_flags;
1633         msginfo->flags.tmp_flags |= tmp_flags;
1634
1635         /* update notification */
1636         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1637                 msginfo_update.msginfo = msginfo;
1638                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1639                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1640                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1641         }
1642 }
1643
1644 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1645 {
1646         FolderItem *item;
1647         MsgInfoUpdate msginfo_update;
1648         MsgPermFlags perm_flags_new, perm_flags_old;
1649         MsgTmpFlags tmp_flags_old;
1650
1651         g_return_if_fail(msginfo != NULL);
1652         item = msginfo->folder;
1653         g_return_if_fail(item != NULL);
1654         
1655         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1656
1657         /* Perm Flags handling */
1658         perm_flags_old = msginfo->flags.perm_flags;
1659         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1660         
1661         if (perm_flags_old != perm_flags_new) {
1662                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1663
1664                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1665
1666                 msginfo_update.msginfo = msginfo;
1667                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1668                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1669                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1670         }
1671
1672         /* Tmp flags hanlding */
1673         tmp_flags_old = msginfo->flags.tmp_flags;
1674         msginfo->flags.tmp_flags &= ~tmp_flags;
1675
1676         /* update notification */
1677         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1678                 msginfo_update.msginfo = msginfo;
1679                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1680                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1681                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1682         }
1683 }
1684
1685 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1686                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1687                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1688 {
1689         FolderItem *item;
1690         MsgInfoUpdate msginfo_update;
1691         MsgPermFlags perm_flags_new, perm_flags_old;
1692         MsgTmpFlags tmp_flags_old;
1693
1694         g_return_if_fail(msginfo != NULL);
1695         item = msginfo->folder;
1696         g_return_if_fail(item != NULL);
1697         
1698         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1699
1700         /* Perm Flags handling */
1701         perm_flags_old = msginfo->flags.perm_flags;
1702         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1703         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1704                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1705         }
1706
1707         if (perm_flags_old != perm_flags_new) {
1708                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1709
1710                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1711
1712         }
1713
1714         /* Tmp flags handling */
1715         tmp_flags_old = msginfo->flags.tmp_flags;
1716         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1717         msginfo->flags.tmp_flags |= add_tmp_flags;
1718
1719         /* update notification */
1720         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1721                 msginfo_update.msginfo = msginfo;
1722                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1723                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1724                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1725         }
1726 }
1727
1728 /*!
1729  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1730  *
1731  *\param        info Current message
1732  *\param        perm_flags Flags to be checked
1733  *\param        parentmsgs Hash of prior msgs to avoid loops
1734  *
1735  *\return       gboolean TRUE if perm_flags are found
1736  */
1737 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1738                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1739 {
1740         MsgInfo *tmp;
1741
1742         g_return_val_if_fail(info != NULL, FALSE);
1743
1744         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1745                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1746                                 info->inreplyto);
1747                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1748                         procmsg_msginfo_free(tmp);
1749                         return TRUE;
1750                 } else if (tmp != NULL) {
1751                         gboolean result;
1752
1753                         if (g_hash_table_lookup(parentmsgs, info)) {
1754                                 debug_print("loop detected: %s%c%d\n",
1755                                         folder_item_get_path(info->folder),
1756                                         G_DIR_SEPARATOR, info->msgnum);
1757                                 result = FALSE;
1758                         } else {
1759                                 g_hash_table_insert(parentmsgs, info, "1");
1760                                 result = procmsg_msg_has_flagged_parent_real(
1761                                     tmp, perm_flags, parentmsgs);
1762                         }
1763                         procmsg_msginfo_free(tmp);
1764                         return result;
1765                 } else {
1766                         return FALSE;
1767                 }
1768         } else
1769                 return FALSE;
1770 }
1771
1772 /*!
1773  *\brief        Callback for cleaning up hash of parentmsgs
1774  */
1775 gboolean parentmsgs_hash_remove(gpointer key,
1776                             gpointer value,
1777                             gpointer user_data)
1778 {
1779         return TRUE;
1780 }
1781
1782 /*!
1783  *\brief        Set up list of parentmsgs
1784  *              See procmsg_msg_has_flagged_parent_real()
1785  */
1786 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1787 {
1788         gboolean result;
1789         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1790
1791         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1792         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1793         g_hash_table_destroy(parentmsgs);
1794         return result;
1795 }
1796
1797 /*!
1798  *\brief        Check if msgs prior in thread are marked
1799  *              See procmsg_msg_has_flagged_parent_real()
1800  */
1801 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1802 {
1803         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1804 }
1805
1806
1807 GSList *procmsg_find_children_func(MsgInfo *info, 
1808                                    GSList *children, GSList *all)
1809 {
1810         GSList *cur;
1811
1812         g_return_val_if_fail(info!=NULL, children);
1813         if (info->msgid == NULL)
1814                 return children;
1815
1816         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1817                 MsgInfo *tmp = (MsgInfo *)cur->data;
1818                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1819                         /* Check if message is already in the list */
1820                         if ((children == NULL) || 
1821                             (g_slist_index(children, tmp) == -1)) {
1822                                 children = g_slist_prepend(children,
1823                                                 procmsg_msginfo_new_ref(tmp));
1824                                 children = procmsg_find_children_func(tmp, 
1825                                                         children, 
1826                                                         all);
1827                         }
1828                 }
1829         }
1830         return children;
1831 }
1832
1833 GSList *procmsg_find_children (MsgInfo *info)
1834 {
1835         GSList *children;
1836         GSList *all, *cur;
1837
1838         g_return_val_if_fail(info!=NULL, NULL);
1839         all = folder_item_get_msg_list(info->folder);
1840         children = procmsg_find_children_func(info, NULL, all);
1841         if (children != NULL) {
1842                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1843                         /* this will not free the used pointers
1844                            created with procmsg_msginfo_new_ref */
1845                         procmsg_msginfo_free((MsgInfo *)cur->data);
1846                 }
1847         }
1848         g_slist_free(all);
1849
1850         return children;
1851 }
1852
1853 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1854 {
1855         GSList *children = procmsg_find_children(info);
1856         GSList *cur;
1857         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1858                 MsgInfo *tmp = (MsgInfo *)cur->data;
1859                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1860                         if(newly_marked) 
1861                                 info->folder->unreadmarked_msgs++;
1862                         else
1863                                 info->folder->unreadmarked_msgs--;
1864                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1865                 }
1866                 procmsg_msginfo_free(tmp);
1867         }
1868         g_slist_free(children);
1869 }
1870
1871 /**
1872  * Set the destination folder for a copy or move operation
1873  *
1874  * \param msginfo The message which's destination folder is changed
1875  * \param to_folder The destination folder for the operation
1876  */
1877 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1878 {
1879         if(msginfo->to_folder != NULL) {
1880                 msginfo->to_folder->op_count--;
1881                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1882         }
1883         msginfo->to_folder = to_folder;
1884         if(to_folder != NULL) {
1885                 to_folder->op_count++;
1886                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1887         }
1888 }
1889
1890 /**
1891  * Apply filtering actions to the msginfo
1892  *
1893  * \param msginfo The MsgInfo describing the message that should be filtered
1894  * \return TRUE if the message was moved and MsgInfo is now invalid,
1895  *         FALSE otherwise
1896  */
1897 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1898 {
1899         MailFilteringData mail_filtering_data;
1900                         
1901         mail_filtering_data.msginfo = msginfo;                  
1902         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1903                 return TRUE;
1904
1905         /* filter if enabled in prefs or move to inbox if not */
1906         if((filtering_rules != NULL) &&
1907            filter_message_by_msginfo(filtering_rules, msginfo))
1908                 return TRUE;
1909                 
1910         hooks_invoke(MAIL_POSTFILTERING_HOOKLIST, msginfo);
1911
1912         return FALSE;
1913 }
1914
1915 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
1916 {
1917         MsgInfo *tmp_msginfo = NULL;
1918         MsgFlags flags = {0, 0};
1919         gchar *tmpfile = get_tmp_file();
1920         FILE *fp = g_fopen(tmpfile, "wb");
1921         
1922         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
1923             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
1924                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
1925                 if (fp) 
1926                         fclose(fp);
1927                 g_free(tmpfile);
1928                 return NULL;
1929         }
1930         
1931         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
1932                 fclose(fp);
1933                 fp = NULL;
1934                 tmp_msginfo = procheader_parse_file(
1935                         tmpfile, flags, 
1936                         TRUE, FALSE);
1937         }
1938         if (fp)
1939                 fclose(fp);
1940
1941         if (tmp_msginfo != NULL) {
1942                 tmp_msginfo->folder = src_msginfo->folder;
1943                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
1944         } else {
1945                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
1946         }
1947
1948         g_free(tmpfile);
1949
1950         return tmp_msginfo;
1951 }