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