0.8.11claws29
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 Hiroyuki Yamamoto
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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "defs.h"
21
22 #include <glib.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 #include "intl.h"
27 #include "main.h"
28 #include "utils.h"
29 #include "procmsg.h"
30 #include "procheader.h"
31 #include "send.h"
32 #include "procmime.h"
33 #include "statusbar.h"
34 #include "folder.h"
35 #include "prefs_common.h"
36 #include "account.h"
37 #if USE_GPGME
38 #  include "rfc2015.h"
39 #endif
40 #include "alertpanel.h"
41 #include "news.h"
42 #include "hooks.h"
43 #include "msgcache.h"
44
45 typedef struct _FlagInfo        FlagInfo;
46
47 struct _FlagInfo
48 {
49         guint    msgnum;
50         MsgFlags flags;
51 };
52
53 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
54 {
55         GHashTable *msg_table;
56
57         if (mlist == NULL) return NULL;
58
59         msg_table = g_hash_table_new(NULL, g_direct_equal);
60         procmsg_msg_hash_table_append(msg_table, mlist);
61
62         return msg_table;
63 }
64
65 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
66 {
67         GSList *cur;
68         MsgInfo *msginfo;
69
70         if (msg_table == NULL || mlist == NULL) return;
71
72         for (cur = mlist; cur != NULL; cur = cur->next) {
73                 msginfo = (MsgInfo *)cur->data;
74
75                 g_hash_table_insert(msg_table,
76                                     GUINT_TO_POINTER(msginfo->msgnum),
77                                     msginfo);
78         }
79 }
80
81 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
82 {
83         GHashTable *msg_table;
84         GSList *cur;
85         MsgInfo *msginfo;
86
87         if (mlist == NULL) return NULL;
88
89         msg_table = g_hash_table_new(NULL, g_direct_equal);
90
91         for (cur = mlist; cur != NULL; cur = cur->next) {
92                 msginfo = (MsgInfo *)cur->data;
93                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
94         }
95
96         return msg_table;
97 }
98
99 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
100 {
101         GSList *cur;
102         MsgInfo *msginfo;
103         gint last = 0;
104
105         for (cur = mlist; cur != NULL; cur = cur->next) {
106                 msginfo = (MsgInfo *)cur->data;
107                 if (msginfo && msginfo->msgnum > last)
108                         last = msginfo->msgnum;
109         }
110
111         return last;
112 }
113
114 void procmsg_msg_list_free(GSList *mlist)
115 {
116         GSList *cur;
117         MsgInfo *msginfo;
118
119         for (cur = mlist; cur != NULL; cur = cur->next) {
120                 msginfo = (MsgInfo *)cur->data;
121                 procmsg_msginfo_free(msginfo);
122         }
123         g_slist_free(mlist);
124 }
125
126 struct MarkSum {
127         gint *new;
128         gint *unread;
129         gint *total;
130         gint *min;
131         gint *max;
132         gint first;
133 };
134
135 static gboolean procmsg_ignore_node(GNode *node, gpointer data)
136 {
137         MsgInfo *msginfo = (MsgInfo *)node->data;
138         
139         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
140         procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
141
142         return FALSE;
143 }
144
145 /* return the reversed thread tree */
146 GNode *procmsg_get_thread_tree(GSList *mlist)
147 {
148         GNode *root, *parent, *node, *next;
149         GHashTable *msgid_table;
150         GHashTable *subject_table;
151         MsgInfo *msginfo;
152         const gchar *msgid;
153         const gchar *subject;
154         GNode *found_subject;
155
156         root = g_node_new(NULL);
157         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
158         subject_table = g_hash_table_new(g_str_hash, g_str_equal);
159
160         for (; mlist != NULL; mlist = mlist->next) {
161                 msginfo = (MsgInfo *)mlist->data;
162                 parent = root;
163
164                 if (msginfo->inreplyto) {
165                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
166                         if (parent == NULL) {
167                                 parent = root;
168                         } else {
169                                 if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
170                                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
171                                         procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
172                                 }
173                         }
174                 }
175                 node = g_node_insert_data_before
176                         (parent, parent == root ? parent->children : NULL,
177                          msginfo);
178                 if ((msgid = msginfo->msgid) &&
179                     g_hash_table_lookup(msgid_table, msgid) == NULL)
180                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
181
182                 if (prefs_common.thread_by_subject) {
183                         subject = msginfo->subject;
184                         found_subject = subject_table_lookup(subject_table,
185                                                              (gchar *) subject);
186                         if (found_subject == NULL)
187                                 subject_table_insert(subject_table, (gchar *) subject,
188                                                      node);
189                         else {
190                                 /* replace if msg in table is older than current one 
191                                  * can add here more stuff. */
192                                 if ( ((MsgInfo*)(found_subject->data))->date_t >
193                                      ((MsgInfo*)(node->data))->date_t )  {
194                                         subject_table_remove(subject_table, (gchar *) subject);
195                                         subject_table_insert(subject_table, (gchar *) subject, node);
196                                 }       
197                         }
198                 }
199         }
200
201         /* complete the unfinished threads */
202         for (node = root->children; node != NULL; ) {
203                 next = node->next;
204                 msginfo = (MsgInfo *)node->data;
205                 parent = NULL;
206                 if (msginfo->inreplyto) 
207                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
208                 if (parent && parent != node) {
209                         g_node_unlink(node);
210                         g_node_insert_before
211                                 (parent, parent->children, node);
212                         /* CLAWS: ignore thread */
213                         if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
214                                 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, procmsg_ignore_node, NULL);
215                         }
216                 }
217                 node = next;
218         }
219
220         /* CLAWS: now see if the first level (below root) still has some nodes that can be
221          * threaded by subject line. we need to handle this in a special way to prevent
222          * circular reference from a node that has already been threaded by IN-REPLY-TO
223          * but is also in the subject line hash table */
224         if (prefs_common.thread_by_subject) {
225                 for (node = root->children; node != NULL; ) {
226                         next = node->next;
227                         msginfo = (MsgInfo *) node->data;
228                         parent = NULL;
229                         if (subject_is_reply(msginfo->subject)) {
230                                 parent = subject_table_lookup(subject_table,
231                                                               msginfo->subject);
232                                 /* the node may already be threaded by IN-REPLY-TO,
233                                    so go up in the tree to find the parent node */
234                                 if (parent != NULL) {
235                                         if (g_node_is_ancestor(node, parent))
236                                                 parent = NULL;
237                                         if (parent == node)
238                                                 parent = NULL;
239                                 }
240
241                                 if (parent) {
242                                         g_node_unlink(node);
243                                         g_node_append(parent, node);
244                                         /* CLAWS: ignore thread */
245                                         if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
246                                                 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, procmsg_ignore_node, NULL);
247                                         }
248                                 }
249                         }                                       
250                         node = next;
251                 }       
252         }
253         
254         g_hash_table_destroy(subject_table);
255         g_hash_table_destroy(msgid_table);
256
257         return root;
258 }
259
260 void procmsg_move_messages(GSList *mlist)
261 {
262         GSList *cur, *movelist = NULL;
263         MsgInfo *msginfo;
264         FolderItem *dest = NULL;
265
266         if (!mlist) return;
267
268         folder_item_update_freeze();
269
270         for (cur = mlist; cur != NULL; cur = cur->next) {
271                 msginfo = (MsgInfo *)cur->data;
272                 if (!dest) {
273                         dest = msginfo->to_folder;
274                         movelist = g_slist_append(movelist, msginfo);
275                 } else if (dest == msginfo->to_folder) {
276                         movelist = g_slist_append(movelist, msginfo);
277                 } else {
278                         folder_item_move_msgs_with_dest(dest, movelist);
279                         g_slist_free(movelist);
280                         movelist = NULL;
281                         dest = msginfo->to_folder;
282                         movelist = g_slist_append(movelist, msginfo);
283                 }
284                 procmsg_msginfo_set_to_folder(msginfo, NULL);
285         }
286
287         if (movelist) {
288                 folder_item_move_msgs_with_dest(dest, movelist);
289                 g_slist_free(movelist);
290         }
291
292         folder_item_update_thaw();
293 }
294
295 void procmsg_copy_messages(GSList *mlist)
296 {
297         GSList *cur, *copylist = NULL;
298         MsgInfo *msginfo;
299         FolderItem *dest = NULL;
300
301         if (!mlist) return;
302
303         folder_item_update_freeze();
304
305         for (cur = mlist; cur != NULL; cur = cur->next) {
306                 msginfo = (MsgInfo *)cur->data;
307                 if (!dest) {
308                         dest = msginfo->to_folder;
309                         copylist = g_slist_append(copylist, msginfo);
310                 } else if (dest == msginfo->to_folder) {
311                         copylist = g_slist_append(copylist, msginfo);
312                 } else {
313                         folder_item_copy_msgs_with_dest(dest, copylist);
314                         g_slist_free(copylist);
315                         copylist = NULL;
316                         dest = msginfo->to_folder;
317                         copylist = g_slist_append(copylist, msginfo);
318                 }
319                 procmsg_msginfo_set_to_folder(msginfo, NULL);
320         }
321
322         if (copylist) {
323                 folder_item_copy_msgs_with_dest(dest, copylist);
324                 g_slist_free(copylist);
325         }
326
327         folder_item_update_thaw();
328 }
329
330 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
331 {
332         gchar *path, *file;
333
334         g_return_val_if_fail(msginfo != NULL, NULL);
335
336         if (msginfo->plaintext_file)
337                 file = g_strdup(msginfo->plaintext_file);
338         else {
339                 path = folder_item_get_path(msginfo->folder);
340                 file = g_strconcat(path, G_DIR_SEPARATOR_S,
341                                    itos(msginfo->msgnum), NULL);
342                 g_free(path);
343         }
344
345         return file;
346 }
347
348 gchar *procmsg_get_message_file(MsgInfo *msginfo)
349 {
350         gchar *filename = NULL;
351
352         g_return_val_if_fail(msginfo != NULL, NULL);
353
354         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
355         if (!filename)
356                 g_warning("can't fetch message %d\n", msginfo->msgnum);
357
358         return filename;
359 }
360
361 FILE *procmsg_open_message(MsgInfo *msginfo)
362 {
363         FILE *fp;
364         gchar *file;
365
366         g_return_val_if_fail(msginfo != NULL, NULL);
367
368         file = procmsg_get_message_file_path(msginfo);
369         g_return_val_if_fail(file != NULL, NULL);
370
371         if (!is_file_exist(file)) {
372                 g_free(file);
373                 file = procmsg_get_message_file(msginfo);
374                 g_return_val_if_fail(file != NULL, NULL);
375         }
376
377         if ((fp = fopen(file, "rb")) == NULL) {
378                 FILE_OP_ERROR(file, "fopen");
379                 g_free(file);
380                 return NULL;
381         }
382
383         g_free(file);
384
385         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
386                 gchar buf[BUFFSIZE];
387
388                 while (fgets(buf, sizeof(buf), fp) != NULL)
389                         if (buf[0] == '\r' || buf[0] == '\n') break;
390         }
391
392         return fp;
393 }
394
395 #if USE_GPGME
396 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
397 {
398         FILE *fp;
399         MimeInfo *mimeinfo_;
400
401         g_return_val_if_fail(msginfo != NULL, NULL);
402
403         if (mimeinfo) *mimeinfo = NULL;
404
405         if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
406
407         mimeinfo_ = procmime_scan_mime_header(fp);
408         if (!mimeinfo_) {
409                 fclose(fp);
410                 return NULL;
411         }
412
413         if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
414             rfc2015_is_encrypted(mimeinfo_)) {
415                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
416         }
417
418         if (MSG_IS_ENCRYPTED(msginfo->flags) &&
419             (!msginfo->plaintext_file || msginfo->decryption_failed)) {
420                 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
421                 if (msginfo->plaintext_file &&
422                     !msginfo->decryption_failed) {
423                         fclose(fp);
424                         procmime_mimeinfo_free_all(mimeinfo_);
425                         if ((fp = procmsg_open_message(msginfo)) == NULL)
426                                 return NULL;
427                         mimeinfo_ = procmime_scan_mime_header(fp);
428                         if (!mimeinfo_) {
429                                 fclose(fp);
430                                 return NULL;
431                         }
432                 }
433         }
434
435         if (mimeinfo) *mimeinfo = mimeinfo_;
436         return fp;
437 }
438 #endif
439
440 gboolean procmsg_msg_exist(MsgInfo *msginfo)
441 {
442         gchar *path;
443         gboolean ret;
444
445         if (!msginfo) return FALSE;
446
447         path = folder_item_get_path(msginfo->folder);
448         change_dir(path);
449         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
450         g_free(path);
451
452         return ret;
453 }
454
455 void procmsg_empty_trash(void)
456 {
457         FolderItem *trash;
458         GList *cur;
459
460         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
461                 trash = FOLDER(cur->data)->trash;
462                 if (trash && trash->total > 0)
463                         folder_item_remove_all_msg(trash);
464         }
465 }
466
467 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
468 {
469         gint ret = 0;
470         GSList *list, *elem;
471
472         if (!queue)
473                 queue = folder_get_default_queue();
474         g_return_val_if_fail(queue != NULL, -1);
475
476         folder_item_scan(queue);
477         list = folder_item_get_msg_list(queue);
478
479
480         for (elem = list; elem != NULL; elem = elem->next) {
481                 gchar *file;
482                 MsgInfo *msginfo;
483                 
484                 msginfo = (MsgInfo *)(elem->data);
485
486                 file = folder_item_fetch_msg(queue, msginfo->msgnum);
487                 if (file) {
488                         if (procmsg_send_message_queue(file) < 0) {
489                                 g_warning("Sending queued message %d failed.\n", msginfo->msgnum);
490                                 ret = -1;
491                         } else {
492                         /* CLAWS: 
493                          * We save in procmsg_send_message_queue because
494                          * we need the destination folder from the queue
495                          * header
496                                                 
497                                 if (save_msgs)
498                                         procmsg_save_to_outbox
499                                                 (queue->folder->outbox,
500                                                  file, TRUE);
501 */
502                                 folder_item_remove_msg(queue, msginfo->msgnum);
503                         }
504                         g_free(file);
505                 }
506                 procmsg_msginfo_free(msginfo);
507         }
508
509         return ret;
510 }
511
512 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
513 {
514         FILE *fp, *outfp;
515         gchar buf[BUFFSIZE];
516         
517         if ((fp = fopen(in, "rb")) == NULL) {
518                 FILE_OP_ERROR(in, "fopen");
519                 return -1;
520         }
521         if ((outfp = fopen(out, "wb")) == NULL) {
522                 FILE_OP_ERROR(out, "fopen");
523                 fclose(fp);
524                 return -1;
525         }
526         while (fgets(buf, sizeof(buf), fp) != NULL)
527                 if (buf[0] == '\r' || buf[0] == '\n') break;
528         while (fgets(buf, sizeof(buf), fp) != NULL)
529                 fputs(buf, outfp);
530         fclose(outfp);
531         fclose(fp);
532         return 0;
533
534 }
535 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
536                             gboolean is_queued)
537 {
538         gint num;
539         MsgInfo *msginfo;
540
541         debug_print("saving sent message...\n");
542
543         if (!outbox)
544                 outbox = folder_get_default_outbox();
545         g_return_val_if_fail(outbox != NULL, -1);
546
547         /* remove queueing headers */
548         if (is_queued) {
549                 gchar tmp[MAXPATHLEN + 1];
550
551                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
552                            get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
553                 
554                 if (procmsg_remove_special_headers(file, tmp) !=0)
555                         return -1;
556
557                 folder_item_scan(outbox);
558                 if ((num = folder_item_add_msg(outbox, tmp, TRUE)) < 0) {
559                         g_warning("can't save message\n");
560                         unlink(tmp);
561                         return -1;
562                 }
563         } else {
564                 folder_item_scan(outbox);
565                 if ((num = folder_item_add_msg(outbox, file, FALSE)) < 0) {
566                         g_warning("can't save message\n");
567                         return -1;
568                 }
569                 return -1;
570         }
571         msginfo = folder_item_get_msginfo(outbox, num);
572         if (msginfo != NULL) {
573             procmsg_msginfo_unset_flags(msginfo, ~0, 0);
574             procmsg_msginfo_free(msginfo);
575         }
576         folder_item_update(outbox, TRUE);
577
578         return 0;
579 }
580
581 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
582 {
583         static const gchar *def_cmd = "lpr %s";
584         static guint id = 0;
585         gchar *prtmp;
586         FILE *tmpfp, *prfp;
587         gchar buf[1024];
588         gchar *p;
589
590         g_return_if_fail(msginfo);
591
592         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
593                 g_warning("Can't get text part\n");
594                 return;
595         }
596
597         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
598                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
599
600         if ((prfp = fopen(prtmp, "wb")) == NULL) {
601                 FILE_OP_ERROR(prtmp, "fopen");
602                 g_free(prtmp);
603                 fclose(tmpfp);
604                 return;
605         }
606
607         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
608         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
609         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
610         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
611         if (msginfo->newsgroups)
612                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
613         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
614         fputc('\n', prfp);
615
616         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
617                 fputs(buf, prfp);
618
619         fclose(prfp);
620         fclose(tmpfp);
621
622         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
623             !strchr(p + 2, '%'))
624                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
625         else {
626                 if (cmdline)
627                         g_warning("Print command line is invalid: `%s'\n",
628                                   cmdline);
629                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
630         }
631
632         g_free(prtmp);
633
634         g_strchomp(buf);
635         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
636         system(buf);
637 }
638
639 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
640 {
641         msginfo->refcnt++;
642         
643         return msginfo;
644 }
645
646 MsgInfo *procmsg_msginfo_new()
647 {
648         MsgInfo *newmsginfo;
649
650         newmsginfo = g_new0(MsgInfo, 1);
651         newmsginfo->refcnt = 1;
652         
653         return newmsginfo;
654 }
655
656 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
657 {
658         MsgInfo *newmsginfo;
659
660         if (msginfo == NULL) return NULL;
661
662         newmsginfo = g_new0(MsgInfo, 1);
663
664         newmsginfo->refcnt = 1;
665
666 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
667 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
668                         g_strdup(msginfo->mmb) : NULL
669
670         MEMBCOPY(msgnum);
671         MEMBCOPY(size);
672         MEMBCOPY(mtime);
673         MEMBCOPY(date_t);
674         MEMBCOPY(flags);
675
676         MEMBDUP(fromname);
677
678         MEMBDUP(date);
679         MEMBDUP(from);
680         MEMBDUP(to);
681         MEMBDUP(cc);
682         MEMBDUP(newsgroups);
683         MEMBDUP(subject);
684         MEMBDUP(msgid);
685         MEMBDUP(inreplyto);
686         MEMBDUP(xref);
687
688         MEMBCOPY(folder);
689         MEMBCOPY(to_folder);
690
691         MEMBDUP(xface);
692         MEMBDUP(dispositionnotificationto);
693         MEMBDUP(returnreceiptto);
694         MEMBDUP(references);
695
696         MEMBCOPY(score);
697         MEMBCOPY(threadscore);
698
699         return newmsginfo;
700 }
701
702 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
703 {
704         MsgInfo *full_msginfo;
705         gchar *file;
706
707         if (msginfo == NULL) return NULL;
708
709         file = procmsg_get_message_file(msginfo);
710         if (!file) {
711                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
712                 return NULL;
713         }
714
715         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
716         g_free(file);
717         if (!full_msginfo) return NULL;
718
719         full_msginfo->msgnum = msginfo->msgnum;
720         full_msginfo->size = msginfo->size;
721         full_msginfo->mtime = msginfo->mtime;
722         full_msginfo->folder = msginfo->folder;
723 #if USE_GPGME
724         full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
725         full_msginfo->decryption_failed = msginfo->decryption_failed;
726 #endif
727         procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
728
729         return full_msginfo;
730 }
731
732 void procmsg_msginfo_free(MsgInfo *msginfo)
733 {
734         if (msginfo == NULL) return;
735
736         msginfo->refcnt--;
737         if (msginfo->refcnt > 0)
738                 return;
739
740         debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
741
742         if (msginfo->to_folder) {
743                 msginfo->to_folder->op_count--;
744                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
745         }
746
747         g_free(msginfo->fromspace);
748         g_free(msginfo->references);
749         g_free(msginfo->returnreceiptto);
750         g_free(msginfo->dispositionnotificationto);
751         g_free(msginfo->xface);
752
753         g_free(msginfo->fromname);
754
755         g_free(msginfo->date);
756         g_free(msginfo->from);
757         g_free(msginfo->to);
758         g_free(msginfo->cc);
759         g_free(msginfo->newsgroups);
760         g_free(msginfo->subject);
761         g_free(msginfo->msgid);
762         g_free(msginfo->inreplyto);
763         g_free(msginfo->xref);
764
765         g_free(msginfo);
766 }
767
768 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
769 {
770         guint memusage = 0;
771         
772         memusage += sizeof(MsgInfo);
773         if (msginfo->fromname)
774                 memusage += strlen(msginfo->fromname);
775         if (msginfo->date)
776                 memusage += strlen(msginfo->date);
777         if (msginfo->from)
778                 memusage += strlen(msginfo->from);
779         if (msginfo->to)
780                 memusage += strlen(msginfo->to);
781         if (msginfo->cc)
782                 memusage += strlen(msginfo->cc);
783         if (msginfo->newsgroups)
784                 memusage += strlen(msginfo->newsgroups);
785         if (msginfo->subject)
786                 memusage += strlen(msginfo->subject);
787         if (msginfo->msgid)
788                 memusage += strlen(msginfo->msgid);
789         if (msginfo->inreplyto)
790                 memusage += strlen(msginfo->inreplyto);
791         if (msginfo->xface)
792                 memusage += strlen(msginfo->xface);
793         if (msginfo->dispositionnotificationto)
794                 memusage += strlen(msginfo->dispositionnotificationto);
795         if (msginfo->returnreceiptto)
796                 memusage += strlen(msginfo->returnreceiptto);
797         if (msginfo->references)
798                 memusage += strlen(msginfo->references);
799         if (msginfo->fromspace)
800                 memusage += strlen(msginfo->fromspace);
801
802         return memusage;
803 }
804
805 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
806 {
807         const MsgInfo *msginfo1 = a;
808         const MsgInfo *msginfo2 = b;
809
810         if (!msginfo1)
811                 return -1;
812         if (!msginfo2)
813                 return -1;
814
815         return msginfo1->msgnum - msginfo2->msgnum;
816 }
817
818 enum
819 {
820         Q_SENDER           = 0,
821         Q_SMTPSERVER       = 1,
822         Q_RECIPIENTS       = 2,
823         Q_NEWSGROUPS       = 3,
824         Q_MAIL_ACCOUNT_ID  = 4,
825         Q_NEWS_ACCOUNT_ID  = 5,
826         Q_SAVE_COPY_FOLDER = 6,
827         Q_REPLY_MESSAGE_ID = 7,
828         Q_FWD_MESSAGE_ID   = 8
829 };
830
831 gint procmsg_send_message_queue(const gchar *file)
832 {
833         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
834                                        {"SSV:",  NULL, FALSE},
835                                        {"R:",    NULL, FALSE},
836                                        {"NG:",   NULL, FALSE},
837                                        {"MAID:", NULL, FALSE},
838                                        {"NAID:", NULL, FALSE},
839                                        {"SCF:",  NULL, FALSE},
840                                        {"RMID:", NULL, FALSE},
841                                        {"FMID:", NULL, FALSE},
842                                        {NULL,    NULL, FALSE}};
843         FILE *fp;
844         gint filepos;
845         gint mailval = 0, newsval = 0;
846         gchar *from = NULL;
847         gchar *smtpserver = NULL;
848         GSList *to_list = NULL;
849         GSList *newsgroup_list = NULL;
850         gchar *savecopyfolder = NULL;
851         gchar *replymessageid = NULL;
852         gchar *fwdmessageid = NULL;
853         gchar buf[BUFFSIZE];
854         gint hnum;
855         PrefsAccount *mailac = NULL, *newsac = NULL;
856         int local = 0;
857
858         g_return_val_if_fail(file != NULL, -1);
859
860         if ((fp = fopen(file, "rb")) == NULL) {
861                 FILE_OP_ERROR(file, "fopen");
862                 return -1;
863         }
864
865         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
866                != -1) {
867                 gchar *p = buf + strlen(qentry[hnum].name);
868
869                 switch (hnum) {
870                 case Q_SENDER:
871                         if (!from) from = g_strdup(p);
872                         break;
873                 case Q_SMTPSERVER:
874                         if (!smtpserver) smtpserver = g_strdup(p);
875                         break;
876                 case Q_RECIPIENTS:
877                         to_list = address_list_append(to_list, p);
878                         break;
879                 case Q_NEWSGROUPS:
880                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
881                         break;
882                 case Q_MAIL_ACCOUNT_ID:
883                         mailac = account_find_from_id(atoi(p));
884                         break;
885                 case Q_NEWS_ACCOUNT_ID:
886                         newsac = account_find_from_id(atoi(p));
887                         break;
888                 case Q_SAVE_COPY_FOLDER:
889                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
890                         break;
891                 case Q_REPLY_MESSAGE_ID:
892                         if (!replymessageid) replymessageid = g_strdup(p);
893                         break;
894                 case Q_FWD_MESSAGE_ID:
895                         if (!fwdmessageid) fwdmessageid = g_strdup(p);
896                         break;
897                 }
898         }
899         filepos = ftell(fp);
900
901         if (to_list) {
902                 debug_print("Sending message by mail\n");
903                 if (!from) {
904                         g_warning("Queued message header is broken.\n");
905                         mailval = -1;
906                 } else if (mailac && mailac->use_mail_command &&
907                            mailac->mail_command && (* mailac->mail_command)) {
908                         mailval = send_message_local(mailac->mail_command, fp);
909                         local = 1;
910                 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
911                         mailval = send_message_local(prefs_common.extsend_cmd, fp);
912                         local = 1;
913                 } else {
914                         if (!mailac) {
915                                 mailac = account_find_from_smtp_server(from, smtpserver);
916                                 if (!mailac) {
917                                         g_warning("Account not found. "
918                                                     "Using current account...\n");
919                                         mailac = cur_account;
920                                 }
921                         }
922
923                         if (mailac)
924                                 mailval = send_message_smtp(mailac, to_list, fp);
925                         else {
926                                 PrefsAccount tmp_ac;
927
928                                 g_warning("Account not found.\n");
929
930                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
931                                 tmp_ac.address = from;
932                                 tmp_ac.smtp_server = smtpserver;
933                                 tmp_ac.smtpport = SMTP_PORT;
934                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
935                         }
936                 }
937                 if (mailval < 0) {
938                         if (!local)
939                                 alertpanel_error_log(
940                                         _("Error occurred while sending the message to `%s'."),
941                                         mailac ? mailac->smtp_server : smtpserver);
942                         else
943                                 alertpanel_error_log(
944                                         _("Error occurred while sending the message with command `%s'."),
945                                         (mailac && mailac->use_mail_command && 
946                                          mailac->mail_command && (*mailac->mail_command)) ? 
947                                                 mailac->mail_command : prefs_common.extsend_cmd);
948                 }
949         }
950
951         fseek(fp, filepos, SEEK_SET);
952         if (newsgroup_list && (newsval == 0)) {
953                 Folder *folder;
954                 gchar *tmp = NULL;
955                 FILE *tmpfp;
956
957                 /* write to temporary file */
958                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
959                             G_DIR_SEPARATOR, (gint)file);
960                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
961                         FILE_OP_ERROR(tmp, "fopen");
962                         newsval = -1;
963                         alertpanel_error(_("Could not create temporary file for news sending."));
964                 } else {
965                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
966                                 FILE_OP_ERROR(tmp, "chmod");
967                                 g_warning("can't change file mode\n");
968                         }
969
970                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
971                                 if (fputs(buf, tmpfp) == EOF) {
972                                         FILE_OP_ERROR(tmp, "fputs");
973                                         newsval = -1;
974                                         alertpanel_error(_("Error when writing temporary file for news sending."));
975                                 }
976                         }
977                         fclose(tmpfp);
978
979                         if (newsval == 0) {
980                                 debug_print("Sending message by news\n");
981
982                                 folder = FOLDER(newsac->folder);
983
984                                 newsval = news_post(folder, tmp);
985                                 if (newsval < 0) {
986                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
987                                                  newsac->nntp_server);
988                                 }
989                         }
990                         unlink(tmp);
991                 }
992                 g_free(tmp);
993         }
994
995         slist_free_strings(to_list);
996         g_slist_free(to_list);
997         slist_free_strings(newsgroup_list);
998         g_slist_free(newsgroup_list);
999         g_free(from);
1000         g_free(smtpserver);
1001         fclose(fp);
1002
1003         /* save message to outbox */
1004         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1005                 FolderItem *outbox;
1006
1007                 debug_print("saving sent message...\n");
1008
1009                 outbox = folder_find_item_from_identifier(savecopyfolder);
1010                 if (!outbox)
1011                         outbox = folder_get_default_outbox();
1012
1013                 procmsg_save_to_outbox(outbox, file, TRUE);
1014         }
1015
1016         if (replymessageid != NULL || fwdmessageid != NULL) {
1017                 gchar **tokens;
1018                 FolderItem *item;
1019                 
1020                 if (replymessageid != NULL)
1021                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1022                 else
1023                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1024                 item = folder_find_item_from_identifier(tokens[0]);
1025                 if (item != NULL) {
1026                         MsgInfo *msginfo;
1027                         
1028                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1029                         if ((msginfo != NULL) && (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1030                                 procmsg_msginfo_free(msginfo);
1031                                 msginfo = NULL;
1032                         }
1033                         
1034                         if (msginfo == NULL) {
1035                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1036                         }
1037                         
1038                         if (msginfo != NULL) {
1039                                 if (replymessageid != NULL) {
1040                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1041                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1042                                 } 
1043                                 else {
1044                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1045                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1046                                 }
1047                                 procmsg_msginfo_free(msginfo);
1048                         }
1049                 }
1050                 g_strfreev(tokens);
1051         }
1052
1053         g_free(savecopyfolder);
1054         g_free(replymessageid);
1055         g_free(fwdmessageid);
1056         
1057         return (newsval != 0 ? newsval : mailval);
1058 }
1059
1060 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old)
1061 {
1062         MsgPermFlags new = msginfo->flags.perm_flags;
1063
1064         /* NEW flag */
1065         if (!(old & MSG_NEW) && (new & MSG_NEW)) {
1066                 item->new++;
1067         }
1068
1069         if ((old & MSG_NEW) && !(new & MSG_NEW)) {
1070                 item->new--;
1071         }
1072
1073         /* UNREAD flag */
1074         if (!(old & MSG_UNREAD) && (new & MSG_UNREAD)) {
1075                 item->unread++;
1076                 if (procmsg_msg_has_marked_parent(msginfo))
1077                         item->unreadmarked++;
1078         }
1079
1080         if ((old & MSG_UNREAD) && !(new & MSG_UNREAD)) {
1081                 item->unread--;
1082                 if (procmsg_msg_has_marked_parent(msginfo))
1083                         item->unreadmarked--;
1084         }
1085         
1086         /* MARK flag */
1087         if (!(old & MSG_MARKED) && (new & MSG_MARKED)) {
1088                 procmsg_update_unread_children(msginfo, TRUE);
1089         }
1090
1091         if ((old & MSG_MARKED) && !(new & MSG_MARKED)) {
1092                 procmsg_update_unread_children(msginfo, FALSE);
1093         }
1094 }
1095
1096 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1097 {
1098         FolderItem *item;
1099         MsgInfoUpdate msginfo_update;
1100         MsgPermFlags perm_flags_new, perm_flags_old;
1101
1102         g_return_if_fail(msginfo != NULL);
1103         item = msginfo->folder;
1104         g_return_if_fail(item != NULL);
1105         
1106         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1107
1108         /* Perm Flags handling */
1109         perm_flags_old = msginfo->flags.perm_flags;
1110         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1111         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1112                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1113         }
1114
1115         if (perm_flags_old != perm_flags_new) {
1116                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1117
1118                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1119
1120                 msginfo_update.msginfo = msginfo;
1121                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1122                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1123         }
1124
1125         /* Tmp flags hanlding */
1126         msginfo->flags.tmp_flags |= tmp_flags;
1127 }
1128
1129 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1130 {
1131         FolderItem *item;
1132         MsgInfoUpdate msginfo_update;
1133         MsgPermFlags perm_flags_new, perm_flags_old;
1134
1135         g_return_if_fail(msginfo != NULL);
1136         item = msginfo->folder;
1137         g_return_if_fail(item != NULL);
1138         
1139         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1140
1141         /* Perm Flags handling */
1142         perm_flags_old = msginfo->flags.perm_flags;
1143         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1144         
1145         if (perm_flags_old != perm_flags_new) {
1146                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1147
1148                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1149
1150                 msginfo_update.msginfo = msginfo;
1151                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1152                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1153         }
1154
1155         /* Tmp flags hanlding */
1156         msginfo->flags.tmp_flags &= ~tmp_flags;
1157 }
1158
1159 /*!
1160  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1161  *
1162  *\param        info Current message
1163  *\param        perm_flags Flags to be checked
1164  *\param        parentmsgs Hash of prior msgs to avoid loops
1165  *
1166  *\return       gboolean TRUE if perm_flags are found
1167  */
1168 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1169                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1170 {
1171         MsgInfo *tmp;
1172
1173         g_return_val_if_fail(info != NULL, FALSE);
1174
1175         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1176                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1177                                 info->inreplyto);
1178                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1179                         procmsg_msginfo_free(tmp);
1180                         return TRUE;
1181                 } else if (tmp != NULL) {
1182                         gboolean result;
1183
1184                         if (g_hash_table_lookup(parentmsgs, info)) {
1185                                 debug_print("loop detected: %s%c%d\n",
1186                                         folder_item_get_path(info->folder),
1187                                         G_DIR_SEPARATOR, info->msgnum);
1188                                 result = FALSE;
1189                         } else {
1190                                 g_hash_table_insert(parentmsgs, info, "1");
1191                                 result = procmsg_msg_has_flagged_parent_real(
1192                                     tmp, perm_flags, parentmsgs);
1193                         }
1194                         procmsg_msginfo_free(tmp);
1195                         return result;
1196                 } else {
1197                         return FALSE;
1198                 }
1199         } else
1200                 return FALSE;
1201 }
1202
1203 /*!
1204  *\brief        Callback for cleaning up hash of parentmsgs
1205  */
1206 gboolean parentmsgs_hash_remove(gpointer key,
1207                             gpointer value,
1208                             gpointer user_data)
1209 {
1210         return TRUE;
1211 }
1212
1213 /*!
1214  *\brief        Set up list of parentmsgs
1215  *              See procmsg_msg_has_flagged_parent_real()
1216  */
1217 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1218 {
1219         gboolean result;
1220         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1221
1222         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1223         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1224         g_hash_table_destroy(parentmsgs);
1225         return result;
1226 }
1227
1228 /*!
1229  *\brief        Check if msgs prior in thread are marked
1230  *              See procmsg_msg_has_flagged_parent_real()
1231  */
1232 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1233 {
1234         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1235 }
1236
1237
1238 GSList *procmsg_find_children_func(MsgInfo *info, 
1239                                    GSList *children, GSList *all)
1240 {
1241         GSList *cur;
1242
1243         g_return_val_if_fail(info!=NULL, children);
1244         if (info->msgid == NULL)
1245                 return children;
1246
1247         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1248                 MsgInfo *tmp = (MsgInfo *)cur->data;
1249                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1250                         /* Check if message is already in the list */
1251                         if ((children == NULL) || 
1252                             (g_slist_index(children, tmp) == -1)) {
1253                                 children = g_slist_prepend(children,
1254                                                 procmsg_msginfo_new_ref(tmp));
1255                                 children = procmsg_find_children_func(tmp, 
1256                                                         children, 
1257                                                         all);
1258                         }
1259                 }
1260         }
1261         return children;
1262 }
1263
1264 GSList *procmsg_find_children (MsgInfo *info)
1265 {
1266         GSList *children;
1267         GSList *all, *cur;
1268
1269         g_return_val_if_fail(info!=NULL, NULL);
1270         all = folder_item_get_msg_list(info->folder);
1271         children = procmsg_find_children_func(info, NULL, all);
1272         if (children != NULL) {
1273                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1274                         /* this will not free the used pointers
1275                            created with procmsg_msginfo_new_ref */
1276                         procmsg_msginfo_free((MsgInfo *)cur->data);
1277                 }
1278         }
1279         g_slist_free(all);
1280
1281         return children;
1282 }
1283
1284 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1285 {
1286         GSList *children = procmsg_find_children(info);
1287         GSList *cur;
1288         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1289                 MsgInfo *tmp = (MsgInfo *)cur->data;
1290                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1291                         if(newly_marked) 
1292                                 info->folder->unreadmarked++;
1293                         else
1294                                 info->folder->unreadmarked--;
1295                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1296                 }
1297                 procmsg_msginfo_free(tmp);
1298         }
1299         g_slist_free(children);
1300 }
1301
1302 /**
1303  * Set the destination folder for a copy or move operation
1304  *
1305  * \param msginfo The message which's destination folder is changed
1306  * \param to_folder The destination folder for the operation
1307  */
1308 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1309 {
1310         if(msginfo->to_folder != NULL) {
1311                 msginfo->to_folder->op_count--;
1312                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1313         }
1314         msginfo->to_folder = to_folder;
1315         if(to_folder != NULL) {
1316                 to_folder->op_count++;
1317                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1318         }
1319 }