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