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