Fix help about command-line arguments that do require a parameter.
[claws.git] / src / procmsg.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2018 Hiroyuki Yamamoto and the Claws Mail team
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 3 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, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include "defs.h"
20
21 #include <glib.h>
22 #include <glib/gi18n.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <math.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 #include "mainwindow.h"
46 #include "summaryview.h"
47 #include "log.h"
48 #include "tags.h"
49 #include "timing.h"
50 #include "inc.h"
51 #include "privacy.h"
52 #include "file-utils.h"
53
54 extern SessionStats session_stats;
55
56 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr,
57                                             FolderItem *queue, gint msgnum, gboolean *queued_removed);
58 static void procmsg_update_unread_children      (MsgInfo        *info,
59                                          gboolean        newly_marked);
60 enum
61 {
62         Q_SENDER           = 0,
63         Q_SMTPSERVER       = 1,
64         Q_RECIPIENTS       = 2,
65         Q_NEWSGROUPS       = 3,
66         Q_MAIL_ACCOUNT_ID  = 4,
67         Q_NEWS_ACCOUNT_ID  = 5,
68         Q_SAVE_COPY_FOLDER = 6,
69         Q_REPLY_MESSAGE_ID = 7,
70         Q_FWD_MESSAGE_ID   = 8,
71         Q_PRIVACY_SYSTEM   = 9,
72         Q_ENCRYPT          = 10,
73         Q_ENCRYPT_DATA     = 11,
74         Q_CLAWS_HDRS       = 12,
75         Q_PRIVACY_SYSTEM_OLD = 13,
76         Q_ENCRYPT_OLD        = 14,
77         Q_ENCRYPT_DATA_OLD   = 15,
78         Q_CLAWS_HDRS_OLD     = 16,
79 };
80
81 void procmsg_msg_list_free(GSList *mlist)
82 {
83         GSList *cur;
84         MsgInfo *msginfo;
85
86         for (cur = mlist; cur != NULL; cur = cur->next) {
87                 msginfo = (MsgInfo *)cur->data;
88                 procmsg_msginfo_free(&msginfo);
89         }
90         g_slist_free(mlist);
91 }
92
93 MsgNumberList *procmsg_get_number_list_for_msgs(MsgInfoList *msglist)
94 {
95         GSList *cur = NULL;
96         GSList *nums = NULL;
97
98         for (cur = msglist; cur; cur = cur->next) {
99                 MsgInfo *msg = (MsgInfo *)cur->data;
100                 nums = g_slist_prepend(nums, GUINT_TO_POINTER(msg->msgnum));
101         }
102
103         return g_slist_reverse(nums);
104 }
105
106 struct MarkSum {
107         gint *new_msgs;
108         gint *unread_msgs;
109         gint *total_msgs;
110         gint *min;
111         gint *max;
112         gint first;
113 };
114
115 /* CLAWS subject threading:
116   
117   in the first round it inserts subject lines in a 
118   hashtable (subject <-> node)
119
120   the second round finishes the threads by attaching
121   matching subject lines to the one found in the
122   hashtable. will use the oldest node with the same
123   subject that is not more then thread_by_subject_max_age
124   days old (see subject_hashtable_lookup)
125 */  
126
127 static void subject_hashtable_insert(GHashTable *hashtable, GNode *node)
128 {
129         gchar *subject;
130         MsgInfo *msginfo;
131         GSList *list = NULL;
132
133         cm_return_if_fail(hashtable != NULL);
134         cm_return_if_fail(node != NULL);
135         msginfo = (MsgInfo *) node->data;
136         cm_return_if_fail(msginfo != NULL);
137
138         subject = msginfo->subject;
139         if (subject == NULL)
140                 return;
141
142         subject += subject_get_prefix_length(subject);
143
144         list = g_hash_table_lookup(hashtable, subject);
145         list = g_slist_prepend(list, node);
146         g_hash_table_insert(hashtable, subject, list);
147 }
148
149 static GNode *subject_hashtable_lookup(GHashTable *hashtable, MsgInfo *msginfo)
150 {
151         gchar *subject;
152         GSList *list, *cur;
153         GNode *node = NULL, *hashtable_node = NULL;
154         gint prefix_length;
155         MsgInfo *hashtable_msginfo = NULL, *best_msginfo = NULL;
156         gboolean match;
157     
158         cm_return_val_if_fail(hashtable != NULL, NULL);
159
160         subject = msginfo->subject;
161         if (subject == NULL)
162                 return NULL;
163         prefix_length = subject_get_prefix_length(subject);
164         if (prefix_length <= 0)
165                 return NULL;
166         subject += prefix_length;
167         
168         list = g_hash_table_lookup(hashtable, subject);
169         if (list == NULL)
170                 return NULL;
171
172         /* check all nodes with the same subject to find the best parent */
173         for (cur = list; cur; cur = cur->next) {
174                 hashtable_node = (GNode *)cur->data;
175                 hashtable_msginfo = (MsgInfo *) hashtable_node->data;
176                 match = FALSE;
177
178                 /* best node should be the oldest in the found nodes */
179                 /* parent node must not be older then msginfo */
180                 if ((hashtable_msginfo->date_t < msginfo->date_t) &&
181                     ((best_msginfo == NULL) ||
182                      (best_msginfo->date_t > hashtable_msginfo->date_t)))
183                         match = TRUE;
184
185                 /* parent node must not be more then thread_by_subject_max_age
186                    days older then msginfo */
187                 if (fabs(difftime(msginfo->date_t, hashtable_msginfo->date_t)) >
188                     prefs_common.thread_by_subject_max_age * 3600 * 24)
189                         match = FALSE;
190
191                 /* can add new tests for all matching
192                    nodes found by subject */
193
194                 if (match) {
195                         node = hashtable_node;
196                         best_msginfo = hashtable_msginfo;
197                 }
198         }
199
200         return node;
201 }
202
203 static void subject_hashtable_free(gpointer key, gpointer value, gpointer data)
204 {
205         g_slist_free(value);
206 }
207
208 /* return the reversed thread tree */
209 GNode *procmsg_get_thread_tree(GSList *mlist)
210 {
211         GNode *root, *parent, *node, *next;
212         GHashTable *msgid_table;
213         GHashTable *subject_hashtable = NULL;
214         MsgInfo *msginfo;
215         const gchar *msgid;
216         GSList *reflist;
217         START_TIMING("");
218         root = g_node_new(NULL);
219         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
220         
221         if (prefs_common.thread_by_subject) {
222                 subject_hashtable = g_hash_table_new(g_str_hash, g_str_equal);
223         }
224
225         for (; mlist != NULL; mlist = mlist->next) {
226                 msginfo = (MsgInfo *)mlist->data;
227                 parent = root;
228
229                 if (msginfo->inreplyto) {
230                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
231                         if (parent == NULL) {
232                                 parent = root;
233                         }
234                 }
235                 node = g_node_insert_data_before
236                         (parent, parent == root ? parent->children : NULL,
237                          msginfo);
238                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
239                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
240
241                 /* CLAWS: add subject to hashtable (without prefix) */
242                 if (prefs_common.thread_by_subject) {
243                         subject_hashtable_insert(subject_hashtable, node);
244                 }
245         }
246
247         /* complete the unfinished threads */
248         for (node = root->children; node != NULL; ) {
249                 next = node->next;
250                 msginfo = (MsgInfo *)node->data;
251                 parent = NULL;
252                 
253                 if (msginfo->inreplyto)
254                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
255
256                 /* try looking for the indirect parent */
257                 if (!parent && msginfo->references) {
258                         for (reflist = msginfo->references;
259                              reflist != NULL; reflist = reflist->next)
260                                 if ((parent = g_hash_table_lookup
261                                         (msgid_table, reflist->data)) != NULL)
262                                         break;
263                 }                                        
264               
265                 /* node should not be the parent, and node should not
266                    be an ancestor of parent (circular reference) */
267                 if (parent && parent != node &&
268                     !g_node_is_ancestor(node, parent)) {
269                         g_node_unlink(node);
270                         g_node_insert_before
271                                 (parent, parent->children, node);
272                 }
273                
274                 node = next;
275         }
276
277         if (prefs_common.thread_by_subject) {
278                 START_TIMING("thread by subject");
279                 for (node = root->children; node && node != NULL;) {
280                         next = node->next;
281                         msginfo = (MsgInfo *) node->data;
282                         
283                         parent = subject_hashtable_lookup(subject_hashtable, msginfo);
284                         
285                         /* the node may already be threaded by IN-REPLY-TO, so go up 
286                          * in the tree to 
287                            find the parent node */
288                         if (parent != NULL) {
289                                 if (g_node_is_ancestor(node, parent))
290                                         parent = NULL;
291                                 if (parent == node)
292                                         parent = NULL;
293                         }
294                         
295                         if (parent) {
296                                 g_node_unlink(node);
297                                 g_node_append(parent, node);
298                         }
299
300                         node = next;
301                 }       
302                 END_TIMING();
303         }
304         
305         if (prefs_common.thread_by_subject)
306         {
307                 g_hash_table_foreach(subject_hashtable, subject_hashtable_free, NULL);
308                 g_hash_table_destroy(subject_hashtable);
309         }
310
311         g_hash_table_destroy(msgid_table);
312         END_TIMING();
313         return root;
314 }
315
316 gint procmsg_move_messages(GSList *mlist)
317 {
318         GSList *cur, *movelist = NULL;
319         MsgInfo *msginfo;
320         FolderItem *dest = NULL;
321         gint retval = 0;
322         gboolean finished = TRUE;
323         if (!mlist) return 0;
324
325         folder_item_update_freeze();
326
327 next_folder:
328         for (cur = mlist; cur != NULL; cur = cur->next) {
329                 msginfo = (MsgInfo *)cur->data;
330                 if (!msginfo->to_folder) {
331                         continue;
332                 } else {
333                         finished = FALSE;
334                 }
335                 if (!dest) {
336                         dest = msginfo->to_folder;
337                         movelist = g_slist_prepend(movelist, msginfo);
338                 } else if (dest == msginfo->to_folder) {
339                         movelist = g_slist_prepend(movelist, msginfo);
340                 } else {
341                         continue;
342                 }
343                 procmsg_msginfo_set_to_folder(msginfo, NULL);
344         }
345         if (movelist) {
346                 movelist = g_slist_reverse(movelist);
347                 retval |= folder_item_move_msgs(dest, movelist);
348                 g_slist_free(movelist);
349                 movelist = NULL;
350         }
351         if (finished == FALSE) {
352                 finished = TRUE;
353                 dest = NULL;
354                 goto next_folder;
355         }
356
357         folder_item_update_thaw();
358         return retval;
359 }
360
361 void procmsg_copy_messages(GSList *mlist)
362 {
363         GSList *cur, *copylist = NULL;
364         MsgInfo *msginfo;
365         FolderItem *dest = NULL;
366         gboolean finished = TRUE;
367         if (!mlist) return;
368
369         folder_item_update_freeze();
370
371 next_folder:
372         for (cur = mlist; cur != NULL; cur = cur->next) {
373                 msginfo = (MsgInfo *)cur->data;
374                 if (!msginfo->to_folder) {
375                         continue;
376                 } else {
377                         finished = FALSE;
378                 }
379                 if (!dest) {
380                         dest = msginfo->to_folder;
381                         copylist = g_slist_prepend(copylist, msginfo);
382                 } else if (dest == msginfo->to_folder) {
383                         copylist = g_slist_prepend(copylist, msginfo);
384                 } else {
385                         continue;
386                 }
387                 procmsg_msginfo_set_to_folder(msginfo, NULL);
388         }
389         if (copylist) {
390                 copylist = g_slist_reverse(copylist);
391                 folder_item_copy_msgs(dest, copylist);
392                 g_slist_free(copylist);
393                 copylist = NULL;
394         }
395         if (finished == FALSE) {
396                 finished = TRUE;
397                 dest = NULL;
398                 goto next_folder;
399         }
400
401         folder_item_update_thaw();
402 }
403
404 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
405 {
406         cm_return_val_if_fail(msginfo != NULL, NULL);
407
408         return folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
409 }
410
411 gchar *procmsg_get_message_file(MsgInfo *msginfo)
412 {
413         gchar *filename = NULL;
414
415         cm_return_val_if_fail(msginfo != NULL, NULL);
416
417         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
418         if (!filename)
419                 debug_print("can't fetch message %d\n", msginfo->msgnum);
420
421         return filename;
422 }
423
424 gchar *procmsg_get_message_file_full(MsgInfo *msginfo, gboolean headers, gboolean body)
425 {
426         gchar *filename = NULL;
427
428         cm_return_val_if_fail(msginfo != NULL, NULL);
429
430         filename = folder_item_fetch_msg_full(msginfo->folder, msginfo->msgnum,
431                                                 headers, body);
432         if (!filename)
433                 debug_print("can't fetch message %d\n", msginfo->msgnum);
434
435         return filename;
436 }
437
438 GSList *procmsg_get_message_file_list(GSList *mlist)
439 {
440         GSList *file_list = NULL;
441         MsgInfo *msginfo;
442         MsgFileInfo *fileinfo;
443         gchar *file;
444
445         while (mlist != NULL) {
446                 msginfo = (MsgInfo *)mlist->data;
447                 file = procmsg_get_message_file(msginfo);
448                 if (!file) {
449                         procmsg_message_file_list_free(file_list);
450                         return NULL;
451                 }
452                 fileinfo = g_new(MsgFileInfo, 1);
453                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
454                 fileinfo->file = file;
455                 fileinfo->flags = g_new(MsgFlags, 1);
456                 *fileinfo->flags = msginfo->flags;
457                 file_list = g_slist_prepend(file_list, fileinfo);
458                 mlist = mlist->next;
459         }
460
461         file_list = g_slist_reverse(file_list);
462
463         return file_list;
464 }
465
466 void procmsg_message_file_list_free(MsgInfoList *file_list)
467 {
468         GSList *cur;
469         MsgFileInfo *fileinfo;
470
471         for (cur = file_list; cur != NULL; cur = cur->next) {
472                 fileinfo = (MsgFileInfo *)cur->data;
473                 procmsg_msginfo_free(&(fileinfo->msginfo));
474                 g_free(fileinfo->file);
475                 g_free(fileinfo->flags);
476                 g_free(fileinfo);
477         }
478
479         g_slist_free(file_list);
480 }
481
482 FILE *procmsg_open_message(MsgInfo *msginfo, gboolean skip_special_headers)
483 {
484         FILE *fp;
485         gchar *file;
486
487         cm_return_val_if_fail(msginfo != NULL, NULL);
488         
489         file = procmsg_get_message_file_path(msginfo);
490         cm_return_val_if_fail(file != NULL, NULL);
491
492         if (!is_file_exist(file)) {
493                 g_free(file);
494                 file = procmsg_get_message_file(msginfo);
495                 if (!file)
496                         return NULL;
497         }
498
499         if ((fp = claws_fopen(file, "rb")) == NULL) {
500                 FILE_OP_ERROR(file, "claws_fopen");
501                 g_free(file);
502                 return NULL;
503         }
504
505         g_free(file);
506
507         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags) ||
508             skip_special_headers == TRUE) {
509                 gchar buf[BUFFSIZE];
510
511                 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
512                         /* new way */
513                         if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
514                                 strlen("X-Claws-End-Special-Headers:"))) ||
515                             (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
516                                 strlen("X-Sylpheed-End-Special-Headers:"))))
517                                 break;
518                         /* old way */
519                         if (buf[0] == '\r' || buf[0] == '\n') break;
520                         /* from other mailers */
521                         if (!strncmp(buf, "Date: ", 6)
522                         ||  !strncmp(buf, "To: ", 4)
523                         ||  !strncmp(buf, "From: ", 6)
524                         ||  !strncmp(buf, "Subject: ", 9)) {
525                                 rewind(fp);
526                                 break;
527                         }
528                 }
529         }
530
531         return fp;
532 }
533
534 gboolean procmsg_msg_exist(MsgInfo *msginfo)
535 {
536         gboolean ret;
537
538         if (!msginfo) return FALSE;
539
540         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
541
542         return ret;
543 }
544
545 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
546                                 PrefsFilterType type)
547 {
548         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
549                                        {"X-ML-Name:",      NULL, TRUE},
550                                        {"X-List:",         NULL, TRUE},
551                                        {"X-Mailing-list:", NULL, TRUE},
552                                        {"List-Id:",        NULL, TRUE},
553                                        {"X-Sequence:",     NULL, TRUE},
554                                        {"Sender:",         NULL, TRUE},
555                                        {"List-Post:",      NULL, TRUE},
556                                        {NULL,              NULL, FALSE}};
557         enum
558         {
559                 H_X_BEENTHERE    = 0,
560                 H_X_ML_NAME      = 1,
561                 H_X_LIST         = 2,
562                 H_X_MAILING_LIST = 3,
563                 H_LIST_ID        = 4,
564                 H_X_SEQUENCE     = 5,
565                 H_SENDER         = 6,
566                 H_LIST_POST      = 7
567         };
568
569         FILE *fp;
570
571         cm_return_if_fail(msginfo != NULL);
572         cm_return_if_fail(header != NULL);
573         cm_return_if_fail(key != NULL);
574
575         *header = NULL;
576         *key = NULL;
577
578         switch (type) {
579         case FILTER_BY_NONE:
580                 return;
581         case FILTER_BY_AUTO:
582                 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL)
583                         return;
584                 procheader_get_header_fields(fp, hentry);
585                 claws_fclose(fp);
586
587 #define SET_FILTER_KEY(hstr, idx)       \
588 {                                       \
589         *header = g_strdup(hstr);       \
590         *key = hentry[idx].body;        \
591         hentry[idx].body = NULL;        \
592 }
593
594                 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_BEENTHERE].body != NULL) {
598                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
599                 } else if (hentry[H_X_ML_NAME].body != NULL) {
600                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
601                 } else if (hentry[H_X_LIST].body != NULL) {
602                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
603                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
604                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
605                 } else  if (hentry[H_X_SEQUENCE].body != NULL) {
606                         gchar *p;
607
608                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
609                         p = *key;
610                         while (*p != '\0') {
611                                 while (*p != '\0' && !g_ascii_isspace(*p)) p++;
612                                 while (g_ascii_isspace(*p)) p++;
613                                 if (g_ascii_isdigit(*p)) {
614                                         *p = '\0';
615                                         break;
616                                 }
617                         }
618                         g_strstrip(*key);
619                 } else if (hentry[H_SENDER].body != NULL) {
620                         SET_FILTER_KEY("header \"Sender\"", H_SENDER);
621                 } else if (hentry[H_LIST_POST].body != NULL) {
622                         SET_FILTER_KEY("header \"List-Post\"", H_LIST_POST);
623                 } else if (msginfo->to) {
624                         *header = g_strdup("to");
625                         *key = g_strdup(msginfo->to);
626                 } else if (msginfo->subject) {
627                         *header = g_strdup("subject");
628                         *key = g_strdup(msginfo->subject);
629                 }
630
631 #undef SET_FILTER_KEY
632
633                 g_free(hentry[H_X_BEENTHERE].body);
634                 hentry[H_X_BEENTHERE].body = NULL;
635                 g_free(hentry[H_X_ML_NAME].body);
636                 hentry[H_X_ML_NAME].body = NULL;
637                 g_free(hentry[H_X_LIST].body);
638                 hentry[H_X_LIST].body = NULL;
639                 g_free(hentry[H_X_MAILING_LIST].body);
640                 hentry[H_X_MAILING_LIST].body = NULL;
641                 g_free(hentry[H_LIST_ID].body);
642                 hentry[H_LIST_ID].body = NULL;
643                 g_free(hentry[H_SENDER].body);
644                 hentry[H_SENDER].body = NULL;
645                 g_free(hentry[H_LIST_POST].body);
646                 hentry[H_LIST_POST].body = NULL;
647
648                 break;
649         case FILTER_BY_FROM:
650                 *header = g_strdup("from");
651                 *key = g_strdup(msginfo->from);
652                 break;
653         case FILTER_BY_TO:
654                 *header = g_strdup("to");
655                 *key = g_strdup(msginfo->to);
656                 break;
657         case FILTER_BY_SUBJECT:
658                 *header = g_strdup("subject");
659                 *key = g_strdup(msginfo->subject);
660                 break;
661         default:
662                 break;
663         }
664 }
665
666 static void procmsg_empty_trash(FolderItem *trash)
667 {
668         GNode *node, *next;
669
670         if (!trash || 
671             (trash->stype != F_TRASH && 
672              !folder_has_parent_of_type(trash, F_TRASH)))
673                 return;
674
675         if (trash && trash->total_msgs > 0) {
676                 GSList *mlist = folder_item_get_msg_list(trash);
677                 GSList *cur;
678                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
679                         MsgInfo * msginfo = (MsgInfo *) cur->data;
680                         if (MSG_IS_LOCKED(msginfo->flags)) {
681                                 procmsg_msginfo_free(&msginfo);
682                                 continue;
683                         }
684                         if (msginfo->total_size != 0 && 
685                             msginfo->size != (off_t)msginfo->total_size)
686                                 partial_mark_for_delete(msginfo);
687
688                         procmsg_msginfo_free(&msginfo);
689                 }
690                 g_slist_free(mlist);
691                 folder_item_remove_all_msg(trash);
692         }
693
694         if (!trash->node || !trash->node->children)
695                 return;
696
697         node = trash->node->children;
698         while (node != NULL) {
699                 next = node->next;
700                 procmsg_empty_trash(FOLDER_ITEM(node->data));
701                 node = next;
702         }
703 }
704
705 void procmsg_empty_all_trash(void)
706 {
707         FolderItem *trash;
708         GList *cur;
709
710         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
711                 Folder *folder = FOLDER(cur->data);
712                 trash = folder->trash;
713                 procmsg_empty_trash(trash);
714                 if (folder->account && folder->account->set_trash_folder && 
715                     folder_find_item_from_identifier(folder->account->trash_folder))
716                         procmsg_empty_trash(
717                                 folder_find_item_from_identifier(folder->account->trash_folder));
718         }
719 }
720
721 static PrefsAccount *procmsg_get_account_from_file(const gchar *file)
722 {
723         PrefsAccount *mailac = NULL;
724         FILE *fp;
725         int hnum;
726         gchar *buf = NULL;
727         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
728                                        {"SSV:",  NULL, FALSE},
729                                        {"R:",    NULL, FALSE},
730                                        {"NG:",   NULL, FALSE},
731                                        {"MAID:", NULL, FALSE},
732                                        {"NAID:", NULL, FALSE},
733                                        {"SCF:",  NULL, FALSE},
734                                        {"RMID:", NULL, FALSE},
735                                        {"FMID:", NULL, FALSE},
736                                        {"X-Claws-Privacy-System:", NULL, FALSE},
737                                        {"X-Claws-Encrypt:", NULL, FALSE},
738                                        {"X-Claws-Encrypt-Data:", NULL, FALSE},
739                                        {"X-Claws-End-Special-Headers",    NULL, FALSE},
740                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
741                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
742                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
743                                        {NULL,    NULL, FALSE}};
744         
745         cm_return_val_if_fail(file != NULL, NULL);
746
747         if ((fp = claws_fopen(file, "rb")) == NULL) {
748                 FILE_OP_ERROR(file, "claws_fopen");
749                 return NULL;
750         }
751
752         while ((hnum = procheader_get_one_field(&buf, fp, qentry)) != -1 && buf != NULL) {
753                 gchar *p = buf + strlen(qentry[hnum].name);
754
755                 if (hnum == Q_MAIL_ACCOUNT_ID) {
756                         mailac = account_find_from_id(atoi(p));
757                         break;
758                 }
759                 g_free(buf);
760                 buf = NULL;
761         }
762         claws_fclose(fp);
763         return mailac;
764 }
765
766 gchar *procmsg_msginfo_get_avatar(MsgInfo *msginfo, gint type)
767 {
768         GSList *mia;
769
770         if (!msginfo || !msginfo->extradata || !msginfo->extradata->avatars)
771                 return NULL;
772
773         for (mia = msginfo->extradata->avatars; mia; mia = mia->next) {
774                 MsgInfoAvatar *avatar = (MsgInfoAvatar *)mia->data;
775                 if (avatar->avatar_id == type)
776                         return avatar->avatar_src;
777         }
778
779         return NULL;
780 }
781
782 void procmsg_msginfo_add_avatar(MsgInfo *msginfo, gint type, const gchar *data)
783 {
784         MsgInfoAvatar *av;
785
786         if (!msginfo->extradata)
787                 msginfo->extradata = g_new0(MsgInfoExtraData, 1);
788
789         av = g_new0(MsgInfoAvatar, 1);
790         av->avatar_id = type;
791         av->avatar_src = g_strdup(data);
792
793         msginfo->extradata->avatars = g_slist_append(msginfo->extradata->avatars, av);
794 }
795
796 gchar *procmsg_msginfo_get_identifier(MsgInfo *msginfo)
797 {
798         gchar *folder_id;
799         const gchar *msgid;
800         gchar *id;
801
802         cm_return_val_if_fail(msginfo != NULL, NULL);
803         folder_id = folder_item_get_identifier(msginfo->folder);
804         msgid = msginfo->msgid;
805
806         id = g_strconcat(folder_id, G_DIR_SEPARATOR_S, msgid, NULL);
807
808         g_free(folder_id);
809
810         return id;
811 }
812
813 MsgInfo *procmsg_get_msginfo_from_identifier(const gchar *id)
814 {
815         gchar *folder_id = g_strdup(id);
816         gchar *separator = strrchr(folder_id, G_DIR_SEPARATOR);
817         const gchar *msgid;
818         FolderItem *item;
819         MsgInfo *msginfo;
820
821         if (separator == NULL) {
822                 g_free(folder_id);
823                 return NULL;
824         }
825
826         *separator = '\0';
827         msgid = separator + 1;
828
829         item = folder_find_item_from_identifier(folder_id);
830
831         if (item == NULL) {
832                 g_free(folder_id);
833                 return NULL;
834         }
835
836         msginfo = folder_item_get_msginfo_by_msgid(item, msgid);
837         g_free(folder_id);
838
839         return msginfo;
840 }
841
842 static GSList *procmsg_list_sort_by_account(FolderItem *queue, GSList *list)
843 {
844         GSList *result = NULL;
845         GSList *orig = NULL;
846         PrefsAccount *last_account = NULL;
847         MsgInfo *msg = NULL;
848         GSList *cur = NULL;
849         gboolean nothing_to_sort = TRUE;
850
851         if (!list)
852                 return NULL;
853
854         orig = g_slist_copy(list);
855         
856         msg = (MsgInfo *)orig->data;
857         
858         for (cur = orig; cur; cur = cur->next)
859                 debug_print("sort before %s\n", ((MsgInfo *)cur->data)->from);
860         
861         debug_print("\n");
862
863 parse_again:    
864         nothing_to_sort = TRUE;
865         cur = orig;
866         while (cur) {
867                 gchar *file = NULL;
868                 PrefsAccount *ac = NULL;
869                 msg = (MsgInfo *)cur->data;
870                 file = folder_item_fetch_msg(queue, msg->msgnum);
871                 ac = procmsg_get_account_from_file(file);
872                 g_free(file);
873
874                 if (last_account == NULL || (ac != NULL && ac == last_account)) {
875                         result = g_slist_append(result, msg);
876                         orig = g_slist_remove(orig, msg);
877                         last_account = ac;
878                         nothing_to_sort = FALSE;
879                         goto parse_again;
880                 }
881                 cur = cur->next;
882         }
883         
884         if (orig && g_slist_length(orig)) {
885                 if (!last_account && nothing_to_sort) {
886                         /* can't find an account for the rest of the list */
887                         cur = orig;
888                         while (cur) {
889                                 result = g_slist_append(result, cur->data);
890                                 cur = cur->next;
891                         }
892                 } else {
893                         last_account = NULL;
894                         goto parse_again;
895                 }
896         }
897         
898         g_slist_free(orig);
899         
900         for (cur = result; cur; cur = cur->next)
901                 debug_print("sort after %s\n", ((MsgInfo *)cur->data)->from);
902
903         debug_print("\n");
904
905         return result;
906 }
907
908 static gboolean procmsg_is_last_for_account(FolderItem *queue, MsgInfo *msginfo, GSList *elem)
909 {
910         gchar *file = folder_item_fetch_msg(queue, msginfo->msgnum);
911         PrefsAccount *ac = procmsg_get_account_from_file(file);
912         GSList *cur;
913         g_free(file);
914         for (cur = elem; cur; cur = cur->next) {
915                 MsgInfo *cur_msginfo = (MsgInfo *)cur->data;
916                 file = folder_item_fetch_msg(queue, cur_msginfo->msgnum);
917                 
918                 if (cur_msginfo != msginfo && !MSG_IS_LOCKED(cur_msginfo->flags)) {
919                         if (procmsg_get_account_from_file(file) == ac) {
920                                 g_free(file);
921                                 return FALSE;
922                         }
923                 }
924                 
925                 g_free(file);
926         }
927         return TRUE;
928 }
929
930 static gboolean send_queue_lock = FALSE;
931
932 gboolean procmsg_queue_lock(char **errstr)
933 {
934         if (send_queue_lock) {
935                 /* Avoid having to translate two similar strings */
936                 log_warning(LOG_PROTOCOL, "%s\n", _("Already trying to send."));
937                 if (errstr) {
938                         if (*errstr) g_free(*errstr);
939                         *errstr = g_strdup_printf(_("Already trying to send."));
940                 }
941                 return FALSE;
942         }
943         send_queue_lock = TRUE;
944         return TRUE;
945 }
946 void procmsg_queue_unlock(void)
947 {
948         send_queue_lock = FALSE;
949 }
950 /*!
951  *\brief        Send messages in queue
952  *
953  *\param        queue Queue folder to process
954  *\param        save_msgs Unused
955  *
956  *\return       Number of messages sent, negative if an error occurred
957  *              positive if no error occurred
958  */
959 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs, gchar **errstr)
960 {
961         gint sent = 0, err = 0;
962         GSList *list, *elem;
963         GSList *sorted_list = NULL;
964         GNode *node, *next;
965         
966         if (!procmsg_queue_lock(errstr)) {
967                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
968                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
969                 return -1;
970         }
971         inc_lock();
972         if (!queue)
973                 queue = folder_get_default_queue();
974         
975         if (queue == NULL) {
976                 procmsg_queue_unlock();
977                 inc_unlock();
978                 return -1;
979         }
980
981         main_window_set_menu_sensitive(mainwindow_get_mainwindow());
982         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
983
984         folder_item_scan(queue);
985         list = folder_item_get_msg_list(queue);
986
987         /* sort the list per sender account; this helps reusing the same SMTP server */
988         sorted_list = procmsg_list_sort_by_account(queue, list);
989         
990         for (elem = sorted_list; elem != NULL; elem = elem->next) {
991                 gchar *file;
992                 MsgInfo *msginfo;
993                         
994                 msginfo = (MsgInfo *)(elem->data);
995                 if (!MSG_IS_LOCKED(msginfo->flags) && !MSG_IS_DELETED(msginfo->flags)) {
996                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
997                         if (file) {
998                                 gboolean queued_removed = FALSE;
999                                 if (procmsg_send_message_queue_full(file, 
1000                                                 !procmsg_is_last_for_account(queue, msginfo, elem),
1001                                                 errstr, queue, msginfo->msgnum, &queued_removed) < 0) {
1002                                         g_warning("Sending queued message %d failed.",
1003                                                   msginfo->msgnum);
1004                                         err++;
1005                                 } else {
1006                                         sent++; 
1007                                         if (!queued_removed)
1008                                                 folder_item_remove_msg(queue, msginfo->msgnum);
1009                                 }
1010                                 g_free(file);
1011                         }
1012                 }
1013                 /* FIXME: supposedly if only one message is locked, and queue
1014                  * is being flushed, the following free says something like 
1015                  * "freeing msg ## in folder (nil)". */
1016                 procmsg_msginfo_free(&msginfo);
1017         }
1018
1019         g_slist_free(sorted_list);
1020         folder_item_scan(queue);
1021
1022         if (queue->node && queue->node->children) {
1023                 node = queue->node->children;
1024                 while (node != NULL) {
1025                         int res = 0;
1026                         next = node->next;
1027                         send_queue_lock = FALSE;
1028                         res = procmsg_send_queue(FOLDER_ITEM(node->data), save_msgs, errstr);
1029                         send_queue_lock = TRUE;
1030                         if (res < 0) 
1031                                 err = -res;
1032                         else
1033                                 sent += res;
1034                         node = next;
1035                 }
1036         }
1037         procmsg_queue_unlock();
1038         inc_unlock();
1039         main_window_set_menu_sensitive(mainwindow_get_mainwindow());
1040         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1041
1042         return (err != 0 ? -err : sent);
1043 }
1044
1045 gboolean procmsg_is_sending(void)
1046 {
1047         return send_queue_lock;
1048 }
1049
1050 /*!
1051  *\brief        Determine if a queue folder is empty
1052  *
1053  *\param        queue Queue folder to process
1054  *
1055  *\return       TRUE if the queue folder is empty, otherwise return FALSE
1056  */
1057 gboolean procmsg_queue_is_empty(FolderItem *queue)
1058 {
1059         GSList *list;
1060         gboolean res = FALSE;
1061         if (!queue)
1062                 queue = folder_get_default_queue();
1063         cm_return_val_if_fail(queue != NULL, TRUE);
1064
1065         folder_item_scan(queue);
1066         list = folder_item_get_msg_list(queue);
1067         res = (list == NULL);
1068         procmsg_msg_list_free(list);
1069
1070         if (res == TRUE) {
1071                 GNode *node, *next;
1072                 if (queue->node && queue->node->children) {
1073                         node = queue->node->children;
1074                         while (node != NULL) {
1075                                 next = node->next;
1076                                 if (!procmsg_queue_is_empty(FOLDER_ITEM(node->data)))
1077                                         return FALSE;
1078                                 node = next;
1079                         }
1080                 }
1081         }
1082         return res;
1083 }
1084
1085 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
1086 {
1087         FILE *fp, *outfp;
1088         gchar buf[BUFFSIZE];
1089         
1090         if ((fp = claws_fopen(in, "rb")) == NULL) {
1091                 FILE_OP_ERROR(in, "claws_fopen");
1092                 return -1;
1093         }
1094         if ((outfp = claws_fopen(out, "wb")) == NULL) {
1095                 FILE_OP_ERROR(out, "claws_fopen");
1096                 claws_fclose(fp);
1097                 return -1;
1098         }
1099         while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
1100                 /* new way */
1101                 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
1102                         strlen("X-Claws-End-Special-Headers:"))) ||
1103                     (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
1104                         strlen("X-Sylpheed-End-Special-Headers:"))))
1105                         break;
1106                 /* old way */
1107                 if (buf[0] == '\r' || buf[0] == '\n') break;
1108                 /* from other mailers */
1109                 if (!strncmp(buf, "Date: ", 6)
1110                 ||  !strncmp(buf, "To: ", 4)
1111                 ||  !strncmp(buf, "From: ", 6)
1112                 ||  !strncmp(buf, "Subject: ", 9)) {
1113                         rewind(fp);
1114                         break;
1115                 }
1116         }
1117         while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
1118                 if (claws_fputs(buf, outfp) == EOF) {
1119                         FILE_OP_ERROR(out, "claws_fputs");
1120                         claws_fclose(outfp);
1121                         claws_fclose(fp);
1122                         return -1;
1123                 }
1124         }
1125         claws_safe_fclose(outfp);
1126         claws_fclose(fp);
1127         return 0;
1128 }
1129
1130 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
1131                             gboolean is_queued)
1132 {
1133         gint num;
1134         MsgInfo *msginfo, *tmp_msginfo;
1135         MsgFlags flag = {0, 0};
1136         gchar *outbox_path = NULL;
1137
1138         if (!outbox) {
1139                 debug_print("using default outbox\n");
1140                 outbox = folder_get_default_outbox();
1141         }
1142
1143         cm_return_val_if_fail(outbox != NULL, -1);
1144
1145         outbox_path = folder_item_get_path(outbox);
1146         debug_print("saving sent message to %s...\n", outbox_path);
1147         g_free(outbox_path);
1148
1149         /* remove queueing headers */
1150         if (is_queued) {
1151                 gchar tmp[MAXPATHLEN + 1];
1152
1153                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
1154                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
1155                 
1156                 if (procmsg_remove_special_headers(file, tmp) !=0)
1157                         return -1;
1158
1159                 folder_item_scan(outbox);
1160                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
1161                         g_warning("can't save message");
1162                         claws_unlink(tmp);
1163                         return -1;
1164                 }
1165         } else {
1166                 folder_item_scan(outbox);
1167                 if ((num = folder_item_add_msg
1168                         (outbox, file, &flag, FALSE)) < 0) {
1169                         g_warning("can't save message");
1170                         return -1;
1171                 }
1172         }
1173         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
1174         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
1175         if (msginfo != NULL) {
1176                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1177                 procmsg_msginfo_free(&msginfo);                 /* refcnt-- */
1178                 /* tmp_msginfo == msginfo */
1179                 if (tmp_msginfo && msginfo->extradata && 
1180                     (msginfo->extradata->dispositionnotificationto || 
1181                      msginfo->extradata->returnreceiptto)) {
1182                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
1183                 }       
1184                 procmsg_msginfo_free(&tmp_msginfo);             /* refcnt-- */
1185         }
1186
1187         return 0;
1188 }
1189
1190
1191 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1192 {
1193         msginfo->refcnt++;
1194         
1195         return msginfo;
1196 }
1197
1198 MsgInfo *procmsg_msginfo_new(void)
1199 {
1200         MsgInfo *newmsginfo;
1201
1202         newmsginfo = g_new0(MsgInfo, 1);
1203         newmsginfo->refcnt = 1;
1204         
1205         return newmsginfo;
1206 }
1207
1208 static MsgInfoAvatar *procmsg_msginfoavatar_copy(MsgInfoAvatar *avatar)
1209 {
1210         MsgInfoAvatar *newavatar;
1211
1212         if (avatar == NULL) return NULL;
1213
1214         newavatar = g_new0(MsgInfoAvatar, 1);
1215         newavatar->avatar_id = avatar->avatar_id;
1216         newavatar->avatar_src = g_strdup(avatar->avatar_src);
1217
1218         return newavatar;
1219 }
1220
1221 static void procmsg_msginfoavatar_free(MsgInfoAvatar *avatar)
1222 {
1223         if (avatar != NULL) {
1224                 if (avatar->avatar_src != NULL)
1225                         g_free(avatar->avatar_src);
1226                 g_free(avatar);
1227         }
1228 }
1229
1230 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1231 {
1232         MsgInfo *newmsginfo;
1233         GSList *refs;
1234
1235         if (msginfo == NULL) return NULL;
1236
1237         newmsginfo = g_new0(MsgInfo, 1);
1238
1239         newmsginfo->refcnt = 1;
1240
1241 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1242 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1243                         g_strdup(msginfo->mmb) : NULL
1244
1245         MEMBCOPY(msgnum);
1246         MEMBCOPY(size);
1247         MEMBCOPY(mtime);
1248         MEMBCOPY(date_t);
1249
1250         MEMBCOPY(flags);
1251
1252         MEMBDUP(fromname);
1253
1254         MEMBDUP(date);
1255         MEMBDUP(from);
1256         MEMBDUP(to);
1257         MEMBDUP(cc);
1258         MEMBDUP(newsgroups);
1259         MEMBDUP(subject);
1260         MEMBDUP(msgid);
1261         MEMBDUP(inreplyto);
1262         MEMBDUP(xref);
1263
1264         MEMBCOPY(folder);
1265         MEMBCOPY(to_folder);
1266
1267         if (msginfo->extradata) {
1268                 newmsginfo->extradata = g_new0(MsgInfoExtraData, 1);
1269                 if (msginfo->extradata->avatars) {
1270                         newmsginfo->extradata->avatars = slist_copy_deep(msginfo->extradata->avatars,
1271                                                                 (GCopyFunc) procmsg_msginfoavatar_copy);
1272                 }
1273                 MEMBDUP(extradata->dispositionnotificationto);
1274                 MEMBDUP(extradata->returnreceiptto);
1275                 MEMBDUP(extradata->partial_recv);
1276                 MEMBDUP(extradata->account_server);
1277                 MEMBDUP(extradata->account_login);
1278                 MEMBDUP(extradata->list_post);
1279                 MEMBDUP(extradata->list_subscribe);
1280                 MEMBDUP(extradata->list_unsubscribe);
1281                 MEMBDUP(extradata->list_help);
1282                 MEMBDUP(extradata->list_archive);
1283                 MEMBDUP(extradata->list_owner);
1284                 MEMBDUP(extradata->resent_from);
1285         }
1286
1287         refs = msginfo->references;
1288         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1289                 newmsginfo->references = g_slist_prepend
1290                         (newmsginfo->references, g_strdup(refs->data)); 
1291         }
1292         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1293
1294         MEMBCOPY(score);
1295         MEMBDUP(plaintext_file);
1296
1297         return newmsginfo;
1298 }
1299
1300 MsgInfo *procmsg_msginfo_get_full_info_from_file(MsgInfo *msginfo, const gchar *file)
1301 {
1302         MsgInfo *full_msginfo;
1303
1304         if (msginfo == NULL) return NULL;
1305
1306         if (!file || !is_file_exist(file)) {
1307                 g_warning("procmsg_msginfo_get_full_info_from_file(): can't get message file.");
1308                 return NULL;
1309         }
1310
1311         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1312         if (!full_msginfo) return NULL;
1313
1314         msginfo->total_size = full_msginfo->total_size;
1315         msginfo->planned_download = full_msginfo->planned_download;
1316
1317         if (full_msginfo->extradata) {
1318                 if (!msginfo->extradata)
1319                         msginfo->extradata = g_new0(MsgInfoExtraData, 1);
1320                 if (!msginfo->extradata->list_post)
1321                         msginfo->extradata->list_post = g_strdup(full_msginfo->extradata->list_post);
1322                 if (!msginfo->extradata->list_subscribe)
1323                         msginfo->extradata->list_subscribe = g_strdup(full_msginfo->extradata->list_subscribe);
1324                 if (!msginfo->extradata->list_unsubscribe)
1325                         msginfo->extradata->list_unsubscribe = g_strdup(full_msginfo->extradata->list_unsubscribe);
1326                 if (!msginfo->extradata->list_help)
1327                         msginfo->extradata->list_help = g_strdup(full_msginfo->extradata->list_help);
1328                 if (!msginfo->extradata->list_archive)
1329                         msginfo->extradata->list_archive= g_strdup(full_msginfo->extradata->list_archive);
1330                 if (!msginfo->extradata->list_owner)
1331                         msginfo->extradata->list_owner = g_strdup(full_msginfo->extradata->list_owner);
1332                 if (!msginfo->extradata->avatars)
1333                         msginfo->extradata->avatars = slist_copy_deep(full_msginfo->extradata->avatars,
1334                                                                         (GCopyFunc) procmsg_msginfoavatar_copy);
1335                 if (!msginfo->extradata->dispositionnotificationto)
1336                         msginfo->extradata->dispositionnotificationto = 
1337                                 g_strdup(full_msginfo->extradata->dispositionnotificationto);
1338                 if (!msginfo->extradata->returnreceiptto)
1339                         msginfo->extradata->returnreceiptto = g_strdup
1340                                 (full_msginfo->extradata->returnreceiptto);
1341                 if (!msginfo->extradata->partial_recv && full_msginfo->extradata->partial_recv)
1342                         msginfo->extradata->partial_recv = g_strdup
1343                                 (full_msginfo->extradata->partial_recv);
1344                 if (!msginfo->extradata->account_server && full_msginfo->extradata->account_server)
1345                         msginfo->extradata->account_server = g_strdup
1346                                 (full_msginfo->extradata->account_server);
1347                 if (!msginfo->extradata->account_login && full_msginfo->extradata->account_login)
1348                         msginfo->extradata->account_login = g_strdup
1349                                 (full_msginfo->extradata->account_login);
1350                 if (!msginfo->extradata->resent_from && full_msginfo->extradata->resent_from)
1351                         msginfo->extradata->resent_from = g_strdup
1352                                 (full_msginfo->extradata->resent_from);
1353         }
1354         procmsg_msginfo_free(&full_msginfo);
1355
1356         return procmsg_msginfo_new_ref(msginfo);
1357 }
1358
1359 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1360 {
1361         MsgInfo *full_msginfo;
1362         gchar *file;
1363
1364         if (msginfo == NULL) return NULL;
1365
1366         file = procmsg_get_message_file_path(msginfo);
1367         if (!file || !is_file_exist(file)) {
1368                 g_free(file);
1369                 file = procmsg_get_message_file(msginfo);
1370         }
1371         if (!file || !is_file_exist(file)) {
1372                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.");
1373                 return NULL;
1374         }
1375
1376         full_msginfo = procmsg_msginfo_get_full_info_from_file(msginfo, file);
1377         g_free(file);
1378         return full_msginfo;
1379 }
1380
1381 #define FREENULL(n) { g_free(n); n = NULL; }
1382 void procmsg_msginfo_free(MsgInfo **msginfo_ptr)
1383 {
1384         MsgInfo *msginfo = *msginfo_ptr;
1385
1386         if (msginfo == NULL) return;
1387
1388         msginfo->refcnt--;
1389         if (msginfo->refcnt > 0)
1390                 return;
1391
1392         if (msginfo->to_folder) {
1393                 msginfo->to_folder->op_count--;
1394                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1395         }
1396
1397         FREENULL(msginfo->fromspace);
1398
1399         FREENULL(msginfo->fromname);
1400
1401         FREENULL(msginfo->date);
1402         FREENULL(msginfo->from);
1403         FREENULL(msginfo->to);
1404         FREENULL(msginfo->cc);
1405         FREENULL(msginfo->newsgroups);
1406         FREENULL(msginfo->subject);
1407         FREENULL(msginfo->msgid);
1408         FREENULL(msginfo->inreplyto);
1409         FREENULL(msginfo->xref);
1410
1411         if (msginfo->extradata) {
1412                 if (msginfo->extradata->avatars) {
1413                         g_slist_foreach(msginfo->extradata->avatars,
1414                                         (GFunc)procmsg_msginfoavatar_free,
1415                                         NULL);
1416                         g_slist_free(msginfo->extradata->avatars);
1417                         msginfo->extradata->avatars = NULL;
1418                 }
1419                 FREENULL(msginfo->extradata->returnreceiptto);
1420                 FREENULL(msginfo->extradata->dispositionnotificationto);
1421                 FREENULL(msginfo->extradata->list_post);
1422                 FREENULL(msginfo->extradata->list_subscribe);
1423                 FREENULL(msginfo->extradata->list_unsubscribe);
1424                 FREENULL(msginfo->extradata->list_help);
1425                 FREENULL(msginfo->extradata->list_archive);
1426                 FREENULL(msginfo->extradata->list_owner);
1427                 FREENULL(msginfo->extradata->partial_recv);
1428                 FREENULL(msginfo->extradata->account_server);
1429                 FREENULL(msginfo->extradata->account_login);
1430                 FREENULL(msginfo->extradata->resent_from);
1431                 FREENULL(msginfo->extradata);
1432         }
1433         slist_free_strings_full(msginfo->references);
1434         msginfo->references = NULL;
1435         g_slist_free(msginfo->tags);
1436         msginfo->tags = NULL;
1437
1438         FREENULL(msginfo->plaintext_file);
1439
1440         g_free(msginfo);
1441         *msginfo_ptr = NULL;
1442 }
1443 #undef FREENULL
1444
1445 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1446 {
1447         guint memusage = 0;
1448         GSList *tmp;
1449         
1450         memusage += sizeof(MsgInfo);
1451         if (msginfo->fromname)
1452                 memusage += strlen(msginfo->fromname);
1453         if (msginfo->date)
1454                 memusage += strlen(msginfo->date);
1455         if (msginfo->from)
1456                 memusage += strlen(msginfo->from);
1457         if (msginfo->to)
1458                 memusage += strlen(msginfo->to);
1459         if (msginfo->cc)
1460                 memusage += strlen(msginfo->cc);
1461         if (msginfo->newsgroups)
1462                 memusage += strlen(msginfo->newsgroups);
1463         if (msginfo->subject)
1464                 memusage += strlen(msginfo->subject);
1465         if (msginfo->msgid)
1466                 memusage += strlen(msginfo->msgid);
1467         if (msginfo->inreplyto)
1468                 memusage += strlen(msginfo->inreplyto);
1469
1470         for (tmp = msginfo->references; tmp; tmp=tmp->next) {
1471                 gchar *r = (gchar *)tmp->data;
1472                 memusage += r?strlen(r):0 + sizeof(GSList);
1473         }
1474         if (msginfo->fromspace)
1475                 memusage += strlen(msginfo->fromspace);
1476
1477         for (tmp = msginfo->tags; tmp; tmp=tmp->next) {
1478                 memusage += sizeof(GSList);
1479         }
1480         if (msginfo->extradata) {
1481                 memusage += sizeof(MsgInfoExtraData);
1482                 if (msginfo->extradata->avatars) {
1483                         for (tmp = msginfo->extradata->avatars; tmp; tmp = tmp->next) {
1484                                 MsgInfoAvatar *avt = (MsgInfoAvatar *)tmp->data;
1485                                 memusage += (avt->avatar_src)? strlen(avt->avatar_src): 0;
1486                                 memusage += sizeof(MsgInfoAvatar) + sizeof(GSList);
1487                         }
1488                 }
1489                 if (msginfo->extradata->dispositionnotificationto)
1490                         memusage += strlen(msginfo->extradata->dispositionnotificationto);
1491                 if (msginfo->extradata->returnreceiptto)
1492                         memusage += strlen(msginfo->extradata->returnreceiptto);
1493
1494                 if (msginfo->extradata->partial_recv)
1495                         memusage += strlen(msginfo->extradata->partial_recv);
1496                 if (msginfo->extradata->account_server)
1497                         memusage += strlen(msginfo->extradata->account_server);
1498                 if (msginfo->extradata->account_login)
1499                         memusage += strlen(msginfo->extradata->account_login);
1500                 if (msginfo->extradata->resent_from)
1501                         memusage += strlen(msginfo->extradata->resent_from);
1502
1503                 if (msginfo->extradata->list_post)
1504                         memusage += strlen(msginfo->extradata->list_post);
1505                 if (msginfo->extradata->list_subscribe)
1506                         memusage += strlen(msginfo->extradata->list_subscribe);
1507                 if (msginfo->extradata->list_unsubscribe)
1508                         memusage += strlen(msginfo->extradata->list_unsubscribe);
1509                 if (msginfo->extradata->list_help)
1510                         memusage += strlen(msginfo->extradata->list_help);
1511                 if (msginfo->extradata->list_archive)
1512                         memusage += strlen(msginfo->extradata->list_archive);
1513                 if (msginfo->extradata->list_owner)
1514                         memusage += strlen(msginfo->extradata->list_owner);
1515         }
1516         return memusage;
1517 }
1518
1519 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr,
1520                                             FolderItem *queue, gint msgnum, gboolean *queued_removed)
1521 {
1522         static HeaderEntry qentry[] = {
1523                                        {"S:",    NULL, FALSE}, /* 0 */
1524                                        {"SSV:",  NULL, FALSE},
1525                                        {"R:",    NULL, FALSE},
1526                                        {"NG:",   NULL, FALSE},
1527                                        {"MAID:", NULL, FALSE},
1528                                        {"NAID:", NULL, FALSE}, /* 5 */
1529                                        {"SCF:",  NULL, FALSE},
1530                                        {"RMID:", NULL, FALSE},
1531                                        {"FMID:", NULL, FALSE},
1532                                        {"X-Claws-Privacy-System:", NULL, FALSE},
1533                                        {"X-Claws-Encrypt:", NULL, FALSE}, /* 10 */
1534                                        {"X-Claws-Encrypt-Data:", NULL, FALSE},
1535                                        {"X-Claws-End-Special-Headers:", NULL, FALSE},
1536                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1537                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1538                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE}, /* 15 */
1539                                        {"X-Sylpheed-End-Special-Headers:", NULL, FALSE},
1540                                        {NULL,    NULL, FALSE}};
1541         FILE *fp;
1542         gint filepos;
1543         gint mailval = 0, newsval = 0;
1544         gchar *from = NULL;
1545         gchar *smtpserver = NULL;
1546         GSList *to_list = NULL;
1547         GSList *newsgroup_list = NULL;
1548         gchar *savecopyfolder = NULL;
1549         gchar *replymessageid = NULL;
1550         gchar *fwdmessageid = NULL;
1551         gchar *buf;
1552         gint hnum;
1553         PrefsAccount *mailac = NULL, *newsac = NULL;
1554         gboolean encrypt = FALSE;
1555         FolderItem *outbox;
1556
1557         cm_return_val_if_fail(file != NULL, -1);
1558
1559         if ((fp = claws_fopen(file, "rb")) == NULL) {
1560                 FILE_OP_ERROR(file, "claws_fopen");
1561                 if (errstr) {
1562                         if (*errstr) g_free(*errstr);
1563                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1564                 }
1565                 return -1;
1566         }
1567
1568         while ((hnum = procheader_get_one_field(&buf, fp, qentry)) != -1 && buf != NULL) {
1569                 gchar *p = buf + strlen(qentry[hnum].name);
1570
1571                 switch (hnum) {
1572                 case Q_SENDER:
1573                         if (from == NULL) 
1574                                 from = g_strdup(p);
1575                         break;
1576                 case Q_SMTPSERVER:
1577                         if (smtpserver == NULL) 
1578                                 smtpserver = g_strdup(p);
1579                         break;
1580                 case Q_RECIPIENTS:
1581                         to_list = address_list_append(to_list, p);
1582                         break;
1583                 case Q_NEWSGROUPS:
1584                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1585                         break;
1586                 case Q_MAIL_ACCOUNT_ID:
1587                         mailac = account_find_from_id(atoi(p));
1588                         break;
1589                 case Q_NEWS_ACCOUNT_ID:
1590                         newsac = account_find_from_id(atoi(p));
1591                         break;
1592                 case Q_SAVE_COPY_FOLDER:
1593                         if (savecopyfolder == NULL) 
1594                                 savecopyfolder = g_strdup(p);
1595                         break;
1596                 case Q_REPLY_MESSAGE_ID:
1597                         if (replymessageid == NULL) 
1598                                 replymessageid = g_strdup(p);
1599                         break;
1600                 case Q_FWD_MESSAGE_ID:
1601                         if (fwdmessageid == NULL) 
1602                                 fwdmessageid = g_strdup(p);
1603                         break;
1604                 case Q_ENCRYPT:
1605                 case Q_ENCRYPT_OLD:
1606                         if (p[0] == '1') 
1607                                 encrypt = TRUE;
1608                         break;
1609                 case Q_CLAWS_HDRS:
1610                 case Q_CLAWS_HDRS_OLD:
1611                         /* end of special headers reached */
1612                         g_free(buf);
1613                         goto send_mail; /* can't "break;break;" */
1614                 }
1615                 g_free(buf);
1616         }
1617
1618 send_mail:
1619         filepos = ftell(fp);
1620         if (filepos < 0) {
1621                 FILE_OP_ERROR(file, "ftell");
1622                 if (errstr) {
1623                         if (*errstr) g_free(*errstr);
1624                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1625                 }
1626                 return -1;
1627         }
1628
1629         if (to_list) {
1630                 debug_print("Sending message by mail\n");
1631                 if (!from) {
1632                         if (errstr) {
1633                                 if (*errstr) g_free(*errstr);
1634                                 *errstr = g_strdup_printf(_("Queued message header is broken."));
1635                         }
1636                         mailval = -1;
1637                 } else if (mailac && mailac->use_mail_command &&
1638                            mailac->mail_command && (* mailac->mail_command)) {
1639                         mailval = send_message_local(mailac->mail_command, fp);
1640                 } else {
1641                         if (!mailac) {
1642                                 mailac = account_find_from_smtp_server(from, smtpserver);
1643                                 if (!mailac) {
1644                                         g_warning("Account not found. "
1645                                                     "Using current account...");
1646                                         mailac = cur_account;
1647                                 }
1648                         }
1649
1650                         if (mailac) {
1651                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1652                                 if (mailval == -1 && errstr) {
1653                                         if (*errstr) g_free(*errstr);
1654                                         *errstr = g_strdup_printf(_("An error happened during SMTP session."));
1655                                 }
1656                         } else {
1657                                 PrefsAccount tmp_ac;
1658
1659                                 g_warning("Account not found.");
1660
1661                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1662                                 tmp_ac.address = from;
1663                                 tmp_ac.smtp_server = smtpserver;
1664                                 tmp_ac.smtpport = SMTP_PORT;
1665                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1666                                 if (mailval == -1 && errstr) {
1667                                         if (*errstr) g_free(*errstr);
1668                                         *errstr = g_strdup_printf(_("No specific account has been found to "
1669                                                         "send, and an error happened during SMTP session."));
1670                                 }
1671                         }
1672                 }
1673         } else if (!to_list && !newsgroup_list) {
1674                 if (errstr) {
1675                         if (*errstr) g_free(*errstr);
1676                         *errstr = g_strdup(_("Couldn't determine sending information. "
1677                                 "Maybe the email hasn't been generated by Claws Mail."));
1678                 }
1679                 mailval = -1;
1680         }
1681
1682         if (fseek(fp, filepos, SEEK_SET) < 0) {
1683                 FILE_OP_ERROR(file, "fseek");
1684                 mailval = -1;
1685         }
1686
1687         if (newsgroup_list && newsac && (mailval == 0)) {
1688                 Folder *folder;
1689                 gchar *tmp = NULL;
1690                 gchar buf[BUFFSIZE];
1691                 FILE *tmpfp;
1692
1693                 /* write to temporary file */
1694                 tmp = g_strdup_printf("%s%cnntp%p", get_tmp_dir(),
1695                             G_DIR_SEPARATOR, file);
1696                 if ((tmpfp = claws_fopen(tmp, "wb")) == NULL) {
1697                         FILE_OP_ERROR(tmp, "claws_fopen");
1698                         newsval = -1;
1699                         alertpanel_error(_("Couldn't create temporary file for news sending."));
1700                 } else {
1701                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1702                                 FILE_OP_ERROR(tmp, "chmod");
1703                                 g_warning("can't change file mode");
1704                         }
1705
1706                         while ((newsval == 0) && claws_fgets(buf, sizeof(buf), fp) != NULL) {
1707                                 if (claws_fputs(buf, tmpfp) == EOF) {
1708                                         FILE_OP_ERROR(tmp, "claws_fputs");
1709                                         newsval = -1;
1710                                         if (errstr) {
1711                                                 if (*errstr) g_free(*errstr);
1712                                                 *errstr = g_strdup_printf(_("Error when writing temporary file for news sending."));
1713                                         }
1714                                 }
1715                         }
1716                         claws_safe_fclose(tmpfp);
1717
1718                         if (newsval == 0) {
1719                                 debug_print("Sending message by news\n");
1720
1721                                 folder = FOLDER(newsac->folder);
1722
1723                                 newsval = news_post(folder, tmp);
1724                                 if (newsval < 0 && errstr)  {
1725                                         if (*errstr) g_free(*errstr);
1726                                         *errstr = g_strdup_printf(_("Error occurred while posting the message to %s."),
1727                                          newsac->nntp_server);
1728                                 }
1729                         }
1730                         claws_unlink(tmp);
1731                 }
1732                 g_free(tmp);
1733         }
1734
1735         claws_fclose(fp);
1736
1737         /* update session statistics */
1738         if (mailval == 0 && newsval == 0) {
1739                 /* update session stats */
1740                 if (replymessageid)
1741                         session_stats.replied++;
1742                 else if (fwdmessageid)
1743                         session_stats.forwarded++;
1744                 else
1745                         session_stats.sent++;
1746         }
1747
1748         /* save message to outbox */
1749         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1750                 debug_print("saving sent message to %s...\n", savecopyfolder);
1751
1752                 if (!encrypt || !mailac->save_encrypted_as_clear_text) {
1753                         outbox = folder_find_item_from_identifier(savecopyfolder);
1754                         if (!outbox) {
1755                                 gchar *id;
1756                                 outbox = folder_get_default_outbox();
1757                                 if (outbox != NULL) {
1758                                         id = folder_item_get_identifier(outbox);
1759                                         debug_print("%s not found, using %s\n", savecopyfolder, id);
1760                                         g_free(id);
1761                                 } else {
1762                                         debug_print("could not find outbox\n");
1763                                 }
1764                         }
1765                         /* Mail was not saved to outbox before encrypting, save it now. */
1766                         gboolean saved = FALSE;
1767                         *queued_removed = FALSE;
1768                         if (queue && msgnum > 0) {
1769                                 MsgInfo *queued_mail = folder_item_get_msginfo(queue, msgnum);
1770                                 if (folder_item_move_msg(outbox, queued_mail) >= 0) {
1771                                         debug_print("moved queued mail %d to sent folder\n", msgnum);
1772                                         saved = TRUE;
1773                                         *queued_removed = TRUE;
1774                                 } else if (folder_item_copy_msg(outbox, queued_mail) >= 0) {
1775                                         debug_print("copied queued mail %d to sent folder\n", msgnum);
1776                                         saved = TRUE;
1777                                 }
1778                                 procmsg_msginfo_free(&queued_mail);
1779                         }
1780                         if (!saved) {
1781                                 debug_print("resaving queued mail to sent folder\n");
1782                                 procmsg_save_to_outbox(outbox, file, TRUE);
1783                         }
1784                 }
1785         }
1786
1787         if (replymessageid != NULL || fwdmessageid != NULL) {
1788                 gchar **tokens;
1789                 FolderItem *item;
1790                 
1791                 if (replymessageid != NULL)
1792                         tokens = g_strsplit(replymessageid, "\t", 0);
1793                 else
1794                         tokens = g_strsplit(fwdmessageid, "\t", 0);
1795                 item = folder_find_item_from_identifier(tokens[0]);
1796
1797                 /* check if queued message has valid folder and message id */
1798                 if (item != NULL && tokens[2] != NULL) {
1799                         MsgInfo *msginfo;
1800                         
1801                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1802                 
1803                         /* check if referring message exists and has a message id */
1804                         if ((msginfo != NULL) && 
1805                             (msginfo->msgid != NULL) &&
1806                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1807                                 procmsg_msginfo_free(&msginfo);
1808                                 msginfo = NULL;
1809                         }
1810                         
1811                         if (msginfo == NULL) {
1812                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1813                         }
1814                         
1815                         if (msginfo != NULL) {
1816                                 if (replymessageid != NULL) {
1817                                         MsgPermFlags to_unset = 0;
1818
1819                                         if (prefs_common.mark_as_read_on_new_window)
1820                                                 to_unset = (MSG_NEW|MSG_UNREAD);
1821
1822                                         procmsg_msginfo_unset_flags(msginfo, to_unset, 0);
1823                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1824                                 }  else {
1825                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1826                                 }
1827                                 procmsg_msginfo_free(&msginfo);
1828                         }
1829                 }
1830                 g_strfreev(tokens);
1831         }
1832
1833         g_free(from);
1834         g_free(smtpserver);
1835         slist_free_strings_full(to_list);
1836         slist_free_strings_full(newsgroup_list);
1837         g_free(savecopyfolder);
1838         g_free(replymessageid);
1839         g_free(fwdmessageid);
1840
1841         return (newsval != 0 ? newsval : mailval);
1842 }
1843
1844 gint procmsg_send_message_queue(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1845 {
1846         gint result = procmsg_send_message_queue_full(file, FALSE, errstr, queue, msgnum, queued_removed);
1847         main_window_set_menu_sensitive(mainwindow_get_mainwindow());
1848         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1849         return result;
1850 }
1851
1852 gint procmsg_send_message_queue_with_lock(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1853 {
1854         gint val;
1855         if (procmsg_queue_lock(errstr)) {
1856                 val = procmsg_send_message_queue(file, errstr, queue, msgnum, queued_removed);
1857                 procmsg_queue_unlock();
1858                 return val;
1859         }
1860         return -1;
1861 }
1862
1863 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1864 {
1865         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1866
1867         /* NEW flag */
1868         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1869                 item->new_msgs++;
1870         }
1871
1872         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1873                 item->new_msgs--;
1874         }
1875
1876         /* UNREAD flag */
1877         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1878                 item->unread_msgs++;
1879                 if (procmsg_msg_has_marked_parent(msginfo))
1880                         item->unreadmarked_msgs++;
1881         }
1882
1883         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1884                 item->unread_msgs--;
1885                 if (procmsg_msg_has_marked_parent(msginfo))
1886                         item->unreadmarked_msgs--;
1887         }
1888         
1889         /* MARK flag */
1890         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1891                 procmsg_update_unread_children(msginfo, TRUE);
1892                 item->marked_msgs++;
1893         }
1894
1895         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1896                 procmsg_update_unread_children(msginfo, FALSE);
1897                 item->marked_msgs--;
1898         }
1899
1900         if (!(old_flags & MSG_REPLIED) && (new_flags & MSG_REPLIED)) {
1901                 item->replied_msgs++;
1902         }
1903
1904         if ((old_flags & MSG_REPLIED) && !(new_flags & MSG_REPLIED)) {
1905                 item->replied_msgs--;
1906         }
1907
1908         if (!(old_flags & MSG_FORWARDED) && (new_flags & MSG_FORWARDED)) {
1909                 item->forwarded_msgs++;
1910         }
1911
1912         if ((old_flags & MSG_FORWARDED) && !(new_flags & MSG_FORWARDED)) {
1913                 item->forwarded_msgs--;
1914         }
1915
1916         if (!(old_flags & MSG_LOCKED) && (new_flags & MSG_LOCKED)) {
1917                 item->locked_msgs++;
1918         }
1919
1920         if ((old_flags & MSG_LOCKED) && !(new_flags & MSG_LOCKED)) {
1921                 item->locked_msgs--;
1922         }
1923
1924         if ((old_flags & MSG_IGNORE_THREAD) && !(new_flags & MSG_IGNORE_THREAD)) {
1925                 item->ignored_msgs--;
1926         }
1927
1928         if (!(old_flags & MSG_IGNORE_THREAD) && (new_flags & MSG_IGNORE_THREAD)) {
1929                 item->ignored_msgs++;
1930         }
1931
1932         if ((old_flags & MSG_WATCH_THREAD) && !(new_flags & MSG_WATCH_THREAD)) {
1933                 item->watched_msgs--;
1934         }
1935
1936         if (!(old_flags & MSG_WATCH_THREAD) && (new_flags & MSG_WATCH_THREAD)) {
1937                 item->watched_msgs++;
1938         }
1939 }
1940
1941 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1942 {
1943         FolderItem *item;
1944         MsgInfoUpdate msginfo_update;
1945         MsgPermFlags perm_flags_new, perm_flags_old;
1946         MsgTmpFlags tmp_flags_old;
1947
1948         cm_return_if_fail(msginfo != NULL);
1949         item = msginfo->folder;
1950         cm_return_if_fail(item != NULL);
1951         
1952         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1953
1954         /* Perm Flags handling */
1955         perm_flags_old = msginfo->flags.perm_flags;
1956         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1957         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1958                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1959         }
1960         if ((perm_flags & MSG_WATCH_THREAD) || (perm_flags_old & MSG_WATCH_THREAD)) {
1961                 perm_flags_new &= ~(MSG_IGNORE_THREAD);
1962         }
1963
1964         if (perm_flags_old != perm_flags_new) {
1965                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1966
1967                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1968                 summary_update_unread(mainwindow_get_mainwindow()->summaryview, NULL);
1969         }
1970
1971         /* Tmp flags handling */
1972         tmp_flags_old = msginfo->flags.tmp_flags;
1973         msginfo->flags.tmp_flags |= tmp_flags;
1974
1975         /* update notification */
1976         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1977                 msginfo_update.msginfo = msginfo;
1978                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1979                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1980                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1981         }
1982 }
1983
1984 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1985 {
1986         FolderItem *item;
1987         MsgInfoUpdate msginfo_update;
1988         MsgPermFlags perm_flags_new, perm_flags_old;
1989         MsgTmpFlags tmp_flags_old;
1990
1991         cm_return_if_fail(msginfo != NULL);
1992         item = msginfo->folder;
1993         cm_return_if_fail(item != NULL);
1994         
1995         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1996
1997         /* Perm Flags handling */
1998         perm_flags_old = msginfo->flags.perm_flags;
1999         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
2000         
2001         if (perm_flags_old != perm_flags_new) {
2002                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
2003
2004                 update_folder_msg_counts(item, msginfo, perm_flags_old);
2005         }
2006
2007         /* Tmp flags hanlding */
2008         tmp_flags_old = msginfo->flags.tmp_flags;
2009         msginfo->flags.tmp_flags &= ~tmp_flags;
2010
2011         /* update notification */
2012         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
2013                 msginfo_update.msginfo = msginfo;
2014                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
2015                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2016                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
2017         }
2018 }
2019
2020 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
2021                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
2022                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
2023 {
2024         FolderItem *item;
2025         MsgInfoUpdate msginfo_update;
2026         MsgPermFlags perm_flags_new, perm_flags_old;
2027         MsgTmpFlags tmp_flags_old;
2028
2029         cm_return_if_fail(msginfo != NULL);
2030         item = msginfo->folder;
2031         cm_return_if_fail(item != NULL);
2032         
2033         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
2034
2035         /* Perm Flags handling */
2036         perm_flags_old = msginfo->flags.perm_flags;
2037         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
2038         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
2039                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
2040         }
2041         if ((add_perm_flags & MSG_WATCH_THREAD) || (perm_flags_old & MSG_WATCH_THREAD)) {
2042                 perm_flags_new &= ~(MSG_IGNORE_THREAD);
2043         }
2044
2045         if (perm_flags_old != perm_flags_new) {
2046                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
2047
2048                 update_folder_msg_counts(item, msginfo, perm_flags_old);
2049
2050         }
2051
2052         /* Tmp flags handling */
2053         tmp_flags_old = msginfo->flags.tmp_flags;
2054         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
2055         msginfo->flags.tmp_flags |= add_tmp_flags;
2056
2057         /* update notification */
2058         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
2059                 msginfo_update.msginfo = msginfo;
2060                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
2061                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2062                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
2063         }
2064 }
2065
2066 /*!
2067  *\brief        check for flags (e.g. mark) in prior msgs of current thread
2068  *
2069  *\param        info Current message
2070  *\param        perm_flags Flags to be checked
2071  *\param        parentmsgs Hash of prior msgs to avoid loops
2072  *
2073  *\return       gboolean TRUE if perm_flags are found
2074  */
2075 static gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
2076                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
2077 {
2078         MsgInfo *tmp;
2079
2080         cm_return_val_if_fail(info != NULL, FALSE);
2081
2082         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
2083                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
2084                                 info->inreplyto);
2085                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
2086                         procmsg_msginfo_free(&tmp);
2087                         return TRUE;
2088                 } else if (tmp != NULL) {
2089                         gboolean result;
2090
2091                         if (g_hash_table_lookup(parentmsgs, info)) {
2092                                 debug_print("loop detected: %d\n",
2093                                         info->msgnum);
2094                                 result = FALSE;
2095                         } else {
2096                                 g_hash_table_insert(parentmsgs, info, "1");
2097                                 result = procmsg_msg_has_flagged_parent_real(
2098                                     tmp, perm_flags, parentmsgs);
2099                         }
2100                         procmsg_msginfo_free(&tmp);
2101                         return result;
2102                 } else {
2103                         return FALSE;
2104                 }
2105         } else
2106                 return FALSE;
2107 }
2108
2109 /*!
2110  *\brief        Callback for cleaning up hash of parentmsgs
2111  */
2112 static gboolean parentmsgs_hash_remove(gpointer key,
2113                             gpointer value,
2114                             gpointer user_data)
2115 {
2116         return TRUE;
2117 }
2118
2119 /*!
2120  *\brief        Set up list of parentmsgs
2121  *              See procmsg_msg_has_flagged_parent_real()
2122  */
2123 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
2124 {
2125         gboolean result;
2126         static GHashTable *parentmsgs = NULL;
2127         
2128         if (parentmsgs == NULL)
2129                 parentmsgs = g_hash_table_new(NULL, NULL); 
2130
2131         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2132         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2133
2134         return result;
2135 }
2136
2137 /*!
2138  *\brief        Check if msgs prior in thread are marked
2139  *              See procmsg_msg_has_flagged_parent_real()
2140  */
2141 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2142 {
2143         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2144 }
2145
2146
2147 static GSList *procmsg_find_children_func(MsgInfo *info, 
2148                                    GSList *children, GSList *all)
2149 {
2150         GSList *cur;
2151
2152         cm_return_val_if_fail(info!=NULL, children);
2153         if (info->msgid == NULL)
2154                 return children;
2155
2156         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2157                 MsgInfo *tmp = (MsgInfo *)cur->data;
2158                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2159                         /* Check if message is already in the list */
2160                         if ((children == NULL) || 
2161                             (g_slist_index(children, tmp) == -1)) {
2162                                 children = g_slist_prepend(children,
2163                                                 procmsg_msginfo_new_ref(tmp));
2164                                 children = procmsg_find_children_func(tmp, 
2165                                                         children, 
2166                                                         all);
2167                         }
2168                 }
2169         }
2170         return children;
2171 }
2172
2173 static GSList *procmsg_find_children (MsgInfo *info)
2174 {
2175         GSList *children;
2176         GSList *all, *cur;
2177
2178         cm_return_val_if_fail(info!=NULL, NULL);
2179         all = folder_item_get_msg_list(info->folder);
2180         children = procmsg_find_children_func(info, NULL, all);
2181         if (children != NULL) {
2182                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2183                         /* this will not free the used pointers
2184                            created with procmsg_msginfo_new_ref */
2185                         procmsg_msginfo_free((MsgInfo **)&(cur->data));
2186                 }
2187         }
2188         g_slist_free(all);
2189
2190         return children;
2191 }
2192
2193 static void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2194 {
2195         GSList *children = procmsg_find_children(info);
2196         GSList *cur;
2197         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2198                 MsgInfo *tmp = (MsgInfo *)cur->data;
2199                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2200                         if(newly_marked) 
2201                                 info->folder->unreadmarked_msgs++;
2202                         else
2203                                 info->folder->unreadmarked_msgs--;
2204                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2205                 }
2206                 procmsg_msginfo_free(&tmp);
2207         }
2208         g_slist_free(children);
2209 }
2210
2211 /**
2212  * Set the destination folder for a copy or move operation
2213  *
2214  * \param msginfo The message which's destination folder is changed
2215  * \param to_folder The destination folder for the operation
2216  */
2217 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2218 {
2219         if(msginfo->to_folder != NULL) {
2220                 msginfo->to_folder->op_count--;
2221                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2222         }
2223         msginfo->to_folder = to_folder;
2224         if(to_folder != NULL) {
2225                 to_folder->op_count++;
2226                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2227         }
2228 }
2229
2230 /**
2231  * Apply filtering actions to the msginfo
2232  *
2233  * \param msginfo The MsgInfo describing the message that should be filtered
2234  * \return TRUE if the message was moved and MsgInfo is now invalid,
2235  *         FALSE otherwise
2236  */
2237 static gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2238 {
2239         MailFilteringData mail_filtering_data;
2240                         
2241         mail_filtering_data.msginfo = msginfo;                  
2242         mail_filtering_data.msglist = NULL;                     
2243         mail_filtering_data.filtered = NULL;                    
2244         mail_filtering_data.unfiltered = NULL;
2245         mail_filtering_data.account = ac_prefs; 
2246
2247         if (!ac_prefs || ac_prefs->filterhook_on_recv)
2248                 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
2249                 return TRUE;
2250
2251         /* filter if enabled in prefs or move to inbox if not */
2252         if((filtering_rules != NULL) &&
2253                 filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs,
2254                                 FILTERING_INCORPORATION, NULL)) {
2255                 return TRUE;
2256         }
2257                 
2258         return FALSE;
2259 }
2260
2261 void procmsg_msglist_filter(GSList *list, PrefsAccount *ac, 
2262                             GSList **filtered, GSList **unfiltered,
2263                             gboolean do_filter)
2264 {
2265         GSList *cur, *to_do = NULL;
2266         gint total = 0, curnum = 0;
2267         MailFilteringData mail_filtering_data;
2268                         
2269         cm_return_if_fail(filtered != NULL);
2270         cm_return_if_fail(unfiltered != NULL);
2271
2272         *filtered = NULL;
2273         *unfiltered = NULL;
2274         
2275         if (list == NULL)
2276                 return;
2277
2278         total = g_slist_length(list);
2279
2280         if (!do_filter) {
2281                 *filtered = NULL;
2282                 *unfiltered = g_slist_copy(list);
2283                 return;
2284         }
2285
2286         statusbar_print_all(_("Filtering messages...\n"));
2287
2288         mail_filtering_data.msginfo = NULL;                     
2289         mail_filtering_data.msglist = list;                     
2290         mail_filtering_data.filtered = NULL;                    
2291         mail_filtering_data.unfiltered = NULL;  
2292         mail_filtering_data.account = ac;       
2293                         
2294         if (!ac || ac->filterhook_on_recv)
2295         hooks_invoke(MAIL_LISTFILTERING_HOOKLIST, &mail_filtering_data);
2296         
2297         if (mail_filtering_data.filtered == NULL &&
2298             mail_filtering_data.unfiltered == NULL) {
2299                 /* nothing happened */
2300                 debug_print(MAIL_LISTFILTERING_HOOKLIST " did nothing. filtering whole list normally.\n");
2301                 to_do = list;
2302         } 
2303         if (mail_filtering_data.filtered != NULL) {
2304                 /* keep track of what's been filtered by the hooks */
2305                 debug_print(MAIL_LISTFILTERING_HOOKLIST " filtered some stuff. total %d filtered %d unfilt %d.\n",
2306                         g_slist_length(list),
2307                         g_slist_length(mail_filtering_data.filtered),
2308                         g_slist_length(mail_filtering_data.unfiltered));
2309
2310                 *filtered = g_slist_copy(mail_filtering_data.filtered);
2311         }
2312         if (mail_filtering_data.unfiltered != NULL) {
2313                 /* what the hooks didn't handle will go in filtered or 
2314                  * unfiltered in the next loop */
2315                 debug_print(MAIL_LISTFILTERING_HOOKLIST " left unfiltered stuff. total %d filtered %d unfilt %d.\n",
2316                         g_slist_length(list),
2317                         g_slist_length(mail_filtering_data.filtered),
2318                         g_slist_length(mail_filtering_data.unfiltered));
2319                 to_do = mail_filtering_data.unfiltered;
2320         } 
2321
2322         for (cur = to_do; cur; cur = cur->next) {
2323                 MsgInfo *info = (MsgInfo *)cur->data;
2324                 if (procmsg_msginfo_filter(info, ac))
2325                         *filtered = g_slist_prepend(*filtered, info);
2326                 else
2327                         *unfiltered = g_slist_prepend(*unfiltered, info);
2328                 statusbar_progress_all(curnum++, total, prefs_common.statusbar_update_step);
2329         }
2330
2331         g_slist_free(mail_filtering_data.filtered);
2332         g_slist_free(mail_filtering_data.unfiltered);
2333         
2334         *filtered = g_slist_reverse(*filtered);
2335         *unfiltered = g_slist_reverse(*unfiltered);
2336
2337         statusbar_progress_all(0,0,0);
2338         statusbar_pop_all();
2339 }
2340
2341 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2342 {
2343         MsgInfo *tmp_msginfo = NULL;
2344         MsgFlags flags = {0, 0};
2345         gchar *tmpfile = get_tmp_file();
2346         FILE *fp = claws_fopen(tmpfile, "wb");
2347         
2348         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2349             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2350                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2351                 if (fp) 
2352                         claws_fclose(fp);
2353                 g_free(tmpfile);
2354                 return NULL;
2355         }
2356         
2357         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2358                 claws_safe_fclose(fp);
2359                 fp = NULL;
2360                 tmp_msginfo = procheader_parse_file(
2361                         tmpfile, flags, 
2362                         TRUE, FALSE);
2363         }
2364         if (fp)
2365                 claws_safe_fclose(fp);
2366
2367         if (tmp_msginfo != NULL) {
2368                 if (src_msginfo)
2369                         tmp_msginfo->folder = src_msginfo->folder;
2370                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2371         } else {
2372                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2373         }
2374
2375         g_free(tmpfile);
2376
2377         return tmp_msginfo;
2378 }
2379
2380 static GSList *spam_learners = NULL;
2381
2382 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2383 {
2384         if (!g_slist_find(spam_learners, learn_func))
2385                 spam_learners = g_slist_append(spam_learners, learn_func);
2386         if (mainwindow_get_mainwindow()) {
2387                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2388                 summary_set_menu_sensitive(
2389                         mainwindow_get_mainwindow()->summaryview);
2390                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2391         }
2392 }
2393
2394 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2395 {
2396         spam_learners = g_slist_remove(spam_learners, learn_func);
2397         if (mainwindow_get_mainwindow()) {
2398                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2399                 summary_set_menu_sensitive(
2400                         mainwindow_get_mainwindow()->summaryview);
2401                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2402         }
2403 }
2404
2405 gboolean procmsg_spam_can_learn(void)
2406 {
2407         return g_slist_length(spam_learners) > 0;
2408 }
2409
2410 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2411 {
2412         GSList *cur = spam_learners;
2413         int ret = 0;
2414         for (; cur; cur = cur->next) {
2415                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2416                 ret |= func(info, list, spam);
2417         }
2418         return ret;
2419 }
2420
2421 static gchar *spam_folder_item = NULL;
2422 static FolderItem * (*procmsg_spam_get_folder_func)(MsgInfo *msginfo) = NULL;
2423 void procmsg_spam_set_folder (const char *item_identifier, FolderItem *(*spam_get_folder_func)(MsgInfo *info))
2424 {
2425         g_free(spam_folder_item);
2426         if (item_identifier)
2427                 spam_folder_item = g_strdup(item_identifier);
2428         else
2429                 spam_folder_item = NULL;
2430         if (spam_get_folder_func != NULL)
2431                 procmsg_spam_get_folder_func = spam_get_folder_func;
2432         else
2433                 procmsg_spam_get_folder_func = NULL;
2434 }
2435
2436 FolderItem *procmsg_spam_get_folder (MsgInfo *msginfo)
2437 {
2438         FolderItem *item = NULL;
2439         
2440         if (procmsg_spam_get_folder_func) 
2441                 item = procmsg_spam_get_folder_func(msginfo);
2442         if (item == NULL && spam_folder_item)
2443                 item = folder_find_item_from_identifier(spam_folder_item);
2444         if (item == NULL)
2445                 item = folder_get_default_trash();
2446         return item;
2447 }
2448
2449 static void item_has_queued_mails(FolderItem *item, gpointer data)
2450 {
2451         gboolean *result = (gboolean *)data;
2452         if (*result == TRUE)
2453                 return;
2454         if (folder_has_parent_of_type(item, F_QUEUE)) {
2455                 if (item->total_msgs == 0)
2456                         return;
2457                 else {
2458                         GSList *msglist = folder_item_get_msg_list(item);
2459                         GSList *cur;
2460                         for (cur = msglist; cur; cur = cur->next) {
2461                                 MsgInfo *msginfo = (MsgInfo *)cur->data;
2462                                 if (!MSG_IS_DELETED(msginfo->flags) &&
2463                                     !MSG_IS_LOCKED(msginfo->flags)) {
2464                                         *result = TRUE;
2465                                         break;
2466                                 }
2467                         }
2468                         procmsg_msg_list_free(msglist);
2469                 }
2470         }
2471 }
2472
2473 gboolean procmsg_have_queued_mails_fast (void)
2474 {
2475         gboolean result = FALSE;
2476         folder_func_to_all_folders(item_has_queued_mails, &result);
2477         return result;
2478 }
2479
2480 static void item_has_trashed_mails(FolderItem *item, gpointer data)
2481 {
2482         gboolean *result = (gboolean *)data;
2483         if (*result == TRUE)
2484                 return;
2485         if (folder_has_parent_of_type(item, F_TRASH) && item->total_msgs > 0)
2486                 *result = TRUE;
2487 }
2488
2489 gboolean procmsg_have_trashed_mails_fast (void)
2490 {
2491         gboolean result = FALSE;
2492         folder_func_to_all_folders(item_has_trashed_mails, &result);
2493         return result;
2494 }
2495
2496 gchar *procmsg_msginfo_get_tags_str(MsgInfo *msginfo)
2497 {
2498         GSList *cur = NULL;
2499         gchar *tags = NULL;
2500         
2501         if (!msginfo)
2502                 return NULL;
2503
2504         if (msginfo->tags == NULL)
2505                 return NULL;
2506         for (cur = msginfo->tags; cur; cur = cur->next) {
2507                 const gchar *tag = tags_get_tag(GPOINTER_TO_INT(cur->data));
2508                 if (!tag)
2509                         continue;
2510                 if (!tags)
2511                         tags = g_strdup(tag);
2512                 else {
2513                         int olen = strlen(tags);
2514                         int nlen = olen + strlen(tag) + 2 /* strlen(", ") */;
2515                         tags = g_realloc(tags, nlen+1);
2516                         if (!tags)
2517                                 return NULL;
2518                         strcpy(tags+olen, ", ");
2519                         strcpy(tags+olen+2, tag);
2520                         tags[nlen]='\0';
2521                 }
2522         }
2523         return tags;
2524 }
2525
2526 void procmsg_msginfo_update_tags(MsgInfo *msginfo, gboolean set, gint id)
2527 {
2528         GSList changed;
2529
2530         if (id == 0)
2531                 return;
2532
2533         if (!set) {
2534                 msginfo->tags = g_slist_remove(
2535                                         msginfo->tags,
2536                                         GINT_TO_POINTER(id));
2537                 changed.data = GINT_TO_POINTER(id);
2538                 changed.next = NULL;
2539                 folder_item_commit_tags(msginfo->folder, msginfo, NULL, &changed);
2540         } else {
2541                 if (!g_slist_find(msginfo->tags, GINT_TO_POINTER(id))) {
2542                         msginfo->tags = g_slist_append(
2543                                         msginfo->tags,
2544                                         GINT_TO_POINTER(id));
2545                 }
2546                 changed.data = GINT_TO_POINTER(id);
2547                 changed.next = NULL;
2548                 folder_item_commit_tags(msginfo->folder, msginfo, &changed, NULL);
2549         }
2550         
2551 }
2552
2553 void procmsg_msginfo_clear_tags(MsgInfo *msginfo)
2554 {
2555         GSList *unset = msginfo->tags;
2556         msginfo->tags = NULL;
2557         folder_item_commit_tags(msginfo->folder, msginfo, NULL, unset);
2558         g_slist_free(unset);
2559 }