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