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