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