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