2005-08-02 [colin] 1.9.13cvs5
[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                 g_slist_free(mlist);
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         gboolean res = FALSE;
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         res = (list == NULL);
889         procmsg_msg_list_free(list);
890         return res;
891 }
892
893 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
894 {
895         FILE *fp, *outfp;
896         gchar buf[BUFFSIZE];
897         
898         if ((fp = fopen(in, "rb")) == NULL) {
899                 FILE_OP_ERROR(in, "fopen");
900                 return -1;
901         }
902         if ((outfp = fopen(out, "wb")) == NULL) {
903                 FILE_OP_ERROR(out, "fopen");
904                 fclose(fp);
905                 return -1;
906         }
907         while (fgets(buf, sizeof(buf), fp) != NULL)
908                 if (buf[0] == '\r' || buf[0] == '\n') break;
909         while (fgets(buf, sizeof(buf), fp) != NULL)
910                 fputs(buf, outfp);
911         fclose(outfp);
912         fclose(fp);
913         return 0;
914
915 }
916 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
917                             gboolean is_queued)
918 {
919         gint num;
920         MsgInfo *msginfo, *tmp_msginfo;
921         MsgFlags flag = {0, 0};
922
923         debug_print("saving sent message...\n");
924
925         if (!outbox)
926                 outbox = folder_get_default_outbox();
927         g_return_val_if_fail(outbox != NULL, -1);
928
929         /* remove queueing headers */
930         if (is_queued) {
931                 gchar tmp[MAXPATHLEN + 1];
932
933                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
934                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
935                 
936                 if (procmsg_remove_special_headers(file, tmp) !=0)
937                         return -1;
938
939                 folder_item_scan(outbox);
940                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
941                         g_warning("can't save message\n");
942                         unlink(tmp);
943                         return -1;
944                 }
945         } else {
946                 folder_item_scan(outbox);
947                 if ((num = folder_item_add_msg
948                         (outbox, file, &flag, FALSE)) < 0) {
949                         g_warning("can't save message\n");
950                         return -1;
951                 }
952                 return -1;
953         }
954         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
955         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
956         if (msginfo != NULL) {
957                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
958                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
959                 /* tmp_msginfo == msginfo */
960                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
961                     msginfo->returnreceiptto)) {
962                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
963                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
964                 }       
965         }
966
967         return 0;
968 }
969
970 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
971 {
972         static const gchar *def_cmd = "lpr %s";
973         static guint id = 0;
974         gchar *prtmp;
975         FILE *tmpfp, *prfp;
976         gchar buf[1024];
977         gchar *p;
978
979         g_return_if_fail(msginfo);
980
981         if (procmime_msginfo_is_encrypted(msginfo))
982                 tmpfp = procmime_get_first_encrypted_text_content(msginfo);
983         else
984                 tmpfp = procmime_get_first_text_content(msginfo);
985         if (tmpfp == NULL) {
986                 g_warning("Can't get text part\n");
987                 return;
988         }
989
990         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
991                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
992
993         if ((prfp = fopen(prtmp, "wb")) == NULL) {
994                 FILE_OP_ERROR(prtmp, "fopen");
995                 g_free(prtmp);
996                 fclose(tmpfp);
997                 return;
998         }
999
1000         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
1001         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
1002         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
1003         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
1004         if (msginfo->newsgroups)
1005                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
1006         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
1007         fputc('\n', prfp);
1008
1009         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1010                 fputs(buf, prfp);
1011
1012         fclose(prfp);
1013         fclose(tmpfp);
1014
1015         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
1016             !strchr(p + 2, '%'))
1017                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
1018         else {
1019                 if (cmdline)
1020                         g_warning("Print command line is invalid: '%s'\n",
1021                                   cmdline);
1022                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
1023         }
1024
1025         g_free(prtmp);
1026
1027         g_strchomp(buf);
1028         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
1029         system(buf);
1030 }
1031
1032 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1033 {
1034         msginfo->refcnt++;
1035         
1036         return msginfo;
1037 }
1038
1039 MsgInfo *procmsg_msginfo_new(void)
1040 {
1041         MsgInfo *newmsginfo;
1042
1043         newmsginfo = g_new0(MsgInfo, 1);
1044         newmsginfo->refcnt = 1;
1045         
1046         return newmsginfo;
1047 }
1048
1049 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1050 {
1051         MsgInfo *newmsginfo;
1052         GSList *refs;
1053
1054         if (msginfo == NULL) return NULL;
1055
1056         newmsginfo = g_new0(MsgInfo, 1);
1057
1058         newmsginfo->refcnt = 1;
1059
1060 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1061 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1062                         g_strdup(msginfo->mmb) : NULL
1063
1064         MEMBCOPY(msgnum);
1065         MEMBCOPY(size);
1066         MEMBCOPY(mtime);
1067         MEMBCOPY(date_t);
1068
1069         MEMBCOPY(flags);
1070
1071         MEMBDUP(fromname);
1072
1073         MEMBDUP(date);
1074         MEMBDUP(from);
1075         MEMBDUP(to);
1076         MEMBDUP(cc);
1077         MEMBDUP(newsgroups);
1078         MEMBDUP(subject);
1079         MEMBDUP(msgid);
1080         MEMBDUP(inreplyto);
1081         MEMBDUP(xref);
1082
1083         MEMBCOPY(folder);
1084         MEMBCOPY(to_folder);
1085
1086         MEMBDUP(xface);
1087         MEMBDUP(dispositionnotificationto);
1088         MEMBDUP(returnreceiptto);
1089
1090         refs = msginfo->references;
1091         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1092                 newmsginfo->references = g_slist_prepend
1093                         (newmsginfo->references, g_strdup(refs->data)); 
1094         }
1095         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1096
1097         MEMBCOPY(score);
1098         MEMBCOPY(threadscore);
1099         MEMBDUP(plaintext_file);
1100
1101         return newmsginfo;
1102 }
1103
1104 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1105 {
1106         MsgInfo *full_msginfo;
1107         gchar *file;
1108
1109         if (msginfo == NULL) return NULL;
1110
1111         file = procmsg_get_message_file_path(msginfo);
1112         if (!file || !is_file_exist(file)) {
1113                 g_free(file);
1114                 file = procmsg_get_message_file(msginfo);
1115         }
1116         if (!file || !is_file_exist(file)) {
1117                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
1118                 return NULL;
1119         }
1120
1121         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1122         g_free(file);
1123         if (!full_msginfo) return NULL;
1124
1125         /* CLAWS: make sure we add the missing members; see: 
1126          * procheader.c::procheader_get_headernames() */
1127         if (!msginfo->xface)
1128                 msginfo->xface = g_strdup(full_msginfo->xface);
1129         if (!msginfo->dispositionnotificationto)
1130                 msginfo->dispositionnotificationto = 
1131                         g_strdup(full_msginfo->dispositionnotificationto);
1132         if (!msginfo->returnreceiptto)
1133                 msginfo->returnreceiptto = g_strdup
1134                         (full_msginfo->returnreceiptto);
1135         if (!msginfo->partial_recv && full_msginfo->partial_recv)
1136                 msginfo->partial_recv = g_strdup
1137                         (full_msginfo->partial_recv);
1138         msginfo->total_size = full_msginfo->total_size;
1139         if (!msginfo->account_server && full_msginfo->account_server)
1140                 msginfo->account_server = g_strdup
1141                         (full_msginfo->account_server);
1142         if (!msginfo->account_login && full_msginfo->account_login)
1143                 msginfo->account_login = g_strdup
1144                         (full_msginfo->account_login);
1145         msginfo->planned_download = full_msginfo->planned_download;
1146         procmsg_msginfo_free(full_msginfo);
1147
1148         return procmsg_msginfo_new_ref(msginfo);
1149 }
1150
1151 void procmsg_msginfo_free(MsgInfo *msginfo)
1152 {
1153         if (msginfo == NULL) return;
1154
1155         msginfo->refcnt--;
1156         if (msginfo->refcnt > 0)
1157                 return;
1158
1159         if (msginfo->to_folder) {
1160                 msginfo->to_folder->op_count--;
1161                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1162         }
1163
1164         g_free(msginfo->fromspace);
1165         g_free(msginfo->returnreceiptto);
1166         g_free(msginfo->dispositionnotificationto);
1167         g_free(msginfo->xface);
1168
1169         g_free(msginfo->fromname);
1170
1171         g_free(msginfo->date);
1172         g_free(msginfo->from);
1173         g_free(msginfo->to);
1174         g_free(msginfo->cc);
1175         g_free(msginfo->newsgroups);
1176         g_free(msginfo->subject);
1177         g_free(msginfo->msgid);
1178         g_free(msginfo->inreplyto);
1179         g_free(msginfo->xref);
1180
1181         g_free(msginfo->partial_recv);
1182         g_free(msginfo->account_server);
1183         g_free(msginfo->account_login);
1184         
1185         slist_free_strings(msginfo->references);
1186         g_slist_free(msginfo->references);
1187
1188         g_free(msginfo->plaintext_file);
1189
1190         g_free(msginfo);
1191 }
1192
1193 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1194 {
1195         guint memusage = 0;
1196         GSList *refs;
1197         
1198         memusage += sizeof(MsgInfo);
1199         if (msginfo->fromname)
1200                 memusage += strlen(msginfo->fromname);
1201         if (msginfo->date)
1202                 memusage += strlen(msginfo->date);
1203         if (msginfo->from)
1204                 memusage += strlen(msginfo->from);
1205         if (msginfo->to)
1206                 memusage += strlen(msginfo->to);
1207         if (msginfo->cc)
1208                 memusage += strlen(msginfo->cc);
1209         if (msginfo->newsgroups)
1210                 memusage += strlen(msginfo->newsgroups);
1211         if (msginfo->subject)
1212                 memusage += strlen(msginfo->subject);
1213         if (msginfo->msgid)
1214                 memusage += strlen(msginfo->msgid);
1215         if (msginfo->inreplyto)
1216                 memusage += strlen(msginfo->inreplyto);
1217         if (msginfo->xface)
1218                 memusage += strlen(msginfo->xface);
1219         if (msginfo->dispositionnotificationto)
1220                 memusage += strlen(msginfo->dispositionnotificationto);
1221         if (msginfo->returnreceiptto)
1222                 memusage += strlen(msginfo->returnreceiptto);
1223         for (refs = msginfo->references; refs; refs=refs->next) {
1224                 gchar *r = (gchar *)refs->data;
1225                 memusage += r?strlen(r):0;
1226         }
1227         if (msginfo->fromspace)
1228                 memusage += strlen(msginfo->fromspace);
1229
1230         return memusage;
1231 }
1232
1233 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1234 {
1235         const MsgInfo *msginfo1 = a;
1236         const MsgInfo *msginfo2 = b;
1237
1238         if (!msginfo1)
1239                 return -1;
1240         if (!msginfo2)
1241                 return -1;
1242
1243         return msginfo1->msgnum - msginfo2->msgnum;
1244 }
1245
1246 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session)
1247 {
1248         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1249                                        {"SSV:",  NULL, FALSE},
1250                                        {"R:",    NULL, FALSE},
1251                                        {"NG:",   NULL, FALSE},
1252                                        {"MAID:", NULL, FALSE},
1253                                        {"NAID:", NULL, FALSE},
1254                                        {"SCF:",  NULL, FALSE},
1255                                        {"RMID:", NULL, FALSE},
1256                                        {"FMID:", NULL, FALSE},
1257                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1258                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1259                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1260                                        {NULL,    NULL, FALSE}};
1261         FILE *fp;
1262         gint filepos;
1263         gint mailval = 0, newsval = 0;
1264         gchar *from = NULL;
1265         gchar *smtpserver = NULL;
1266         GSList *to_list = NULL;
1267         GSList *newsgroup_list = NULL;
1268         gchar *savecopyfolder = NULL;
1269         gchar *replymessageid = NULL;
1270         gchar *fwdmessageid = NULL;
1271         gchar *privacy_system = NULL;
1272         gboolean encrypt = FALSE;
1273         gchar *encrypt_data = NULL;
1274         gchar buf[BUFFSIZE];
1275         gint hnum;
1276         PrefsAccount *mailac = NULL, *newsac = NULL;
1277         gboolean save_clear_text = TRUE;
1278         gchar *tmp_enc_file = NULL;
1279
1280         int local = 0;
1281
1282         g_return_val_if_fail(file != NULL, -1);
1283
1284         if ((fp = fopen(file, "rb")) == NULL) {
1285                 FILE_OP_ERROR(file, "fopen");
1286                 return -1;
1287         }
1288
1289         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1290                != -1) {
1291                 gchar *p = buf + strlen(qentry[hnum].name);
1292
1293                 switch (hnum) {
1294                 case Q_SENDER:
1295                         if (from == NULL) 
1296                                 from = g_strdup(p);
1297                         break;
1298                 case Q_SMTPSERVER:
1299                         if (smtpserver == NULL) 
1300                                 smtpserver = g_strdup(p);
1301                         break;
1302                 case Q_RECIPIENTS:
1303                         to_list = address_list_append(to_list, p);
1304                         break;
1305                 case Q_NEWSGROUPS:
1306                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1307                         break;
1308                 case Q_MAIL_ACCOUNT_ID:
1309                         mailac = account_find_from_id(atoi(p));
1310                         break;
1311                 case Q_NEWS_ACCOUNT_ID:
1312                         newsac = account_find_from_id(atoi(p));
1313                         break;
1314                 case Q_SAVE_COPY_FOLDER:
1315                         if (savecopyfolder == NULL) 
1316                                 savecopyfolder = g_strdup(p);
1317                         break;
1318                 case Q_REPLY_MESSAGE_ID:
1319                         if (replymessageid == NULL) 
1320                                 replymessageid = g_strdup(p);
1321                         break;
1322                 case Q_FWD_MESSAGE_ID:
1323                         if (fwdmessageid == NULL) 
1324                                 fwdmessageid = g_strdup(p);
1325                         break;
1326                 case Q_PRIVACY_SYSTEM:
1327                         if (privacy_system == NULL) 
1328                                 privacy_system = g_strdup(p);
1329                         break;
1330                 case Q_ENCRYPT:
1331                         if (p[0] == '1') 
1332                                 encrypt = TRUE;
1333                         break;
1334                 case Q_ENCRYPT_DATA:
1335                         if (encrypt_data == NULL) 
1336                                 encrypt_data = g_strdup(p);
1337                         break;
1338                 }
1339         }
1340         filepos = ftell(fp);
1341
1342         if (encrypt) {
1343                 MimeInfo *mimeinfo;
1344
1345                 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1346
1347                 fclose(fp);
1348                 fp = NULL;
1349
1350                 mimeinfo = procmime_scan_queue_file(file);
1351                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1352                 || (fp = my_tmpfile()) == NULL
1353                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1354                         if (fp)
1355                                 fclose(fp);
1356                         procmime_mimeinfo_free_all(mimeinfo);
1357                         g_free(from);
1358                         g_free(smtpserver);
1359                         slist_free_strings(to_list);
1360                         g_slist_free(to_list);
1361                         slist_free_strings(newsgroup_list);
1362                         g_slist_free(newsgroup_list);
1363                         g_free(savecopyfolder);
1364                         g_free(replymessageid);
1365                         g_free(fwdmessageid);
1366                         g_free(privacy_system);
1367                         g_free(encrypt_data);
1368                         return -1;
1369                 }
1370                 
1371                 rewind(fp);
1372                 if (!save_clear_text) {
1373                         gchar *content = NULL;
1374                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1375                         if (tmpfp) {
1376                                 fclose(tmpfp);
1377
1378                                 content = file_read_stream_to_str(fp);
1379                                 rewind(fp);
1380
1381                                 str_write_to_file(content, tmp_enc_file);
1382                                 g_free(content);
1383                         } else {
1384                                 g_warning("couldn't get tempfile\n");
1385                         }
1386                 } 
1387                 
1388                 procmime_mimeinfo_free_all(mimeinfo);
1389                 
1390                 filepos = 0;
1391         }
1392
1393         if (to_list) {
1394                 debug_print("Sending message by mail\n");
1395                 if (!from) {
1396                         g_warning("Queued message header is broken.\n");
1397                         mailval = -1;
1398                 } else if (mailac && mailac->use_mail_command &&
1399                            mailac->mail_command && (* mailac->mail_command)) {
1400                         mailval = send_message_local(mailac->mail_command, fp);
1401                         local = 1;
1402                 } else {
1403                         if (!mailac) {
1404                                 mailac = account_find_from_smtp_server(from, smtpserver);
1405                                 if (!mailac) {
1406                                         g_warning("Account not found. "
1407                                                     "Using current account...\n");
1408                                         mailac = cur_account;
1409                                 }
1410                         }
1411
1412                         if (mailac)
1413                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1414                         else {
1415                                 PrefsAccount tmp_ac;
1416
1417                                 g_warning("Account not found.\n");
1418
1419                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1420                                 tmp_ac.address = from;
1421                                 tmp_ac.smtp_server = smtpserver;
1422                                 tmp_ac.smtpport = SMTP_PORT;
1423                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1424                         }
1425                 }
1426         }
1427
1428         fseek(fp, filepos, SEEK_SET);
1429         if (newsgroup_list && (mailval == 0)) {
1430                 Folder *folder;
1431                 gchar *tmp = NULL;
1432                 FILE *tmpfp;
1433
1434                 /* write to temporary file */
1435                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1436                             G_DIR_SEPARATOR, (gint)file);
1437                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1438                         FILE_OP_ERROR(tmp, "fopen");
1439                         newsval = -1;
1440                         alertpanel_error(_("Could not create temporary file for news sending."));
1441                 } else {
1442                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1443                                 FILE_OP_ERROR(tmp, "chmod");
1444                                 g_warning("can't change file mode\n");
1445                         }
1446
1447                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1448                                 if (fputs(buf, tmpfp) == EOF) {
1449                                         FILE_OP_ERROR(tmp, "fputs");
1450                                         newsval = -1;
1451                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1452                                 }
1453                         }
1454                         fclose(tmpfp);
1455
1456                         if (newsval == 0) {
1457                                 debug_print("Sending message by news\n");
1458
1459                                 folder = FOLDER(newsac->folder);
1460
1461                                 newsval = news_post(folder, tmp);
1462                                 if (newsval < 0) {
1463                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1464                                                  newsac->nntp_server);
1465                                 }
1466                         }
1467                         unlink(tmp);
1468                 }
1469                 g_free(tmp);
1470         }
1471
1472         fclose(fp);
1473
1474         /* save message to outbox */
1475         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1476                 FolderItem *outbox;
1477
1478                 debug_print("saving sent message...\n");
1479
1480                 outbox = folder_find_item_from_identifier(savecopyfolder);
1481                 if (!outbox)
1482                         outbox = folder_get_default_outbox();
1483                         
1484                 if (save_clear_text || tmp_enc_file == NULL) {
1485                         procmsg_save_to_outbox(outbox, file, TRUE);
1486                 } else {
1487                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1488                 }
1489         }
1490
1491         if (tmp_enc_file != NULL) {
1492                 unlink(tmp_enc_file);
1493                 free(tmp_enc_file);
1494                 tmp_enc_file = NULL;
1495         }
1496
1497         if (replymessageid != NULL || fwdmessageid != NULL) {
1498                 gchar **tokens;
1499                 FolderItem *item;
1500                 
1501                 if (replymessageid != NULL)
1502                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1503                 else
1504                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1505                 item = folder_find_item_from_identifier(tokens[0]);
1506
1507                 /* check if queued message has valid folder and message id */
1508                 if (item != NULL && tokens[2] != NULL) {
1509                         MsgInfo *msginfo;
1510                         
1511                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1512                 
1513                         /* check if referring message exists and has a message id */
1514                         if ((msginfo != NULL) && 
1515                             (msginfo->msgid != NULL) &&
1516                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1517                                 procmsg_msginfo_free(msginfo);
1518                                 msginfo = NULL;
1519                         }
1520                         
1521                         if (msginfo == NULL) {
1522                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1523                         }
1524                         
1525                         if (msginfo != NULL) {
1526                                 if (replymessageid != NULL) {
1527                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1528                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1529                                 }  else {
1530                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1531                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1532                                 }
1533                                 procmsg_msginfo_free(msginfo);
1534                         }
1535                 }
1536                 g_strfreev(tokens);
1537         }
1538
1539         g_free(from);
1540         g_free(smtpserver);
1541         slist_free_strings(to_list);
1542         g_slist_free(to_list);
1543         slist_free_strings(newsgroup_list);
1544         g_slist_free(newsgroup_list);
1545         g_free(savecopyfolder);
1546         g_free(replymessageid);
1547         g_free(fwdmessageid);
1548         g_free(privacy_system);
1549         g_free(encrypt_data);
1550
1551         return (newsval != 0 ? newsval : mailval);
1552 }
1553
1554 gint procmsg_send_message_queue(const gchar *file)
1555 {
1556         return procmsg_send_message_queue_full(file, FALSE);
1557 }
1558
1559 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1560 {
1561         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1562
1563         /* NEW flag */
1564         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1565                 item->new_msgs++;
1566         }
1567
1568         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1569                 item->new_msgs--;
1570         }
1571
1572         /* UNREAD flag */
1573         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1574                 item->unread_msgs++;
1575                 if (procmsg_msg_has_marked_parent(msginfo))
1576                         item->unreadmarked_msgs++;
1577         }
1578
1579         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1580                 item->unread_msgs--;
1581                 if (procmsg_msg_has_marked_parent(msginfo))
1582                         item->unreadmarked_msgs--;
1583         }
1584         
1585         /* MARK flag */
1586         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1587                 procmsg_update_unread_children(msginfo, TRUE);
1588                 item->marked_msgs++;
1589         }
1590
1591         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1592                 procmsg_update_unread_children(msginfo, FALSE);
1593                 item->marked_msgs--;
1594         }
1595 }
1596
1597 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1598 {
1599         FolderItem *item;
1600         MsgInfoUpdate msginfo_update;
1601         MsgPermFlags perm_flags_new, perm_flags_old;
1602         MsgTmpFlags tmp_flags_old;
1603
1604         g_return_if_fail(msginfo != NULL);
1605         item = msginfo->folder;
1606         g_return_if_fail(item != NULL);
1607         
1608         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1609
1610         /* Perm Flags handling */
1611         perm_flags_old = msginfo->flags.perm_flags;
1612         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1613         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1614                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1615         }
1616
1617         if (perm_flags_old != perm_flags_new) {
1618                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1619
1620                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1621
1622         }
1623
1624         /* Tmp flags handling */
1625         tmp_flags_old = msginfo->flags.tmp_flags;
1626         msginfo->flags.tmp_flags |= tmp_flags;
1627
1628         /* update notification */
1629         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1630                 msginfo_update.msginfo = msginfo;
1631                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1632                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1633                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1634         }
1635 }
1636
1637 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1638 {
1639         FolderItem *item;
1640         MsgInfoUpdate msginfo_update;
1641         MsgPermFlags perm_flags_new, perm_flags_old;
1642         MsgTmpFlags tmp_flags_old;
1643
1644         g_return_if_fail(msginfo != NULL);
1645         item = msginfo->folder;
1646         g_return_if_fail(item != NULL);
1647         
1648         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1649
1650         /* Perm Flags handling */
1651         perm_flags_old = msginfo->flags.perm_flags;
1652         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1653         
1654         if (perm_flags_old != perm_flags_new) {
1655                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1656
1657                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1658
1659                 msginfo_update.msginfo = msginfo;
1660                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1661                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1662                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1663         }
1664
1665         /* Tmp flags hanlding */
1666         tmp_flags_old = msginfo->flags.tmp_flags;
1667         msginfo->flags.tmp_flags &= ~tmp_flags;
1668
1669         /* update notification */
1670         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1671                 msginfo_update.msginfo = msginfo;
1672                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1673                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1674                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1675         }
1676 }
1677
1678 /*!
1679  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1680  *
1681  *\param        info Current message
1682  *\param        perm_flags Flags to be checked
1683  *\param        parentmsgs Hash of prior msgs to avoid loops
1684  *
1685  *\return       gboolean TRUE if perm_flags are found
1686  */
1687 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1688                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1689 {
1690         MsgInfo *tmp;
1691
1692         g_return_val_if_fail(info != NULL, FALSE);
1693
1694         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1695                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1696                                 info->inreplyto);
1697                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1698                         procmsg_msginfo_free(tmp);
1699                         return TRUE;
1700                 } else if (tmp != NULL) {
1701                         gboolean result;
1702
1703                         if (g_hash_table_lookup(parentmsgs, info)) {
1704                                 debug_print("loop detected: %s%c%d\n",
1705                                         folder_item_get_path(info->folder),
1706                                         G_DIR_SEPARATOR, info->msgnum);
1707                                 result = FALSE;
1708                         } else {
1709                                 g_hash_table_insert(parentmsgs, info, "1");
1710                                 result = procmsg_msg_has_flagged_parent_real(
1711                                     tmp, perm_flags, parentmsgs);
1712                         }
1713                         procmsg_msginfo_free(tmp);
1714                         return result;
1715                 } else {
1716                         return FALSE;
1717                 }
1718         } else
1719                 return FALSE;
1720 }
1721
1722 /*!
1723  *\brief        Callback for cleaning up hash of parentmsgs
1724  */
1725 gboolean parentmsgs_hash_remove(gpointer key,
1726                             gpointer value,
1727                             gpointer user_data)
1728 {
1729         return TRUE;
1730 }
1731
1732 /*!
1733  *\brief        Set up list of parentmsgs
1734  *              See procmsg_msg_has_flagged_parent_real()
1735  */
1736 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1737 {
1738         gboolean result;
1739         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1740
1741         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1742         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1743         g_hash_table_destroy(parentmsgs);
1744         return result;
1745 }
1746
1747 /*!
1748  *\brief        Check if msgs prior in thread are marked
1749  *              See procmsg_msg_has_flagged_parent_real()
1750  */
1751 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1752 {
1753         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1754 }
1755
1756
1757 GSList *procmsg_find_children_func(MsgInfo *info, 
1758                                    GSList *children, GSList *all)
1759 {
1760         GSList *cur;
1761
1762         g_return_val_if_fail(info!=NULL, children);
1763         if (info->msgid == NULL)
1764                 return children;
1765
1766         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1767                 MsgInfo *tmp = (MsgInfo *)cur->data;
1768                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1769                         /* Check if message is already in the list */
1770                         if ((children == NULL) || 
1771                             (g_slist_index(children, tmp) == -1)) {
1772                                 children = g_slist_prepend(children,
1773                                                 procmsg_msginfo_new_ref(tmp));
1774                                 children = procmsg_find_children_func(tmp, 
1775                                                         children, 
1776                                                         all);
1777                         }
1778                 }
1779         }
1780         return children;
1781 }
1782
1783 GSList *procmsg_find_children (MsgInfo *info)
1784 {
1785         GSList *children;
1786         GSList *all, *cur;
1787
1788         g_return_val_if_fail(info!=NULL, NULL);
1789         all = folder_item_get_msg_list(info->folder);
1790         children = procmsg_find_children_func(info, NULL, all);
1791         if (children != NULL) {
1792                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1793                         /* this will not free the used pointers
1794                            created with procmsg_msginfo_new_ref */
1795                         procmsg_msginfo_free((MsgInfo *)cur->data);
1796                 }
1797         }
1798         g_slist_free(all);
1799
1800         return children;
1801 }
1802
1803 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1804 {
1805         GSList *children = procmsg_find_children(info);
1806         GSList *cur;
1807         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1808                 MsgInfo *tmp = (MsgInfo *)cur->data;
1809                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1810                         if(newly_marked) 
1811                                 info->folder->unreadmarked_msgs++;
1812                         else
1813                                 info->folder->unreadmarked_msgs--;
1814                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1815                 }
1816                 procmsg_msginfo_free(tmp);
1817         }
1818         g_slist_free(children);
1819 }
1820
1821 /**
1822  * Set the destination folder for a copy or move operation
1823  *
1824  * \param msginfo The message which's destination folder is changed
1825  * \param to_folder The destination folder for the operation
1826  */
1827 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1828 {
1829         if(msginfo->to_folder != NULL) {
1830                 msginfo->to_folder->op_count--;
1831                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1832         }
1833         msginfo->to_folder = to_folder;
1834         if(to_folder != NULL) {
1835                 to_folder->op_count++;
1836                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1837         }
1838 }
1839
1840 /**
1841  * Apply filtering actions to the msginfo
1842  *
1843  * \param msginfo The MsgInfo describing the message that should be filtered
1844  * \return TRUE if the message was moved and MsgInfo is now invalid,
1845  *         FALSE otherwise
1846  */
1847 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1848 {
1849         MailFilteringData mail_filtering_data;
1850                         
1851         mail_filtering_data.msginfo = msginfo;                  
1852         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1853                 return TRUE;
1854
1855         /* filter if enabled in prefs or move to inbox if not */
1856         if((filtering_rules != NULL) &&
1857            filter_message_by_msginfo(filtering_rules, msginfo))
1858                 return TRUE;
1859
1860         return FALSE;
1861 }
1862
1863 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
1864 {
1865         MsgInfo *tmp_msginfo = NULL;
1866         MsgFlags flags = {0, 0};
1867         
1868         
1869         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
1870             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
1871                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
1872                 return NULL;
1873         }
1874                     
1875         if (mimeinfo->content == MIMECONTENT_MEM) {
1876                 gchar *tmpfile = get_tmp_file();
1877                 str_write_to_file(mimeinfo->data.mem, tmpfile);
1878                 g_free(mimeinfo->data.mem);
1879                 mimeinfo->content = MIMECONTENT_FILE;
1880                 mimeinfo->data.filename = g_strdup(tmpfile);
1881                 g_free(tmpfile);
1882         }
1883
1884         tmp_msginfo = procheader_parse_file(mimeinfo->data.filename,
1885                                 flags, TRUE, FALSE);
1886
1887         if (tmp_msginfo != NULL) {
1888                 tmp_msginfo->folder = src_msginfo->folder;
1889                 tmp_msginfo->plaintext_file = g_strdup(mimeinfo->data.filename);
1890         } else {
1891                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
1892         }
1893         
1894         return tmp_msginfo;
1895 }