* src/procmsg.c
[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, "rb")) == 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, "rb")) == 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, "ab")) == 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, "wb")) == 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, "rb")) == 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 (mimeinfo) *mimeinfo = mimeinfo_;
795         return fp;
796 }
797 #endif
798
799 gboolean procmsg_msg_exist(MsgInfo *msginfo)
800 {
801         gchar *path;
802         gboolean ret;
803
804         if (!msginfo) return FALSE;
805
806         path = folder_item_get_path(msginfo->folder);
807         change_dir(path);
808         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
809         g_free(path);
810
811         return ret;
812 }
813
814 void procmsg_empty_trash(void)
815 {
816         FolderItem *trash;
817         GList *cur;
818
819         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
820                 trash = FOLDER(cur->data)->trash;
821                 if (trash && trash->total > 0)
822                         folder_item_remove_all_msg(trash);
823         }
824 }
825
826 gint procmsg_send_queue(void)
827 {
828         FolderItem *queue;
829         gint i;
830         gint ret = 0;
831
832         queue = folder_get_default_queue();
833         g_return_val_if_fail(queue != NULL, -1);
834         folder_item_scan(queue);
835         if (queue->last_num < 0) return -1;
836         else if (queue->last_num == 0) return 0;
837
838         for (i = 1; i <= queue->last_num; i++) {
839                 gchar *file;
840
841                 file = folder_item_fetch_msg(queue, i);
842                 if (file) {
843                         if (procmsg_send_message_queue(file) < 0) {
844                                 g_warning(_("Sending queued message %d failed.\n"), i);
845                                 ret = -1;
846                         } else
847                                 folder_item_remove_msg(queue, i);
848                         g_free(file);
849                 }
850         }
851
852         return ret;
853 }
854
855 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
856 {
857         static const gchar *def_cmd = "lpr %s";
858         static guint id = 0;
859         gchar *prtmp;
860         FILE *tmpfp, *prfp;
861         gchar buf[1024];
862         gchar *p;
863
864         g_return_if_fail(msginfo);
865
866         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
867                 g_warning(_("Can't get text part\n"));
868                 return;
869         }
870
871         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
872                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
873
874         if ((prfp = fopen(prtmp, "wb")) == NULL) {
875                 FILE_OP_ERROR(prtmp, "fopen");
876                 g_free(prtmp);
877                 fclose(tmpfp);
878                 return;
879         }
880
881         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
882         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
883         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
884         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
885         if (msginfo->newsgroups)
886                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
887         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
888         fputc('\n', prfp);
889
890         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
891                 fputs(buf, prfp);
892
893         fclose(prfp);
894         fclose(tmpfp);
895
896         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
897             !strchr(p + 2, '%'))
898                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
899         else {
900                 if (cmdline)
901                         g_warning(_("Print command line is invalid: `%s'\n"),
902                                   cmdline);
903                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
904         }
905
906         g_free(prtmp);
907
908         g_strchomp(buf);
909         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
910         system(buf);
911 }
912
913 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
914 {
915         MsgInfo *newmsginfo;
916
917         if (msginfo == NULL) return NULL;
918
919         newmsginfo = g_new0(MsgInfo, 1);
920
921 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
922 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
923                         g_strdup(msginfo->mmb) : NULL
924
925         MEMBCOPY(msgnum);
926         MEMBCOPY(size);
927         MEMBCOPY(mtime);
928         MEMBCOPY(date_t);
929         MEMBCOPY(flags);
930
931         MEMBDUP(fromname);
932
933         MEMBDUP(date);
934         MEMBDUP(from);
935         MEMBDUP(to);
936         MEMBDUP(cc);
937         MEMBDUP(newsgroups);
938         MEMBDUP(subject);
939         MEMBDUP(msgid);
940         MEMBDUP(inreplyto);
941
942         MEMBCOPY(folder);
943         MEMBCOPY(to_folder);
944
945         MEMBDUP(xface);
946         MEMBDUP(dispositionnotificationto);
947         MEMBDUP(returnreceiptto);
948         MEMBDUP(references);
949
950         MEMBCOPY(score);
951         MEMBCOPY(threadscore);
952
953         return newmsginfo;
954 }
955
956 void procmsg_msginfo_free(MsgInfo *msginfo)
957 {
958         if (msginfo == NULL) return;
959
960         g_free(msginfo->fromspace);
961         g_free(msginfo->references);
962         g_free(msginfo->returnreceiptto);
963         g_free(msginfo->dispositionnotificationto);
964         g_free(msginfo->xface);
965
966         g_free(msginfo->fromname);
967
968         g_free(msginfo->date);
969         g_free(msginfo->from);
970         g_free(msginfo->to);
971         g_free(msginfo->cc);
972         g_free(msginfo->newsgroups);
973         g_free(msginfo->subject);
974         g_free(msginfo->msgid);
975         g_free(msginfo->inreplyto);
976
977         g_free(msginfo);
978 }
979
980 static gint procmsg_cmp_msgnum(gconstpointer a, gconstpointer b)
981 {
982         const MsgInfo *msginfo = a;
983         const guint msgnum = GPOINTER_TO_UINT(b);
984
985         if (!msginfo)
986                 return -1;
987
988         return msginfo->msgnum - msgnum;
989 }
990
991 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
992 {
993         const MsgInfo *msginfo1 = a;
994         const MsgInfo *msginfo2 = b;
995
996         if (!msginfo1)
997                 return -1;
998         if (!msginfo2)
999                 return -1;
1000
1001         return msginfo1->msgnum - msginfo2->msgnum;
1002 }
1003
1004 static gint procmsg_cmp_flag_msgnum(gconstpointer a, gconstpointer b)
1005 {
1006         const FlagInfo *finfo = a;
1007         const guint msgnum = GPOINTER_TO_UINT(b);
1008
1009         if (!finfo)
1010                 return -1;
1011
1012         return finfo->msgnum - msgnum;
1013 }
1014
1015 enum
1016 {
1017         Q_SENDER           = 0,
1018         Q_SMTPSERVER       = 1,
1019         Q_RECIPIENTS       = 2,
1020         Q_NEWSGROUPS       = 3,
1021         Q_MAIL_ACCOUNT_ID  = 4,
1022         Q_NEWS_ACCOUNT_ID  = 5,
1023         Q_SAVE_COPY_FOLDER = 6
1024 };
1025
1026 gint procmsg_send_message_queue(const gchar *file)
1027 {
1028         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1029                                        {"SSV:",  NULL, FALSE},
1030                                        {"R:",    NULL, FALSE},
1031                                        {"NG:",   NULL, FALSE},
1032                                        {"MAID:", NULL, FALSE},
1033                                        {"NAID:", NULL, FALSE},
1034                                        {"SCF:",  NULL, FALSE},
1035                                        {NULL,    NULL, FALSE}};
1036         FILE *fp;
1037         gint filepos;
1038         gint mailval = 0, newsval = 0;
1039         gchar *from = NULL;
1040         gchar *smtpserver = NULL;
1041         GSList *to_list = NULL;
1042         GSList *newsgroup_list = NULL;
1043         gchar *savecopyfolder = NULL;
1044         gchar buf[BUFFSIZE];
1045         gint hnum;
1046         PrefsAccount *mailac = NULL, *newsac = NULL;
1047         gchar *tmp = NULL;
1048         int local = 0;
1049
1050         g_return_val_if_fail(file != NULL, -1);
1051
1052         if ((fp = fopen(file, "rb")) == NULL) {
1053                 FILE_OP_ERROR(file, "fopen");
1054                 return -1;
1055         }
1056
1057         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1058                != -1) {
1059                 gchar *p = buf + strlen(qentry[hnum].name);
1060
1061                 switch (hnum) {
1062                 case Q_SENDER:
1063                         if (!from) from = g_strdup(p);
1064                         break;
1065                 case Q_SMTPSERVER:
1066                         if (!smtpserver) smtpserver = g_strdup(p);
1067                         break;
1068                 case Q_RECIPIENTS:
1069                         to_list = address_list_append(to_list, p);
1070                         break;
1071                 case Q_NEWSGROUPS:
1072                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1073                         break;
1074                 case Q_MAIL_ACCOUNT_ID:
1075                         mailac = account_find_from_id(atoi(p));
1076                         break;
1077                 case Q_NEWS_ACCOUNT_ID:
1078                         newsac = account_find_from_id(atoi(p));
1079                         break;
1080                 case Q_SAVE_COPY_FOLDER:
1081                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1082                         break;
1083                 }
1084         }
1085         filepos = ftell(fp);
1086
1087         if(newsgroup_list || prefs_common.savemsg) {
1088                 FILE *tmpfp;
1089
1090                 /* write to temporary file */
1091                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1092                             G_DIR_SEPARATOR, (gint)file);
1093                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1094                         FILE_OP_ERROR(tmp, "fopen");
1095                         newsval = -1;
1096                 }
1097                 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1098                         FILE_OP_ERROR(tmp, "chmod");
1099                         g_warning(_("can't change file mode\n"));
1100                 }
1101
1102                 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1103                         if (fputs(buf, tmpfp) == EOF) {
1104                                 FILE_OP_ERROR(tmp, "fputs");
1105                                 newsval = -1;
1106                         }
1107                 }
1108                 fclose(tmpfp);
1109         }
1110
1111         fseek(fp, filepos, SEEK_SET);
1112         if (to_list) {
1113                 debug_print(_("Sending message by mail\n"));
1114                 if(!from) {
1115                         g_warning(_("Queued message header is broken.\n"));
1116                         mailval = -1;
1117                 } else if (mailac && mailac->use_mail_command &&
1118                            mailac->mail_command && (* mailac->mail_command)) {
1119                         mailval = send_message_local(mailac->mail_command, fp);
1120                         local = 1;
1121                 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1122                         mailval = send_message_local(prefs_common.extsend_cmd, fp);
1123                         local = 1;
1124                 } else {
1125                         if (!mailac) {
1126                                 mailac = account_find_from_smtp_server(from, smtpserver);
1127                                 if (!mailac) {
1128                                         g_warning(_("Account not found. "
1129                                                     "Using current account...\n"));
1130                                         mailac = cur_account;
1131                                 }
1132                         }
1133
1134                         if (mailac)
1135                                 mailval = send_message_smtp(mailac, to_list, fp);
1136                         else {
1137                                 PrefsAccount tmp_ac;
1138
1139                                 g_warning(_("Account not found.\n"));
1140
1141                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1142                                 tmp_ac.address = from;
1143                                 tmp_ac.smtp_server = smtpserver;
1144                                 tmp_ac.smtpport = SMTP_PORT;
1145                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1146                         }
1147                 }
1148                 if (mailval < 0) {
1149                         if (!local)
1150                                 alertpanel_error(
1151                                         _("Error occurred while sending the message to `%s'."),
1152                                         mailac ? mailac->smtp_server : smtpserver);
1153                         else
1154                                 alertpanel_error(
1155                                         _("Error occurred while sending the message with command `%s'."),
1156                                         (mailac && mailac->use_mail_command && 
1157                                          mailac->mail_command && (*mailac->mail_command)) ? 
1158                                                 mailac->mail_command : prefs_common.extsend_cmd);
1159                 }
1160         }
1161
1162         if(newsgroup_list && (newsval == 0)) {
1163                 Folder *folder;
1164
1165                 debug_print(_("Sending message by news\n"));
1166
1167                 folder = FOLDER(newsac->folder);
1168
1169                 newsval = news_post(folder, tmp);
1170                 if (newsval < 0) {
1171                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1172                                  newsac->nntp_server);
1173                 }
1174         }
1175
1176         /* save message to outbox */
1177         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1178                 FolderItem *folder;
1179                 gchar *path;
1180                 gint num;
1181                 FILE *fp;
1182
1183                 debug_print(_("saving sent message...\n"));
1184
1185                 folder = folder_find_item_from_identifier(savecopyfolder);
1186                 if(!folder)
1187                         folder = folder_get_default_outbox();
1188                 path = folder_item_get_path(folder);
1189                 if (!is_dir_exist(path))
1190                         make_dir_hier(path);
1191
1192                 folder_item_scan(folder);
1193                 if ((num = folder_item_add_msg(folder, tmp, FALSE)) < 0) {
1194                         g_warning(_("can't save message\n"));
1195                 }
1196
1197                 if(num) {
1198                         if ((fp = procmsg_open_mark_file(path, TRUE)) == NULL)
1199                                 g_warning(_("can't open mark file\n"));
1200                         else {
1201                                 MsgInfo newmsginfo;
1202
1203                                 newmsginfo.msgnum = num;
1204                                 newmsginfo.flags.perm_flags = 0;
1205                                 newmsginfo.flags.tmp_flags = 0;
1206                                 procmsg_write_flags(&newmsginfo, fp);
1207                                 fclose(fp);
1208                         }
1209                 }
1210                 g_free(path);
1211         }
1212
1213         slist_free_strings(to_list);
1214         g_slist_free(to_list);
1215         slist_free_strings(newsgroup_list);
1216         g_slist_free(newsgroup_list);
1217         g_free(from);
1218         g_free(smtpserver);
1219         fclose(fp);
1220         if(tmp) {
1221                 unlink(tmp);
1222                 g_free(tmp);
1223         }
1224
1225         return (newsval != 0 ? newsval : mailval);
1226 }