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