From 0b3cbf57875fd692e4ba0b336fefa4bee1ed00dc Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Mon, 26 Feb 2018 11:23:00 +0100 Subject: Initial commit for sylpheed 3.7.0 --- libsylph/procmsg.c | 2446 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2446 insertions(+) create mode 100644 libsylph/procmsg.c (limited to 'libsylph/procmsg.c') diff --git a/libsylph/procmsg.c b/libsylph/procmsg.c new file mode 100644 index 0000000..28d16f3 --- /dev/null +++ b/libsylph/procmsg.c @@ -0,0 +1,2446 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2013 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "utils.h" +#include "procmsg.h" +#include "procheader.h" +#include "account.h" +#include "procmime.h" +#include "prefs_common.h" +#include "folder.h" +#include "codeconv.h" + +typedef struct _MsgFlagInfo { + guint msgnum; + MsgFlags flags; +} MsgFlagInfo; + +static GSList *procmsg_read_cache_queue (FolderItem *item, + gboolean scan_file); + +static void mark_sum_func (gpointer key, + gpointer value, + gpointer data); + +static GHashTable *procmsg_read_mark_file (FolderItem *item); +static void procmsg_write_mark_file (FolderItem *item, + GHashTable *mark_table); + +static GMappedFile *procmsg_open_cache_file_mmap(FolderItem *item, + DataOpenMode mode); + +static gint procmsg_cmp_by_mark (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_unread (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_mime (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_label (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_number (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_size (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_date (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_from (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_to (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_subject (gconstpointer a, + gconstpointer b); + + +GHashTable *procmsg_msg_hash_table_create(GSList *mlist) +{ + GHashTable *msg_table; + + if (mlist == NULL) return NULL; + + msg_table = g_hash_table_new(NULL, g_direct_equal); + procmsg_msg_hash_table_append(msg_table, mlist); + + return msg_table; +} + +void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + + if (msg_table == NULL || mlist == NULL) return; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + g_hash_table_insert(msg_table, + GUINT_TO_POINTER(msginfo->msgnum), + msginfo); + } +} + +GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist) +{ + GHashTable *msg_table; + GSList *cur; + MsgInfo *msginfo; + + if (mlist == NULL) return NULL; + + msg_table = g_hash_table_new(NULL, g_direct_equal); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + g_hash_table_insert(msg_table, msginfo->to_folder, msginfo); + } + + return msg_table; +} + +gint procmsg_read_cache_data_str(FILE *fp, gchar **str) +{ + gchar buf[BUFFSIZE]; + guint32 len; + gchar *tmp = NULL; + + if (fread(&len, sizeof(len), 1, fp) != 1) + return -1; + + if (len > G_MAXINT) + return -1; + + while (len > 0) { + size_t size = MIN(len, BUFFSIZE - 1); + + if (fread(buf, size, 1, fp) != 1) { + if (tmp) + g_free(tmp); + *str = NULL; + return -1; + } + + buf[size] = '\0'; + if (tmp) { + *str = g_strconcat(tmp, buf, NULL); + g_free(tmp); + tmp = *str; + } else + tmp = *str = g_strdup(buf); + + len -= size; + } + + return 0; +} + +static gint procmsg_read_cache_data_str_mem(const gchar **p, const gchar *endp, gchar **str) +{ + guint32 len; + + if (endp - *p < sizeof(len)) + return -1; + + memcpy(&len, *p, sizeof(len)); + *p += sizeof(len); + if (len > G_MAXINT || len > endp - *p) + return -1; + + if (len > 0) { + *str = g_strndup(*p, len); + *p += len; + } + + return 0; +} + +#define READ_CACHE_DATA(data) \ +{ \ + if (procmsg_read_cache_data_str_mem(&p, endp, &data) < 0) { \ + g_warning("Cache data is corrupted\n"); \ + procmsg_msginfo_free(msginfo); \ + procmsg_msg_list_free(mlist); \ + g_mapped_file_free(mapfile); \ + return NULL; \ + } \ +} + +#define READ_CACHE_DATA_INT(n) \ +{ \ + if (endp - p < sizeof(guint32)) { \ + g_warning("Cache data is corrupted\n"); \ + procmsg_msginfo_free(msginfo); \ + procmsg_msg_list_free(mlist); \ + g_mapped_file_free(mapfile); \ + return NULL; \ + } else { \ + guint32 idata; \ + memcpy(&idata, p, sizeof(idata)); \ + n = idata; \ + p += sizeof(guint32); \ + } \ +} + +GSList *procmsg_read_cache(FolderItem *item, gboolean scan_file) +{ + GSList *mlist = NULL; + GSList *last = NULL; + GMappedFile *mapfile; + const gchar *filep; + gsize file_len; + const gchar *p, *endp; + MsgInfo *msginfo; + MsgFlags default_flags; + guint32 num; + guint refnum; + FolderType type; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + type = FOLDER_TYPE(item->folder); + + default_flags.perm_flags = MSG_NEW|MSG_UNREAD; + default_flags.tmp_flags = 0; + if (type == F_MH || type == F_IMAP) { + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(default_flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(default_flags, MSG_DRAFT); + } + } + if (type == F_IMAP) { + MSG_SET_TMP_FLAGS(default_flags, MSG_IMAP); + } else if (type == F_NEWS) { + MSG_SET_TMP_FLAGS(default_flags, MSG_NEWS); + } + + if (type == F_MH) { + gchar *path; + + path = folder_item_get_path(item); + if (change_dir(path) < 0) { + g_free(path); + return NULL; + } + g_free(path); + } + + mapfile = procmsg_open_cache_file_mmap(item, DATA_READ); + if (!mapfile) { + item->cache_dirty = TRUE; + return NULL; + } + + debug_print("Reading summary cache...\n"); + + filep = g_mapped_file_get_contents(mapfile); + file_len = g_mapped_file_get_length(mapfile); + endp = filep + file_len; + p = filep + sizeof(guint32); /* version */ + + while (endp - p >= sizeof(num)) { + msginfo = g_new0(MsgInfo, 1); + + READ_CACHE_DATA_INT(msginfo->msgnum); + + READ_CACHE_DATA_INT(msginfo->size); + READ_CACHE_DATA_INT(msginfo->mtime); + READ_CACHE_DATA_INT(msginfo->date_t); + READ_CACHE_DATA_INT(msginfo->flags.tmp_flags); + + READ_CACHE_DATA(msginfo->fromname); + + READ_CACHE_DATA(msginfo->date); + READ_CACHE_DATA(msginfo->from); + READ_CACHE_DATA(msginfo->to); + READ_CACHE_DATA(msginfo->newsgroups); + READ_CACHE_DATA(msginfo->subject); + READ_CACHE_DATA(msginfo->msgid); + READ_CACHE_DATA(msginfo->inreplyto); + + READ_CACHE_DATA_INT(refnum); + for (; refnum != 0; refnum--) { + gchar *ref; + + READ_CACHE_DATA(ref); + msginfo->references = + g_slist_prepend(msginfo->references, ref); + } + if (msginfo->references) + msginfo->references = + g_slist_reverse(msginfo->references); + + MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags); + MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags); + + /* if the message file doesn't exist or is changed, + don't add the data */ + if ((type == F_MH && scan_file && + folder_item_is_msg_changed(item, msginfo)) || + msginfo->msgnum == 0) { + procmsg_msginfo_free(msginfo); + item->cache_dirty = TRUE; + } else { + msginfo->folder = item; + + if (!mlist) + last = mlist = g_slist_append(NULL, msginfo); + else { + last = g_slist_append(last, msginfo); + last = last->next; + } + } + } + + g_mapped_file_free(mapfile); + + if (item->cache_queue) { + GSList *qlist; + qlist = procmsg_read_cache_queue(item, scan_file); + mlist = g_slist_concat(mlist, qlist); + } + + debug_print("done.\n"); + + return mlist; +} + +#undef READ_CACHE_DATA +#undef READ_CACHE_DATA_INT + +static GSList *procmsg_read_cache_queue(FolderItem *item, gboolean scan_file) +{ + FolderType type; + MsgInfo *msginfo; + MsgFlags default_flags; + GSList *cur; + GSList *qlist = NULL; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + + if (!item->cache_queue) + return NULL; + + debug_print("Reading cache queue...\n"); + + type = FOLDER_TYPE(item->folder); + default_flags.perm_flags = MSG_NEW|MSG_UNREAD; + default_flags.tmp_flags = 0; + + for (cur = item->cache_queue; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + debug_print("read cache queue: %s/%d\n", + item->path, msginfo->msgnum); + + MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags); + MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags); + + if ((type == F_MH && scan_file && + folder_item_is_msg_changed(item, msginfo))) { + procmsg_msginfo_free(msginfo); + item->cache_dirty = TRUE; + } else { + msginfo->folder = item; + qlist = g_slist_prepend(qlist, msginfo); + } + } + + g_slist_free(item->cache_queue); + item->cache_queue = NULL; + item->cache_dirty = TRUE; + + return qlist; +} + +static void mark_unset_new_func(gpointer key, gpointer value, gpointer data) +{ + MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW); +} + +void procmsg_set_flags(GSList *mlist, FolderItem *item) +{ + GSList *cur; + gint new = 0, unread = 0, total = 0; + gint lastnum = 0; + gint unflagged = 0; + gboolean mark_queue_exist; + MsgInfo *msginfo; + GHashTable *mark_table; + MsgFlags *flags; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + debug_print("Marking the messages...\n"); + + mark_queue_exist = (item->mark_queue != NULL); + mark_table = procmsg_read_mark_file(item); + if (!mark_table) { + item->new = item->unread = item->total = g_slist_length(mlist); + item->updated = TRUE; + item->mark_dirty = TRUE; + return; + } + + /* unset new flags if new (unflagged) messages exist */ + if (!mark_queue_exist) { + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + flags = g_hash_table_lookup + (mark_table, GUINT_TO_POINTER(msginfo->msgnum)); + if (!flags) { + g_hash_table_foreach(mark_table, + mark_unset_new_func, NULL); + item->mark_dirty = TRUE; + break; + } + } + } + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + if (lastnum < msginfo->msgnum) + lastnum = msginfo->msgnum; + + flags = g_hash_table_lookup + (mark_table, GUINT_TO_POINTER(msginfo->msgnum)); + + if (flags != NULL) { + /* add the permanent flags only */ + msginfo->flags.perm_flags = flags->perm_flags; + if (MSG_IS_NEW(*flags)) + ++new; + if (MSG_IS_UNREAD(*flags)) + ++unread; + if (FOLDER_TYPE(item->folder) == F_IMAP) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP); + } else if (FOLDER_TYPE(item->folder) == F_NEWS) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS); + } + } else { + ++unflagged; + ++new; + ++unread; + } + + ++total; + } + + item->new = new; + item->unread = unread; + item->total = total; + item->unmarked_num = unflagged; + item->last_num = lastnum; + item->updated = TRUE; + + if (unflagged > 0) + item->mark_dirty = TRUE; + + debug_print("new: %d unread: %d unflagged: %d total: %d\n", + new, unread, unflagged, total); + + hash_free_value_mem(mark_table); + g_hash_table_destroy(mark_table); +} + +static void mark_all_read_func(gpointer key, gpointer value, gpointer data) +{ + MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW|MSG_UNREAD); +} + +void procmsg_mark_all_read(FolderItem *item) +{ + GHashTable *mark_table; + + debug_print("Marking all messages as read\n"); + + mark_table = procmsg_read_mark_file(item); + if (mark_table) { + g_hash_table_foreach(mark_table, mark_all_read_func, NULL); + procmsg_write_mark_file(item, mark_table); + hash_free_value_mem(mark_table); + g_hash_table_destroy(mark_table); + } + + if (item->mark_queue) { + GSList *cur; + MsgFlagInfo *flaginfo; + + for (cur = item->mark_queue; cur != NULL; cur = cur->next) { + flaginfo = (MsgFlagInfo *)cur->data; + MSG_UNSET_PERM_FLAGS + (flaginfo->flags, MSG_NEW|MSG_UNREAD); + } + item->mark_dirty = TRUE; + } + + item->new = item->unread = 0; +} + +static FolderSortType cmp_func_sort_type; + +GSList *procmsg_sort_msg_list(GSList *mlist, FolderSortKey sort_key, + FolderSortType sort_type) +{ + GCompareFunc cmp_func; + + switch (sort_key) { + case SORT_BY_MARK: + cmp_func = procmsg_cmp_by_mark; break; + case SORT_BY_UNREAD: + cmp_func = procmsg_cmp_by_unread; break; + case SORT_BY_MIME: + cmp_func = procmsg_cmp_by_mime; break; + case SORT_BY_LABEL: + cmp_func = procmsg_cmp_by_label; break; + case SORT_BY_NUMBER: + cmp_func = procmsg_cmp_by_number; break; + case SORT_BY_SIZE: + cmp_func = procmsg_cmp_by_size; break; + case SORT_BY_DATE: + cmp_func = procmsg_cmp_by_date; break; + case SORT_BY_FROM: + cmp_func = procmsg_cmp_by_from; break; + case SORT_BY_SUBJECT: + cmp_func = procmsg_cmp_by_subject; break; + case SORT_BY_TO: + cmp_func = procmsg_cmp_by_to; break; + default: + return mlist; + } + + cmp_func_sort_type = sort_type; + + mlist = g_slist_sort(mlist, cmp_func); + + return mlist; +} + +gint procmsg_get_last_num_in_msg_list(GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + gint last = 0; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (msginfo && msginfo->msgnum > last) + last = msginfo->msgnum; + } + + return last; +} + +void procmsg_msg_list_free(GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + procmsg_msginfo_free(msginfo); + } + g_slist_free(mlist); +} + +void procmsg_write_cache(MsgInfo *msginfo, FILE *fp) +{ + MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK; + GSList *cur; + + WRITE_CACHE_DATA_INT(msginfo->msgnum, fp); + WRITE_CACHE_DATA_INT(msginfo->size, fp); + WRITE_CACHE_DATA_INT(msginfo->mtime, fp); + WRITE_CACHE_DATA_INT(msginfo->date_t, fp); + WRITE_CACHE_DATA_INT(flags, fp); + + WRITE_CACHE_DATA(msginfo->fromname, fp); + + WRITE_CACHE_DATA(msginfo->date, fp); + WRITE_CACHE_DATA(msginfo->from, fp); + WRITE_CACHE_DATA(msginfo->to, fp); + WRITE_CACHE_DATA(msginfo->newsgroups, fp); + WRITE_CACHE_DATA(msginfo->subject, fp); + WRITE_CACHE_DATA(msginfo->msgid, fp); + WRITE_CACHE_DATA(msginfo->inreplyto, fp); + + WRITE_CACHE_DATA_INT(g_slist_length(msginfo->references), fp); + for (cur = msginfo->references; cur != NULL; cur = cur->next) { + WRITE_CACHE_DATA((gchar *)cur->data, fp); + } +} + +void procmsg_write_flags(MsgInfo *msginfo, FILE *fp) +{ + MsgPermFlags flags = msginfo->flags.perm_flags; + + WRITE_CACHE_DATA_INT(msginfo->msgnum, fp); + WRITE_CACHE_DATA_INT(flags, fp); +} + +void procmsg_write_cache_list(FolderItem *item, GSList *mlist) +{ + FILE *fp; + GSList *cur; + + g_return_if_fail(item != NULL); + + debug_print("Writing summary cache (%s)\n", item->path); + + fp = procmsg_open_cache_file(item, DATA_WRITE); + if (fp == NULL) + return; + + for (cur = mlist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + procmsg_write_cache(msginfo, fp); + } + + if (item->cache_queue) + procmsg_flush_cache_queue(item, fp); + + fclose(fp); + item->cache_dirty = FALSE; +} + +void procmsg_write_flags_list(FolderItem *item, GSList *mlist) +{ + FILE *fp; + GSList *cur; + + g_return_if_fail(item != NULL); + + debug_print("Writing summary flags (%s)\n", item->path); + + fp = procmsg_open_mark_file(item, DATA_WRITE); + if (fp == NULL) + return; + + for (cur = mlist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + procmsg_write_flags(msginfo, fp); + } + + if (item->mark_queue) + procmsg_flush_mark_queue(item, fp); + + fclose(fp); + item->mark_dirty = FALSE; +} + +static gint cmp_by_item(gconstpointer a, gconstpointer b) +{ + const MsgInfo *msginfo1 = a; + const MsgInfo *msginfo2 = b; + + if (msginfo1->folder == msginfo2->folder) + return msginfo1->msgnum - msginfo2->msgnum; + + return msginfo1->folder - msginfo2->folder; +} + +void procmsg_write_flags_for_multiple_folders(GSList *mlist) +{ + GSList *tmp_list, *cur; + FolderItem *prev_item = NULL; + FILE *fp = NULL; + + if (!mlist) + return; + + tmp_list = g_slist_copy(mlist); + tmp_list = g_slist_sort(tmp_list, cmp_by_item); + + for (cur = tmp_list; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + FolderItem *item = msginfo->folder; + + if (prev_item != item) { + if (fp) + fclose(fp); + fp = procmsg_open_mark_file(item, DATA_APPEND); + if (!fp) { + g_warning("can't open mark file\n"); + break; + } + item->updated = TRUE; + } + procmsg_write_flags(msginfo, fp); + prev_item = item; + } + + if (fp) + fclose(fp); + g_slist_free(tmp_list); +} + +void procmsg_flush_mark_queue(FolderItem *item, FILE *fp) +{ + MsgFlagInfo *flaginfo; + MsgInfo msginfo = {0}; + gboolean append = FALSE; + GSList *qlist, *cur; + + g_return_if_fail(item != NULL); + + if (!item->mark_queue) + return; + + debug_print("flushing mark_queue: %s ...\n", item->path); + + if (!fp) { + append = TRUE; + fp = procmsg_open_mark_file(item, DATA_APPEND); + g_return_if_fail(fp != NULL); + } + + qlist = g_slist_reverse(item->mark_queue); + item->mark_queue = NULL; + + for (cur = qlist; cur != NULL; cur = cur->next) { + flaginfo = (MsgFlagInfo *)cur->data; + + msginfo.msgnum = flaginfo->msgnum; + msginfo.flags = flaginfo->flags; + procmsg_write_flags(&msginfo, fp); + g_free(flaginfo); + } + + g_slist_free(qlist); + + if (append) + fclose(fp); +} + +void procmsg_add_mark_queue(FolderItem *item, gint num, MsgFlags flags) +{ + MsgFlagInfo *flaginfo; + + flaginfo = g_new(MsgFlagInfo, 1); + flaginfo->msgnum = num; + flaginfo->flags = flags; + item->mark_queue = g_slist_prepend(item->mark_queue, flaginfo); +} + +void procmsg_flaginfo_list_free(GSList *flaglist) +{ + GSList *cur; + MsgFlagInfo *flaginfo; + + for (cur = flaglist; cur != NULL; cur = cur->next) { + flaginfo = (MsgFlagInfo *)cur->data; + g_free(flaginfo); + } + g_slist_free(flaglist); +} + +void procmsg_flush_cache_queue(FolderItem *item, FILE *fp) +{ + MsgInfo *msginfo; + gboolean append = FALSE; + GSList *qlist, *cur; + + g_return_if_fail(item != NULL); + + if (!item->cache_queue) + return; + + debug_print("flushing cache_queue: %s ...\n", item->path); + + if (!fp) { + append = TRUE; + fp = procmsg_open_cache_file(item, DATA_APPEND); + g_return_if_fail(fp != NULL); + } + + qlist = g_slist_reverse(item->cache_queue); + item->cache_queue = NULL; + + for (cur = qlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + debug_print("flush cache queue: %s/%d\n", + item->path, msginfo->msgnum); + procmsg_write_cache(msginfo, fp); + procmsg_msginfo_free(msginfo); + } + + g_slist_free(qlist); + + if (append) + fclose(fp); +} + +void procmsg_add_cache_queue(FolderItem *item, gint num, MsgInfo *msginfo) +{ + MsgInfo *queue_msginfo; + + g_return_if_fail(msginfo != NULL); + + queue_msginfo = procmsg_msginfo_copy(msginfo); + queue_msginfo->msgnum = num; + queue_msginfo->folder = item; + if (queue_msginfo->file_path) { + g_free(queue_msginfo->file_path); + queue_msginfo->file_path = NULL; + } + + debug_print("procmsg_add_cache_queue: add msg cache: %s/%d\n", + item->path, num); + item->cache_queue = g_slist_prepend(item->cache_queue, queue_msginfo); +} + +gboolean procmsg_flush_folder(FolderItem *item) +{ + gboolean flushed = FALSE; + gint n_new, n_unread, n_total, n_min, n_max; + + g_return_val_if_fail(item != NULL, FALSE); + g_return_val_if_fail(item->folder != NULL, FALSE); + + if (FOLDER_TYPE(item->folder) != F_MH || item->last_num < 0) { + folder_item_scan(item); + return TRUE; + } + + if (item->mark_queue && !item->opened) + flushed = TRUE; + procmsg_get_mark_sum(item, &n_new, &n_unread, &n_total, &n_min, &n_max, + 0); + item->unmarked_num = 0; + item->new = n_new; + item->unread = n_unread; + item->total = n_total; + + if (item->cache_queue && !item->opened) { + procmsg_flush_cache_queue(item, NULL); + flushed = TRUE; + } + + if (flushed) + debug_print("procmsg_flush_folder: flushed %s\n", item->path); + + return flushed; +} + +static void procmsg_flush_folder_foreach_func(gpointer key, gpointer val, + gpointer data) +{ + procmsg_flush_folder(FOLDER_ITEM(key)); +} + +void procmsg_flush_folder_foreach(GHashTable *folder_table) +{ + g_hash_table_foreach(folder_table, procmsg_flush_folder_foreach_func, + NULL); +} + +void procmsg_add_flags(FolderItem *item, gint num, MsgFlags flags) +{ + FILE *fp; + MsgInfo msginfo; + + g_return_if_fail(item != NULL); + + if (item->opened) { + procmsg_add_mark_queue(item, num, flags); + return; + } + + if ((fp = procmsg_open_mark_file(item, DATA_APPEND)) == NULL) { + g_warning(_("can't open mark file\n")); + return; + } + + msginfo.msgnum = num; + msginfo.flags = flags; + + procmsg_write_flags(&msginfo, fp); + fclose(fp); +} + +struct MarkSum { + gint *new; + gint *unread; + gint *total; + gint *min; + gint *max; + gint first; +}; + +static void mark_sum_func(gpointer key, gpointer value, gpointer data) +{ + MsgFlags *flags = value; + gint num = GPOINTER_TO_INT(key); + struct MarkSum *marksum = data; + + if (marksum->first <= num) { + if (MSG_IS_NEW(*flags)) (*marksum->new)++; + if (MSG_IS_UNREAD(*flags)) (*marksum->unread)++; + if (num > *marksum->max) *marksum->max = num; + if (num < *marksum->min || *marksum->min == 0) *marksum->min = num; + (*marksum->total)++; + } + + g_free(flags); +} + +void procmsg_get_mark_sum(FolderItem *item, + gint *new, gint *unread, gint *total, + gint *min, gint *max, + gint first) +{ + GHashTable *mark_table; + struct MarkSum marksum; + + *new = *unread = *total = *min = *max = 0; + marksum.new = new; + marksum.unread = unread; + marksum.total = total; + marksum.min = min; + marksum.max = max; + marksum.first = first; + + mark_table = procmsg_read_mark_file(item); + + if (mark_table) { + g_hash_table_foreach(mark_table, mark_sum_func, &marksum); + g_hash_table_destroy(mark_table); + } +} + +static GHashTable *procmsg_read_mark_file(FolderItem *item) +{ + FILE *fp; + GHashTable *mark_table = NULL; + guint32 idata; + guint num; + MsgFlags *flags; + MsgPermFlags perm_flags; + GSList *cur; + + if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL) + return NULL; + + mark_table = g_hash_table_new(NULL, g_direct_equal); + + while (fread(&idata, sizeof(idata), 1, fp) == 1) { + num = idata; + if (fread(&idata, sizeof(idata), 1, fp) != 1) break; + perm_flags = idata; + + flags = g_hash_table_lookup(mark_table, GUINT_TO_POINTER(num)); + if (flags != NULL) + g_free(flags); + + flags = g_new0(MsgFlags, 1); + flags->perm_flags = perm_flags; + + g_hash_table_insert(mark_table, GUINT_TO_POINTER(num), flags); + } + + fclose(fp); + + if (item->mark_queue) { + g_hash_table_foreach(mark_table, mark_unset_new_func, NULL); + item->mark_dirty = TRUE; + } + + for (cur = item->mark_queue; cur != NULL; cur = cur->next) { + MsgFlagInfo *flaginfo = (MsgFlagInfo *)cur->data; + + flags = g_hash_table_lookup(mark_table, + GUINT_TO_POINTER(flaginfo->msgnum)); + if (flags != NULL) + g_free(flags); + + flags = g_new0(MsgFlags, 1); + flags->perm_flags = flaginfo->flags.perm_flags; + + g_hash_table_insert(mark_table, + GUINT_TO_POINTER(flaginfo->msgnum), flags); + + } + + if (item->mark_queue && !item->opened) { + procmsg_write_mark_file(item, mark_table); + procmsg_flaginfo_list_free(item->mark_queue); + item->mark_queue = NULL; + item->mark_dirty = FALSE; + } + + return mark_table; +} + +static void write_mark_func(gpointer key, gpointer value, gpointer data) +{ + MsgInfo msginfo; + + msginfo.msgnum = GPOINTER_TO_UINT(key); + msginfo.flags.perm_flags = ((MsgFlags *)value)->perm_flags; + procmsg_write_flags(&msginfo, (FILE *)data); +} + +static void procmsg_write_mark_file(FolderItem *item, GHashTable *mark_table) +{ + FILE *fp; + + if ((fp = procmsg_open_mark_file(item, DATA_WRITE)) == NULL) { + g_warning("procmsg_write_mark_file: cannot open mark file."); + return; + } + g_hash_table_foreach(mark_table, write_mark_func, fp); + fclose(fp); +} + +FILE *procmsg_open_data_file(const gchar *file, guint version, + DataOpenMode mode, gchar *buf, size_t buf_size) +{ + FILE *fp; + guint32 data_ver = 0; + + g_return_val_if_fail(file != NULL, NULL); + + if (mode == DATA_WRITE) { + if ((fp = g_fopen(file, "wb")) == NULL) { + if (errno == EACCES) { + change_file_mode_rw(NULL, file); + if ((fp = g_fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + return NULL; + } + } else { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + return NULL; + } + } + if (change_file_mode_rw(fp, file) < 0) + FILE_OP_ERROR(file, "chmod"); + + WRITE_CACHE_DATA_INT(version, fp); + return fp; + } + + /* check version */ + if ((fp = g_fopen(file, "rb")) == NULL) { + if (errno == EACCES) { + change_file_mode_rw(NULL, file); + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + } + } else { + debug_print("Mark/Cache file '%s' not found\n", file); + } + } + + if (fp) { + if (buf && buf_size > 0) + setvbuf(fp, buf, _IOFBF, buf_size); + if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1) { + g_warning("%s: cannot read mark/cache file (truncated?)\n", file); + fclose(fp); + fp = NULL; + } else if (version != data_ver) { + g_message("%s: Mark/Cache version is different (%u != %u). Discarding it.\n", + file, data_ver, version); + fclose(fp); + fp = NULL; + } + } + + if (mode == DATA_READ) + return fp; + + if (fp) { + /* reopen with append mode */ + fclose(fp); + if ((fp = g_fopen(file, "ab")) == NULL) { + if (errno == EACCES) { + change_file_mode_rw(NULL, file); + if ((fp = g_fopen(file, "ab")) == NULL) { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + } + } else { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + } + } + } else { + /* open with overwrite mode if mark file doesn't exist or + version is different */ + fp = procmsg_open_data_file(file, version, DATA_WRITE, buf, + buf_size); + } + + return fp; +} + +static GMappedFile *procmsg_open_cache_file_mmap(FolderItem *item, + DataOpenMode mode) +{ + gchar *cachefile; + GMappedFile *map = NULL; + GError *error = NULL; + gsize size; + guint32 data_ver = 0; + gchar *p; + + if (mode != DATA_READ) + return NULL; + + cachefile = folder_item_get_cache_file(item); + if (cachefile) { + map = g_mapped_file_new(cachefile, FALSE, &error); + if (!map) { + if (error && error->code == G_FILE_ERROR_NOENT) + debug_print("%s: mark/cache file not found\n", cachefile); + else if (error) + g_warning("%s: cannot open mark/cache file: %s", cachefile, error->message); + else + g_warning("%s: cannot open mark/cache file", cachefile); + if (error) + g_error_free(error); + g_free(cachefile); + return NULL; + } + size = g_mapped_file_get_length(map); + if (size < sizeof(data_ver)) { + g_warning("%s: cannot read mark/cache file (truncated?)", cachefile); + g_mapped_file_free(map); + g_free(cachefile); + return NULL; + } + p = g_mapped_file_get_contents(map); + data_ver = *(guint32 *)p; + if (CACHE_VERSION != data_ver) { + g_message("%s: Mark/Cache version is different (%u != %u). Discarding it.\n", + cachefile, data_ver, CACHE_VERSION); + g_mapped_file_free(map); + g_free(cachefile); + return NULL; + } + g_free(cachefile); + } + + return map; +} + +FILE *procmsg_open_cache_file(FolderItem *item, DataOpenMode mode) +{ + gchar *cachefile; + FILE *fp; + + cachefile = folder_item_get_cache_file(item); + fp = procmsg_open_data_file(cachefile, CACHE_VERSION, mode, NULL, 0); + g_free(cachefile); + + return fp; +} + +FILE *procmsg_open_mark_file(FolderItem *item, DataOpenMode mode) +{ + gchar *markfile; + FILE *fp; + + markfile = folder_item_get_mark_file(item); + fp = procmsg_open_data_file(markfile, MARK_VERSION, mode, NULL, 0); + g_free(markfile); + + return fp; +} + +void procmsg_clear_cache(FolderItem *item) +{ + FILE *fp; + + fp = procmsg_open_cache_file(item, DATA_WRITE); + if (fp) + fclose(fp); +} + +void procmsg_clear_mark(FolderItem *item) +{ + FILE *fp; + + fp = procmsg_open_mark_file(item, DATA_WRITE); + if (fp) + fclose(fp); +} + +/* return the reversed thread tree */ +GNode *procmsg_get_thread_tree(GSList *mlist) +{ + GNode *root, *parent, *node, *next; + GHashTable *table; + MsgInfo *msginfo; + const gchar *msgid; + GSList *reflist; + + root = g_node_new(NULL); + table = g_hash_table_new(g_str_hash, g_str_equal); + + for (; mlist != NULL; mlist = mlist->next) { + msginfo = (MsgInfo *)mlist->data; + parent = root; + + /* only look for the real parent first */ + if (msginfo->inreplyto) { + parent = g_hash_table_lookup(table, msginfo->inreplyto); + if (parent == NULL) + parent = root; + } + + node = g_node_insert_data_before + (parent, parent == root ? parent->children : NULL, + msginfo); + if ((msgid = msginfo->msgid) && + g_hash_table_lookup(table, msgid) == NULL) + g_hash_table_insert(table, (gchar *)msgid, node); + } + + /* complete the unfinished threads */ + for (node = root->children; node != NULL; ) { + next = node->next; + msginfo = (MsgInfo *)node->data; + parent = NULL; + + if (msginfo->inreplyto) + parent = g_hash_table_lookup(table, msginfo->inreplyto); + + /* try looking for the indirect parent */ + if (!parent && msginfo->references) { + for (reflist = msginfo->references; + reflist != NULL; reflist = reflist->next) + if ((parent = g_hash_table_lookup + (table, reflist->data)) != NULL) + break; + } + + /* node should not be the parent, and node should not + be an ancestor of parent (circular reference) */ + if (parent && parent != node && + !g_node_is_ancestor(node, parent)) { + g_node_unlink(node); + g_node_insert_before + (parent, parent->children, node); + } + node = next; + } + + g_hash_table_destroy(table); + + return root; +} + +static gboolean procmsg_thread_date_func(GNode *node, gpointer data) +{ + guint *tdate = (guint *)data; + MsgInfo *msginfo = (MsgInfo *)node->data; + + if (*tdate < msginfo->date_t) + *tdate = msginfo->date_t; + + return FALSE; +} + +guint procmsg_get_thread_date(GNode *node) +{ + guint tdate = 0; + + g_return_val_if_fail(node != NULL && node->parent != NULL && + node->parent->parent == NULL, 0); + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + procmsg_thread_date_func, &tdate); + + return tdate; +} + +gint procmsg_move_messages(GSList *mlist) +{ + GSList *cur, *movelist = NULL; + MsgInfo *msginfo; + FolderItem *dest = NULL; + GHashTable *hash; + gint val = 0; + + if (!mlist) return 0; + + hash = procmsg_to_folder_hash_table_create(mlist); + folder_item_scan_foreach(hash); + g_hash_table_destroy(hash); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (!dest) { + dest = msginfo->to_folder; + movelist = g_slist_append(movelist, msginfo); + } else if (dest == msginfo->to_folder) { + movelist = g_slist_append(movelist, msginfo); + } else { + val = folder_item_move_msgs(dest, movelist); + g_slist_free(movelist); + movelist = NULL; + if (val == -1) + return val; + dest = msginfo->to_folder; + movelist = g_slist_append(movelist, msginfo); + } + } + + if (movelist) { + val = folder_item_move_msgs(dest, movelist); + g_slist_free(movelist); + } + + return val == -1 ? -1 : 0; +} + +gint procmsg_copy_messages(GSList *mlist) +{ + GSList *cur, *copylist = NULL; + MsgInfo *msginfo; + FolderItem *dest = NULL; + GHashTable *hash; + gint val = 0; + + if (!mlist) return 0; + + hash = procmsg_to_folder_hash_table_create(mlist); + folder_item_scan_foreach(hash); + g_hash_table_destroy(hash); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (!dest) { + dest = msginfo->to_folder; + copylist = g_slist_append(copylist, msginfo); + } else if (dest == msginfo->to_folder) { + copylist = g_slist_append(copylist, msginfo); + } else { + val = folder_item_copy_msgs(dest, copylist); + g_slist_free(copylist); + copylist = NULL; + if (val == -1) + return val; + dest = msginfo->to_folder; + copylist = g_slist_append(copylist, msginfo); + } + } + + if (copylist) { + val = folder_item_copy_msgs(dest, copylist); + g_slist_free(copylist); + } + + return val == -1 ? -1 : 0; +} + +gint procmsg_add_messages_from_queue(FolderItem *dest, GSList *mlist, + gboolean is_move) +{ + MsgInfo *msginfo; + GSList *cur; + gchar *file; + FILE *fp; + gchar buf[BUFFSIZE]; + gchar *dest_file; + gboolean is_error = FALSE; + FolderItem *src; + MsgFlags flags; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(mlist != NULL, -1); + + msginfo = (MsgInfo *)mlist->data; + if (!msginfo || !msginfo->folder || msginfo->folder->stype != F_QUEUE || + !MSG_IS_QUEUED(msginfo->flags) || dest->stype == F_QUEUE) + return -1; + + debug_print("procmsg_add_messages_from_queue: adding messages from queue folder\n"); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + flags = msginfo->flags; + if (!MSG_IS_QUEUED(flags)) + return -1; + MSG_UNSET_TMP_FLAGS(flags, MSG_QUEUED); + src = msginfo->folder; + file = procmsg_get_message_file(msginfo); + if (!file) + return -1; + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "folder_item_move_msgs: fopen"); + g_free(file); + return -1; + } + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (buf[0] == '\r' || buf[0] == '\n') + break; + } + if (ferror(fp)) { + fclose(fp); + g_free(file); + return -1; + } + + dest_file = get_tmp_file(); + debug_print("copy queued msg: %s -> %s\n", file, dest_file); + + if (copy_file_part(fp, ftell(fp), G_MAXINT, dest_file) < 0) { + fclose(fp); + is_error = TRUE; + } else { + fclose(fp); + if (folder_item_add_msg(dest, dest_file, &flags, TRUE) < 0) { + g_unlink(dest_file); + is_error = TRUE; + } else if (is_move && + folder_item_remove_msg(src, msginfo) < 0) + is_error = TRUE; + } + + g_free(dest_file); + g_free(file); + if (is_error) + return -1; + } + + return 0; +} + +gchar *procmsg_get_message_file_path(MsgInfo *msginfo) +{ + gchar *path, *file; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if (msginfo->encinfo && msginfo->encinfo->plaintext_file) + file = g_strdup(msginfo->encinfo->plaintext_file); + else if (msginfo->file_path) + return g_strdup(msginfo->file_path); + else { + gchar nstr[16]; + path = folder_item_get_path(msginfo->folder); + file = g_strconcat(path, G_DIR_SEPARATOR_S, + utos_buf(nstr, msginfo->msgnum), NULL); + g_free(path); + } + + return file; +} + +gchar *procmsg_get_message_file(MsgInfo *msginfo) +{ + gchar *filename = NULL; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if (msginfo->file_path) + return g_strdup(msginfo->file_path); + + filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum); + if (!filename) + debug_print(_("can't fetch message %d\n"), msginfo->msgnum); + + return filename; +} + +GSList *procmsg_get_message_file_list(GSList *mlist) +{ + GSList *file_list = NULL; + MsgInfo *msginfo; + MsgFileInfo *fileinfo; + gchar *file; + + while (mlist != NULL) { + msginfo = (MsgInfo *)mlist->data; + file = procmsg_get_message_file(msginfo); + if (!file) { + procmsg_message_file_list_free(file_list); + return NULL; + } + fileinfo = g_new(MsgFileInfo, 1); + fileinfo->file = file; + fileinfo->flags = g_new(MsgFlags, 1); + *fileinfo->flags = msginfo->flags; + file_list = g_slist_prepend(file_list, fileinfo); + mlist = mlist->next; + } + + file_list = g_slist_reverse(file_list); + + return file_list; +} + +void procmsg_message_file_list_free(GSList *file_list) +{ + GSList *cur; + MsgFileInfo *fileinfo; + + for (cur = file_list; cur != NULL; cur = cur->next) { + fileinfo = (MsgFileInfo *)cur->data; + g_free(fileinfo->file); + g_free(fileinfo->flags); + g_free(fileinfo); + } + + g_slist_free(file_list); +} + +FILE *procmsg_open_message(MsgInfo *msginfo) +{ + FILE *fp; + gchar *file; + + g_return_val_if_fail(msginfo != NULL, NULL); + + file = procmsg_get_message_file_path(msginfo); + g_return_val_if_fail(file != NULL, NULL); + + if (!is_file_exist(file)) { + g_free(file); + file = procmsg_get_message_file(msginfo); + if (!file) + return NULL; + } + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "procmsg_open_message: fopen"); + g_free(file); + return NULL; + } + + g_free(file); + + if (MSG_IS_QUEUED(msginfo->flags)) { + gchar buf[BUFFSIZE]; + + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + } + + return fp; +} + +static DecryptMessageFunc decrypt_message_func = NULL; +static gboolean auto_decrypt = TRUE; + +void procmsg_set_decrypt_message_func(DecryptMessageFunc func) +{ + decrypt_message_func = func; +} + +void procmsg_set_auto_decrypt_message(gboolean enabled) +{ + auto_decrypt = enabled; +} + +FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo) +{ + FILE *fp; + + if (decrypt_message_func && auto_decrypt) + return decrypt_message_func(msginfo, mimeinfo); + + *mimeinfo = NULL; + if ((fp = procmsg_open_message(msginfo)) == NULL) + return NULL; + *mimeinfo = procmime_scan_mime_header(fp); + + return fp; +} + +gboolean procmsg_msg_exist(MsgInfo *msginfo) +{ + gchar *path; + gboolean ret; + + if (!msginfo) return FALSE; + + path = folder_item_get_path(msginfo->folder); + change_dir(path); + ret = !folder_item_is_msg_changed(msginfo->folder, msginfo); + g_free(path); + + return ret; +} + +gboolean procmsg_trash_messages_exist(void) +{ + FolderItem *trash; + GList *cur; + + for (cur = folder_get_list(); cur != NULL; cur = cur->next) { + trash = FOLDER(cur->data)->trash; + if (trash && trash->total > 0) + return TRUE; + } + + return FALSE; +} + +void procmsg_empty_trash(FolderItem *trash) +{ + if (!trash) + return; + + g_return_if_fail(trash->stype == F_TRASH || trash->stype == F_JUNK); + + if (trash->total > 0) { + debug_print("Emptying messages in %s ...\n", trash->path); + + folder_item_remove_all_msg(trash); + procmsg_clear_cache(trash); + procmsg_clear_mark(trash); + trash->cache_dirty = FALSE; + trash->mark_dirty = FALSE; + } +} + +void procmsg_empty_all_trash(void) +{ + FolderItem *trash; + GList *cur; + + for (cur = folder_get_list(); cur != NULL; cur = cur->next) { + trash = FOLDER(cur->data)->trash; + procmsg_empty_trash(trash); + } +} + +static gboolean remove_all_cached_messages_func(GNode *node, gpointer data) +{ + FolderItem *item; + gchar *dir; + + g_return_val_if_fail(node->data != NULL, FALSE); + + item = FOLDER_ITEM(node->data); + if (!item->path || item->stype == F_VIRTUAL) + return FALSE; + + dir = folder_item_get_path(item); + if (is_dir_exist(dir)) { + debug_print("removing all cached messages in '%s' ...\n", + item->path); + remove_all_numbered_files(dir); + } + g_free(dir); + + return FALSE; +} + +void procmsg_remove_all_cached_messages(Folder *folder) +{ + g_return_if_fail(folder != NULL); + g_return_if_fail(FOLDER_IS_REMOTE(folder)); + + debug_print("Removing all caches in the mailbox '%s' ...\n", + folder->name); + + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + remove_all_cached_messages_func, NULL); +} + +gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file) +{ + gint num; + MsgFlags flag = {0, 0}; + + debug_print("saving sent message...\n"); + + if (!outbox) + outbox = folder_get_default_outbox(); + g_return_val_if_fail(outbox != NULL, -1); + + folder_item_scan(outbox); + if ((num = folder_item_add_msg(outbox, file, &flag, FALSE)) < 0) { + g_warning("can't save message\n"); + return -1; + } + procmsg_flush_folder(outbox); + + return 0; +} + +static guint print_id = 0; + +static gint print_command_exec(const gchar *file, const gchar *cmdline) +{ + static const gchar *def_cmd = "lpr %s"; + gchar buf[1024]; + +#ifdef G_OS_WIN32 + if (canonicalize_file_replace(file) < 0) + return -1; +#endif + + if (cmdline && str_find_format_times(cmdline, 's') == 1) + g_snprintf(buf, sizeof(buf) - 1, cmdline, file); + else { + if (cmdline) { + g_warning(_("Print command line is invalid: `%s'\n"), + cmdline); + return -1; + } + +#ifdef G_OS_WIN32 + execute_print_file(file); + return 0; +#else + g_snprintf(buf, sizeof(buf) - 1, def_cmd, file); +#endif + } + + g_strchomp(buf); + if (buf[strlen(buf) - 1] != '&') + strcat(buf, "&"); + + if (system(buf) != 0) + return -1; + + return 0; +} + +static void procmsg_write_headers(MsgInfo *msginfo, MimeInfo *partinfo, + FILE *fp, FILE *dest_fp, + const gchar *encoding, gboolean all_headers) +{ + GPtrArray *headers; + gint i; + + if (all_headers) + headers = procheader_get_header_array_asis(fp, NULL); + else + headers = procheader_get_header_array_for_display(fp, NULL); + + for (i = 0; i < headers->len; i++) { + Header *hdr; + gchar *file_str; + const gchar *body; + + hdr = g_ptr_array_index(headers, i); + + if (partinfo) { + if (!g_ascii_strcasecmp(hdr->name, "Subject") || + !g_ascii_strcasecmp(hdr->name, "From") || + !g_ascii_strcasecmp(hdr->name, "To") || + !g_ascii_strcasecmp(hdr->name, "Cc")) { + unfold_line(hdr->body); + } + + body = hdr->body; + while (g_ascii_isspace(*body)) + body++; + } else { + if (!g_ascii_strcasecmp(hdr->name, "Subject")) + body = msginfo->subject; + else if (!g_ascii_strcasecmp(hdr->name, "From")) + body = msginfo->from; + else if (!g_ascii_strcasecmp(hdr->name, "To")) + body = msginfo->to; + else if (!g_ascii_strcasecmp(hdr->name, "Cc")) { + unfold_line(hdr->body); + body = hdr->body; + while (g_ascii_isspace(*body)) + body++; + } else { + body = hdr->body; + while (g_ascii_isspace(*body)) + body++; + } + } + + if (body && *body != '\0') { + file_str = conv_codeset_strdup + (body, CS_INTERNAL, encoding); + fprintf(dest_fp, "%s: %s\n", hdr->name, + file_str ? file_str : body); + g_free(file_str); + } else { + fprintf(dest_fp, "%s: (none)\n", hdr->name); + } + } + + procheader_header_array_destroy(headers); +} + +void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline, + gboolean all_headers) +{ + gchar *prtmp; + + g_return_if_fail(msginfo != NULL); + + prtmp = g_strdup_printf("%s%cprinttmp-%08x.txt", + get_mime_tmp_dir(), G_DIR_SEPARATOR, + print_id++); + + if (procmsg_save_message_as_text(msginfo, prtmp, + conv_get_locale_charset_str(), + all_headers) == 0) + print_command_exec(prtmp, cmdline); + + g_free(prtmp); +} + +void procmsg_print_message_part(MsgInfo *msginfo, MimeInfo *partinfo, + const gchar *cmdline, gboolean all_headers) +{ + FILE *msgfp, *tmpfp, *prfp; + gchar *prtmp; + gchar buf[BUFFSIZE]; + + if ((msgfp = procmsg_open_message(msginfo)) == NULL) { + return; + } + + if ((tmpfp = procmime_get_text_content + (partinfo, msgfp, conv_get_locale_charset_str())) == NULL) { + fclose(msgfp); + return; + } + fclose(msgfp); + + prtmp = g_strdup_printf("%s%cprinttmp-%08x.txt", + get_mime_tmp_dir(), G_DIR_SEPARATOR, + print_id++); + if ((prfp = g_fopen(prtmp, "w")) == NULL) { + FILE_OP_ERROR(prtmp, "procmsg_print_message_part: fopen"); + g_free(prtmp); + fclose(tmpfp); + return; + } + + while (fgets(buf, sizeof(buf), tmpfp) != NULL) + fputs(buf, prfp); + + fclose(prfp); + fclose(tmpfp); + + print_command_exec(prtmp, cmdline); + + g_free(prtmp); +} + +gint procmsg_save_message_as_text(MsgInfo *msginfo, const gchar *dest, + const gchar *encoding, gboolean all_headers) +{ + MimeInfo *mimeinfo, *partinfo; + FILE *fp; + FILE *tmpfp; + FILE *destfp; + gchar buf[BUFFSIZE]; + gchar *part_str; + gint ret = 0; + + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + + mimeinfo = procmime_scan_message(msginfo); + if (!mimeinfo) + return -1; + if ((fp = procmsg_open_message(msginfo)) == NULL) { + procmime_mimeinfo_free_all(mimeinfo); + return -1; + } + if ((destfp = g_fopen(dest, "w")) == NULL) { + fclose(fp); + procmime_mimeinfo_free_all(mimeinfo); + return -1; + } + procmsg_write_headers(msginfo, mimeinfo, fp, destfp, encoding, all_headers); + fputc('\n', destfp); + + partinfo = mimeinfo; + + while (partinfo != NULL) { + if (fseek(fp, partinfo->fpos, SEEK_SET) < 0) + break; + + if (partinfo->filename || partinfo->name) + g_snprintf(buf, sizeof(buf), "\n[%s %s (%s)]\n", + partinfo->filename ? partinfo->filename : + partinfo->name, + partinfo->content_type, + to_human_readable(partinfo->content_size)); + else + g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n", + partinfo->content_type, + to_human_readable(partinfo->content_size)); + part_str = conv_codeset_strdup(buf, CS_INTERNAL, encoding); + if (!part_str) + part_str = g_strdup(buf); + + if (partinfo->mime_type == MIME_TEXT || + partinfo->mime_type == MIME_TEXT_HTML) { + if (!partinfo->main && + partinfo->parent && + partinfo->parent->children != partinfo) { + fputs(part_str, destfp); + } + + if ((tmpfp = procmime_get_text_content(partinfo, fp, encoding)) == NULL) { + g_free(part_str); + break; + } + while (fgets(buf, sizeof(buf), tmpfp) != NULL) + fputs(buf, destfp); + + fclose(tmpfp); + } else if (partinfo->mime_type == MIME_MESSAGE_RFC822) { + fputs(part_str, destfp); + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + procmsg_write_headers(msginfo, partinfo, fp, destfp, encoding, all_headers); + fputc('\n', destfp); + } else if (partinfo->mime_type != MIME_MULTIPART) { + fputs(part_str, destfp); + } + + g_free(part_str); + + if (partinfo->parent && partinfo->parent->content_type && + !g_ascii_strcasecmp(partinfo->parent->content_type, + "multipart/alternative")) + partinfo = partinfo->parent->next; + else + partinfo = procmime_mimeinfo_next(partinfo); + } + + if (fclose(destfp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + g_unlink(dest); + ret = -1; + } + + fclose(fp); + procmime_mimeinfo_free_all(mimeinfo); + + return ret; +} + +/** + * procmsg_concat_partial_messages: + * @mlist: list of MsgInfo* including message/partial messages. + * @file: output file name of concatenated message. + * + * Concatenate @mlist which consists of message/partial messages and + * output to @file. If @mlist has different partial id, the first one + * is used. + * + * Return value: 0 on success, or -1 if failed. + **/ +gint procmsg_concat_partial_messages(GSList *mlist, const gchar *file) +{ + static HeaderEntry hentry[] = {{"Content-Type:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + FILE *fp; + gchar buf[BUFFSIZE]; + FILE *tmp_fp; + gchar *part_id = NULL; + gint total = 0; + MsgInfo *msg_array[100] = {NULL}; + MsgInfo *msginfo; + MimeInfo *mimeinfo; + GSList *cur; + gint i; + + g_return_val_if_fail(mlist != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + + debug_print("procmsg_concat_partial_messages\n"); + + for (cur = mlist; cur != NULL; cur = cur->next) { + gint n = 0; + gint t = 0; + gchar *cur_id = NULL; + + msginfo = (MsgInfo *)cur->data; + + fp = procmsg_open_message_decrypted(msginfo, &mimeinfo); + if (!fp) + continue; + if (!mimeinfo->content_type || + g_ascii_strcasecmp(mimeinfo->content_type, "message/partial") != 0) + goto skip; + + rewind(fp); + if (procheader_get_one_field(buf, sizeof(buf), fp, hentry) == -1) + goto skip; + + procmime_scan_content_type_partial(buf + strlen(hentry[0].name), + &t, &cur_id, &n); + if (n == 0 || n > 100 || t > 100 || (t > 0 && n > t)) { + debug_print("bad partial number (%d/%d), skip\n", n, t); + g_free(cur_id); + goto skip; + } + + debug_print("partial: %d/%d id=%s\n", n, t, cur_id); + if (!part_id) + part_id = g_strdup(cur_id); + if (total == 0) + total = t; + + if ((t > 0 && total != t) || (total > 0 && n > total) || + strcmp(part_id, cur_id) != 0) { + debug_print("skip\n"); + g_free(cur_id); + goto skip; + } + + msg_array[n - 1] = msginfo; + + g_free(cur_id); +skip: + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + } + + if (!part_id) { + debug_print("piece not found\n"); + return -1; + } + + debug_print("part_id = %s , total = %d\n", part_id, total); + g_free(part_id); + + if (total == 0) { + debug_print("total number not found\n"); + return -1; + } + + /* check if all pieces exist */ + for (i = 0; i < total; i++) { + if (msg_array[i] == NULL) { + debug_print("message part %d not exist\n", i + 1); + return -1; + } + } + + /* concatenate parts */ + if ((tmp_fp = g_fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + for (i = 0; i < total; i++) { + msginfo = msg_array[i]; + off_t out_size; + gint empty_line_size = 0; + + fp = procmsg_open_message_decrypted(msginfo, &mimeinfo); + if (!fp) { + g_warning("cannot open message part %d\n", i + 1); + fclose(tmp_fp); + g_unlink(file); + return -1; + } + + /* write out first headers */ + if (i == 0) { + rewind(fp); + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + if (!g_ascii_strncasecmp(buf, "Content-", 8) || + !g_ascii_strncasecmp(buf, "Subject", 7) || + !g_ascii_strncasecmp(buf, "Message-ID", 10) || + !g_ascii_strncasecmp(buf, "Encrypted", 9) || + !g_ascii_strncasecmp(buf, "MIME-Version", 12)) + continue; + fputs(buf, tmp_fp); + fputs("\n", tmp_fp); + } + + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + if (!g_ascii_strncasecmp(buf, "Content-", 8) || + !g_ascii_strncasecmp(buf, "Subject", 7) || + !g_ascii_strncasecmp(buf, "Message-ID", 10) || + !g_ascii_strncasecmp(buf, "Encrypted", 9) || + !g_ascii_strncasecmp(buf, "MIME-Version", 12)) { + fputs(buf, tmp_fp); + fputs("\n", tmp_fp); + } + } + + /* header-body separator */ + fputs("\n", tmp_fp); + } + + out_size = get_left_file_size(fp); + if (out_size < 0) { + g_warning("cannot tell left file size of part %d\n", i + 1); + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + fclose(tmp_fp); + g_unlink(file); + return -1; + } + empty_line_size = get_last_empty_line_size(fp, out_size); + if (empty_line_size < 0) { + g_warning("cannot get last empty line size of part %d\n", i + 1); + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + fclose(tmp_fp); + g_unlink(file); + return -1; + } + + if (append_file_part(fp, ftell(fp), out_size - empty_line_size, + tmp_fp) < 0) { + g_warning("write failed\n"); + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + fclose(tmp_fp); + g_unlink(file); + return -1; + } + + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + } + + fclose(tmp_fp); + + return 0; +} + +static gboolean procmsg_get_flags(FolderItem *item, gint num, + MsgPermFlags *flags) +{ + FILE *fp; + guint32 idata; + gint read_num; + MsgPermFlags perm_flags; + gboolean found = FALSE; + GSList *cur; + + if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL) + return FALSE; + + while (fread(&idata, sizeof(idata), 1, fp) == 1) { + read_num = idata; + if (fread(&idata, sizeof(idata), 1, fp) != 1) + break; + perm_flags = idata; + if (read_num == num) { + *flags = perm_flags; + found = TRUE; + break; + } + } + + fclose(fp); + if (found) + return TRUE; + + for (cur = item->mark_queue; cur != NULL; cur = cur->next) { + MsgFlagInfo *flaginfo = (MsgFlagInfo *)cur->data; + + if (flaginfo->msgnum == num) { + *flags = flaginfo->flags.perm_flags; + found = TRUE; + break; + } + } + + return found; +} + +MsgInfo *procmsg_get_msginfo(FolderItem *item, gint num) +{ + MsgInfo *msginfo; + FolderType type; + + g_return_val_if_fail(item->folder != NULL, NULL); + + msginfo = folder_item_get_msginfo(item, num); + if (!msginfo) + return NULL; + + type = FOLDER_TYPE(item->folder); + if (type == F_MH || type == F_IMAP) { + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT); + } + } + if (type == F_IMAP) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP); + } else if (type == F_NEWS) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS); + } + + if (type == F_MH || type == F_NEWS) { + MsgPermFlags flags = 0; + if (procmsg_get_flags(item, num, &flags)) + msginfo->flags.perm_flags = flags; + } + + return msginfo; +} + +MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo) +{ + MsgInfo *newmsginfo; + + if (msginfo == NULL) return NULL; + + newmsginfo = g_new0(MsgInfo, 1); + +#define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb +#define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \ + g_strdup(msginfo->mmb) : NULL + + MEMBCOPY(msgnum); + MEMBCOPY(size); + MEMBCOPY(mtime); + MEMBCOPY(date_t); + + MEMBCOPY(flags); + + MEMBDUP(fromname); + + MEMBDUP(date); + MEMBDUP(from); + MEMBDUP(to); + MEMBDUP(cc); + MEMBDUP(newsgroups); + MEMBDUP(subject); + MEMBDUP(msgid); + MEMBDUP(inreplyto); + + MEMBCOPY(folder); + MEMBCOPY(to_folder); + + MEMBDUP(xface); + + MEMBDUP(file_path); + + if (msginfo->encinfo) { + newmsginfo->encinfo = g_new0(MsgEncryptInfo, 1); + MEMBDUP(encinfo->plaintext_file); + MEMBDUP(encinfo->sigstatus); + MEMBDUP(encinfo->sigstatus_full); + MEMBCOPY(encinfo->decryption_failed); + } + + return newmsginfo; +} + +MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo) +{ + MsgInfo *full_msginfo; + gchar *file; + + if (msginfo == NULL) return NULL; + + file = procmsg_get_message_file(msginfo); + if (!file) { + g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n"); + return NULL; + } + + full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE); + g_free(file); + if (!full_msginfo) return NULL; + + full_msginfo->msgnum = msginfo->msgnum; + full_msginfo->size = msginfo->size; + full_msginfo->mtime = msginfo->mtime; + full_msginfo->folder = msginfo->folder; + full_msginfo->to_folder = msginfo->to_folder; + + full_msginfo->file_path = g_strdup(msginfo->file_path); + + if (msginfo->encinfo) { + full_msginfo->encinfo = g_new0(MsgEncryptInfo, 1); + full_msginfo->encinfo->plaintext_file = + g_strdup(msginfo->encinfo->plaintext_file); + full_msginfo->encinfo->sigstatus = + g_strdup(msginfo->encinfo->sigstatus); + full_msginfo->encinfo->sigstatus_full = + g_strdup(msginfo->encinfo->sigstatus_full); + full_msginfo->encinfo->decryption_failed = + msginfo->encinfo->decryption_failed; + } + + return full_msginfo; +} + +gboolean procmsg_msginfo_equal(MsgInfo *msginfo_a, MsgInfo *msginfo_b) +{ + if (!msginfo_a || !msginfo_b) + return FALSE; + + if (msginfo_a == msginfo_b) + return TRUE; + + if (msginfo_a->folder == msginfo_b->folder && + msginfo_a->msgnum == msginfo_b->msgnum && + msginfo_a->size == msginfo_b->size && + msginfo_a->mtime == msginfo_b->mtime) + return TRUE; + + return FALSE; +} + +void procmsg_msginfo_free(MsgInfo *msginfo) +{ + if (msginfo == NULL) return; + + g_free(msginfo->xface); + + g_free(msginfo->fromname); + + g_free(msginfo->date); + g_free(msginfo->from); + g_free(msginfo->to); + g_free(msginfo->cc); + g_free(msginfo->newsgroups); + g_free(msginfo->subject); + g_free(msginfo->msgid); + g_free(msginfo->inreplyto); + + slist_free_strings(msginfo->references); + g_slist_free(msginfo->references); + + g_free(msginfo->file_path); + + if (msginfo->encinfo) { + g_free(msginfo->encinfo->plaintext_file); + g_free(msginfo->encinfo->sigstatus); + g_free(msginfo->encinfo->sigstatus_full); + g_free(msginfo->encinfo); + } + + g_free(msginfo); +} + +gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b) +{ + const MsgInfo *msginfo1 = a; + const MsgInfo *msginfo2 = b; + + if (!msginfo1 || !msginfo2) + return 0; + + return msginfo1->msgnum - msginfo2->msgnum; +} + +#define CMP_FUNC_DEF(func_name, val) \ +static gint func_name(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + gint ret; \ + \ + if (!msginfo1 || !msginfo2) \ + return 0; \ + \ + ret = (val); \ + if (ret == 0) \ + ret = msginfo1->date_t - msginfo2->date_t; \ + \ + return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} + +CMP_FUNC_DEF(procmsg_cmp_by_mark, + MSG_IS_MARKED(msginfo1->flags) - MSG_IS_MARKED(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_unread, + MSG_IS_UNREAD(msginfo1->flags) - MSG_IS_UNREAD(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_mime, + MSG_IS_MIME(msginfo1->flags) - MSG_IS_MIME(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_label, + MSG_GET_COLORLABEL(msginfo1->flags) - + MSG_GET_COLORLABEL(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_size, msginfo1->size - msginfo2->size) + +#undef CMP_FUNC_DEF +#define CMP_FUNC_DEF(func_name, val) \ +static gint func_name(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + \ + if (!msginfo1 || !msginfo2) \ + return 0; \ + \ + return (val) * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} + +CMP_FUNC_DEF(procmsg_cmp_by_number, msginfo1->msgnum - msginfo2->msgnum) +CMP_FUNC_DEF(procmsg_cmp_by_date, msginfo1->date_t - msginfo2->date_t) + +#undef CMP_FUNC_DEF +#define CMP_FUNC_DEF(func_name, var_name) \ +static gint func_name(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + gint ret; \ + \ + if (!msginfo1->var_name) \ + return (msginfo2->var_name != NULL) * \ + (cmp_func_sort_type == SORT_ASCENDING ? -1 : 1);\ + if (!msginfo2->var_name) \ + return (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ + \ + ret = g_ascii_strcasecmp \ + (msginfo1->var_name, msginfo2->var_name); \ + if (ret == 0) \ + ret = msginfo1->date_t - msginfo2->date_t; \ + \ + return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} + +CMP_FUNC_DEF(procmsg_cmp_by_from, fromname) +CMP_FUNC_DEF(procmsg_cmp_by_to, to) + +#undef CMP_FUNC_DEF + +static gint procmsg_cmp_by_subject(gconstpointer a, gconstpointer b) +{ + const MsgInfo *msginfo1 = a; + const MsgInfo *msginfo2 = b; + gint ret; + + if (!msginfo1->subject) + return (msginfo2->subject != NULL) * + (cmp_func_sort_type == SORT_ASCENDING ? -1 : 1); + if (!msginfo2->subject) + return (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); + + ret = subject_compare_for_sort(msginfo1->subject, msginfo2->subject); + if (ret == 0) + ret = msginfo1->date_t - msginfo2->date_t; + + return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); +} -- cgit v1.3