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