aboutsummaryrefslogtreecommitdiff
path: root/libsylph
diff options
context:
space:
mode:
authorhiro <hiro@ee746299-78ed-0310-b773-934348b2243d>2005-09-05 10:00:53 +0000
committerhiro <hiro@ee746299-78ed-0310-b773-934348b2243d>2005-09-05 10:00:53 +0000
commit3bf24b9336184fe9e28f7e09b9c5200a5f82b7d2 (patch)
tree51ccac6f26dcdf9fcfac1a7879477bfde2759b80 /libsylph
parent11776e5a524745b01ac145439ac2892a29bd0826 (diff)
moved more modules to libsylph.
git-svn-id: svn://sylpheed.sraoss.jp/sylpheed/trunk@548 ee746299-78ed-0310-b773-934348b2243d
Diffstat (limited to 'libsylph')
-rw-r--r--libsylph/Makefile.am21
-rw-r--r--libsylph/account.c416
-rw-r--r--libsylph/account.h61
-rw-r--r--libsylph/customheader.c233
-rw-r--r--libsylph/customheader.h45
-rw-r--r--libsylph/enums.h55
-rw-r--r--libsylph/filter.c1421
-rw-r--r--libsylph/filter.h214
-rw-r--r--libsylph/folder.c1583
-rw-r--r--libsylph/folder.h395
-rw-r--r--libsylph/imap.c4105
-rw-r--r--libsylph/imap.h114
-rw-r--r--libsylph/md5.c433
-rw-r--r--libsylph/md5.h49
-rw-r--r--libsylph/mh.c1431
-rw-r--r--libsylph/mh.h38
-rw-r--r--libsylph/news.c1062
-rw-r--r--libsylph/news.h59
-rw-r--r--libsylph/nntp.c431
-rw-r--r--libsylph/nntp.h109
-rw-r--r--libsylph/pop.c857
-rw-r--r--libsylph/pop.h153
-rw-r--r--libsylph/prefs_account.c254
-rw-r--r--libsylph/prefs_account.h182
-rw-r--r--libsylph/prefs_common.c429
-rw-r--r--libsylph/prefs_common.h257
-rw-r--r--libsylph/procheader.c799
-rw-r--r--libsylph/procheader.h93
-rw-r--r--libsylph/procmime.c1158
-rw-r--r--libsylph/procmime.h186
-rw-r--r--libsylph/procmsg.c1412
-rw-r--r--libsylph/procmsg.h285
-rw-r--r--libsylph/smtp.c613
-rw-r--r--libsylph/smtp.h117
34 files changed, 19069 insertions, 1 deletions
diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am
index c0cfc8f8..31771946 100644
--- a/libsylph/Makefile.am
+++ b/libsylph/Makefile.am
@@ -1,5 +1,7 @@
-AM_CPPFLAGS = -DG_LOG_DOMAIN=\"LibSylph\"
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"LibSylph\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\"
INCLUDES = $(GLIB_CFLAGS) -I$(top_srcdir) -I$(includedir)
@@ -8,13 +10,30 @@ noinst_LTLIBRARIES = libsylph.la
libsylph_la_SOURCES = \
defs.h \
+ enums.h \
+ account.c account.h \
base64.c base64.h \
codeconv.c codeconv.h \
+ customheader.c customheader.h \
+ filter.c filter.h \
+ folder.c folder.h \
html.c html.h \
+ imap.c imap.h \
+ md5.c md5.h \
+ mh.c mh.h \
+ news.c news.h \
+ nntp.c nntp.h \
+ pop.c pop.h \
prefs.c prefs.h \
+ prefs_account.c prefs_account.h \
+ prefs_common.c prefs_common.h \
+ procheader.c procheader.h \
+ procmime.c procmime.h \
+ procmsg.c procmsg.h \
quoted-printable.c quoted-printable.h \
recv.c recv.h \
session.c session.h \
+ smtp.c smtp.h \
socket.c socket.h \
ssl.c ssl.h \
stringtable.c stringtable.h \
diff --git a/libsylph/account.c b/libsylph/account.c
new file mode 100644
index 00000000..2ae0ca43
--- /dev/null
+++ b/libsylph/account.c
@@ -0,0 +1,416 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "folder.h"
+#include "account.h"
+#include "prefs.h"
+#include "prefs_account.h"
+#include "procmsg.h"
+#include "procheader.h"
+#include "utils.h"
+
+#define PREFSBUFSIZE 1024
+
+PrefsAccount *cur_account;
+
+static GList *account_list = NULL;
+
+
+void account_read_config_all(void)
+{
+ GSList *ac_label_list = NULL, *cur;
+ gchar *rcpath;
+ FILE *fp;
+ gchar buf[PREFSBUFSIZE];
+ PrefsAccount *ac_prefs;
+
+ debug_print(_("Reading all config for each account...\n"));
+
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL);
+ if ((fp = g_fopen(rcpath, "rb")) == NULL) {
+ if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
+ g_free(rcpath);
+ return;
+ }
+ g_free(rcpath);
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (!strncmp(buf, "[Account: ", 10)) {
+ strretchomp(buf);
+ memmove(buf, buf + 1, strlen(buf));
+ buf[strlen(buf) - 1] = '\0';
+ debug_print("Found label: %s\n", buf);
+ ac_label_list = g_slist_append(ac_label_list,
+ g_strdup(buf));
+ }
+ }
+ fclose(fp);
+
+ /* read config data from file */
+ cur_account = NULL;
+ for (cur = ac_label_list; cur != NULL; cur = cur->next) {
+ ac_prefs = prefs_account_new();
+ prefs_account_read_config(ac_prefs, (gchar *)cur->data);
+ account_list = g_list_append(account_list, ac_prefs);
+ if (ac_prefs->is_default)
+ cur_account = ac_prefs;
+ }
+ /* if default is not set, assume first account as default */
+ if (!cur_account && account_list) {
+ ac_prefs = (PrefsAccount *)account_list->data;
+ account_set_as_default(ac_prefs);
+ cur_account = ac_prefs;
+ }
+
+ while (ac_label_list) {
+ g_free(ac_label_list->data);
+ ac_label_list = g_slist_remove(ac_label_list,
+ ac_label_list->data);
+ }
+}
+
+void account_write_config_all(void)
+{
+ prefs_account_write_config_all(account_list);
+}
+
+PrefsAccount *account_find_from_smtp_server(const gchar *address,
+ const gchar *smtp_server)
+{
+ GList *cur;
+ PrefsAccount *ac;
+
+ g_return_val_if_fail(address != NULL, NULL);
+ g_return_val_if_fail(smtp_server != NULL, NULL);
+
+ for (cur = account_list; cur != NULL; cur = cur->next) {
+ ac = (PrefsAccount *)cur->data;
+ if (!strcmp2(address, ac->address) &&
+ !strcmp2(smtp_server, ac->smtp_server))
+ return ac;
+ }
+
+ return NULL;
+}
+
+/*
+ * account_find_from_address:
+ * @address: Email address string.
+ *
+ * Find a mail (not news) account with the specified email address.
+ *
+ * Return value: The found account, or NULL if not found.
+ */
+PrefsAccount *account_find_from_address(const gchar *address)
+{
+ GList *cur;
+ PrefsAccount *ac;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ for (cur = account_list; cur != NULL; cur = cur->next) {
+ ac = (PrefsAccount *)cur->data;
+ if (ac->protocol != A_NNTP && ac->address &&
+ strcasestr(address, ac->address) != NULL)
+ return ac;
+ }
+
+ return NULL;
+}
+
+PrefsAccount *account_find_from_id(gint id)
+{
+ GList *cur;
+ PrefsAccount *ac;
+
+ for (cur = account_list; cur != NULL; cur = cur->next) {
+ ac = (PrefsAccount *)cur->data;
+ if (id == ac->account_id)
+ return ac;
+ }
+
+ return NULL;
+}
+
+PrefsAccount *account_find_from_item(FolderItem *item)
+{
+ PrefsAccount *ac;
+
+ g_return_val_if_fail(item != NULL, NULL);
+
+ ac = item->account;
+ if (!ac) {
+ FolderItem *cur_item = item->parent;
+ while (cur_item != NULL) {
+ if (cur_item->account && cur_item->ac_apply_sub) {
+ ac = cur_item->account;
+ break;
+ }
+ cur_item = cur_item->parent;
+ }
+ }
+ if (!ac)
+ ac = item->folder->account;
+
+ return ac;
+}
+
+PrefsAccount *account_find_from_message_file(const gchar *file)
+{
+ static HeaderEntry hentry[] = {{"From:", NULL, FALSE},
+ {"X-Sylpheed-Account-Id:", NULL, FALSE},
+ {"AID:", NULL, FALSE},
+ {NULL, NULL, FALSE}};
+
+ enum
+ {
+ H_FROM = 0,
+ H_X_SYLPHEED_ACCOUNT_ID = 1,
+ H_AID = 2
+ };
+
+ PrefsAccount *ac = NULL;
+ FILE *fp;
+ gchar *str;
+ gchar buf[BUFFSIZE];
+ gint hnum;
+
+ g_return_val_if_fail(file != NULL, NULL);
+
+ if ((fp = g_fopen(file, "rb")) == NULL) {
+ FILE_OP_ERROR(file, "fopen");
+ return NULL;
+ }
+
+ while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
+ != -1) {
+ str = buf + strlen(hentry[hnum].name);
+ if (hnum == H_FROM)
+ ac = account_find_from_address(str);
+ else if (hnum == H_X_SYLPHEED_ACCOUNT_ID || hnum == H_AID) {
+ PrefsAccount *tmp_ac;
+
+ tmp_ac = account_find_from_id(atoi(str));
+ if (tmp_ac) {
+ ac = tmp_ac;
+ break;
+ }
+ }
+ }
+
+ fclose(fp);
+ return ac;
+}
+
+PrefsAccount *account_find_from_msginfo(MsgInfo *msginfo)
+{
+ gchar *file;
+ PrefsAccount *ac;
+
+ file = procmsg_get_message_file(msginfo);
+ ac = account_find_from_message_file(file);
+ g_free(file);
+
+ if (!ac && msginfo->folder)
+ ac = account_find_from_item(msginfo->folder);
+
+ return ac;
+}
+
+void account_foreach(AccountFunc func, gpointer user_data)
+{
+ GList *cur;
+
+ for (cur = account_list; cur != NULL; cur = cur->next)
+ if (func((PrefsAccount *)cur->data, user_data) != 0)
+ return;
+}
+
+GList *account_get_list(void)
+{
+ return account_list;
+}
+
+void account_list_free(void)
+{
+ g_list_free(account_list);
+ account_list = NULL;
+}
+
+void account_append(PrefsAccount *ac_prefs)
+{
+ account_list = g_list_append(account_list, ac_prefs);
+}
+
+void account_set_as_default(PrefsAccount *ac_prefs)
+{
+ PrefsAccount *ap;
+ GList *cur;
+
+ for (cur = account_list; cur != NULL; cur = cur->next) {
+ ap = (PrefsAccount *)cur->data;
+ if (ap->is_default)
+ ap->is_default = FALSE;
+ }
+
+ ac_prefs->is_default = TRUE;
+}
+
+PrefsAccount *account_get_default(void)
+{
+ PrefsAccount *ap;
+ GList *cur;
+
+ for (cur = account_list; cur != NULL; cur = cur->next) {
+ ap = (PrefsAccount *)cur->data;
+ if (ap->is_default)
+ return ap;
+ }
+
+ return NULL;
+}
+
+#if 0
+void account_set_missing_folder(void)
+{
+ PrefsAccount *ap;
+ GList *cur;
+
+ for (cur = account_list; cur != NULL; cur = cur->next) {
+ ap = (PrefsAccount *)cur->data;
+ if ((ap->protocol == A_IMAP4 || ap->protocol == A_NNTP) &&
+ !ap->folder) {
+ Folder *folder;
+
+ if (ap->protocol == A_IMAP4) {
+ folder = folder_new(F_IMAP, ap->account_name,
+ ap->recv_server);
+ } else {
+ folder = folder_new(F_NEWS, ap->account_name,
+ ap->nntp_server);
+ }
+
+ folder->account = ap;
+ ap->folder = REMOTE_FOLDER(folder);
+ folder_add(folder);
+ if (ap->protocol == A_IMAP4) {
+ if (main_window_toggle_online_if_offline
+ (main_window_get())) {
+ folder->klass->create_tree(folder);
+ statusbar_pop_all();
+ }
+ }
+ }
+ }
+}
+#endif
+
+FolderItem *account_get_special_folder(PrefsAccount *ac_prefs,
+ SpecialFolderItemType type)
+{
+ FolderItem *item = NULL;
+
+ g_return_val_if_fail(ac_prefs != NULL, NULL);
+
+ switch (type) {
+ case F_INBOX:
+ if (ac_prefs->folder)
+ item = FOLDER(ac_prefs->folder)->inbox;
+ if (!item)
+ item = folder_get_default_inbox();
+ break;
+ case F_OUTBOX:
+ if (ac_prefs->set_sent_folder && ac_prefs->sent_folder) {
+ item = folder_find_item_from_identifier
+ (ac_prefs->sent_folder);
+ }
+ if (!item) {
+ if (ac_prefs->folder)
+ item = FOLDER(ac_prefs->folder)->outbox;
+ if (!item)
+ item = folder_get_default_outbox();
+ }
+ break;
+ case F_DRAFT:
+ if (ac_prefs->set_draft_folder && ac_prefs->draft_folder) {
+ item = folder_find_item_from_identifier
+ (ac_prefs->draft_folder);
+ }
+ if (!item) {
+ if (ac_prefs->folder)
+ item = FOLDER(ac_prefs->folder)->draft;
+ if (!item)
+ item = folder_get_default_draft();
+ }
+ break;
+ case F_QUEUE:
+ if (ac_prefs->folder)
+ item = FOLDER(ac_prefs->folder)->queue;
+ if (!item)
+ item = folder_get_default_queue();
+ break;
+ case F_TRASH:
+ if (ac_prefs->set_trash_folder && ac_prefs->trash_folder) {
+ item = folder_find_item_from_identifier
+ (ac_prefs->trash_folder);
+ }
+ if (!item) {
+ if (ac_prefs->folder)
+ item = FOLDER(ac_prefs->folder)->trash;
+ if (!item)
+ item = folder_get_default_trash();
+ }
+ break;
+ default:
+ break;
+ }
+
+ return item;
+}
+
+void account_destroy(PrefsAccount *ac_prefs)
+{
+ g_return_if_fail(ac_prefs != NULL);
+
+ folder_unref_account_all(ac_prefs);
+
+ prefs_account_free(ac_prefs);
+ account_list = g_list_remove(account_list, ac_prefs);
+
+ if (cur_account == ac_prefs) cur_account = NULL;
+ if (!cur_account && account_list) {
+ cur_account = account_get_default();
+ if (!cur_account) {
+ ac_prefs = (PrefsAccount *)account_list->data;
+ account_set_as_default(ac_prefs);
+ cur_account = ac_prefs;
+ }
+ }
+}
diff --git a/libsylph/account.h b/libsylph/account.h
new file mode 100644
index 00000000..0cef7c50
--- /dev/null
+++ b/libsylph/account.h
@@ -0,0 +1,61 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __ACCOUNT_H__
+#define __ACCOUNT_H__
+
+#include <glib.h>
+
+#include "prefs.h"
+#include "prefs_account.h"
+#include "folder.h"
+#include "procmsg.h"
+
+typedef gint (*AccountFunc) (PrefsAccount *ac_prefs,
+ gpointer user_data);
+
+extern PrefsAccount *cur_account;
+
+void account_read_config_all (void);
+void account_write_config_all (void);
+
+PrefsAccount *account_find_from_smtp_server (const gchar *address,
+ const gchar *smtp_server);
+PrefsAccount *account_find_from_address (const gchar *address);
+PrefsAccount *account_find_from_id (gint id);
+PrefsAccount *account_find_from_item (FolderItem *item);
+PrefsAccount *account_find_from_message_file (const gchar *file);
+PrefsAccount *account_find_from_msginfo (MsgInfo *msginfo);
+
+void account_foreach (AccountFunc func,
+ gpointer user_data);
+GList *account_get_list (void);
+void account_list_free (void);
+void account_append (PrefsAccount *ac_prefs);
+
+void account_set_as_default (PrefsAccount *ac_prefs);
+PrefsAccount *account_get_default (void);
+
+//void account_set_missing_folder(void);
+FolderItem *account_get_special_folder(PrefsAccount *ac_prefs,
+ SpecialFolderItemType type);
+
+void account_destroy (PrefsAccount *ac_prefs);
+
+#endif /* __ACCOUNT_H__ */
diff --git a/libsylph/customheader.c b/libsylph/customheader.c
new file mode 100644
index 00000000..c145484d
--- /dev/null
+++ b/libsylph/customheader.c
@@ -0,0 +1,233 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "customheader.h"
+#include "prefs.h"
+#include "prefs_account.h"
+#include "utils.h"
+
+
+void custom_header_read_config(PrefsAccount *ac)
+{
+ gchar *rcpath;
+ FILE *fp;
+ gchar buf[PREFSBUFSIZE];
+ CustomHeader *ch;
+
+ debug_print("Reading custom header configuration...\n");
+
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+ CUSTOM_HEADER_RC, NULL);
+ if ((fp = g_fopen(rcpath, "rb")) == NULL) {
+ if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
+ g_free(rcpath);
+ ac->customhdr_list = NULL;
+ return;
+ }
+ g_free(rcpath);
+
+ /* remove all previous headers list */
+ while (ac->customhdr_list != NULL) {
+ ch = (CustomHeader *)ac->customhdr_list->data;
+ custom_header_free(ch);
+ ac->customhdr_list = g_slist_remove(ac->customhdr_list, ch);
+ }
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ ch = custom_header_read_str(buf);
+ if (ch) {
+ if (ch->account_id == ac->account_id) {
+ ac->customhdr_list =
+ g_slist_append(ac->customhdr_list, ch);
+ } else
+ custom_header_free(ch);
+ }
+ }
+
+ fclose(fp);
+}
+
+void custom_header_write_config(PrefsAccount *ac)
+{
+ gchar *rcpath;
+ PrefFile *pfile;
+ GSList *cur;
+ gchar buf[PREFSBUFSIZE];
+ FILE * fp;
+ CustomHeader *ch;
+
+ GSList *all_hdrs = NULL;
+
+ debug_print("Writing custom header configuration...\n");
+
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+ CUSTOM_HEADER_RC, NULL);
+
+ if ((fp = g_fopen(rcpath, "rb")) == NULL) {
+ if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
+ } else {
+ all_hdrs = NULL;
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ ch = custom_header_read_str(buf);
+ if (ch) {
+ if (ch->account_id != ac->account_id)
+ all_hdrs =
+ g_slist_append(all_hdrs, ch);
+ else
+ custom_header_free(ch);
+ }
+ }
+
+ fclose(fp);
+ }
+
+ if ((pfile = prefs_file_open(rcpath)) == NULL) {
+ g_warning("failed to write configuration to file\n");
+ g_free(rcpath);
+ return;
+ }
+
+ for (cur = all_hdrs; cur != NULL; cur = cur->next) {
+ CustomHeader *hdr = (CustomHeader *)cur->data;
+ gchar *chstr;
+
+ chstr = custom_header_get_str(hdr);
+ if (fputs(chstr, pfile->fp) == EOF ||
+ fputc('\n', pfile->fp) == EOF) {
+ FILE_OP_ERROR(rcpath, "fputs || fputc");
+ prefs_file_close_revert(pfile);
+ g_free(rcpath);
+ g_free(chstr);
+ return;
+ }
+ g_free(chstr);
+ }
+
+ for (cur = ac->customhdr_list; cur != NULL; cur = cur->next) {
+ CustomHeader *hdr = (CustomHeader *)cur->data;
+ gchar *chstr;
+
+ chstr = custom_header_get_str(hdr);
+ if (fputs(chstr, pfile->fp) == EOF ||
+ fputc('\n', pfile->fp) == EOF) {
+ FILE_OP_ERROR(rcpath, "fputs || fputc");
+ prefs_file_close_revert(pfile);
+ g_free(rcpath);
+ g_free(chstr);
+ return;
+ }
+ g_free(chstr);
+ }
+
+ g_free(rcpath);
+
+ while (all_hdrs != NULL) {
+ ch = (CustomHeader *)all_hdrs->data;
+ custom_header_free(ch);
+ all_hdrs = g_slist_remove(all_hdrs, ch);
+ }
+
+ if (prefs_file_close(pfile) < 0) {
+ g_warning("failed to write configuration to file\n");
+ return;
+ }
+}
+
+gchar *custom_header_get_str(CustomHeader *ch)
+{
+ return g_strdup_printf("%i:%s: %s",
+ ch->account_id, ch->name,
+ ch->value ? ch->value : "");
+}
+
+CustomHeader *custom_header_read_str(const gchar *buf)
+{
+ CustomHeader *ch;
+ gchar *account_id_str;
+ gint id;
+ gchar *name;
+ gchar *value;
+ gchar *tmp;
+
+ Xstrdup_a(tmp, buf, return NULL);
+
+ account_id_str = tmp;
+
+ name = strchr(account_id_str, ':');
+ if (!name)
+ return NULL;
+ else {
+ gchar *endp;
+
+ *name++ = '\0';
+ id = strtol(account_id_str, &endp, 10);
+ if (*endp != '\0') return NULL;
+ }
+
+ value = strchr(name, ':');
+ if (!value) return NULL;
+
+ *value++ = '\0';
+
+ g_strstrip(name);
+ g_strstrip(value);
+
+ ch = g_new0(CustomHeader, 1);
+ ch->account_id = id;
+ ch->name = *name ? g_strdup(name) : NULL;
+ ch->value = *value ? g_strdup(value) : NULL;
+
+ return ch;
+}
+
+CustomHeader *custom_header_find(GSList *header_list, const gchar *header)
+{
+ GSList *cur;
+ CustomHeader *chdr;
+
+ for (cur = header_list; cur != NULL; cur = cur->next) {
+ chdr = (CustomHeader *)cur->data;
+ if (!g_ascii_strcasecmp(chdr->name, header))
+ return chdr;
+ }
+
+ return NULL;
+}
+
+void custom_header_free(CustomHeader *ch)
+{
+ if (!ch) return;
+
+ g_free(ch->name);
+ g_free(ch->value);
+ g_free(ch);
+}
diff --git a/libsylph/customheader.h b/libsylph/customheader.h
new file mode 100644
index 00000000..27ae9373
--- /dev/null
+++ b/libsylph/customheader.h
@@ -0,0 +1,45 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __CUSTOMHEADER_H__
+#define __CUSTOMHEADER_H__
+
+#include <glib.h>
+
+typedef struct _CustomHeader CustomHeader;
+
+#include "prefs_account.h"
+
+struct _CustomHeader
+{
+ gint account_id;
+ gchar *name;
+ gchar *value;
+};
+
+void custom_header_read_config (PrefsAccount *ac);
+void custom_header_write_config (PrefsAccount *ac);
+
+gchar *custom_header_get_str (CustomHeader *ch);
+CustomHeader *custom_header_read_str (const gchar *buf);
+CustomHeader *custom_header_find (GSList *header_list,
+ const gchar *header);
+void custom_header_free (CustomHeader *ch);
+
+#endif /* __CUSTOMHEADER_H__ */
diff --git a/libsylph/enums.h b/libsylph/enums.h
new file mode 100644
index 00000000..13ac2a18
--- /dev/null
+++ b/libsylph/enums.h
@@ -0,0 +1,55 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __ENUMS_H__
+#define __ENUMS_H__
+
+typedef enum
+{
+ TOOLBAR_NONE,
+ TOOLBAR_ICON,
+ TOOLBAR_TEXT,
+ TOOLBAR_BOTH
+} ToolbarStyle;
+
+typedef enum
+{
+ S_COL_MARK,
+ S_COL_UNREAD,
+ S_COL_MIME,
+ S_COL_SUBJECT,
+ S_COL_FROM,
+ S_COL_DATE,
+ S_COL_SIZE,
+ S_COL_NUMBER,
+
+ S_COL_MSG_INFO,
+
+ S_COL_LABEL,
+ S_COL_TO,
+
+ S_COL_FOREGROUND,
+ S_COL_BOLD,
+
+ N_SUMMARY_COLS
+} SummaryColumnType;
+
+#define N_SUMMARY_VISIBLE_COLS S_COL_MSG_INFO
+
+#endif /* __ENUMS_H__ */
diff --git a/libsylph/filter.c b/libsylph/filter.c
new file mode 100644
index 00000000..efc2fa02
--- /dev/null
+++ b/libsylph/filter.c
@@ -0,0 +1,1421 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#if HAVE_REGEX_H
+# include <regex.h>
+#endif
+#include <time.h>
+
+#include "filter.h"
+#include "procmsg.h"
+#include "procheader.h"
+#include "folder.h"
+#include "utils.h"
+#include "xml.h"
+#include "prefs.h"
+#include "prefs_common.h"
+#include "prefs_account.h"
+#include "account.h"
+
+typedef enum
+{
+ FLT_O_CONTAIN = 1 << 0,
+ FLT_O_CASE_SENS = 1 << 1,
+ FLT_O_REGEX = 1 << 2
+} FilterOldFlag;
+
+static gboolean filter_match_cond (FilterCond *cond,
+ MsgInfo *msginfo,
+ GSList *hlist,
+ FilterInfo *fltinfo);
+static gboolean filter_match_header_cond(FilterCond *cond,
+ GSList *hlist);
+
+static void filter_cond_free (FilterCond *cond);
+static void filter_action_free (FilterAction *action);
+
+
+gint filter_apply(GSList *fltlist, const gchar *file, FilterInfo *fltinfo)
+{
+ MsgInfo *msginfo;
+ gint ret = 0;
+
+ g_return_val_if_fail(file != NULL, -1);
+ g_return_val_if_fail(fltinfo != NULL, -1);
+
+ if (!fltlist) return 0;
+
+ msginfo = procheader_parse_file(file, fltinfo->flags, FALSE);
+ if (!msginfo) return 0;
+ msginfo->file_path = g_strdup(file);
+
+ ret = filter_apply_msginfo(fltlist, msginfo, fltinfo);
+
+ procmsg_msginfo_free(msginfo);
+
+ return ret;
+}
+
+gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo,
+ FilterInfo *fltinfo)
+{
+ gchar *file;
+ GSList *hlist, *cur;
+ FilterRule *rule;
+ gint ret = 0;
+
+ g_return_val_if_fail(msginfo != NULL, -1);
+ g_return_val_if_fail(fltinfo != NULL, -1);
+
+ if (!fltlist) return 0;
+
+ file = procmsg_get_message_file(msginfo);
+ hlist = procheader_get_header_list_from_file(file);
+ if (!hlist) {
+ g_free(file);
+ return 0;
+ }
+
+ for (cur = fltlist; cur != NULL; cur = cur->next) {
+ rule = (FilterRule *)cur->data;
+ if (!rule->enabled) continue;
+ if (filter_match_rule(rule, msginfo, hlist, fltinfo)) {
+ ret = filter_action_exec(rule, msginfo, file, fltinfo);
+ if (ret < 0) {
+ g_warning("filter_action_exec() returned error\n");
+ break;
+ }
+ if (fltinfo->drop_done == TRUE ||
+ fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE)
+ break;
+ }
+ }
+
+ procheader_header_list_destroy(hlist);
+ g_free(file);
+
+ return ret;
+}
+
+gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file,
+ FilterInfo *fltinfo)
+{
+ FolderItem *dest_folder = NULL;
+ FilterAction *action;
+ GSList *cur;
+ gchar *cmdline;
+ gboolean copy_to_self = FALSE;
+
+ g_return_val_if_fail(rule != NULL, -1);
+ g_return_val_if_fail(msginfo != NULL, -1);
+ g_return_val_if_fail(file != NULL, -1);
+ g_return_val_if_fail(fltinfo != NULL, -1);
+
+ for (cur = rule->action_list; cur != NULL; cur = cur->next) {
+ action = (FilterAction *)cur->data;
+
+ switch (action->type) {
+ case FLT_ACTION_MARK:
+ debug_print("filter_action_exec(): mark\n");
+ MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED);
+ fltinfo->actions[action->type] = TRUE;
+ break;
+ case FLT_ACTION_COLOR_LABEL:
+ debug_print("filter_action_exec(): color label: %d\n",
+ action->int_value);
+ MSG_UNSET_PERM_FLAGS(fltinfo->flags,
+ MSG_CLABEL_FLAG_MASK);
+ MSG_SET_COLORLABEL_VALUE(fltinfo->flags,
+ action->int_value);
+ fltinfo->actions[action->type] = TRUE;
+ break;
+ case FLT_ACTION_MARK_READ:
+ debug_print("filter_action_exec(): mark as read\n");
+ if (msginfo->folder) {
+ if (MSG_IS_NEW(fltinfo->flags))
+ msginfo->folder->new--;
+ if (MSG_IS_UNREAD(fltinfo->flags))
+ msginfo->folder->unread--;
+ }
+ MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD);
+ fltinfo->actions[action->type] = TRUE;
+ break;
+ case FLT_ACTION_EXEC:
+ cmdline = g_strconcat(action->str_value, " ", file,
+ NULL);
+ execute_command_line(cmdline, FALSE);
+ g_free(cmdline);
+ fltinfo->actions[action->type] = TRUE;
+ break;
+ case FLT_ACTION_EXEC_ASYNC:
+ cmdline = g_strconcat(action->str_value, " ", file,
+ NULL);
+ execute_command_line(cmdline, TRUE);
+ g_free(cmdline);
+ fltinfo->actions[action->type] = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ for (cur = rule->action_list; cur != NULL; cur = cur->next) {
+ action = (FilterAction *)cur->data;
+
+ switch (action->type) {
+ case FLT_ACTION_MOVE:
+ case FLT_ACTION_COPY:
+ dest_folder = folder_find_item_from_identifier
+ (action->str_value);
+ if (!dest_folder) {
+ g_warning("dest folder '%s' not found\n",
+ action->str_value);
+ return -1;
+ }
+ debug_print("filter_action_exec(): %s: dest_folder = %s\n",
+ action->type == FLT_ACTION_COPY ?
+ "copy" : "move", action->str_value);
+
+ if (msginfo->folder) {
+ gint val;
+
+ /* local filtering */
+ if (msginfo->folder == dest_folder)
+ copy_to_self = TRUE;
+ else {
+ if (action->type == FLT_ACTION_COPY) {
+ val = folder_item_copy_msg
+ (dest_folder, msginfo);
+ if (val == -1)
+ return -1;
+ }
+ fltinfo->actions[action->type] = TRUE;
+ }
+ } else {
+ if (folder_item_add_msg(dest_folder, file,
+ &fltinfo->flags,
+ FALSE) < 0)
+ return -1;
+ fltinfo->actions[action->type] = TRUE;
+ }
+
+ fltinfo->dest_list = g_slist_append(fltinfo->dest_list,
+ dest_folder);
+ if (action->type == FLT_ACTION_MOVE) {
+ fltinfo->move_dest = dest_folder;
+ fltinfo->drop_done = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (fltinfo->drop_done == TRUE)
+ return 0;
+
+ for (cur = rule->action_list; cur != NULL; cur = cur->next) {
+ action = (FilterAction *)cur->data;
+
+ switch (action->type) {
+ case FLT_ACTION_NOT_RECEIVE:
+ debug_print("filter_action_exec(): don't receive\n");
+ fltinfo->drop_done = TRUE;
+ fltinfo->actions[action->type] = TRUE;
+ return 0;
+ case FLT_ACTION_DELETE:
+ debug_print("filter_action_exec(): delete\n");
+ if (msginfo->folder) {
+ /* local filtering */
+ if (copy_to_self == FALSE)
+ fltinfo->actions[action->type] = TRUE;
+ } else
+ fltinfo->actions[action->type] = TRUE;
+
+ fltinfo->drop_done = TRUE;
+ return 0;
+ case FLT_ACTION_STOP_EVAL:
+ debug_print("filter_action_exec(): stop evaluation\n");
+ fltinfo->actions[action->type] = TRUE;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static gboolean strmatch_regex(const gchar *haystack, const gchar *needle)
+{
+#if HAVE_REGEX_H && HAVE_REGCOMP
+ gint ret = 0;
+ regex_t preg;
+ regmatch_t pmatch[1];
+
+ ret = regcomp(&preg, needle, REG_EXTENDED|REG_ICASE);
+ if (ret != 0) return FALSE;
+
+ ret = regexec(&preg, haystack, 1, pmatch, 0);
+ regfree(&preg);
+
+ if (ret == REG_NOMATCH) return FALSE;
+
+ if (pmatch[0].rm_so != -1)
+ return TRUE;
+ else
+#endif
+ return FALSE;
+}
+
+gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist,
+ FilterInfo *fltinfo)
+{
+ FilterCond *cond;
+ GSList *cur;
+ gboolean matched;
+
+ g_return_val_if_fail(rule->cond_list != NULL, FALSE);
+ g_return_val_if_fail(rule->action_list != NULL, FALSE);
+
+ switch (rule->timing) {
+ case FLT_TIMING_ANY:
+ break;
+ case FLT_TIMING_ON_RECEIVE:
+ if (msginfo->folder != NULL)
+ return FALSE;
+ break;
+ case FLT_TIMING_MANUAL:
+ if (msginfo->folder == NULL)
+ return FALSE;
+ break;
+ default:
+ break;
+ }
+
+ if (rule->bool_op == FLT_AND) {
+ for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
+ cond = (FilterCond *)cur->data;
+ matched = filter_match_cond(cond, msginfo, hlist,
+ fltinfo);
+ if (matched == FALSE)
+ return FALSE;
+ }
+
+ return TRUE;
+ } else if (rule->bool_op == FLT_OR) {
+ for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
+ cond = (FilterCond *)cur->data;
+ matched = filter_match_cond(cond, msginfo, hlist,
+ fltinfo);
+ if (matched == TRUE)
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo,
+ GSList *hlist, FilterInfo *fltinfo)
+{
+ gboolean matched = FALSE;
+ gchar *file;
+ gchar *cmdline;
+ PrefsAccount *cond_ac;
+
+ switch (cond->type) {
+ case FLT_COND_HEADER:
+ case FLT_COND_ANY_HEADER:
+ case FLT_COND_TO_OR_CC:
+ return filter_match_header_cond(cond, hlist);
+ case FLT_COND_BODY:
+ matched = procmime_find_string(msginfo, cond->str_value,
+ cond->match_func);
+ break;
+ case FLT_COND_CMD_TEST:
+ file = procmsg_get_message_file(msginfo);
+ cmdline = g_strconcat(cond->str_value, " ", file, NULL);
+ matched = (execute_command_line(cmdline, FALSE) == 0);
+ g_free(cmdline);
+ g_free(file);
+ break;
+ case FLT_COND_SIZE_GREATER:
+ matched = (msginfo->size > cond->int_value * 1024);
+ break;
+ case FLT_COND_AGE_GREATER:
+ matched = (time(NULL) - msginfo->date_t >
+ cond->int_value * 24 * 60 * 60);
+ break;
+ case FLT_COND_ACCOUNT:
+ cond_ac = account_find_from_id(cond->int_value);
+ matched = (cond_ac != NULL && cond_ac == fltinfo->account);
+ break;
+ default:
+ g_warning("filter_match_cond(): unknown condition: %d\n",
+ cond->type);
+ return FALSE;
+ }
+
+ if (FLT_IS_NOT_MATCH(cond->match_flag))
+ matched = !matched;
+
+ return matched;
+}
+
+static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist)
+{
+ gboolean matched = FALSE;
+ GSList *cur;
+ Header *header;
+
+ for (cur = hlist; cur != NULL; cur = cur->next) {
+ header = (Header *)cur->data;
+
+ switch (cond->type) {
+ case FLT_COND_HEADER:
+ if (!g_ascii_strcasecmp
+ (header->name, cond->header_name)) {
+ if (!cond->str_value ||
+ cond->match_func(header->body,
+ cond->str_value))
+ matched = TRUE;
+ }
+ break;
+ case FLT_COND_ANY_HEADER:
+ if (!cond->str_value ||
+ cond->match_func(header->body, cond->str_value))
+ matched = TRUE;
+ break;
+ case FLT_COND_TO_OR_CC:
+ if (!g_ascii_strcasecmp(header->name, "To") ||
+ !g_ascii_strcasecmp(header->name, "Cc")) {
+ if (!cond->str_value ||
+ cond->match_func(header->body,
+ cond->str_value))
+ matched = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (matched == TRUE)
+ break;
+ }
+
+ if (FLT_IS_NOT_MATCH(cond->match_flag))
+ matched = !matched;
+
+ return matched;
+}
+
+#define RETURN_IF_TAG_NOT_MATCH(tag_name) \
+ if (strcmp2(xmlnode->tag->tag, tag_name) != 0) { \
+ g_warning("tag name != \"" tag_name "\"\n"); \
+ filter_cond_list_free(cond_list); \
+ filter_action_list_free(cond_list); \
+ return FALSE; \
+ } \
+
+#define STR_SWITCH(sw) { const gchar *sw_str = sw;
+#define STR_CASE_BEGIN(str) if (!strcmp(sw_str, str)) {
+#define STR_CASE(str) } else if (!strcmp(sw_str, str)) {
+#define STR_CASE_END } }
+
+static gboolean filter_xml_node_func(GNode *node, gpointer data)
+{
+ XMLNode *xmlnode;
+ GList *list;
+ const gchar *name = NULL;
+ FilterTiming timing = FLT_TIMING_ANY;
+ gboolean enabled = TRUE;
+ FilterRule *rule;
+ FilterBoolOp bool_op = FLT_OR;
+ GSList *cond_list = NULL;
+ GSList *action_list = NULL;
+ GNode *child, *cond_child, *action_child;
+ GSList **fltlist = (GSList **)data;
+
+ if (g_node_depth(node) != 2) return FALSE;
+ g_return_val_if_fail(node->data != NULL, FALSE);
+
+ xmlnode = node->data;
+ RETURN_IF_TAG_NOT_MATCH("rule");
+
+ for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
+ XMLAttr *attr = (XMLAttr *)list->data;
+
+ if (!attr || !attr->name || !attr->value) continue;
+ if (!strcmp(attr->name, "name"))
+ name = attr->value;
+ else if (!strcmp(attr->name, "timing")) {
+ if (!strcmp(attr->value, "any"))
+ timing = FLT_TIMING_ANY;
+ else if (!strcmp(attr->value, "receive"))
+ timing = FLT_TIMING_ON_RECEIVE;
+ else if (!strcmp(attr->value, "manual"))
+ timing = FLT_TIMING_MANUAL;
+ } else if (!strcmp(attr->name, "enabled")) {
+ if (!strcmp(attr->value, "true"))
+ enabled = TRUE;
+ else
+ enabled = FALSE;
+ }
+ }
+
+ /* condition list */
+ child = node->children;
+ if (!child) {
+ g_warning("<rule> must have children\n");
+ return FALSE;
+ }
+ xmlnode = child->data;
+ RETURN_IF_TAG_NOT_MATCH("condition-list");
+ for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
+ XMLAttr *attr = (XMLAttr *)list->data;
+
+ if (attr && attr->name && attr->value &&
+ !strcmp(attr->name, "bool")) {
+ if (!strcmp(attr->value, "or"))
+ bool_op = FLT_OR;
+ else
+ bool_op = FLT_AND;
+ }
+ }
+
+ for (cond_child = child->children; cond_child != NULL;
+ cond_child = cond_child->next) {
+ const gchar *type = NULL;
+ const gchar *name = NULL;
+ const gchar *value = NULL;
+ FilterCond *cond;
+ FilterCondType cond_type = FLT_COND_HEADER;
+ FilterMatchType match_type = FLT_CONTAIN;
+ FilterMatchFlag match_flag = 0;
+
+ xmlnode = cond_child->data;
+ if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
+
+ for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
+ XMLAttr *attr = (XMLAttr *)list->data;
+
+ if (!attr || !attr->name || !attr->value) continue;
+ if (!strcmp(attr->name, "type"))
+ type = attr->value;
+ else if (!strcmp(attr->name, "name"))
+ name = attr->value;
+ }
+
+ if (type) {
+ filter_rule_match_type_str_to_enum
+ (type, &match_type, &match_flag);
+ }
+ value = xmlnode->element;
+
+ STR_SWITCH(xmlnode->tag->tag)
+ STR_CASE_BEGIN("match-header")
+ cond_type = FLT_COND_HEADER;
+ STR_CASE("match-any-header")
+ cond_type = FLT_COND_ANY_HEADER;
+ STR_CASE("match-to-or-cc")
+ cond_type = FLT_COND_TO_OR_CC;
+ STR_CASE("match-body-text")
+ cond_type = FLT_COND_BODY;
+ STR_CASE("command-test")
+ cond_type = FLT_COND_CMD_TEST;
+ STR_CASE("size")
+ cond_type = FLT_COND_SIZE_GREATER;
+ STR_CASE("age")
+ cond_type = FLT_COND_AGE_GREATER;
+ STR_CASE("account-id")
+ cond_type = FLT_COND_ACCOUNT;
+ STR_CASE_END
+
+ cond = filter_cond_new(cond_type, match_type, match_flag,
+ name, value);
+ cond_list = g_slist_append(cond_list, cond);
+ }
+
+ /* action list */
+ child = child->next;
+ if (!child) {
+ g_warning("<rule> must have multiple children\n");
+ filter_cond_list_free(cond_list);
+ return FALSE;
+ }
+ xmlnode = child->data;
+ RETURN_IF_TAG_NOT_MATCH("action-list");
+
+ for (action_child = child->children; action_child != NULL;
+ action_child = action_child->next) {
+ FilterAction *action;
+ FilterActionType action_type = FLT_ACTION_NONE;
+
+ xmlnode = action_child->data;
+ if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
+
+ STR_SWITCH(xmlnode->tag->tag)
+ STR_CASE_BEGIN("move")
+ action_type = FLT_ACTION_MOVE;
+ STR_CASE("copy")
+ action_type = FLT_ACTION_COPY;
+ STR_CASE("not-receive")
+ action_type = FLT_ACTION_NOT_RECEIVE;
+ STR_CASE("delete")
+ action_type = FLT_ACTION_DELETE;
+ STR_CASE("exec")
+ action_type = FLT_ACTION_EXEC;
+ STR_CASE("exec-async")
+ action_type = FLT_ACTION_EXEC_ASYNC;
+ STR_CASE("mark")
+ action_type = FLT_ACTION_MARK;
+ STR_CASE("color-label")
+ action_type = FLT_ACTION_COLOR_LABEL;
+ STR_CASE("mark-as-read")
+ action_type = FLT_ACTION_MARK_READ;
+ STR_CASE("forward")
+ action_type = FLT_ACTION_FORWARD;
+ STR_CASE("forward-as-attachment")
+ action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT;
+ STR_CASE("redirect")
+ action_type = FLT_ACTION_REDIRECT;
+ STR_CASE("stop-eval")
+ action_type = FLT_ACTION_STOP_EVAL;
+ STR_CASE_END
+
+ action = filter_action_new(action_type, xmlnode->element);
+ action_list = g_slist_append(action_list, action);
+ }
+
+ if (name && cond_list && action_list) {
+ rule = filter_rule_new(name, bool_op, cond_list, action_list);
+ rule->timing = timing;
+ rule->enabled = enabled;
+ *fltlist = g_slist_prepend(*fltlist, rule);
+ }
+
+ return FALSE;
+}
+
+#undef RETURN_IF_TAG_NOT_MATCH
+#undef STR_SWITCH
+#undef STR_CASE_BEGIN
+#undef STR_CASE
+#undef STR_CASE_END
+
+GSList *filter_xml_node_to_filter_list(GNode *node)
+{
+ GSList *fltlist = NULL;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
+ filter_xml_node_func, &fltlist);
+ fltlist = g_slist_reverse(fltlist);
+
+ return fltlist;
+}
+
+void filter_read_config(void)
+{
+ gchar *rcpath;
+ GNode *node;
+ FilterRule *rule;
+
+ debug_print("Reading filter configuration...\n");
+
+ /* remove all previous filter list */
+ while (prefs_common.fltlist != NULL) {
+ rule = (FilterRule *)prefs_common.fltlist->data;
+ filter_rule_free(rule);
+ prefs_common.fltlist = g_slist_remove(prefs_common.fltlist,
+ rule);
+ }
+
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
+ NULL);
+ if (!is_file_exist(rcpath)) {
+ g_free(rcpath);
+ return;
+ }
+
+ node = xml_parse_file(rcpath);
+ if (!node) {
+ g_warning("Can't parse %s\n", rcpath);
+ g_free(rcpath);
+ return;
+ }
+ g_free(rcpath);
+
+ prefs_common.fltlist = filter_xml_node_to_filter_list(node);
+
+ xml_free_tree(node);
+}
+
+#define NODE_NEW(tag, text) \
+ node = xml_node_new(xml_tag_new(tag), text)
+#define ADD_ATTR(name, value) \
+ xml_tag_add_attr(node->tag, xml_attr_new(name, value))
+
+void filter_write_config(void)
+{
+ gchar *rcpath;
+ PrefFile *pfile;
+ GSList *cur;
+
+ debug_print("Writing filter configuration...\n");
+
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
+ NULL);
+ if ((pfile = prefs_file_open(rcpath)) == NULL) {
+ g_warning("failed to write filter configuration to file\n");
+ g_free(rcpath);
+ return;
+ }
+
+ xml_file_put_xml_decl(pfile->fp);
+ fputs("\n<filter>\n", pfile->fp);
+
+ for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) {
+ FilterRule *rule = (FilterRule *)cur->data;
+ GSList *cur_cond;
+ GSList *cur_action;
+ gchar match_type[16];
+
+ fputs(" <rule name=\"", pfile->fp);
+ xml_file_put_escape_str(pfile->fp, rule->name);
+ fprintf(pfile->fp, "\" timing=\"%s\"",
+ rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" :
+ rule->timing == FLT_TIMING_MANUAL ? "manual" : "any");
+ fprintf(pfile->fp, " enabled=\"%s\">\n",
+ rule->enabled ? "true" : "false");
+
+ fprintf(pfile->fp, " <condition-list bool=\"%s\">\n",
+ rule->bool_op == FLT_OR ? "or" : "and");
+
+ for (cur_cond = rule->cond_list; cur_cond != NULL;
+ cur_cond = cur_cond->next) {
+ FilterCond *cond = (FilterCond *)cur_cond->data;
+ XMLNode *node = NULL;
+
+ switch (cond->match_type) {
+ case FLT_CONTAIN:
+ strcpy(match_type,
+ FLT_IS_NOT_MATCH(cond->match_flag)
+ ? "not-contain" : "contains");
+ break;
+ case FLT_EQUAL:
+ strcpy(match_type,
+ FLT_IS_NOT_MATCH(cond->match_flag)
+ ? "is-not" : "is");
+ break;
+ case FLT_REGEX:
+ strcpy(match_type,
+ FLT_IS_NOT_MATCH(cond->match_flag)
+ ? "not-regex" : "regex");
+ break;
+ default:
+ match_type[0] = '\0';
+ break;
+ }
+
+ switch (cond->type) {
+ case FLT_COND_HEADER:
+ NODE_NEW("match-header", cond->str_value);
+ ADD_ATTR("type", match_type);
+ ADD_ATTR("name", cond->header_name);
+ break;
+ case FLT_COND_ANY_HEADER:
+ NODE_NEW("match-any-header", cond->str_value);
+ ADD_ATTR("type", match_type);
+ break;
+ case FLT_COND_TO_OR_CC:
+ NODE_NEW("match-to-or-cc", cond->str_value);
+ ADD_ATTR("type", match_type);
+ break;
+ case FLT_COND_BODY:
+ NODE_NEW("match-body-text", cond->str_value);
+ ADD_ATTR("type", match_type);
+ break;
+ case FLT_COND_CMD_TEST:
+ NODE_NEW("command-test", cond->str_value);
+ break;
+ case FLT_COND_SIZE_GREATER:
+ NODE_NEW("size", itos(cond->int_value));
+ ADD_ATTR("type",
+ FLT_IS_NOT_MATCH(cond->match_flag)
+ ? "lt" : "gt");
+ break;
+ case FLT_COND_AGE_GREATER:
+ NODE_NEW("age", itos(cond->int_value));
+ ADD_ATTR("type",
+ FLT_IS_NOT_MATCH(cond->match_flag)
+ ? "lt" : "gt");
+ break;
+ case FLT_COND_ACCOUNT:
+ NODE_NEW("account-id", itos(cond->int_value));
+ break;
+ default:
+ break;
+ }
+
+ if (node) {
+ fputs(" ", pfile->fp);
+ xml_file_put_node(pfile->fp, node);
+ xml_free_node(node);
+ }
+ }
+
+ fputs(" </condition-list>\n", pfile->fp);
+
+ fputs(" <action-list>\n", pfile->fp);
+
+ for (cur_action = rule->action_list; cur_action != NULL;
+ cur_action = cur_action->next) {
+ FilterAction *action = (FilterAction *)cur_action->data;
+ XMLNode *node = NULL;
+
+ switch (action->type) {
+ case FLT_ACTION_MOVE:
+ NODE_NEW("move", action->str_value);
+ break;
+ case FLT_ACTION_COPY:
+ NODE_NEW("copy", action->str_value);
+ break;
+ case FLT_ACTION_NOT_RECEIVE:
+ NODE_NEW("not-receive", NULL);
+ break;
+ case FLT_ACTION_DELETE:
+ NODE_NEW("delete", NULL);
+ break;
+ case FLT_ACTION_EXEC:
+ NODE_NEW("exec", action->str_value);
+ break;
+ case FLT_ACTION_EXEC_ASYNC:
+ NODE_NEW("exec-async", action->str_value);
+ break;
+ case FLT_ACTION_MARK:
+ NODE_NEW("mark", NULL);
+ break;
+ case FLT_ACTION_COLOR_LABEL:
+ NODE_NEW("color-label", action->str_value);
+ break;
+ case FLT_ACTION_MARK_READ:
+ NODE_NEW("mark-as-read", NULL);
+ break;
+ case FLT_ACTION_FORWARD:
+ NODE_NEW("forward", action->str_value);
+ break;
+ case FLT_ACTION_FORWARD_AS_ATTACHMENT:
+ NODE_NEW("forward-as-attachment",
+ action->str_value);
+ break;
+ case FLT_ACTION_REDIRECT:
+ NODE_NEW("redirect", action->str_value);
+ break;
+ case FLT_ACTION_STOP_EVAL:
+ NODE_NEW("stop-eval", NULL);
+ break;
+ default:
+ break;
+ }
+
+ if (node) {
+ fputs(" ", pfile->fp);
+ xml_file_put_node(pfile->fp, node);
+ xml_free_node(node);
+ }
+ }
+
+ fputs(" </action-list>\n", pfile->fp);
+
+ fputs(" </rule>\n", pfile->fp);
+ }
+
+ fputs("</filter>\n", pfile->fp);
+
+ g_free(rcpath);
+
+ if (prefs_file_close(pfile) < 0) {
+ g_warning(_("failed to write configuration to file\n"));
+ return;
+ }
+}
+
+#undef NODE_NEW
+#undef ADD_ATTR
+
+gchar *filter_get_str(FilterRule *rule)
+{
+ gchar *str;
+ FilterCond *cond1, *cond2;
+ FilterAction *action = NULL;
+ GSList *cur;
+ gint flag1 = 0, flag2 = 0;
+
+ cond1 = (FilterCond *)rule->cond_list->data;
+ if (rule->cond_list->next)
+ cond2 = (FilterCond *)rule->cond_list->next->data;
+ else
+ cond2 = NULL;
+
+ /* new -> old flag conversion */
+ switch (cond1->match_type) {
+ case FLT_CONTAIN:
+ case FLT_EQUAL:
+ flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN;
+ if (FLT_IS_CASE_SENS(cond1->match_flag))
+ flag1 |= FLT_O_CASE_SENS;
+ break;
+ case FLT_REGEX:
+ flag1 = FLT_O_REGEX; break;
+ default:
+ break;
+ }
+ if (cond2) {
+ switch (cond2->match_type) {
+ case FLT_CONTAIN:
+ case FLT_EQUAL:
+ flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN;
+ if (FLT_IS_CASE_SENS(cond2->match_flag))
+ flag2 |= FLT_O_CASE_SENS;
+ break;
+ case FLT_REGEX:
+ flag2 = FLT_O_REGEX; break;
+ default:
+ break;
+ }
+ } else
+ flag2 = FLT_O_CONTAIN;
+
+ for (cur = rule->action_list; cur != NULL; cur = cur->next) {
+ action = (FilterAction *)cur->data;
+ if (action->type == FLT_ACTION_MOVE ||
+ action->type == FLT_ACTION_NOT_RECEIVE ||
+ action->type == FLT_ACTION_DELETE)
+ break;
+ }
+
+ str = g_strdup_printf
+ ("%s:%s:%c:%s:%s:%s:%d:%d:%c",
+ cond1->header_name, cond1->str_value ? cond1->str_value : "",
+ (cond2 && cond2->header_name) ?
+ (rule->bool_op == FLT_AND ? '&' : '|') : ' ',
+ (cond2 && cond2->header_name) ? cond2->header_name : "",
+ (cond2 && cond2->str_value) ? cond2->str_value : "",
+ (action && action->str_value) ? action->str_value : "",
+ flag1, flag2,
+ (action && action->type == FLT_ACTION_MOVE) ? 'm' :
+ (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' :
+ (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' ');
+
+ return str;
+}
+
+#define PARSE_ONE_PARAM(p, srcp) \
+{ \
+ p = strchr(srcp, '\t'); \
+ if (!p) return NULL; \
+ else \
+ *p++ = '\0'; \
+}
+
+FilterRule *filter_read_str(const gchar *str)
+{
+ FilterRule *rule;
+ FilterBoolOp bool_op;
+ gint flag;
+ FilterCond *cond;
+ FilterMatchType match_type;
+ FilterMatchFlag match_flag;
+ FilterAction *action;
+ GSList *cond_list = NULL;
+ GSList *action_list = NULL;
+ gchar *tmp;
+ gchar *rule_name;
+ gchar *name1, *body1, *op, *name2, *body2, *dest;
+ gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL;
+
+ Xstrdup_a(tmp, str, return NULL);
+
+ name1 = tmp;
+ PARSE_ONE_PARAM(body1, name1);
+ PARSE_ONE_PARAM(op, body1);
+ PARSE_ONE_PARAM(name2, op);
+ PARSE_ONE_PARAM(body2, name2);
+ PARSE_ONE_PARAM(dest, body2);
+ if (strchr(dest, '\t')) {
+ gchar *p;
+
+ PARSE_ONE_PARAM(flag1, dest);
+ PARSE_ONE_PARAM(flag2, flag1);
+ PARSE_ONE_PARAM(action1, flag2);
+ if ((p = strchr(action1, '\t'))) *p = '\0';
+ }
+
+ bool_op = (*op == '&') ? FLT_AND : FLT_OR;
+
+ if (*name1) {
+ if (flag1)
+ flag = (FilterOldFlag)strtoul(flag1, NULL, 10);
+ else
+ flag = FLT_O_CONTAIN;
+ match_type = FLT_CONTAIN;
+ match_flag = 0;
+ if (flag & FLT_O_REGEX)
+ match_type = FLT_REGEX;
+ else if (!(flag & FLT_O_CONTAIN))
+ match_flag = FLT_NOT_MATCH;
+ if (flag & FLT_O_CASE_SENS)
+ match_flag |= FLT_CASE_SENS;
+ cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
+ name1, body1);
+ cond_list = g_slist_append(cond_list, cond);
+ }
+ if (*name2) {
+ if (flag2)
+ flag = (FilterOldFlag)strtoul(flag2, NULL, 10);
+ else
+ flag = FLT_O_CONTAIN;
+ match_type = FLT_CONTAIN;
+ match_flag = 0;
+ if (flag & FLT_O_REGEX)
+ match_type = FLT_REGEX;
+ else if (!(flag & FLT_O_CONTAIN))
+ match_flag = FLT_NOT_MATCH;
+ if (flag & FLT_O_CASE_SENS)
+ match_flag |= FLT_CASE_SENS;
+ cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
+ name2, body2);
+ cond_list = g_slist_append(cond_list, cond);
+ }
+
+ action = filter_action_new(FLT_ACTION_MOVE,
+ *dest ? g_strdup(dest) : NULL);
+ if (action1) {
+ switch (*action1) {
+ case 'm': action->type = FLT_ACTION_MOVE; break;
+ case 'n': action->type = FLT_ACTION_NOT_RECEIVE; break;
+ case 'd': action->type = FLT_ACTION_DELETE; break;
+ default: g_warning("Invalid action: `%c'\n", *action1);
+ }
+ }
+ action_list = g_slist_append(action_list, action);
+
+ Xstrdup_a(rule_name, str, return NULL);
+ subst_char(rule_name, '\t', ':');
+
+ rule = filter_rule_new(rule_name, bool_op, cond_list, action_list);
+
+ return rule;
+}
+
+FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op,
+ GSList *cond_list, GSList *action_list)
+{
+ FilterRule *rule;
+
+ rule = g_new0(FilterRule, 1);
+ rule->name = g_strdup(name);
+ rule->bool_op = bool_op;
+ rule->cond_list = cond_list;
+ rule->action_list = action_list;
+ rule->timing = FLT_TIMING_ANY;
+ rule->enabled = TRUE;
+
+ return rule;
+}
+
+FilterCond *filter_cond_new(FilterCondType type,
+ FilterMatchType match_type,
+ FilterMatchFlag match_flag,
+ const gchar *header, const gchar *value)
+{
+ FilterCond *cond;
+
+ cond = g_new0(FilterCond, 1);
+ cond->type = type;
+ cond->match_type = match_type;
+ cond->match_flag = match_flag;
+
+ if (type == FLT_COND_HEADER)
+ cond->header_name =
+ (header && *header) ? g_strdup(header) : NULL;
+ else
+ cond->header_name = NULL;
+
+ cond->str_value = (value && *value) ? g_strdup(value) : NULL;
+ if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER)
+ cond->int_value = atoi(value);
+ else
+ cond->int_value = 0;
+
+ if (match_type == FLT_REGEX)
+ cond->match_func = strmatch_regex;
+ else if (match_type == FLT_EQUAL) {
+ if (FLT_IS_CASE_SENS(match_flag))
+ cond->match_func = str_find_equal;
+ else
+ cond->match_func = str_case_find_equal;
+ } else {
+ if (FLT_IS_CASE_SENS(match_flag))
+ cond->match_func = str_find;
+ else
+ cond->match_func = str_case_find;
+ }
+
+ return cond;
+}
+
+FilterAction *filter_action_new(FilterActionType type, const gchar *str)
+{
+ FilterAction *action;
+
+ action = g_new0(FilterAction, 1);
+ action->type = type;
+
+ action->str_value = (str && *str) ? g_strdup(str) : NULL;
+ if (type == FLT_ACTION_COLOR_LABEL && str)
+ action->int_value = atoi(str);
+ else
+ action->int_value = 0;
+
+ return action;
+}
+
+FilterInfo *filter_info_new(void)
+{
+ FilterInfo *fltinfo;
+
+ fltinfo = g_new0(FilterInfo, 1);
+ fltinfo->dest_list = NULL;
+ fltinfo->move_dest = NULL;
+ fltinfo->drop_done = FALSE;
+
+ return fltinfo;
+}
+
+void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path,
+ const gchar *new_path)
+{
+ FilterAction *action;
+ GSList *cur;
+ gchar *base;
+ gchar *dest_path;
+ gint oldpathlen;
+
+ oldpathlen = strlen(old_path);
+
+ for (cur = rule->action_list; cur != NULL; cur = cur->next) {
+ action = (FilterAction *)cur->data;
+
+ if (action->type != FLT_ACTION_MOVE &&
+ action->type != FLT_ACTION_COPY)
+ continue;
+
+ if (action->str_value &&
+ !strncmp(old_path, action->str_value, oldpathlen)) {
+ base = action->str_value + oldpathlen;
+ if (*base != G_DIR_SEPARATOR && *base != '\0')
+ continue;
+ while (*base == G_DIR_SEPARATOR) base++;
+ if (*base == '\0')
+ dest_path = g_strdup(new_path);
+ else
+ dest_path = g_strconcat(new_path,
+ G_DIR_SEPARATOR_S,
+ base, NULL);
+ debug_print("filter_rule_rename_dest_path(): "
+ "renaming %s -> %s\n",
+ action->str_value, dest_path);
+ g_free(action->str_value);
+ action->str_value = dest_path;
+ }
+ }
+}
+
+void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path)
+{
+ FilterAction *action;
+ GSList *cur;
+ GSList *next;
+ gint pathlen;
+
+ pathlen = strlen(path);
+
+ for (cur = rule->action_list; cur != NULL; cur = next) {
+ action = (FilterAction *)cur->data;
+ next = cur->next;
+
+ if (action->type != FLT_ACTION_MOVE &&
+ action->type != FLT_ACTION_COPY)
+ continue;
+
+ if (action->str_value &&
+ !strncmp(path, action->str_value, pathlen) &&
+ (action->str_value[pathlen] == G_DIR_SEPARATOR ||
+ action->str_value[pathlen] == '\0')) {
+ debug_print("filter_rule_delete_action_by_dest_path(): "
+ "deleting %s\n", action->str_value);
+ rule->action_list = g_slist_remove
+ (rule->action_list, action);
+ filter_action_free(action);
+ }
+ }
+}
+
+void filter_list_rename_path(const gchar *old_path, const gchar *new_path)
+{
+ GSList *cur;
+
+ g_return_if_fail(old_path != NULL);
+ g_return_if_fail(new_path != NULL);
+
+ for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) {
+ FilterRule *rule = (FilterRule *)cur->data;
+ filter_rule_rename_dest_path(rule, old_path, new_path);
+ }
+
+ filter_write_config();
+}
+
+void filter_list_delete_path(const gchar *path)
+{
+ GSList *cur;
+ GSList *next;
+
+ g_return_if_fail(path != NULL);
+
+ for (cur = prefs_common.fltlist; cur != NULL; cur = next) {
+ FilterRule *rule = (FilterRule *)cur->data;
+ next = cur->next;
+
+ filter_rule_delete_action_by_dest_path(rule, path);
+ if (!rule->action_list) {
+ prefs_common.fltlist =
+ g_slist_remove(prefs_common.fltlist, rule);
+ filter_rule_free(rule);
+ }
+ }
+
+ filter_write_config();
+}
+
+void filter_rule_match_type_str_to_enum(const gchar *match_type,
+ FilterMatchType *type,
+ FilterMatchFlag *flag)
+{
+ g_return_if_fail(match_type != NULL);
+
+ *type = FLT_CONTAIN;
+ *flag = 0;
+
+ if (!strcmp(match_type, "contains")) {
+ *type = FLT_CONTAIN;
+ } else if (!strcmp(match_type, "not-contain")) {
+ *type = FLT_CONTAIN;
+ *flag = FLT_NOT_MATCH;
+ } else if (!strcmp(match_type, "is")) {
+ *type = FLT_EQUAL;
+ } else if (!strcmp(match_type, "is-not")) {
+ *type = FLT_EQUAL;
+ *flag = FLT_NOT_MATCH;
+ } else if (!strcmp(match_type, "regex")) {
+ *type = FLT_REGEX;
+ } else if (!strcmp(match_type, "not-regex")) {
+ *type = FLT_REGEX;
+ *flag = FLT_NOT_MATCH;
+ } else if (!strcmp(match_type, "gt")) {
+ } else if (!strcmp(match_type, "lt")) {
+ *flag = FLT_NOT_MATCH;
+ }
+}
+
+void filter_get_keyword_from_msg(MsgInfo *msginfo, gchar **header, gchar **key,
+ FilterCreateType type)
+{
+ static HeaderEntry hentry[] = {{"List-Id:", NULL, TRUE},
+ {"X-ML-Name:", NULL, TRUE},
+ {"X-List:", NULL, TRUE},
+ {"X-Mailing-list:", NULL, TRUE},
+ {"X-Sequence:", NULL, TRUE},
+ {NULL, NULL, FALSE}};
+ enum
+ {
+ H_LIST_ID = 0,
+ H_X_ML_NAME = 1,
+ H_X_LIST = 2,
+ H_X_MAILING_LIST = 3,
+ H_X_SEQUENCE = 4
+ };
+
+ FILE *fp;
+
+ g_return_if_fail(msginfo != NULL);
+ g_return_if_fail(header != NULL);
+ g_return_if_fail(key != NULL);
+
+ *header = NULL;
+ *key = NULL;
+
+ switch (type) {
+ case FLT_BY_NONE:
+ return;
+ case FLT_BY_AUTO:
+ if ((fp = procmsg_open_message(msginfo)) == NULL)
+ return;
+ procheader_get_header_fields(fp, hentry);
+ fclose(fp);
+
+#define SET_FILTER_KEY(hstr, idx) \
+{ \
+ *header = g_strdup(hstr); \
+ *key = hentry[idx].body; \
+ hentry[idx].body = NULL; \
+}
+
+ if (hentry[H_LIST_ID].body != NULL) {
+ SET_FILTER_KEY("List-Id", H_LIST_ID);
+ extract_list_id_str(*key);
+ } else if (hentry[H_X_ML_NAME].body != NULL) {
+ SET_FILTER_KEY("X-ML-Name", H_X_ML_NAME);
+ } else if (hentry[H_X_LIST].body != NULL) {
+ SET_FILTER_KEY("X-List", H_X_LIST);
+ } else if (hentry[H_X_MAILING_LIST].body != NULL) {
+ SET_FILTER_KEY("X-Mailing-list", H_X_MAILING_LIST);
+ } else if (hentry[H_X_SEQUENCE].body != NULL) {
+ gchar *p;
+
+ SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
+ p = *key;
+ while (*p != '\0') {
+ while (*p != '\0' && !g_ascii_isspace(*p)) p++;
+ while (g_ascii_isspace(*p)) p++;
+ if (g_ascii_isdigit(*p)) {
+ *p = '\0';
+ break;
+ }
+ }
+ g_strstrip(*key);
+ } else if (msginfo->subject) {
+ *header = g_strdup("Subject");
+ *key = g_strdup(msginfo->subject);
+ }
+
+#undef SET_FILTER_KEY
+
+ g_free(hentry[H_LIST_ID].body);
+ hentry[H_LIST_ID].body = NULL;
+ g_free(hentry[H_X_ML_NAME].body);
+ hentry[H_X_ML_NAME].body = NULL;
+ g_free(hentry[H_X_LIST].body);
+ hentry[H_X_LIST].body = NULL;
+ g_free(hentry[H_X_MAILING_LIST].body);
+ hentry[H_X_MAILING_LIST].body = NULL;
+
+ break;
+ case FLT_BY_FROM:
+ *header = g_strdup("From");
+ *key = g_strdup(msginfo->from);
+ break;
+ case FLT_BY_TO:
+ *header = g_strdup("To");
+ *key = g_strdup(msginfo->to);
+ break;
+ case FLT_BY_SUBJECT:
+ *header = g_strdup("Subject");
+ *key = g_strdup(msginfo->subject);
+ break;
+ default:
+ break;
+ }
+}
+
+void filter_rule_list_free(GSList *fltlist)
+{
+ GSList *cur;
+
+ for (cur = fltlist; cur != NULL; cur = cur->next)
+ filter_rule_free((FilterRule *)cur->data);
+ g_slist_free(fltlist);
+}
+
+void filter_rule_free(FilterRule *rule)
+{
+ if (!rule) return;
+
+ g_free(rule->name);
+
+ filter_cond_list_free(rule->cond_list);
+ filter_action_list_free(rule->action_list);
+
+ g_free(rule);
+}
+
+void filter_cond_list_free(GSList *cond_list)
+{
+ GSList *cur;
+
+ for (cur = cond_list; cur != NULL; cur = cur->next)
+ filter_cond_free((FilterCond *)cur->data);
+ g_slist_free(cond_list);
+}
+
+void filter_action_list_free(GSList *action_list)
+{
+ GSList *cur;
+
+ for (cur = action_list; cur != NULL; cur = cur->next)
+ filter_action_free((FilterAction *)cur->data);
+ g_slist_free(action_list);
+}
+
+static void filter_cond_free(FilterCond *cond)
+{
+ g_free(cond->header_name);
+ g_free(cond->str_value);
+ g_free(cond);
+}
+
+static void filter_action_free(FilterAction *action)
+{
+ g_free(action->str_value);
+ g_free(action);
+}
+
+void filter_info_free(FilterInfo *fltinfo)
+{
+ g_slist_free(fltinfo->dest_list);
+ g_free(fltinfo);
+}
diff --git a/libsylph/filter.h b/libsylph/filter.h
new file mode 100644
index 00000000..dc38047e
--- /dev/null
+++ b/libsylph/filter.h
@@ -0,0 +1,214 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __FILTER_H__
+#define __FILTER_H__
+
+#include <glib.h>
+
+#include "folder.h"
+#include "procmsg.h"
+#include "utils.h"
+
+typedef struct _FilterCond FilterCond;
+typedef struct _FilterAction FilterAction;
+typedef struct _FilterRule FilterRule;
+typedef struct _FilterInfo FilterInfo;
+
+typedef enum
+{
+ FLT_TIMING_ANY,
+ FLT_TIMING_ON_RECEIVE,
+ FLT_TIMING_MANUAL
+} FilterTiming;
+
+typedef enum
+{
+ FLT_COND_HEADER,
+ FLT_COND_ANY_HEADER,
+ FLT_COND_TO_OR_CC,
+ FLT_COND_BODY,
+ FLT_COND_CMD_TEST,
+ FLT_COND_SIZE_GREATER,
+ FLT_COND_AGE_GREATER,
+ FLT_COND_ACCOUNT
+} FilterCondType;
+
+typedef enum
+{
+ FLT_CONTAIN,
+ FLT_EQUAL,
+ FLT_REGEX
+} FilterMatchType;
+
+typedef enum
+{
+ FLT_NOT_MATCH = 1 << 0,
+ FLT_CASE_SENS = 1 << 1
+} FilterMatchFlag;
+
+typedef enum
+{
+ FLT_OR,
+ FLT_AND
+} FilterBoolOp;
+
+typedef enum
+{
+ FLT_ACTION_MOVE,
+ FLT_ACTION_COPY,
+ FLT_ACTION_NOT_RECEIVE,
+ FLT_ACTION_DELETE,
+ FLT_ACTION_EXEC,
+ FLT_ACTION_EXEC_ASYNC,
+ FLT_ACTION_MARK,
+ FLT_ACTION_COLOR_LABEL,
+ FLT_ACTION_MARK_READ,
+ FLT_ACTION_FORWARD,
+ FLT_ACTION_FORWARD_AS_ATTACHMENT,
+ FLT_ACTION_REDIRECT,
+ FLT_ACTION_STOP_EVAL,
+ FLT_ACTION_NONE
+} FilterActionType;
+
+typedef enum
+{
+ FLT_BY_NONE,
+ FLT_BY_AUTO,
+ FLT_BY_FROM,
+ FLT_BY_TO,
+ FLT_BY_SUBJECT
+} FilterCreateType;
+
+#define FLT_IS_NOT_MATCH(flag) ((flag & FLT_NOT_MATCH) != 0)
+#define FLT_IS_CASE_SENS(flag) ((flag & FLT_CASE_SENS) != 0)
+
+struct _FilterCond
+{
+ FilterCondType type;
+
+ gchar *header_name;
+
+ gchar *str_value;
+ gint int_value;
+
+ FilterMatchType match_type;
+ FilterMatchFlag match_flag;
+
+ StrFindFunc match_func;
+};
+
+struct _FilterAction
+{
+ FilterActionType type;
+
+ gchar *str_value;
+ gint int_value;
+};
+
+struct _FilterRule
+{
+ gchar *name;
+
+ FilterBoolOp bool_op;
+
+ GSList *cond_list;
+ GSList *action_list;
+
+ FilterTiming timing;
+ gboolean enabled;
+};
+
+struct _FilterInfo
+{
+ PrefsAccount *account;
+ MsgFlags flags;
+
+ gboolean actions[FLT_ACTION_NONE];
+ GSList *dest_list;
+ FolderItem *move_dest;
+ gboolean drop_done;
+};
+
+gint filter_apply (GSList *fltlist,
+ const gchar *file,
+ FilterInfo *fltinfo);
+gint filter_apply_msginfo (GSList *fltlist,
+ MsgInfo *msginfo,
+ FilterInfo *fltinfo);
+
+gint filter_action_exec (FilterRule *rule,
+ MsgInfo *msginfo,
+ const gchar *file,
+ FilterInfo *fltinfo);
+
+gboolean filter_match_rule (FilterRule *rule,
+ MsgInfo *msginfo,
+ GSList *hlist,
+ FilterInfo *fltinfo);
+
+/* read / write config */
+GSList *filter_xml_node_to_filter_list (GNode *node);
+void filter_read_config (void);
+void filter_write_config (void);
+
+/* for old filterrc */
+gchar *filter_get_str (FilterRule *rule);
+FilterRule *filter_read_str (const gchar *str);
+
+FilterRule *filter_rule_new (const gchar *name,
+ FilterBoolOp bool_op,
+ GSList *cond_list,
+ GSList *action_list);
+FilterCond *filter_cond_new (FilterCondType type,
+ FilterMatchType match_type,
+ FilterMatchFlag match_flag,
+ const gchar *header,
+ const gchar *body);
+FilterAction *filter_action_new (FilterActionType type,
+ const gchar *str);
+FilterInfo *filter_info_new (void);
+
+void filter_rule_rename_dest_path (FilterRule *rule,
+ const gchar *old_path,
+ const gchar *new_path);
+void filter_rule_delete_action_by_dest_path
+ (FilterRule *rule,
+ const gchar *path);
+
+void filter_list_rename_path (const gchar *old_path,
+ const gchar *new_path);
+void filter_list_delete_path (const gchar *path);
+
+void filter_rule_match_type_str_to_enum (const gchar *type_str,
+ FilterMatchType *type,
+ FilterMatchFlag *flag);
+
+void filter_get_keyword_from_msg (MsgInfo *msginfo,
+ gchar **header,
+ gchar **key,
+ FilterCreateType type);
+
+void filter_rule_list_free (GSList *fltlist);
+void filter_rule_free (FilterRule *rule);
+void filter_cond_list_free (GSList *cond_list);
+void filter_action_list_free (GSList *action_list);
+void filter_info_free (FilterInfo *info);
+
+#endif /* __FILTER_H__ */
diff --git a/libsylph/folder.c b/libsylph/folder.c
new file mode 100644
index 00000000..25db6b24
--- /dev/null
+++ b/libsylph/folder.c
@@ -0,0 +1,1583 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "folder.h"
+#include "session.h"
+#include "imap.h"
+#include "news.h"
+#include "mh.h"
+#include "utils.h"
+#include "xml.h"
+#include "codeconv.h"
+#include "prefs.h"
+#include "account.h"
+#include "prefs_account.h"
+
+static GList *folder_list = NULL;
+
+static void folder_init (Folder *folder,
+ const gchar *name);
+
+static gboolean folder_read_folder_func (GNode *node,
+ gpointer data);
+static gchar *folder_get_list_path (void);
+static void folder_write_list_recursive (GNode *node,
+ gpointer data);
+
+
+Folder *folder_new(FolderType type, const gchar *name, const gchar *path)
+{
+ Folder *folder = NULL;
+
+ name = name ? name : path;
+ switch (type) {
+ case F_MH:
+ folder = mh_get_class()->folder_new(name, path);
+ break;
+ case F_IMAP:
+ folder = imap_get_class()->folder_new(name, path);
+ break;
+ case F_NEWS:
+ folder = news_get_class()->folder_new(name, path);
+ break;
+ default:
+ return NULL;
+ }
+
+ return folder;
+}
+
+static void folder_init(Folder *folder, const gchar *name)
+{
+ FolderItem *item;
+
+ g_return_if_fail(folder != NULL);
+
+ folder_set_name(folder, name);
+ folder->account = NULL;
+ folder->inbox = NULL;
+ folder->outbox = NULL;
+ folder->draft = NULL;
+ folder->queue = NULL;
+ folder->trash = NULL;
+ folder->ui_func = NULL;
+ folder->ui_func_data = NULL;
+ item = folder_item_new(name, NULL);
+ item->folder = folder;
+ folder->node = item->node = g_node_new(item);
+ folder->data = NULL;
+}
+
+void folder_local_folder_init(Folder *folder, const gchar *name,
+ const gchar *path)
+{
+ folder_init(folder, name);
+ LOCAL_FOLDER(folder)->rootpath = g_strdup(path);
+}
+
+void folder_remote_folder_init(Folder *folder, const gchar *name,
+ const gchar *path)
+{
+ folder_init(folder, name);
+ REMOTE_FOLDER(folder)->session = NULL;
+}
+
+void folder_destroy(Folder *folder)
+{
+ g_return_if_fail(folder != NULL);
+ g_return_if_fail(folder->klass->destroy != NULL);
+
+ folder->klass->destroy(folder);
+
+ folder_list = g_list_remove(folder_list, folder);
+
+ folder_tree_destroy(folder);
+ g_free(folder->name);
+ g_free(folder);
+}
+
+void folder_local_folder_destroy(LocalFolder *lfolder)
+{
+ g_return_if_fail(lfolder != NULL);
+
+ g_free(lfolder->rootpath);
+}
+
+void folder_remote_folder_destroy(RemoteFolder *rfolder)
+{
+ g_return_if_fail(rfolder != NULL);
+
+ if (rfolder->session)
+ session_destroy(rfolder->session);
+}
+
+#if 0
+Folder *mbox_folder_new(const gchar *name, const gchar *path)
+{
+ /* not yet implemented */
+ return NULL;
+}
+
+Folder *maildir_folder_new(const gchar *name, const gchar *path)
+{
+ /* not yet implemented */
+ return NULL;
+}
+#endif
+
+FolderItem *folder_item_new(const gchar *name, const gchar *path)
+{
+ FolderItem *item;
+
+ item = g_new0(FolderItem, 1);
+
+ item->stype = F_NORMAL;
+ item->name = g_strdup(name);
+ item->path = g_strdup(path);
+ item->mtime = 0;
+ item->new = 0;
+ item->unread = 0;
+ item->total = 0;
+ item->unmarked_num = 0;
+ item->last_num = -1;
+ item->no_sub = FALSE;
+ item->no_select = FALSE;
+ item->collapsed = FALSE;
+ item->threaded = TRUE;
+ item->opened = FALSE;
+ item->updated = FALSE;
+ item->cache_dirty = FALSE;
+ item->mark_dirty = FALSE;
+ item->node = NULL;
+ item->parent = NULL;
+ item->folder = NULL;
+ item->account = NULL;
+ item->ac_apply_sub = FALSE;
+ item->auto_to = NULL;
+ item->use_auto_to_on_reply = FALSE;
+ item->auto_cc = NULL;
+ item->auto_bcc = NULL;
+ item->auto_replyto = NULL;
+ item->mark_queue = NULL;
+ item->data = NULL;
+
+ return item;
+}
+
+void folder_item_append(FolderItem *parent, FolderItem *item)
+{
+ g_return_if_fail(parent != NULL);
+ g_return_if_fail(parent->folder != NULL);
+ g_return_if_fail(parent->node != NULL);
+ g_return_if_fail(item != NULL);
+
+ item->parent = parent;
+ item->folder = parent->folder;
+ item->node = g_node_append_data(parent->node, item);
+}
+
+static gboolean folder_item_remove_func(GNode *node, gpointer data)
+{
+ FolderItem *item = FOLDER_ITEM(node->data);
+
+ folder_item_destroy(item);
+ return FALSE;
+}
+
+void folder_item_remove(FolderItem *item)
+{
+ GNode *node;
+
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+ g_return_if_fail(item->node != NULL);
+
+ node = item->node;
+
+ if (item->folder->node == node)
+ item->folder->node = NULL;
+
+ g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+ folder_item_remove_func, NULL);
+ g_node_destroy(node);
+}
+
+void folder_item_remove_children(FolderItem *item)
+{
+ GNode *node, *next;
+
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+ g_return_if_fail(item->node != NULL);
+
+ node = item->node->children;
+ while (node != NULL) {
+ next = node->next;
+ folder_item_remove(FOLDER_ITEM(node->data));
+ node = next;
+ }
+}
+
+void folder_item_destroy(FolderItem *item)
+{
+ Folder *folder;
+
+ g_return_if_fail(item != NULL);
+
+ folder = item->folder;
+ if (folder) {
+ if (folder->inbox == item)
+ folder->inbox = NULL;
+ else if (folder->outbox == item)
+ folder->outbox = NULL;
+ else if (folder->draft == item)
+ folder->draft = NULL;
+ else if (folder->queue == item)
+ folder->queue = NULL;
+ else if (folder->trash == item)
+ folder->trash = NULL;
+ }
+
+ g_free(item->name);
+ g_free(item->path);
+ g_free(item->auto_to);
+ g_free(item->auto_cc);
+ g_free(item->auto_bcc);
+ g_free(item->auto_replyto);
+ g_free(item);
+}
+
+gint folder_item_compare(FolderItem *item_a, FolderItem *item_b)
+{
+ gint ret;
+ gchar *str_a, *str_b;
+
+ if (!item_a || !item_b)
+ return 0;
+ if (!item_a->parent || !item_b->parent)
+ return 0;
+ if (!item_a->name || !item_b->name)
+ return 0;
+
+ /* if both a and b are special folders, sort them according to
+ * their types (which is in-order). Note that this assumes that
+ * there are no multiple folders of a special type. */
+ if (item_a->stype != F_NORMAL && item_b->stype != F_NORMAL)
+ return item_a->stype - item_b->stype;
+
+ /* if b is normal folder, and a is not, b is smaller (ends up
+ * lower in the list) */
+ if (item_a->stype != F_NORMAL && item_b->stype == F_NORMAL)
+ return item_b->stype - item_a->stype;
+
+ /* if b is special folder, and a is not, b is larger (ends up
+ * higher in the list) */
+ if (item_a->stype == F_NORMAL && item_b->stype != F_NORMAL)
+ return item_b->stype - item_a->stype;
+
+ /* otherwise just compare the folder names */
+ str_a = g_utf8_casefold(item_a->name, -1);
+ str_b = g_utf8_casefold(item_b->name, -1);
+ ret = g_utf8_collate(str_a, str_b);
+ g_free(str_b);
+ g_free(str_a);
+
+ return ret;
+}
+
+void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data)
+{
+ g_return_if_fail(folder != NULL);
+
+ folder->ui_func = func;
+ folder->ui_func_data = data;
+}
+
+void folder_set_name(Folder *folder, const gchar *name)
+{
+ g_return_if_fail(folder != NULL);
+
+ g_free(folder->name);
+ folder->name = name ? g_strdup(name) : NULL;
+ if (folder->node && folder->node->data) {
+ FolderItem *item = (FolderItem *)folder->node->data;
+
+ g_free(item->name);
+ item->name = name ? g_strdup(name) : NULL;
+ }
+}
+
+void folder_tree_destroy(Folder *folder)
+{
+ g_return_if_fail(folder != NULL);
+
+ if (folder->node)
+ folder_item_remove(FOLDER_ITEM(folder->node->data));
+}
+
+void folder_add(Folder *folder)
+{
+ Folder *cur_folder;
+ GList *cur;
+ gint i;
+
+ g_return_if_fail(folder != NULL);
+
+ for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) {
+ cur_folder = FOLDER(cur->data);
+ if (FOLDER_TYPE(folder) == F_MH) {
+ if (FOLDER_TYPE(cur_folder) != F_MH) break;
+ } else if (FOLDER_TYPE(folder) == F_IMAP) {
+ if (FOLDER_TYPE(cur_folder) != F_MH &&
+ FOLDER_TYPE(cur_folder) != F_IMAP) break;
+ } else if (FOLDER_TYPE(folder) == F_NEWS) {
+ if (FOLDER_TYPE(cur_folder) != F_MH &&
+ FOLDER_TYPE(cur_folder) != F_IMAP &&
+ FOLDER_TYPE(cur_folder) != F_NEWS) break;
+ }
+ }
+
+ folder_list = g_list_insert(folder_list, folder, i);
+}
+
+GList *folder_get_list(void)
+{
+ return folder_list;
+}
+
+gint folder_read_list(void)
+{
+ GNode *node;
+ XMLNode *xmlnode;
+ gchar *path;
+
+ path = folder_get_list_path();
+ if (!is_file_exist(path)) return -1;
+ node = xml_parse_file(path);
+ if (!node) return -1;
+
+ xmlnode = node->data;
+ if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) {
+ g_warning("wrong folder list\n");
+ xml_free_tree(node);
+ return -1;
+ }
+
+ g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
+ folder_read_folder_func, NULL);
+
+ xml_free_tree(node);
+ if (folder_list)
+ return 0;
+ else
+ return -1;
+}
+
+void folder_write_list(void)
+{
+ GList *list;
+ Folder *folder;
+ gchar *path;
+ PrefFile *pfile;
+
+ path = folder_get_list_path();
+ if ((pfile = prefs_file_open(path)) == NULL) return;
+
+ fprintf(pfile->fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
+ CS_INTERNAL);
+ fputs("\n<folderlist>\n", pfile->fp);
+
+ for (list = folder_list; list != NULL; list = list->next) {
+ folder = list->data;
+ folder_write_list_recursive(folder->node, pfile->fp);
+ }
+
+ fputs("</folderlist>\n", pfile->fp);
+
+ if (prefs_file_close(pfile) < 0)
+ g_warning("failed to write folder list.\n");
+}
+
+struct TotalMsgStatus
+{
+ guint new;
+ guint unread;
+ guint total;
+ GString *str;
+};
+
+static gboolean folder_get_status_full_all_func(GNode *node, gpointer data)
+{
+ FolderItem *item;
+ struct TotalMsgStatus *status = (struct TotalMsgStatus *)data;
+ gchar *id;
+
+ g_return_val_if_fail(node->data != NULL, FALSE);
+
+ item = FOLDER_ITEM(node->data);
+
+ if (!item->path) return FALSE;
+
+ status->new += item->new;
+ status->unread += item->unread;
+ status->total += item->total;
+
+ if (status->str) {
+ id = folder_item_get_identifier(item);
+ g_string_sprintfa(status->str, "%5d %5d %5d %s\n",
+ item->new, item->unread,
+ item->total, id);
+ g_free(id);
+ }
+
+ return FALSE;
+}
+
+static void folder_get_status_full_all(GString *str, guint *new, guint *unread,
+ guint *total)
+{
+ GList *list;
+ Folder *folder;
+ struct TotalMsgStatus status;
+
+ status.new = status.unread = status.total = 0;
+ status.str = str;
+
+ debug_print("Counting total number of messages...\n");
+
+ for (list = folder_list; list != NULL; list = list->next) {
+ folder = FOLDER(list->data);
+ if (folder->node)
+ g_node_traverse(folder->node, G_PRE_ORDER,
+ G_TRAVERSE_ALL, -1,
+ folder_get_status_full_all_func,
+ &status);
+ }
+
+ *new = status.new;
+ *unread = status.unread;
+ *total = status.total;
+}
+
+gchar *folder_get_status(GPtrArray *folders, gboolean full)
+{
+ guint new, unread, total;
+ GString *str;
+ gint i;
+ gchar *ret;
+
+ new = unread = total = 0;
+
+ str = g_string_new(NULL);
+
+ if (folders) {
+ for (i = 0; i < folders->len; i++) {
+ FolderItem *item;
+
+ item = g_ptr_array_index(folders, i);
+ new += item->new;
+ unread += item->unread;
+ total += item->total;
+
+ if (full) {
+ gchar *id;
+
+ id = folder_item_get_identifier(item);
+ g_string_sprintfa(str, "%5d %5d %5d %s\n",
+ item->new, item->unread,
+ item->total, id);
+ g_free(id);
+ }
+ }
+ } else {
+ folder_get_status_full_all(full ? str : NULL,
+ &new, &unread, &total);
+ }
+
+ if (full)
+ g_string_sprintfa(str, "%5d %5d %5d\n", new, unread, total);
+ else
+ g_string_sprintfa(str, "%d %d %d\n", new, unread, total);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+
+ return ret;
+}
+
+Folder *folder_find_from_path(const gchar *path)
+{
+ GList *list;
+ Folder *folder;
+
+ for (list = folder_list; list != NULL; list = list->next) {
+ folder = list->data;
+ if (FOLDER_TYPE(folder) == F_MH &&
+ !path_cmp(LOCAL_FOLDER(folder)->rootpath, path))
+ return folder;
+ }
+
+ return NULL;
+}
+
+Folder *folder_find_from_name(const gchar *name, FolderType type)
+{
+ GList *list;
+ Folder *folder;
+
+ for (list = folder_list; list != NULL; list = list->next) {
+ folder = list->data;
+ if (FOLDER_TYPE(folder) == type &&
+ strcmp2(name, folder->name) == 0)
+ return folder;
+ }
+
+ return NULL;
+}
+
+static gboolean folder_item_find_func(GNode *node, gpointer data)
+{
+ FolderItem *item = node->data;
+ gpointer *d = data;
+ const gchar *path = d[0];
+
+ if (path_cmp(path, item->path) != 0)
+ return FALSE;
+
+ d[1] = item;
+
+ return TRUE;
+}
+
+FolderItem *folder_find_item_from_path(const gchar *path)
+{
+ Folder *folder;
+ gpointer d[2];
+
+ folder = folder_get_default_folder();
+ g_return_val_if_fail(folder != NULL, NULL);
+
+ d[0] = (gpointer)path;
+ d[1] = NULL;
+ g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+ folder_item_find_func, d);
+ return d[1];
+}
+
+FolderItem *folder_find_child_item_by_name(FolderItem *item, const gchar *name)
+{
+ GNode *node;
+ FolderItem *child;
+
+ for (node = item->node->children; node != NULL; node = node->next) {
+ child = FOLDER_ITEM(node->data);
+ if (strcmp2(g_basename(child->path), name) == 0)
+ return child;
+ }
+
+ return NULL;
+}
+
+static const struct {
+ gchar *str;
+ FolderType type;
+} type_str_table[] = {
+ {"#mh" , F_MH},
+ {"#mbox" , F_MBOX},
+ {"#maildir", F_MAILDIR},
+ {"#imap" , F_IMAP},
+ {"#news" , F_NEWS}
+};
+
+static gchar *folder_get_type_string(FolderType type)
+{
+ gint i;
+
+ for (i = 0; i < sizeof(type_str_table) / sizeof(type_str_table[0]);
+ i++) {
+ if (type_str_table[i].type == type)
+ return type_str_table[i].str;
+ }
+
+ return NULL;
+}
+
+static FolderType folder_get_type_from_string(const gchar *str)
+{
+ gint i;
+
+ for (i = 0; i < sizeof(type_str_table) / sizeof(type_str_table[0]);
+ i++) {
+ if (g_ascii_strcasecmp(type_str_table[i].str, str) == 0)
+ return type_str_table[i].type;
+ }
+
+ return F_UNKNOWN;
+}
+
+gchar *folder_get_identifier(Folder *folder)
+{
+ gchar *type_str;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+
+ type_str = folder_get_type_string(FOLDER_TYPE(folder));
+ return g_strconcat(type_str, "/", folder->name, NULL);
+}
+
+gchar *folder_item_get_identifier(FolderItem *item)
+{
+ gchar *id;
+ gchar *folder_id;
+
+ g_return_val_if_fail(item != NULL, NULL);
+
+ if (!item->path) {
+ if (!item->parent)
+ return folder_get_identifier(item->folder);
+ else
+ return NULL;
+ }
+
+ folder_id = folder_get_identifier(item->folder);
+ id = g_strconcat(folder_id, "/", item->path, NULL);
+#ifdef G_OS_WIN32
+ subst_char(id, G_DIR_SEPARATOR, '/');
+#endif
+ g_free(folder_id);
+
+ return id;
+}
+
+FolderItem *folder_find_item_from_identifier(const gchar *identifier)
+{
+ Folder *folder;
+ gpointer d[2];
+ gchar *str;
+ gchar *p;
+ gchar *name;
+ gchar *path;
+ FolderType type;
+
+ g_return_val_if_fail(identifier != NULL, NULL);
+
+ if (*identifier != '#')
+ return folder_find_item_from_path(identifier);
+
+ Xstrdup_a(str, identifier, return NULL);
+
+ p = strchr(str, '/');
+ if (!p)
+ return folder_find_item_from_path(identifier);
+ *p = '\0';
+ p++;
+ type = folder_get_type_from_string(str);
+ if (type == F_UNKNOWN)
+ return folder_find_item_from_path(identifier);
+
+ name = p;
+ p = strchr(p, '/');
+ if (!p)
+ return folder_find_item_from_path(identifier);
+ *p = '\0';
+ p++;
+
+ folder = folder_find_from_name(name, type);
+ if (!folder)
+ return folder_find_item_from_path(identifier);
+
+ path = p;
+
+ d[0] = (gpointer)path;
+ d[1] = NULL;
+ g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+ folder_item_find_func, d);
+ return d[1];
+}
+
+Folder *folder_get_default_folder(void)
+{
+ return folder_list ? FOLDER(folder_list->data) : NULL;
+}
+
+FolderItem *folder_get_default_inbox(void)
+{
+ Folder *folder;
+
+ if (!folder_list) return NULL;
+ folder = FOLDER(folder_list->data);
+ g_return_val_if_fail(folder != NULL, NULL);
+ return folder->inbox;
+}
+
+FolderItem *folder_get_default_outbox(void)
+{
+ Folder *folder;
+
+ if (!folder_list) return NULL;
+ folder = FOLDER(folder_list->data);
+ g_return_val_if_fail(folder != NULL, NULL);
+ return folder->outbox;
+}
+
+FolderItem *folder_get_default_draft(void)
+{
+ Folder *folder;
+
+ if (!folder_list) return NULL;
+ folder = FOLDER(folder_list->data);
+ g_return_val_if_fail(folder != NULL, NULL);
+ return folder->draft;
+}
+
+FolderItem *folder_get_default_queue(void)
+{
+ Folder *folder;
+
+ if (!folder_list) return NULL;
+ folder = FOLDER(folder_list->data);
+ g_return_val_if_fail(folder != NULL, NULL);
+ return folder->queue;
+}
+
+FolderItem *folder_get_default_trash(void)
+{
+ Folder *folder;
+
+ if (!folder_list) return NULL;
+ folder = FOLDER(folder_list->data);
+ g_return_val_if_fail(folder != NULL, NULL);
+ return folder->trash;
+}
+
+#define CREATE_FOLDER_IF_NOT_EXIST(member, dir, type) \
+{ \
+ if (!folder->member) { \
+ item = folder_item_new(dir, dir); \
+ item->stype = type; \
+ folder_item_append(rootitem, item); \
+ folder->member = item; \
+ } \
+}
+
+void folder_set_missing_folders(void)
+{
+ Folder *folder;
+ FolderItem *rootitem;
+ FolderItem *item;
+ GList *list;
+
+ for (list = folder_list; list != NULL; list = list->next) {
+ folder = list->data;
+ if (FOLDER_TYPE(folder) != F_MH) continue;
+ rootitem = FOLDER_ITEM(folder->node->data);
+ g_return_if_fail(rootitem != NULL);
+
+ if (folder->inbox && folder->outbox && folder->draft &&
+ folder->queue && folder->trash)
+ continue;
+
+ if (folder->klass->create_tree(folder) < 0) {
+ g_warning("%s: can't create the folder tree.\n",
+ LOCAL_FOLDER(folder)->rootpath);
+ continue;
+ }
+
+ CREATE_FOLDER_IF_NOT_EXIST(inbox, INBOX_DIR, F_INBOX);
+ CREATE_FOLDER_IF_NOT_EXIST(outbox, OUTBOX_DIR, F_OUTBOX);
+ CREATE_FOLDER_IF_NOT_EXIST(draft, DRAFT_DIR, F_DRAFT);
+ CREATE_FOLDER_IF_NOT_EXIST(queue, QUEUE_DIR, F_QUEUE);
+ CREATE_FOLDER_IF_NOT_EXIST(trash, TRASH_DIR, F_TRASH);
+ }
+}
+
+static gboolean folder_unref_account_func(GNode *node, gpointer data)
+{
+ FolderItem *item = node->data;
+ PrefsAccount *account = data;
+
+ if (item->account == account)
+ item->account = NULL;
+
+ return FALSE;
+}
+
+void folder_unref_account_all(PrefsAccount *account)
+{
+ Folder *folder;
+ GList *list;
+
+ if (!account) return;
+
+ for (list = folder_list; list != NULL; list = list->next) {
+ folder = list->data;
+ if (folder->account == account)
+ folder->account = NULL;
+ g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+ folder_unref_account_func, account);
+ }
+}
+
+#undef CREATE_FOLDER_IF_NOT_EXIST
+
+gchar *folder_get_path(Folder *folder)
+{
+ gchar *path;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+
+ if (FOLDER_TYPE(folder) == F_MH) {
+ path = g_filename_from_utf8(LOCAL_FOLDER(folder)->rootpath,
+ -1, NULL, NULL, NULL);
+ if (!path) {
+ g_warning("folder_get_path: faild to convert character set\n");
+ path = g_strdup(LOCAL_FOLDER(folder)->rootpath);
+ }
+ } else if (FOLDER_TYPE(folder) == F_IMAP) {
+ g_return_val_if_fail(folder->account != NULL, NULL);
+ path = g_strconcat(get_imap_cache_dir(),
+ G_DIR_SEPARATOR_S,
+ folder->account->recv_server,
+ G_DIR_SEPARATOR_S,
+ folder->account->userid,
+ NULL);
+ } else if (FOLDER_TYPE(folder) == F_NEWS) {
+ g_return_val_if_fail(folder->account != NULL, NULL);
+ path = g_strconcat(get_news_cache_dir(),
+ G_DIR_SEPARATOR_S,
+ folder->account->nntp_server,
+ NULL);
+ } else
+ path = NULL;
+
+ return path;
+}
+
+gchar *folder_item_get_path(FolderItem *item)
+{
+ gchar *folder_path;
+ gchar *item_path = NULL, *path;
+
+ g_return_val_if_fail(item != NULL, NULL);
+
+ folder_path = folder_get_path(item->folder);
+ g_return_val_if_fail(folder_path != NULL, NULL);
+
+ if (item->path) {
+ item_path = g_filename_from_utf8(item->path, -1,
+ NULL, NULL, NULL);
+ if (!item_path) {
+ g_warning("folder_item_get_path: faild to convert character set\n");
+ item_path = g_strdup(item->path);
+ }
+#ifdef G_OS_WIN32
+ subst_char(item_path, '/', G_DIR_SEPARATOR);
+#endif
+ }
+
+ if (g_path_is_absolute(folder_path)) {
+ if (item_path)
+ path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
+ item_path, NULL);
+ else
+ path = g_strdup(folder_path);
+ } else {
+ if (item_path)
+ path = g_strconcat(get_mail_base_dir(),
+ G_DIR_SEPARATOR_S, folder_path,
+ G_DIR_SEPARATOR_S, item_path, NULL);
+ else
+ path = g_strconcat(get_mail_base_dir(),
+ G_DIR_SEPARATOR_S, folder_path,
+ NULL);
+ }
+
+ g_free(item_path);
+ g_free(folder_path);
+ return path;
+}
+
+gint folder_item_scan(FolderItem *item)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(item != NULL, -1);
+
+ folder = item->folder;
+ return folder->klass->scan(folder, item);
+}
+
+static void folder_item_scan_foreach_func(gpointer key, gpointer val,
+ gpointer data)
+{
+ folder_item_scan(FOLDER_ITEM(key));
+}
+
+void folder_item_scan_foreach(GHashTable *table)
+{
+ g_hash_table_foreach(table, folder_item_scan_foreach_func, NULL);
+}
+
+GSList *folder_item_get_msg_list(FolderItem *item, gboolean use_cache)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(item != NULL, NULL);
+
+ folder = item->folder;
+ return folder->klass->get_msg_list(folder, item, use_cache);
+}
+
+gchar *folder_item_fetch_msg(FolderItem *item, gint num)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(item != NULL, NULL);
+
+ folder = item->folder;
+
+ return folder->klass->fetch_msg(folder, item, num);
+}
+
+gint folder_item_fetch_all_msg(FolderItem *item)
+{
+ Folder *folder;
+ GSList *mlist;
+ GSList *cur;
+ gint num = 0;
+ gint ret = 0;
+
+ g_return_val_if_fail(item != NULL, -1);
+
+ debug_print("fetching all messages in %s ...\n", item->path);
+
+ folder = item->folder;
+
+ if (folder->ui_func)
+ folder->ui_func(folder, item, folder->ui_func_data ?
+ folder->ui_func_data : GINT_TO_POINTER(num));
+
+ mlist = folder_item_get_msg_list(item, TRUE);
+
+ for (cur = mlist; cur != NULL; cur = cur->next) {
+ MsgInfo *msginfo = (MsgInfo *)cur->data;
+ gchar *msg;
+
+ num++;
+ if (folder->ui_func)
+ folder->ui_func(folder, item,
+ folder->ui_func_data ?
+ folder->ui_func_data :
+ GINT_TO_POINTER(num));
+
+ msg = folder_item_fetch_msg(item, msginfo->msgnum);
+ if (!msg) {
+ g_warning("Can't fetch message %d. Aborting.\n",
+ msginfo->msgnum);
+ ret = -1;
+ break;
+ }
+ g_free(msg);
+ }
+
+ procmsg_msg_list_free(mlist);
+
+ return ret;
+}
+
+MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(item != NULL, NULL);
+
+ folder = item->folder;
+
+ return folder->klass->get_msginfo(folder, item, num);
+}
+
+gint folder_item_add_msg(FolderItem *dest, const gchar *file, MsgFlags *flags,
+ gboolean remove_source)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(file != NULL, -1);
+ g_return_val_if_fail(dest->folder->klass->add_msg != NULL, -1);
+
+ folder = dest->folder;
+
+ return folder->klass->add_msg(folder, dest, file, flags, remove_source);
+}
+
+gint folder_item_add_msgs(FolderItem *dest, GSList *file_list,
+ gboolean remove_source, gint *first)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(file_list != NULL, -1);
+ g_return_val_if_fail(dest->folder->klass->add_msgs != NULL, -1);
+
+ folder = dest->folder;
+
+ return folder->klass->add_msgs(folder, dest, file_list, remove_source,
+ first);
+}
+
+gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msginfo != NULL, -1);
+ g_return_val_if_fail(dest->folder->klass->move_msg != NULL, -1);
+
+ folder = dest->folder;
+
+ return folder->klass->move_msg(folder, dest, msginfo);
+}
+
+gint folder_item_move_msgs(FolderItem *dest, GSList *msglist)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msglist != NULL, -1);
+ g_return_val_if_fail(dest->folder->klass->move_msgs != NULL, -1);
+
+ folder = dest->folder;
+
+ return folder->klass->move_msgs(folder, dest, msglist);
+}
+
+gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msginfo != NULL, -1);
+ g_return_val_if_fail(dest->folder->klass->copy_msg != NULL, -1);
+
+ folder = dest->folder;
+
+ return folder->klass->copy_msg(folder, dest, msginfo);
+}
+
+gint folder_item_copy_msgs(FolderItem *dest, GSList *msglist)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msglist != NULL, -1);
+ g_return_val_if_fail(dest->folder->klass->copy_msgs != NULL, -1);
+
+ folder = dest->folder;
+
+ return folder->klass->copy_msgs(folder, dest, msglist);
+}
+
+gint folder_item_remove_msg(FolderItem *item, MsgInfo *msginfo)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(item != NULL, -1);
+ g_return_val_if_fail(item->folder->klass->remove_msg != NULL, -1);
+
+ folder = item->folder;
+
+ return folder->klass->remove_msg(folder, item, msginfo);
+}
+
+gint folder_item_remove_msgs(FolderItem *item, GSList *msglist)
+{
+ Folder *folder;
+ gint ret = 0;
+
+ g_return_val_if_fail(item != NULL, -1);
+
+ folder = item->folder;
+ if (folder->klass->remove_msgs) {
+ return folder->klass->remove_msgs(folder, item, msglist);
+ }
+
+ while (msglist != NULL) {
+ MsgInfo *msginfo = (MsgInfo *)msglist->data;
+
+ ret = folder_item_remove_msg(item, msginfo);
+ if (ret != 0) break;
+ msglist = msglist->next;
+ }
+
+ return ret;
+}
+
+gint folder_item_remove_all_msg(FolderItem *item)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(item != NULL, -1);
+ g_return_val_if_fail(item->folder->klass->remove_all_msg != NULL, -1);
+
+ folder = item->folder;
+
+ return folder->klass->remove_all_msg(folder, item);
+}
+
+gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(item != NULL, FALSE);
+ g_return_val_if_fail(item->folder->klass->is_msg_changed != NULL,
+ FALSE);
+
+ folder = item->folder;
+ return folder->klass->is_msg_changed(folder, item, msginfo);
+}
+
+gint folder_item_close(FolderItem *item)
+{
+ Folder *folder;
+
+ g_return_val_if_fail(item != NULL, -1);
+
+ item->opened = FALSE;
+ folder = item->folder;
+ return folder->klass->close(folder, item);
+}
+
+gchar *folder_item_get_cache_file(FolderItem *item)
+{
+ gchar *path;
+ gchar *file;
+
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(item->path != NULL, NULL);
+
+ path = folder_item_get_path(item);
+ g_return_val_if_fail(path != NULL, NULL);
+ if (!is_dir_exist(path))
+ make_dir_hier(path);
+ file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL);
+ g_free(path);
+
+ return file;
+}
+
+gchar *folder_item_get_mark_file(FolderItem *item)
+{
+ gchar *path;
+ gchar *file;
+
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(item->path != NULL, NULL);
+
+ path = folder_item_get_path(item);
+ g_return_val_if_fail(path != NULL, NULL);
+ if (!is_dir_exist(path))
+ make_dir_hier(path);
+ file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
+ g_free(path);
+
+ return file;
+}
+
+static gboolean folder_build_tree(GNode *node, gpointer data)
+{
+ Folder *folder = FOLDER(data);
+ FolderItem *item;
+ XMLNode *xmlnode;
+ GList *list;
+ SpecialFolderItemType stype = F_NORMAL;
+ const gchar *name = NULL;
+ const gchar *path = NULL;
+ PrefsAccount *account = NULL;
+ gboolean no_sub = FALSE, no_select = FALSE, collapsed = FALSE,
+ threaded = TRUE, ac_apply_sub = FALSE;
+ FolderSortKey sort_key = SORT_BY_NONE;
+ FolderSortType sort_type = SORT_ASCENDING;
+ gint new = 0, unread = 0, total = 0;
+ time_t mtime = 0;
+ gboolean use_auto_to_on_reply = FALSE;
+ gchar *auto_to = NULL, *auto_cc = NULL, *auto_bcc = NULL,
+ *auto_replyto = NULL;
+ gboolean trim_summary_subject = FALSE, trim_compose_subject = FALSE;
+
+ g_return_val_if_fail(node->data != NULL, FALSE);
+ if (!node->parent) return FALSE;
+
+ xmlnode = node->data;
+ if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
+ g_warning("tag name != \"folderitem\"\n");
+ return FALSE;
+ }
+
+ list = xmlnode->tag->attr;
+ for (; list != NULL; list = list->next) {
+ XMLAttr *attr = list->data;
+
+ if (!attr || !attr->name || !attr->value) continue;
+ if (!strcmp(attr->name, "type")) {
+ if (!g_ascii_strcasecmp(attr->value, "normal"))
+ stype = F_NORMAL;
+ else if (!g_ascii_strcasecmp(attr->value, "inbox"))
+ stype = F_INBOX;
+ else if (!g_ascii_strcasecmp(attr->value, "outbox"))
+ stype = F_OUTBOX;
+ else if (!g_ascii_strcasecmp(attr->value, "draft"))
+ stype = F_DRAFT;
+ else if (!g_ascii_strcasecmp(attr->value, "queue"))
+ stype = F_QUEUE;
+ else if (!g_ascii_strcasecmp(attr->value, "trash"))
+ stype = F_TRASH;
+ } else if (!strcmp(attr->name, "name"))
+ name = attr->value;
+ else if (!strcmp(attr->name, "path"))
+ path = attr->value;
+ else if (!strcmp(attr->name, "mtime"))
+ mtime = strtoul(attr->value, NULL, 10);
+ else if (!strcmp(attr->name, "new"))
+ new = atoi(attr->value);
+ else if (!strcmp(attr->name, "unread"))
+ unread = atoi(attr->value);
+ else if (!strcmp(attr->name, "total"))
+ total = atoi(attr->value);
+ else if (!strcmp(attr->name, "no_sub"))
+ no_sub = *attr->value == '1' ? TRUE : FALSE;
+ else if (!strcmp(attr->name, "no_select"))
+ no_select = *attr->value == '1' ? TRUE : FALSE;
+ else if (!strcmp(attr->name, "collapsed"))
+ collapsed = *attr->value == '1' ? TRUE : FALSE;
+ else if (!strcmp(attr->name, "threaded"))
+ threaded = *attr->value == '1' ? TRUE : FALSE;
+ else if (!strcmp(attr->name, "sort_key")) {
+ if (!strcmp(attr->value, "none"))
+ sort_key = SORT_BY_NONE;
+ else if (!strcmp(attr->value, "number"))
+ sort_key = SORT_BY_NUMBER;
+ else if (!strcmp(attr->value, "size"))
+ sort_key = SORT_BY_SIZE;
+ else if (!strcmp(attr->value, "date"))
+ sort_key = SORT_BY_DATE;
+ else if (!strcmp(attr->value, "from"))
+ sort_key = SORT_BY_FROM;
+ else if (!strcmp(attr->value, "subject"))
+ sort_key = SORT_BY_SUBJECT;
+ else if (!strcmp(attr->value, "score"))
+ sort_key = SORT_BY_SCORE;
+ else if (!strcmp(attr->value, "label"))
+ sort_key = SORT_BY_LABEL;
+ else if (!strcmp(attr->value, "mark"))
+ sort_key = SORT_BY_MARK;
+ else if (!strcmp(attr->value, "unread"))
+ sort_key = SORT_BY_UNREAD;
+ else if (!strcmp(attr->value, "mime"))
+ sort_key = SORT_BY_MIME;
+ else if (!strcmp(attr->value, "to"))
+ sort_key = SORT_BY_TO;
+ } else if (!strcmp(attr->name, "sort_type")) {
+ if (!strcmp(attr->value, "ascending"))
+ sort_type = SORT_ASCENDING;
+ else
+ sort_type = SORT_DESCENDING;
+ } else if (!strcmp(attr->name, "account_id")) {
+ account = account_find_from_id(atoi(attr->value));
+ if (!account) g_warning("account_id: %s not found\n",
+ attr->value);
+ } else if (!strcmp(attr->name, "account_apply_sub"))
+ ac_apply_sub = *attr->value == '1' ? TRUE : FALSE;
+ else if (!strcmp(attr->name, "to"))
+ auto_to = g_strdup(attr->value);
+ else if (!strcmp(attr->name, "use_auto_to_on_reply"))
+ use_auto_to_on_reply =
+ *attr->value == '1' ? TRUE : FALSE;
+ else if (!strcmp(attr->name, "cc"))
+ auto_cc = g_strdup(attr->value);
+ else if (!strcmp(attr->name, "bcc"))
+ auto_bcc = g_strdup(attr->value);
+ else if (!strcmp(attr->name, "replyto"))
+ auto_replyto = g_strdup(attr->value);
+ else if (!strcmp(attr->name, "trim_summary_subject")) {
+ trim_summary_subject =
+ *attr->value == '1' ? TRUE : FALSE;
+ } else if (!strcmp(attr->name, "trim_compose_subject")) {
+ trim_compose_subject =
+ *attr->value = '1' ? TRUE : FALSE;
+ }
+ }
+
+ item = folder_item_new(name, path);
+ item->stype = stype;
+ item->mtime = mtime;
+ item->new = new;
+ item->unread = unread;
+ item->total = total;
+ item->no_sub = no_sub;
+ item->no_select = no_select;
+ item->collapsed = collapsed;
+ item->threaded = threaded;
+ item->sort_key = sort_key;
+ item->sort_type = sort_type;
+ item->node = node;
+ item->parent = FOLDER_ITEM(node->parent->data);
+ item->folder = folder;
+ switch (stype) {
+ case F_INBOX: folder->inbox = item; break;
+ case F_OUTBOX: folder->outbox = item; break;
+ case F_DRAFT: folder->draft = item; break;
+ case F_QUEUE: folder->queue = item; break;
+ case F_TRASH: folder->trash = item; break;
+ default: break;
+ }
+ item->account = account;
+ item->ac_apply_sub = ac_apply_sub;
+ item->auto_to = auto_to;
+ item->use_auto_to_on_reply = use_auto_to_on_reply;
+ item->auto_cc = auto_cc;
+ item->auto_bcc = auto_bcc;
+ item->auto_replyto = auto_replyto;
+ item->trim_summary_subject = trim_summary_subject;
+ item->trim_compose_subject = trim_compose_subject;
+ node->data = item;
+ xml_free_node(xmlnode);
+
+ return FALSE;
+}
+
+static gboolean folder_read_folder_func(GNode *node, gpointer data)
+{
+ Folder *folder;
+ FolderItem *item;
+ XMLNode *xmlnode;
+ GList *list;
+ FolderType type = F_UNKNOWN;
+ const gchar *name = NULL;
+ const gchar *path = NULL;
+ PrefsAccount *account = NULL;
+ gboolean collapsed = FALSE, threaded = TRUE, ac_apply_sub = FALSE;
+
+ if (g_node_depth(node) != 2) return FALSE;
+ g_return_val_if_fail(node->data != NULL, FALSE);
+
+ xmlnode = node->data;
+ if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
+ g_warning("tag name != \"folder\"\n");
+ return TRUE;
+ }
+ g_node_unlink(node);
+ list = xmlnode->tag->attr;
+ for (; list != NULL; list = list->next) {
+ XMLAttr *attr = list->data;
+
+ if (!attr || !attr->name || !attr->value) continue;
+ if (!strcmp(attr->name, "type")) {
+ if (!g_ascii_strcasecmp(attr->value, "mh"))
+ type = F_MH;
+ else if (!g_ascii_strcasecmp(attr->value, "mbox"))
+ type = F_MBOX;
+ else if (!g_ascii_strcasecmp(attr->value, "maildir"))
+ type = F_MAILDIR;
+ else if (!g_ascii_strcasecmp(attr->value, "imap"))
+ type = F_IMAP;
+ else if (!g_ascii_strcasecmp(attr->value, "news"))
+ type = F_NEWS;
+ } else if (!strcmp(attr->name, "name"))
+ name = attr->value;
+ else if (!strcmp(attr->name, "path"))
+ path = attr->value;
+ else if (!strcmp(attr->name, "collapsed"))
+ collapsed = *attr->value == '1' ? TRUE : FALSE;
+ else if (!strcmp(attr->name, "threaded"))
+ threaded = *attr->value == '1' ? TRUE : FALSE;
+ else if (!strcmp(attr->name, "account_id")) {
+ account = account_find_from_id(atoi(attr->value));
+ if (!account) g_warning("account_id: %s not found\n",
+ attr->value);
+ } else if (!strcmp(attr->name, "account_apply_sub"))
+ ac_apply_sub = *attr->value == '1' ? TRUE : FALSE;
+ }
+
+ folder = folder_new(type, name, path);
+ g_return_val_if_fail(folder != NULL, FALSE);
+ folder->account = account;
+ if (account && (type == F_IMAP || type == F_NEWS))
+ account->folder = REMOTE_FOLDER(folder);
+ item = FOLDER_ITEM(folder->node->data);
+ node->data = item;
+ item->node = node;
+ g_node_destroy(folder->node);
+ folder->node = node;
+ folder_add(folder);
+ item->collapsed = collapsed;
+ item->threaded = threaded;
+ item->account = account;
+ item->ac_apply_sub = ac_apply_sub;
+
+ g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+ folder_build_tree, folder);
+
+ return FALSE;
+}
+
+static gchar *folder_get_list_path(void)
+{
+ static gchar *filename = NULL;
+
+ if (!filename)
+ filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+ FOLDER_LIST, NULL);
+
+ return filename;
+}
+
+#define PUT_ESCAPE_STR(fp, attr, str) \
+{ \
+ fputs(" " attr "=\"", fp); \
+ xml_file_put_escape_str(fp, str); \
+ fputs("\"", fp); \
+}
+
+static void folder_write_list_recursive(GNode *node, gpointer data)
+{
+ FILE *fp = (FILE *)data;
+ FolderItem *item;
+ gint i, depth;
+ static gchar *folder_type_str[] = {"mh", "mbox", "maildir", "imap",
+ "news", "unknown"};
+ static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
+ "draft", "queue", "trash"};
+ static gchar *sort_key_str[] = {"none", "number", "size", "date",
+ "from", "subject", "score", "label",
+ "mark", "unread", "mime", "to"};
+
+ g_return_if_fail(node != NULL);
+ g_return_if_fail(fp != NULL);
+
+ item = FOLDER_ITEM(node->data);
+ g_return_if_fail(item != NULL);
+
+ depth = g_node_depth(node);
+ for (i = 0; i < depth; i++)
+ fputs(" ", fp);
+ if (depth == 1) {
+ Folder *folder = item->folder;
+
+ fprintf(fp, "<folder type=\"%s\"",
+ folder_type_str[FOLDER_TYPE(folder)]);
+ if (folder->name)
+ PUT_ESCAPE_STR(fp, "name", folder->name);
+ if (FOLDER_TYPE(folder) == F_MH)
+ PUT_ESCAPE_STR(fp, "path",
+ LOCAL_FOLDER(folder)->rootpath);
+ if (item->collapsed && node->children)
+ fputs(" collapsed=\"1\"", fp);
+ if (folder->account)
+ fprintf(fp, " account_id=\"%d\"",
+ folder->account->account_id);
+ if (item->ac_apply_sub)
+ fputs(" account_apply_sub=\"1\"", fp);
+ } else {
+ fprintf(fp, "<folderitem type=\"%s\"",
+ folder_item_stype_str[item->stype]);
+ if (item->name)
+ PUT_ESCAPE_STR(fp, "name", item->name);
+ if (item->path)
+ PUT_ESCAPE_STR(fp, "path", item->path);
+
+ if (item->no_sub)
+ fputs(" no_sub=\"1\"", fp);
+ if (item->no_select)
+ fputs(" no_select=\"1\"", fp);
+ if (item->collapsed && node->children)
+ fputs(" collapsed=\"1\"", fp);
+ if (item->threaded)
+ fputs(" threaded=\"1\"", fp);
+ else
+ fputs(" threaded=\"0\"", fp);
+
+ if (item->sort_key != SORT_BY_NONE) {
+ fprintf(fp, " sort_key=\"%s\"",
+ sort_key_str[item->sort_key]);
+ if (item->sort_type == SORT_ASCENDING)
+ fprintf(fp, " sort_type=\"ascending\"");
+ else
+ fprintf(fp, " sort_type=\"descending\"");
+ }
+
+ fprintf(fp,
+ " mtime=\"%lu\" new=\"%d\" unread=\"%d\" total=\"%d\"",
+ item->mtime, item->new, item->unread, item->total);
+
+ if (item->account)
+ fprintf(fp, " account_id=\"%d\"",
+ item->account->account_id);
+ if (item->ac_apply_sub)
+ fputs(" account_apply_sub=\"1\"", fp);
+
+ if (item->auto_to)
+ PUT_ESCAPE_STR(fp, "to", item->auto_to);
+ if (item->use_auto_to_on_reply)
+ fputs(" use_auto_to_on_reply=\"1\"", fp);
+ if (item->auto_cc)
+ PUT_ESCAPE_STR(fp, "cc", item->auto_cc);
+ if (item->auto_bcc)
+ PUT_ESCAPE_STR(fp, "bcc", item->auto_bcc);
+ if (item->auto_replyto)
+ PUT_ESCAPE_STR(fp, "replyto", item->auto_replyto);
+
+ if (item->trim_summary_subject)
+ fputs(" trim_summary_subject=\"1\"", fp);
+ if (item->trim_compose_subject)
+ fputs(" trim_compose_subject=\"1\"", fp);
+ }
+
+ if (node->children) {
+ GNode *child;
+ fputs(">\n", fp);
+
+ child = node->children;
+ while (child) {
+ GNode *cur;
+
+ cur = child;
+ child = cur->next;
+ folder_write_list_recursive(cur, data);
+ }
+
+ for (i = 0; i < depth; i++)
+ fputs(" ", fp);
+ fprintf(fp, "</%s>\n", depth == 1 ? "folder" : "folderitem");
+ } else
+ fputs(" />\n", fp);
+}
+
+#undef PUT_ESCAPE_STR
diff --git a/libsylph/folder.h b/libsylph/folder.h
new file mode 100644
index 00000000..0908e241
--- /dev/null
+++ b/libsylph/folder.h
@@ -0,0 +1,395 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __FOLDER_H__
+#define __FOLDER_H__
+
+#include <glib.h>
+#include <time.h>
+
+typedef struct _Folder Folder;
+typedef struct _FolderClass FolderClass;
+
+typedef struct _LocalFolder LocalFolder;
+typedef struct _RemoteFolder RemoteFolder;
+#if 0
+typedef struct _MboxFolder MboxFolder;
+typedef struct _MaildirFolder MaildirFolder;
+#endif
+
+typedef struct _FolderItem FolderItem;
+
+#define FOLDER(obj) ((Folder *)obj)
+#define FOLDER_CLASS(obj) (FOLDER(obj)->klass)
+#define FOLDER_TYPE(obj) (FOLDER(obj)->klass->type)
+
+#define LOCAL_FOLDER(obj) ((LocalFolder *)obj)
+#define REMOTE_FOLDER(obj) ((RemoteFolder *)obj)
+
+#define FOLDER_IS_LOCAL(obj) (FOLDER_TYPE(obj) == F_MH || \
+ FOLDER_TYPE(obj) == F_MBOX || \
+ FOLDER_TYPE(obj) == F_MAILDIR)
+
+#if 0
+#define MBOX_FOLDER(obj) ((MboxFolder *)obj)
+#define MAILDIR_FOLDER(obj) ((MaildirFolder *)obj)
+#endif
+
+#define FOLDER_ITEM(obj) ((FolderItem *)obj)
+
+#define FOLDER_ITEM_CAN_ADD(obj) \
+ ((obj) && FOLDER_ITEM(obj)->folder && \
+ FOLDER_ITEM(obj)->path && \
+ (FOLDER_IS_LOCAL(FOLDER_ITEM(obj)->folder) || \
+ FOLDER_TYPE(FOLDER_ITEM(obj)->folder) == F_IMAP) && \
+ !FOLDER_ITEM(obj)->no_select)
+
+typedef enum
+{
+ F_MH,
+ F_MBOX,
+ F_MAILDIR,
+ F_IMAP,
+ F_NEWS,
+ F_UNKNOWN
+} FolderType;
+
+typedef enum
+{
+ F_NORMAL,
+ F_INBOX,
+ F_OUTBOX,
+ F_DRAFT,
+ F_QUEUE,
+ F_TRASH
+} SpecialFolderItemType;
+
+typedef enum
+{
+ SORT_BY_NONE,
+ SORT_BY_NUMBER,
+ SORT_BY_SIZE,
+ SORT_BY_DATE,
+ SORT_BY_FROM,
+ SORT_BY_SUBJECT,
+ SORT_BY_SCORE,
+ SORT_BY_LABEL,
+ SORT_BY_MARK,
+ SORT_BY_UNREAD,
+ SORT_BY_MIME,
+ SORT_BY_TO
+} FolderSortKey;
+
+typedef enum
+{
+ SORT_ASCENDING,
+ SORT_DESCENDING
+} FolderSortType;
+
+typedef void (*FolderUIFunc) (Folder *folder,
+ FolderItem *item,
+ gpointer data);
+typedef void (*FolderDestroyNotify) (Folder *folder,
+ FolderItem *item,
+ gpointer data);
+
+#include "prefs_account.h"
+#include "session.h"
+#include "procmsg.h"
+
+struct _Folder
+{
+ FolderClass *klass;
+
+ gchar *name;
+ PrefsAccount *account;
+
+ FolderItem *inbox;
+ FolderItem *outbox;
+ FolderItem *draft;
+ FolderItem *queue;
+ FolderItem *trash;
+
+ FolderUIFunc ui_func;
+ gpointer ui_func_data;
+
+ GNode *node;
+
+ gpointer data;
+};
+
+struct _FolderClass
+{
+ FolderType type;
+
+ /* virtual functions */
+ Folder * (*folder_new) (const gchar *name,
+ const gchar *path);
+ void (*destroy) (Folder *folder);
+
+ gint (*scan_tree) (Folder *folder);
+ gint (*create_tree) (Folder *folder);
+
+ GSList * (*get_msg_list) (Folder *folder,
+ FolderItem *item,
+ gboolean use_cache);
+ /* return value is locale charset */
+ gchar * (*fetch_msg) (Folder *folder,
+ FolderItem *item,
+ gint num);
+ MsgInfo * (*get_msginfo) (Folder *folder,
+ FolderItem *item,
+ gint num);
+ gint (*add_msg) (Folder *folder,
+ FolderItem *dest,
+ const gchar *file,
+ MsgFlags *flags,
+ gboolean remove_source);
+ gint (*add_msgs) (Folder *folder,
+ FolderItem *dest,
+ GSList *file_list,
+ gboolean remove_source,
+ gint *first);
+ gint (*move_msg) (Folder *folder,
+ FolderItem *dest,
+ MsgInfo *msginfo);
+ gint (*move_msgs) (Folder *folder,
+ FolderItem *dest,
+ GSList *msglist);
+ gint (*copy_msg) (Folder *folder,
+ FolderItem *dest,
+ MsgInfo *msginfo);
+ gint (*copy_msgs) (Folder *folder,
+ FolderItem *dest,
+ GSList *msglist);
+ gint (*remove_msg) (Folder *folder,
+ FolderItem *item,
+ MsgInfo *msginfo);
+ gint (*remove_msgs) (Folder *folder,
+ FolderItem *item,
+ GSList *msglist);
+ gint (*remove_all_msg) (Folder *folder,
+ FolderItem *item);
+ gboolean (*is_msg_changed) (Folder *folder,
+ FolderItem *item,
+ MsgInfo *msginfo);
+ gint (*close) (Folder *folder,
+ FolderItem *item);
+ gint (*scan) (Folder *folder,
+ FolderItem *item);
+
+ FolderItem * (*create_folder) (Folder *folder,
+ FolderItem *parent,
+ const gchar *name);
+ gint (*rename_folder) (Folder *folder,
+ FolderItem *item,
+ const gchar *name);
+ gint (*move_folder) (Folder *folder,
+ FolderItem *item,
+ FolderItem *new_parent);
+ gint (*remove_folder) (Folder *folder,
+ FolderItem *item);
+};
+
+struct _LocalFolder
+{
+ Folder folder;
+
+ gchar *rootpath;
+};
+
+struct _RemoteFolder
+{
+ Folder folder;
+
+ Session *session;
+};
+
+#if 0
+struct _MboxFolder
+{
+ LocalFolder lfolder;
+};
+
+struct _MaildirFolder
+{
+ LocalFolder lfolder;
+};
+#endif
+
+struct _FolderItem
+{
+ SpecialFolderItemType stype;
+
+ gchar *name; /* UTF-8 */
+ gchar *path; /* UTF-8 */
+
+ time_t mtime;
+
+ gint new;
+ gint unread;
+ gint total;
+ gint unmarked_num;
+
+ gint last_num;
+
+ /* special flags */
+ guint no_sub : 1; /* no child allowed? */
+ guint no_select : 1; /* not selectable? */
+ guint collapsed : 1; /* collapsed item */
+ guint threaded : 1; /* threaded folder view */
+
+ guint opened : 1; /* opened by summary view */
+ guint updated : 1; /* folderview should be updated */
+
+ guint cache_dirty : 1; /* cache file needs to be updated */
+ guint mark_dirty : 1; /* mark file needs to be updated */
+
+ FolderSortKey sort_key;
+ FolderSortType sort_type;
+
+ GNode *node;
+
+ FolderItem *parent;
+
+ Folder *folder;
+
+ PrefsAccount *account;
+
+ gboolean ac_apply_sub;
+
+ gchar *auto_to;
+ gboolean use_auto_to_on_reply;
+ gchar *auto_cc;
+ gchar *auto_bcc;
+ gchar *auto_replyto;
+
+ gboolean trim_summary_subject;
+ gboolean trim_compose_subject;
+
+ GSList *mark_queue;
+
+ gpointer data;
+};
+
+Folder *folder_new (FolderType type,
+ const gchar *name,
+ const gchar *path);
+void folder_local_folder_init (Folder *folder,
+ const gchar *name,
+ const gchar *path);
+void folder_remote_folder_init (Folder *folder,
+ const gchar *name,
+ const gchar *path);
+
+void folder_destroy (Folder *folder);
+void folder_local_folder_destroy (LocalFolder *lfolder);
+void folder_remote_folder_destroy(RemoteFolder *rfolder);
+
+FolderItem *folder_item_new (const gchar *name,
+ const gchar *path);
+void folder_item_append (FolderItem *parent,
+ FolderItem *item);
+void folder_item_remove (FolderItem *item);
+void folder_item_remove_children (FolderItem *item);
+void folder_item_destroy (FolderItem *item);
+
+gint folder_item_compare (FolderItem *item_a,
+ FolderItem *item_b);
+
+void folder_set_ui_func (Folder *folder,
+ FolderUIFunc func,
+ gpointer data);
+void folder_set_name (Folder *folder,
+ const gchar *name);
+void folder_tree_destroy (Folder *folder);
+
+void folder_add (Folder *folder);
+
+GList *folder_get_list (void);
+gint folder_read_list (void);
+void folder_write_list (void);
+
+gchar *folder_get_status (GPtrArray *folders,
+ gboolean full);
+
+Folder *folder_find_from_path (const gchar *path);
+Folder *folder_find_from_name (const gchar *name,
+ FolderType type);
+FolderItem *folder_find_item_from_path (const gchar *path);
+FolderItem *folder_find_child_item_by_name (FolderItem *item,
+ const gchar *name);
+gchar *folder_get_identifier (Folder *folder);
+gchar *folder_item_get_identifier (FolderItem *item);
+FolderItem *folder_find_item_from_identifier (const gchar *identifier);
+
+Folder *folder_get_default_folder (void);
+FolderItem *folder_get_default_inbox (void);
+FolderItem *folder_get_default_outbox (void);
+FolderItem *folder_get_default_draft (void);
+FolderItem *folder_get_default_queue (void);
+FolderItem *folder_get_default_trash (void);
+
+void folder_set_missing_folders (void);
+void folder_unref_account_all (PrefsAccount *account);
+
+/* return value is locale encoded file name */
+gchar *folder_get_path (Folder *folder);
+gchar *folder_item_get_path (FolderItem *item);
+
+gint folder_item_scan (FolderItem *item);
+void folder_item_scan_foreach (GHashTable *table);
+GSList *folder_item_get_msg_list (FolderItem *item,
+ gboolean use_cache);
+/* return value is locale charset */
+gchar *folder_item_fetch_msg (FolderItem *item,
+ gint num);
+gint folder_item_fetch_all_msg (FolderItem *item);
+MsgInfo *folder_item_get_msginfo (FolderItem *item,
+ gint num);
+gint folder_item_add_msg (FolderItem *dest,
+ const gchar *file,
+ MsgFlags *flags,
+ gboolean remove_source);
+gint folder_item_add_msgs (FolderItem *dest,
+ GSList *file_list,
+ gboolean remove_source,
+ gint *first);
+gint folder_item_move_msg (FolderItem *dest,
+ MsgInfo *msginfo);
+gint folder_item_move_msgs (FolderItem *dest,
+ GSList *msglist);
+gint folder_item_copy_msg (FolderItem *dest,
+ MsgInfo *msginfo);
+gint folder_item_copy_msgs (FolderItem *dest,
+ GSList *msglist);
+gint folder_item_remove_msg (FolderItem *item,
+ MsgInfo *msginfo);
+gint folder_item_remove_msgs (FolderItem *item,
+ GSList *msglist);
+gint folder_item_remove_all_msg (FolderItem *item);
+gboolean folder_item_is_msg_changed (FolderItem *item,
+ MsgInfo *msginfo);
+/* return value is locale chaset */
+gchar *folder_item_get_cache_file (FolderItem *item);
+gchar *folder_item_get_mark_file (FolderItem *item);
+
+gint folder_item_close (FolderItem *item);
+
+#endif /* __FOLDER_H__ */
diff --git a/libsylph/imap.c b/libsylph/imap.c
new file mode 100644
index 00000000..a179c23c
--- /dev/null
+++ b/libsylph/imap.c
@@ -0,0 +1,4105 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#if HAVE_ICONV
+# include <iconv.h>
+#endif
+
+#include "imap.h"
+#include "socket.h"
+#include "ssl.h"
+#include "recv.h"
+#include "procmsg.h"
+#include "procheader.h"
+#include "folder.h"
+#include "prefs_account.h"
+#include "codeconv.h"
+#include "md5.h"
+#include "base64.h"
+#include "utils.h"
+#include "prefs_common.h"
+
+#define IMAP4_PORT 143
+#if USE_SSL
+#define IMAPS_PORT 993
+#endif
+
+#define IMAP_CMD_LIMIT 1000
+
+#define QUOTE_IF_REQUIRED(out, str) \
+{ \
+ if (*str != '"' && strpbrk(str, " \t(){}%*") != NULL) { \
+ gchar *__tmp; \
+ gint len; \
+ \
+ len = strlen(str) + 3; \
+ Xalloca(__tmp, len, return IMAP_ERROR); \
+ g_snprintf(__tmp, len, "\"%s\"", str); \
+ out = __tmp; \
+ } else { \
+ Xstrdup_a(out, str, return IMAP_ERROR); \
+ } \
+}
+
+static GList *session_list = NULL;
+
+static void imap_folder_init (Folder *folder,
+ const gchar *name,
+ const gchar *path);
+
+static Folder *imap_folder_new (const gchar *name,
+ const gchar *path);
+static void imap_folder_destroy (Folder *folder);
+
+static Session *imap_session_new (PrefsAccount *account);
+static gint imap_session_connect (IMAPSession *session);
+static gint imap_session_reconnect (IMAPSession *session);
+static void imap_session_destroy (Session *session);
+/* static void imap_session_destroy_all (void); */
+
+static gint imap_search_flags (IMAPSession *session,
+ GArray **uids,
+ GHashTable **flags_table);
+static gint imap_fetch_flags (IMAPSession *session,
+ GArray **uids,
+ GHashTable **flags_table);
+
+static GSList *imap_get_msg_list (Folder *folder,
+ FolderItem *item,
+ gboolean use_cache);
+static gchar *imap_fetch_msg (Folder *folder,
+ FolderItem *item,
+ gint uid);
+static MsgInfo *imap_get_msginfo (Folder *folder,
+ FolderItem *item,
+ gint uid);
+static gint imap_add_msg (Folder *folder,
+ FolderItem *dest,
+ const gchar *file,
+ MsgFlags *flags,
+ gboolean remove_source);
+static gint imap_add_msgs (Folder *folder,
+ FolderItem *dest,
+ GSList *file_list,
+ gboolean remove_source,
+ gint *first);
+
+static gint imap_move_msg (Folder *folder,
+ FolderItem *dest,
+ MsgInfo *msginfo);
+static gint imap_move_msgs (Folder *folder,
+ FolderItem *dest,
+ GSList *msglist);
+static gint imap_copy_msg (Folder *folder,
+ FolderItem *dest,
+ MsgInfo *msginfo);
+static gint imap_copy_msgs (Folder *folder,
+ FolderItem *dest,
+ GSList *msglist);
+
+static gint imap_remove_msg (Folder *folder,
+ FolderItem *item,
+ MsgInfo *msginfo);
+static gint imap_remove_msgs (Folder *folder,
+ FolderItem *item,
+ GSList *msglist);
+static gint imap_remove_all_msg (Folder *folder,
+ FolderItem *item);
+
+static gboolean imap_is_msg_changed (Folder *folder,
+ FolderItem *item,
+ MsgInfo *msginfo);
+
+static gint imap_close (Folder *folder,
+ FolderItem *item);
+
+static gint imap_scan_folder (Folder *folder,
+ FolderItem *item);
+static gint imap_scan_tree (Folder *folder);
+
+static gint imap_create_tree (Folder *folder);
+
+static FolderItem *imap_create_folder (Folder *folder,
+ FolderItem *parent,
+ const gchar *name);
+static gint imap_rename_folder (Folder *folder,
+ FolderItem *item,
+ const gchar *name);
+static gint imap_move_folder (Folder *folder,
+ FolderItem *item,
+ FolderItem *new_parent);
+static gint imap_remove_folder (Folder *folder,
+ FolderItem *item);
+
+static IMAPSession *imap_session_get (Folder *folder);
+
+static gint imap_greeting (IMAPSession *session);
+static gint imap_auth (IMAPSession *session,
+ const gchar *user,
+ const gchar *pass,
+ IMAPAuthType type);
+
+static gint imap_scan_tree_recursive (IMAPSession *session,
+ FolderItem *item);
+static GSList *imap_parse_list (IMAPSession *session,
+ const gchar *real_path,
+ gchar *separator);
+
+static void imap_create_missing_folders (Folder *folder);
+static FolderItem *imap_create_special_folder
+ (Folder *folder,
+ SpecialFolderItemType stype,
+ const gchar *name);
+
+static gint imap_do_copy_msgs (Folder *folder,
+ FolderItem *dest,
+ GSList *msglist,
+ gboolean remove_source);
+static gint imap_remove_msgs_by_seq_set (Folder *folder,
+ FolderItem *item,
+ GSList *seq_list);
+
+static GSList *imap_get_uncached_messages (IMAPSession *session,
+ FolderItem *item,
+ guint32 first_uid,
+ guint32 last_uid,
+ gboolean update_count);
+static void imap_delete_cached_message (FolderItem *item,
+ guint32 uid);
+static GSList *imap_delete_cached_messages (GSList *mlist,
+ FolderItem *item,
+ guint32 first_uid,
+ guint32 last_uid);
+static void imap_delete_all_cached_messages (FolderItem *item);
+
+#if USE_SSL
+static SockInfo *imap_open (const gchar *server,
+ gushort port,
+ SSLType ssl_type);
+#else
+static SockInfo *imap_open (const gchar *server,
+ gushort port);
+#endif
+
+static gint imap_msg_list_change_perm_flags (GSList *msglist,
+ MsgPermFlags flags,
+ gboolean is_set);
+static gchar *imap_get_flag_str (IMAPFlags flags);
+static gint imap_set_message_flags (IMAPSession *session,
+ const gchar *seq_set,
+ IMAPFlags flags,
+ gboolean is_set);
+static gint imap_select (IMAPSession *session,
+ IMAPFolder *folder,
+ const gchar *path,
+ gint *exists,
+ gint *recent,
+ gint *unseen,
+ guint32 *uid_validity);
+static gint imap_status (IMAPSession *session,
+ IMAPFolder *folder,
+ const gchar *path,
+ gint *messages,
+ gint *recent,
+ guint32 *uid_next,
+ guint32 *uid_validity,
+ gint *unseen);
+
+static void imap_parse_namespace (IMAPSession *session,
+ IMAPFolder *folder);
+static void imap_get_namespace_by_list (IMAPSession *session,
+ IMAPFolder *folder);
+static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
+ const gchar *path);
+static gchar imap_get_path_separator (IMAPFolder *folder,
+ const gchar *path);
+static gchar *imap_get_real_path (IMAPFolder *folder,
+ const gchar *path);
+
+static gchar *imap_parse_atom (IMAPSession *session,
+ gchar *src,
+ gchar *dest,
+ gint dest_len,
+ GString *str);
+static MsgFlags imap_parse_flags (const gchar *flag_str);
+static IMAPFlags imap_parse_imap_flags (const gchar *flag_str);
+static MsgInfo *imap_parse_envelope (IMAPSession *session,
+ FolderItem *item,
+ GString *line_str);
+
+static gboolean imap_has_capability (IMAPSession *session,
+ const gchar *capability);
+static void imap_capability_free (IMAPSession *session);
+
+/* low-level IMAP4rev1 commands */
+static gint imap_cmd_capability (IMAPSession *session);
+static gint imap_cmd_authenticate
+ (IMAPSession *session,
+ const gchar *user,
+ const gchar *pass,
+ IMAPAuthType type);
+static gint imap_cmd_login (IMAPSession *session,
+ const gchar *user,
+ const gchar *pass);
+static gint imap_cmd_logout (IMAPSession *session);
+static gint imap_cmd_noop (IMAPSession *session);
+#if USE_SSL
+static gint imap_cmd_starttls (IMAPSession *session);
+#endif
+static gint imap_cmd_namespace (IMAPSession *session,
+ gchar **ns_str);
+static gint imap_cmd_list (IMAPSession *session,
+ const gchar *ref,
+ const gchar *mailbox,
+ GPtrArray *argbuf);
+static gint imap_cmd_do_select (IMAPSession *session,
+ const gchar *folder,
+ gboolean examine,
+ gint *exists,
+ gint *recent,
+ gint *unseen,
+ guint32 *uid_validity);
+static gint imap_cmd_select (IMAPSession *session,
+ const gchar *folder,
+ gint *exists,
+ gint *recent,
+ gint *unseen,
+ guint32 *uid_validity);
+static gint imap_cmd_examine (IMAPSession *session,
+ const gchar *folder,
+ gint *exists,
+ gint *recent,
+ gint *unseen,
+ guint32 *uid_validity);
+static gint imap_cmd_create (IMAPSession *session,
+ const gchar *folder);
+static gint imap_cmd_rename (IMAPSession *session,
+ const gchar *oldfolder,
+ const gchar *newfolder);
+static gint imap_cmd_delete (IMAPSession *session,
+ const gchar *folder);
+static gint imap_cmd_envelope (IMAPSession *session,
+ const gchar *seq_set);
+static gint imap_cmd_search (IMAPSession *session,
+ const gchar *criteria,
+ GArray **result);
+static gint imap_cmd_fetch (IMAPSession *session,
+ guint32 uid,
+ const gchar *filename);
+static gint imap_cmd_append (IMAPSession *session,
+ const gchar *destfolder,
+ const gchar *file,
+ IMAPFlags flags,
+ guint32 *new_uid);
+static gint imap_cmd_copy (IMAPSession *session,
+ const gchar *seq_set,
+ const gchar *destfolder);
+static gint imap_cmd_store (IMAPSession *session,
+ const gchar *seq_set,
+ const gchar *sub_cmd);
+static gint imap_cmd_expunge (IMAPSession *session);
+static gint imap_cmd_close (IMAPSession *session);
+
+static gint imap_cmd_ok (IMAPSession *session,
+ GPtrArray *argbuf);
+static void imap_cmd_gen_send (IMAPSession *session,
+ const gchar *format, ...);
+static gint imap_cmd_gen_recv (IMAPSession *session,
+ gchar **ret);
+
+/* misc utility functions */
+static gchar *strchr_cpy (const gchar *src,
+ gchar ch,
+ gchar *dest,
+ gint len);
+static gchar *get_quoted (const gchar *src,
+ gchar ch,
+ gchar *dest,
+ gint len);
+static gchar *search_array_contain_str (GPtrArray *array,
+ gchar *str);
+static gchar *search_array_str (GPtrArray *array,
+ gchar *str);
+static void imap_path_separator_subst (gchar *str,
+ gchar separator);
+
+static gchar *imap_modified_utf7_to_utf8 (const gchar *mutf7_str);
+static gchar *imap_utf8_to_modified_utf7 (const gchar *from);
+
+static GSList *imap_get_seq_set_from_msglist (GSList *msglist);
+static void imap_seq_set_free (GSList *seq_list);
+
+static GHashTable *imap_get_uid_table (GArray *array);
+
+static gboolean imap_rename_folder_func (GNode *node,
+ gpointer data);
+
+static FolderClass imap_class =
+{
+ F_IMAP,
+
+ imap_folder_new,
+ imap_folder_destroy,
+
+ imap_scan_tree,
+ imap_create_tree,
+
+ imap_get_msg_list,
+ imap_fetch_msg,
+ imap_get_msginfo,
+ imap_add_msg,
+ imap_add_msgs,
+ imap_move_msg,
+ imap_move_msgs,
+ imap_copy_msg,
+ imap_copy_msgs,
+ imap_remove_msg,
+ imap_remove_msgs,
+ imap_remove_all_msg,
+ imap_is_msg_changed,
+ imap_close,
+ imap_scan_folder,
+
+ imap_create_folder,
+ imap_rename_folder,
+ imap_move_folder,
+ imap_remove_folder
+};
+
+
+FolderClass *imap_get_class(void)
+{
+ return &imap_class;
+}
+
+static Folder *imap_folder_new(const gchar *name, const gchar *path)
+{
+ Folder *folder;
+
+ folder = (Folder *)g_new0(IMAPFolder, 1);
+ imap_folder_init(folder, name, path);
+
+ return folder;
+}
+
+static void imap_folder_destroy(Folder *folder)
+{
+ gchar *dir;
+
+ dir = folder_get_path(folder);
+ if (is_dir_exist(dir))
+ remove_dir_recursive(dir);
+ g_free(dir);
+
+ folder_remote_folder_destroy(REMOTE_FOLDER(folder));
+}
+
+static void imap_folder_init(Folder *folder, const gchar *name,
+ const gchar *path)
+{
+ folder->klass = imap_get_class();
+ folder_remote_folder_init(folder, name, path);
+}
+
+static IMAPSession *imap_session_get(Folder *folder)
+{
+ RemoteFolder *rfolder = REMOTE_FOLDER(folder);
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL);
+ g_return_val_if_fail(folder->account != NULL, NULL);
+
+ if (!prefs_common.online_mode)
+ return NULL;
+
+ if (!rfolder->session) {
+ rfolder->session = imap_session_new(folder->account);
+ if (rfolder->session)
+ imap_parse_namespace(IMAP_SESSION(rfolder->session),
+ IMAP_FOLDER(folder));
+ return IMAP_SESSION(rfolder->session);
+ }
+
+ if (time(NULL) - rfolder->session->last_access_time <
+ SESSION_TIMEOUT_INTERVAL) {
+ return IMAP_SESSION(rfolder->session);
+ }
+
+ if (imap_cmd_noop(IMAP_SESSION(rfolder->session)) != IMAP_SUCCESS) {
+ log_warning(_("IMAP4 connection to %s has been"
+ " disconnected. Reconnecting...\n"),
+ folder->account->recv_server);
+ if (imap_session_reconnect(IMAP_SESSION(rfolder->session))
+ == IMAP_SUCCESS)
+ imap_parse_namespace(IMAP_SESSION(rfolder->session),
+ IMAP_FOLDER(folder));
+ else {
+ session_destroy(rfolder->session);
+ rfolder->session = NULL;
+ }
+ }
+
+ return IMAP_SESSION(rfolder->session);
+}
+
+static gint imap_greeting(IMAPSession *session)
+{
+ gchar *greeting;
+ gint ok;
+
+ if ((ok = imap_cmd_gen_recv(session, &greeting)) != IMAP_SUCCESS)
+ return ok;
+
+ if (greeting[0] != '*' || greeting[1] != ' ')
+ ok = IMAP_ERROR;
+ else if (!strncmp(greeting + 2, "OK", 2))
+ ok = IMAP_SUCCESS;
+ else if (!strncmp(greeting + 2, "PREAUTH", 7)) {
+ session->authenticated = TRUE;
+ ok = IMAP_SUCCESS;
+ } else
+ ok = IMAP_ERROR;
+
+ g_free(greeting);
+ return ok;
+}
+
+static gint imap_auth(IMAPSession *session, const gchar *user,
+ const gchar *pass, IMAPAuthType type)
+{
+ gboolean nologin;
+ gint ok = IMAP_AUTHFAIL;
+
+ nologin = imap_has_capability(session, "LOGINDISABLED");
+
+ switch (type) {
+ case 0:
+ if (imap_has_capability(session, "AUTH=CRAM-MD5"))
+ ok = imap_cmd_authenticate(session, user, pass, type);
+ else if (nologin)
+ log_print(_("IMAP4 server disables LOGIN.\n"));
+ else
+ ok = imap_cmd_login(session, user, pass);
+ break;
+ case IMAP_AUTH_LOGIN:
+ if (nologin)
+ log_warning(_("IMAP4 server disables LOGIN.\n"));
+ else
+ ok = imap_cmd_login(session, user, pass);
+ break;
+ case IMAP_AUTH_CRAM_MD5:
+ ok = imap_cmd_authenticate(session, user, pass, type);
+ break;
+ default:
+ break;
+ }
+
+ if (ok == IMAP_SUCCESS)
+ session->authenticated = TRUE;
+
+ return ok;
+}
+
+static Session *imap_session_new(PrefsAccount *account)
+{
+ IMAPSession *session;
+ gushort port;
+
+ g_return_val_if_fail(account != NULL, NULL);
+ g_return_val_if_fail(account->recv_server != NULL, NULL);
+ g_return_val_if_fail(account->userid != NULL, NULL);
+
+#if USE_SSL
+ port = account->set_imapport ? account->imapport
+ : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
+#else
+ port = account->set_imapport ? account->imapport : IMAP4_PORT;
+#endif
+
+ session = g_new0(IMAPSession, 1);
+
+ session_init(SESSION(session));
+
+ SESSION(session)->type = SESSION_IMAP;
+ SESSION(session)->sock = NULL;
+ SESSION(session)->server = g_strdup(account->recv_server);
+ SESSION(session)->port = port;
+#if USE_SSL
+ SESSION(session)->ssl_type = account->ssl_imap;
+#endif
+ SESSION(session)->last_access_time = time(NULL);
+ SESSION(session)->data = account;
+
+ SESSION(session)->destroy = imap_session_destroy;
+
+ session->authenticated = FALSE;
+ session->capability = NULL;
+ session->uidplus = FALSE;
+ session->mbox = NULL;
+ session->cmd_count = 0;
+
+ session_list = g_list_append(session_list, session);
+
+ if (imap_session_connect(session) != IMAP_SUCCESS) {
+ session_destroy(SESSION(session));
+ return NULL;
+ }
+
+ return SESSION(session);
+}
+
+static gint imap_session_connect(IMAPSession *session)
+{
+ SockInfo *sock;
+ PrefsAccount *account;
+ gchar *pass;
+
+ g_return_val_if_fail(session != NULL, IMAP_ERROR);
+
+ account = (PrefsAccount *)(SESSION(session)->data);
+
+ log_message(_("creating IMAP4 connection to %s:%d ...\n"),
+ SESSION(session)->server, SESSION(session)->port);
+
+ pass = account->passwd;
+ if (!pass) {
+ gchar *tmp_pass;
+ tmp_pass = input_query_password(account->recv_server,
+ account->userid);
+ if (!tmp_pass)
+ return IMAP_ERROR;
+ Xstrdup_a(pass, tmp_pass,
+ {g_free(tmp_pass); return IMAP_ERROR;});
+ g_free(tmp_pass);
+ }
+
+#if USE_SSL
+ if ((sock = imap_open(SESSION(session)->server, SESSION(session)->port,
+ SESSION(session)->ssl_type)) == NULL)
+#else
+ if ((sock = imap_open(SESSION(session)->server, SESSION(session)->port))
+ == NULL)
+#endif
+ return IMAP_ERROR;
+
+ SESSION(session)->sock = sock;
+
+ if (imap_greeting(session) != IMAP_SUCCESS)
+ return IMAP_ERROR;
+ if (imap_cmd_capability(session) != IMAP_SUCCESS)
+ return IMAP_ERROR;
+
+ if (imap_has_capability(session, "UIDPLUS"))
+ session->uidplus = TRUE;
+
+#if USE_SSL
+ if (account->ssl_imap == SSL_STARTTLS &&
+ imap_has_capability(session, "STARTTLS")) {
+ gint ok;
+
+ ok = imap_cmd_starttls(session);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("Can't start TLS session.\n"));
+ return IMAP_ERROR;
+ }
+ if (!ssl_init_socket_with_method(sock, SSL_METHOD_TLSv1))
+ return IMAP_SOCKET;
+
+ /* capability can be changed after STARTTLS */
+ if (imap_cmd_capability(session) != IMAP_SUCCESS)
+ return IMAP_ERROR;
+ }
+#endif
+
+ if (!session->authenticated &&
+ imap_auth(session, account->userid, pass, account->imap_auth_type)
+ != IMAP_SUCCESS) {
+ imap_cmd_logout(session);
+ return IMAP_AUTHFAIL;
+ }
+
+ return IMAP_SUCCESS;
+}
+
+static gint imap_session_reconnect(IMAPSession *session)
+{
+ g_return_val_if_fail(session != NULL, IMAP_ERROR);
+
+ session_disconnect(SESSION(session));
+
+ imap_capability_free(session);
+ session->uidplus = FALSE;
+ g_free(session->mbox);
+ session->mbox = NULL;
+ session->authenticated = FALSE;
+ SESSION(session)->state = SESSION_READY;
+
+ return imap_session_connect(session);
+}
+
+static void imap_session_destroy(Session *session)
+{
+ imap_capability_free(IMAP_SESSION(session));
+ g_free(IMAP_SESSION(session)->mbox);
+ session_list = g_list_remove(session_list, session);
+}
+
+#if 0
+static void imap_session_destroy_all(void)
+{
+ while (session_list != NULL) {
+ IMAPSession *session = (IMAPSession *)session_list->data;
+
+ imap_cmd_logout(session);
+ session_destroy(SESSION(session));
+ }
+}
+#endif
+
+#define THROW goto catch
+
+static gint imap_search_flags(IMAPSession *session, GArray **uids,
+ GHashTable **flags_table)
+{
+ gint ok;
+ gint i;
+ GArray *flag_uids;
+ GHashTable *unseen_table;
+ GHashTable *flagged_table;
+ GHashTable *answered_table;
+ guint32 uid;
+ IMAPFlags flags;
+
+ ok = imap_cmd_search(session, "ALL", uids);
+ if (ok != IMAP_SUCCESS) return ok;
+
+ ok = imap_cmd_search(session, "UNSEEN", &flag_uids);
+ if (ok != IMAP_SUCCESS) {
+ g_array_free(*uids, TRUE);
+ return ok;
+ }
+ unseen_table = imap_get_uid_table(flag_uids);
+ g_array_free(flag_uids, TRUE);
+ ok = imap_cmd_search(session, "FLAGGED", &flag_uids);
+ if (ok != IMAP_SUCCESS) {
+ g_hash_table_destroy(unseen_table);
+ g_array_free(*uids, TRUE);
+ return ok;
+ }
+ flagged_table = imap_get_uid_table(flag_uids);
+ g_array_free(flag_uids, TRUE);
+ ok = imap_cmd_search(session, "ANSWERED", &flag_uids);
+ if (ok != IMAP_SUCCESS) {
+ g_hash_table_destroy(flagged_table);
+ g_hash_table_destroy(unseen_table);
+ g_array_free(*uids, TRUE);
+ return ok;
+ }
+ answered_table = imap_get_uid_table(flag_uids);
+ g_array_free(flag_uids, TRUE);
+
+ *flags_table = g_hash_table_new(NULL, g_direct_equal);
+
+ for (i = 0; i < (*uids)->len; i++) {
+ uid = g_array_index(*uids, guint32, i);
+ flags = IMAP_FLAG_DRAFT;
+ if (!g_hash_table_lookup(unseen_table, GUINT_TO_POINTER(uid)))
+ flags |= IMAP_FLAG_SEEN;
+ if (g_hash_table_lookup(flagged_table, GUINT_TO_POINTER(uid)))
+ flags |= IMAP_FLAG_FLAGGED;
+ if (g_hash_table_lookup(answered_table, GUINT_TO_POINTER(uid)))
+ flags |= IMAP_FLAG_ANSWERED;
+ g_hash_table_insert(*flags_table, GUINT_TO_POINTER(uid),
+ GINT_TO_POINTER(flags));
+ }
+
+ g_hash_table_destroy(answered_table);
+ g_hash_table_destroy(flagged_table);
+ g_hash_table_destroy(unseen_table);
+
+ return IMAP_SUCCESS;
+}
+
+static gint imap_fetch_flags(IMAPSession *session, GArray **uids,
+ GHashTable **flags_table)
+{
+ gint ok;
+ gchar *tmp;
+ gchar *cur_pos;
+ gchar buf[IMAPBUFSIZE];
+ guint32 uid;
+ IMAPFlags flags;
+
+ imap_cmd_gen_send(session, "UID FETCH 1:* (UID FLAGS)");
+
+ *uids = g_array_new(FALSE, FALSE, sizeof(guint32));
+ *flags_table = g_hash_table_new(NULL, g_direct_equal);
+
+ while ((ok = imap_cmd_gen_recv(session, &tmp)) == IMAP_SUCCESS) {
+ if (tmp[0] != '*' || tmp[1] != ' ') {
+ g_free(tmp);
+ break;
+ }
+ cur_pos = tmp + 2;
+
+#define PARSE_ONE_ELEMENT(ch) \
+{ \
+ cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
+ if (cur_pos == NULL) { \
+ g_warning("cur_pos == NULL\n"); \
+ g_free(tmp); \
+ g_hash_table_destroy(*flags_table); \
+ g_array_free(*uids, TRUE); \
+ return IMAP_ERROR; \
+ } \
+}
+
+ PARSE_ONE_ELEMENT(' ');
+ PARSE_ONE_ELEMENT(' ');
+ if (strcmp(buf, "FETCH") != 0) {
+ g_free(tmp);
+ continue;
+ }
+ if (*cur_pos != '(') {
+ g_free(tmp);
+ continue;
+ }
+ cur_pos++;
+ uid = 0;
+ flags = 0;
+
+ while (*cur_pos != '\0' && *cur_pos != ')') {
+ while (*cur_pos == ' ') cur_pos++;
+
+ if (!strncmp(cur_pos, "UID ", 4)) {
+ cur_pos += 4;
+ uid = strtoul(cur_pos, &cur_pos, 10);
+ } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
+ cur_pos += 6;
+ if (*cur_pos != '(') {
+ g_warning("*cur_pos != '('\n");
+ break;
+ }
+ cur_pos++;
+ PARSE_ONE_ELEMENT(')');
+ flags = imap_parse_imap_flags(buf);
+ flags |= IMAP_FLAG_DRAFT;
+ } else {
+ g_warning("invalid FETCH response: %s\n", cur_pos);
+ break;
+ }
+ }
+
+#undef PARSE_ONE_ELEMENT
+
+ if (uid > 0) {
+ g_array_append_val(*uids, uid);
+ g_hash_table_insert(*flags_table, GUINT_TO_POINTER(uid),
+ GINT_TO_POINTER(flags));
+ }
+
+ g_free(tmp);
+ }
+
+ if (ok != IMAP_SUCCESS) {
+ g_hash_table_destroy(*flags_table);
+ g_array_free(*uids, TRUE);
+ }
+
+ return ok;
+}
+
+static GSList *imap_get_msg_list(Folder *folder, FolderItem *item,
+ gboolean use_cache)
+{
+ GSList *mlist = NULL;
+ IMAPSession *session;
+ gint ok, exists = 0, recent = 0, unseen = 0;
+ guint32 uid_validity = 0;
+ guint32 first_uid = 0, last_uid = 0;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL);
+ g_return_val_if_fail(folder->account != NULL, NULL);
+
+ item->new = item->unread = item->total = 0;
+
+ session = imap_session_get(folder);
+
+ if (!session) {
+ mlist = procmsg_read_cache(item, FALSE);
+ item->last_num = procmsg_get_last_num_in_msg_list(mlist);
+ procmsg_set_flags(mlist, item);
+ return mlist;
+ }
+
+ ok = imap_select(session, IMAP_FOLDER(folder), item->path,
+ &exists, &recent, &unseen, &uid_validity);
+ if (ok != IMAP_SUCCESS) THROW;
+
+ if (exists == 0) {
+ imap_delete_all_cached_messages(item);
+ return NULL;
+ }
+
+ /* invalidate current cache if UIDVALIDITY has been changed */
+ if (item->mtime != uid_validity) {
+ debug_print("imap_get_msg_list: "
+ "UIDVALIDITY has been changed.\n");
+ use_cache = FALSE;
+ }
+
+ if (use_cache) {
+ GArray *uids;
+ GHashTable *msg_table;
+ GHashTable *flags_table;
+ guint32 cache_last;
+ guint32 begin = 0;
+ GSList *cur, *next = NULL;
+ MsgInfo *msginfo;
+ IMAPFlags imap_flags;
+
+ /* get cache data */
+ mlist = procmsg_read_cache(item, FALSE);
+ procmsg_set_flags(mlist, item);
+ cache_last = procmsg_get_last_num_in_msg_list(mlist);
+
+ /* get all UID list and flags */
+ ok = imap_search_flags(session, &uids, &flags_table);
+ if (ok != IMAP_SUCCESS) {
+ if (ok == IMAP_SOCKET || ok == IMAP_IOERR) THROW;
+ ok = imap_fetch_flags(session, &uids, &flags_table);
+ if (ok != IMAP_SUCCESS) THROW;
+ }
+
+ if (uids->len > 0) {
+ first_uid = g_array_index(uids, guint32, 0);
+ last_uid = g_array_index(uids, guint32, uids->len - 1);
+ } else {
+ g_array_free(uids, TRUE);
+ g_hash_table_destroy(flags_table);
+ THROW;
+ }
+
+ /* sync message flags with server */
+ for (cur = mlist; cur != NULL; cur = next) {
+ msginfo = (MsgInfo *)cur->data;
+ next = cur->next;
+ imap_flags = GPOINTER_TO_INT(g_hash_table_lookup
+ (flags_table,
+ GUINT_TO_POINTER(msginfo->msgnum)));
+
+ if (imap_flags == 0) {
+ debug_print("imap_get_msg_list: "
+ "message %u has been deleted.\n",
+ msginfo->msgnum);
+ imap_delete_cached_message
+ (item, msginfo->msgnum);
+ if (MSG_IS_NEW(msginfo->flags))
+ item->new--;
+ if (MSG_IS_UNREAD(msginfo->flags))
+ item->unread--;
+ item->total--;
+ mlist = g_slist_remove(mlist, msginfo);
+ procmsg_msginfo_free(msginfo);
+ item->cache_dirty = TRUE;
+ item->mark_dirty = TRUE;
+ continue;
+ }
+
+ if (!IMAP_IS_SEEN(imap_flags)) {
+ if (!MSG_IS_UNREAD(msginfo->flags)) {
+ item->unread++;
+ MSG_SET_PERM_FLAGS(msginfo->flags,
+ MSG_UNREAD);
+ item->mark_dirty = TRUE;
+ }
+ } else {
+ if (MSG_IS_NEW(msginfo->flags)) {
+ item->new--;
+ item->mark_dirty = TRUE;
+ }
+ if (MSG_IS_UNREAD(msginfo->flags)) {
+ item->unread--;
+ item->mark_dirty = TRUE;
+ }
+ MSG_UNSET_PERM_FLAGS(msginfo->flags,
+ MSG_NEW|MSG_UNREAD);
+ }
+
+ if (IMAP_IS_FLAGGED(imap_flags)) {
+ if (!MSG_IS_MARKED(msginfo->flags)) {
+ MSG_SET_PERM_FLAGS(msginfo->flags,
+ MSG_MARKED);
+ item->mark_dirty = TRUE;
+ }
+ } else {
+ if (MSG_IS_MARKED(msginfo->flags)) {
+ MSG_UNSET_PERM_FLAGS(msginfo->flags,
+ MSG_MARKED);
+ item->mark_dirty = TRUE;
+ }
+ }
+ if (IMAP_IS_ANSWERED(imap_flags)) {
+ if (!MSG_IS_REPLIED(msginfo->flags)) {
+ MSG_SET_PERM_FLAGS(msginfo->flags,
+ MSG_REPLIED);
+ item->mark_dirty = TRUE;
+ }
+ } else {
+ if (MSG_IS_REPLIED(msginfo->flags)) {
+ MSG_UNSET_PERM_FLAGS(msginfo->flags,
+ MSG_REPLIED);
+ item->mark_dirty = TRUE;
+ }
+ }
+ }
+
+ /* check for the first new message */
+ msg_table = procmsg_msg_hash_table_create(mlist);
+ if (msg_table == NULL)
+ begin = first_uid;
+ else {
+ gint i;
+
+ for (i = 0; i < uids->len; i++) {
+ guint32 uid;
+
+ uid = g_array_index(uids, guint32, i);
+ if (g_hash_table_lookup
+ (msg_table, GUINT_TO_POINTER(uid))
+ == NULL) {
+ debug_print("imap_get_msg_list: "
+ "first new UID: %u\n", uid);
+ begin = uid;
+ break;
+ }
+ }
+ g_hash_table_destroy(msg_table);
+ }
+
+ g_array_free(uids, TRUE);
+ g_hash_table_destroy(flags_table);
+
+ /* remove ununsed caches */
+ if (first_uid > 0 && last_uid > 0) {
+ mlist = imap_delete_cached_messages
+ (mlist, item, 0, first_uid - 1);
+ mlist = imap_delete_cached_messages
+ (mlist, item, begin > 0 ? begin : last_uid + 1,
+ UINT_MAX);
+ }
+
+ if (begin > 0 && begin <= last_uid) {
+ GSList *newlist;
+ newlist = imap_get_uncached_messages(session, item,
+ begin, last_uid,
+ TRUE);
+ if (newlist)
+ item->cache_dirty = TRUE;
+ mlist = g_slist_concat(mlist, newlist);
+ }
+ } else {
+ imap_delete_all_cached_messages(item);
+ mlist = imap_get_uncached_messages(session, item, 0, 0, TRUE);
+ last_uid = procmsg_get_last_num_in_msg_list(mlist);
+ item->cache_dirty = TRUE;
+ }
+
+ item->mtime = uid_validity;
+
+ mlist = procmsg_sort_msg_list(mlist, item->sort_key, item->sort_type);
+
+ item->last_num = last_uid;
+
+ debug_print("cache_dirty: %d, mark_dirty: %d\n",
+ item->cache_dirty, item->mark_dirty);
+
+catch:
+ return mlist;
+}
+
+#undef THROW
+
+static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
+{
+ gchar *path, *filename;
+ IMAPSession *session;
+ gint ok;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+
+ path = folder_item_get_path(item);
+ if (!is_dir_exist(path))
+ make_dir_hier(path);
+ filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
+ g_free(path);
+
+ if (is_file_exist(filename)) {
+ debug_print("message %d has been already cached.\n", uid);
+ return filename;
+ }
+
+ session = imap_session_get(folder);
+ if (!session) {
+ g_free(filename);
+ return NULL;
+ }
+
+ ok = imap_select(session, IMAP_FOLDER(folder), item->path,
+ NULL, NULL, NULL, NULL);
+ if (ok != IMAP_SUCCESS) {
+ g_warning("can't select mailbox %s\n", item->path);
+ g_free(filename);
+ return NULL;
+ }
+
+ debug_print("getting message %d...\n", uid);
+ ok = imap_cmd_fetch(session, (guint32)uid, filename);
+
+ if (ok != IMAP_SUCCESS) {
+ g_warning("can't fetch message %d\n", uid);
+ g_free(filename);
+ return NULL;
+ }
+
+ return filename;
+}
+
+static MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
+{
+ IMAPSession *session;
+ GSList *list;
+ MsgInfo *msginfo = NULL;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+
+ session = imap_session_get(folder);
+ g_return_val_if_fail(session != NULL, NULL);
+
+ list = imap_get_uncached_messages(session, item, uid, uid, FALSE);
+ if (list) {
+ msginfo = (MsgInfo *)list->data;
+ list->data = NULL;
+ }
+ procmsg_msg_list_free(list);
+
+ return msginfo;
+}
+
+static gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
+ MsgFlags *flags, gboolean remove_source)
+{
+ GSList file_list;
+ MsgFileInfo fileinfo;
+
+ g_return_val_if_fail(file != NULL, -1);
+
+ fileinfo.file = (gchar *)file;
+ fileinfo.flags = flags;
+ file_list.data = &fileinfo;
+ file_list.next = NULL;
+
+ return imap_add_msgs(folder, dest, &file_list, remove_source, NULL);
+}
+
+static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
+ gboolean remove_source, gint *first)
+{
+ gchar *destdir;
+ IMAPSession *session;
+ gint messages, recent, unseen;
+ guint32 uid_next, uid_validity;
+ guint32 last_uid = 0;
+ GSList *cur;
+ MsgFileInfo *fileinfo;
+ gint ok;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(file_list != NULL, -1);
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ ok = imap_status(session, IMAP_FOLDER(folder), dest->path,
+ &messages, &recent, &uid_next, &uid_validity, &unseen);
+ if (ok != IMAP_SUCCESS) {
+ g_warning("can't append messages\n");
+ return -1;
+ }
+
+ destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
+
+ if (!session->uidplus)
+ last_uid = uid_next - 1;
+ if (first)
+ *first = uid_next;
+
+ for (cur = file_list; cur != NULL; cur = cur->next) {
+ IMAPFlags iflags = 0;
+ guint32 new_uid = 0;
+
+ fileinfo = (MsgFileInfo *)cur->data;
+
+ if (fileinfo->flags) {
+ if (MSG_IS_MARKED(*fileinfo->flags))
+ iflags |= IMAP_FLAG_FLAGGED;
+ if (MSG_IS_REPLIED(*fileinfo->flags))
+ iflags |= IMAP_FLAG_ANSWERED;
+ if (!MSG_IS_UNREAD(*fileinfo->flags))
+ iflags |= IMAP_FLAG_SEEN;
+ }
+
+ if (dest->stype == F_OUTBOX ||
+ dest->stype == F_QUEUE ||
+ dest->stype == F_DRAFT ||
+ dest->stype == F_TRASH)
+ iflags |= IMAP_FLAG_SEEN;
+
+ ok = imap_cmd_append(session, destdir, fileinfo->file, iflags,
+ &new_uid);
+
+ if (ok != IMAP_SUCCESS) {
+ g_warning("can't append message %s\n", fileinfo->file);
+ g_free(destdir);
+ return -1;
+ }
+
+ if (!session->uidplus)
+ last_uid++;
+ else if (last_uid < new_uid)
+ last_uid = new_uid;
+
+ dest->last_num = last_uid;
+ dest->total++;
+ dest->updated = TRUE;
+
+ if (fileinfo->flags) {
+ if (MSG_IS_UNREAD(*fileinfo->flags))
+ dest->unread++;
+ } else
+ dest->unread++;
+ }
+
+ g_free(destdir);
+
+ if (remove_source) {
+ for (cur = file_list; cur != NULL; cur = cur->next) {
+ fileinfo = (MsgFileInfo *)cur->data;
+ if (g_unlink(fileinfo->file) < 0)
+ FILE_OP_ERROR(fileinfo->file, "unlink");
+ }
+ }
+
+ return last_uid;
+}
+
+static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist,
+ gboolean remove_source)
+{
+ FolderItem *src;
+ gchar *destdir;
+ GSList *seq_list, *cur;
+ MsgInfo *msginfo;
+ IMAPSession *session;
+ gint ok = IMAP_SUCCESS;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msglist != NULL, -1);
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ msginfo = (MsgInfo *)msglist->data;
+
+ src = msginfo->folder;
+ if (src == dest) {
+ g_warning("the src folder is identical to the dest.\n");
+ return -1;
+ }
+
+ ok = imap_select(session, IMAP_FOLDER(folder), src->path,
+ NULL, NULL, NULL, NULL);
+ if (ok != IMAP_SUCCESS)
+ return ok;
+
+ destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
+
+ seq_list = imap_get_seq_set_from_msglist(msglist);
+
+ for (cur = seq_list; cur != NULL; cur = cur->next) {
+ gchar *seq_set = (gchar *)cur->data;
+
+ if (remove_source)
+ debug_print("Moving message %s%c[%s] to %s ...\n",
+ src->path, G_DIR_SEPARATOR,
+ seq_set, destdir);
+ else
+ debug_print("Copying message %s%c[%s] to %s ...\n",
+ src->path, G_DIR_SEPARATOR,
+ seq_set, destdir);
+
+ ok = imap_cmd_copy(session, seq_set, destdir);
+ if (ok != IMAP_SUCCESS) {
+ imap_seq_set_free(seq_list);
+ return -1;
+ }
+ }
+
+ dest->updated = TRUE;
+
+ if (remove_source) {
+ imap_remove_msgs_by_seq_set(folder, src, seq_list);
+ if (ok != IMAP_SUCCESS) {
+ imap_seq_set_free(seq_list);
+ return ok;
+ }
+ }
+
+ imap_seq_set_free(seq_list);
+
+ for (cur = msglist; cur != NULL; cur = cur->next) {
+ msginfo = (MsgInfo *)cur->data;
+ dest->total++;
+ if (MSG_IS_NEW(msginfo->flags))
+ dest->new++;
+ if (MSG_IS_UNREAD(msginfo->flags))
+ dest->unread++;
+
+ if (remove_source) {
+ src->total--;
+ if (MSG_IS_NEW(msginfo->flags))
+ src->new--;
+ if (MSG_IS_UNREAD(msginfo->flags))
+ src->unread--;
+ MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
+ }
+ }
+
+ g_free(destdir);
+
+ if (ok == IMAP_SUCCESS)
+ return 0;
+ else
+ return -1;
+}
+
+static gint imap_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
+{
+ GSList msglist;
+
+ g_return_val_if_fail(msginfo != NULL, -1);
+
+ msglist.data = msginfo;
+ msglist.next = NULL;
+
+ return imap_move_msgs(folder, dest, &msglist);
+}
+
+static gint imap_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
+{
+ MsgInfo *msginfo;
+ GSList *file_list;
+ gint ret = 0;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msglist != NULL, -1);
+
+ msginfo = (MsgInfo *)msglist->data;
+ g_return_val_if_fail(msginfo->folder != NULL, -1);
+
+ if (folder == msginfo->folder->folder)
+ return imap_do_copy_msgs(folder, dest, msglist, TRUE);
+
+ file_list = procmsg_get_message_file_list(msglist);
+ g_return_val_if_fail(file_list != NULL, -1);
+
+ ret = imap_add_msgs(folder, dest, file_list, FALSE, NULL);
+
+ procmsg_message_file_list_free(file_list);
+
+ if (ret != -1)
+ ret = folder_item_remove_msgs(msginfo->folder, msglist);
+
+ return ret;
+}
+
+static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
+{
+ GSList msglist;
+
+ g_return_val_if_fail(msginfo != NULL, -1);
+
+ msglist.data = msginfo;
+ msglist.next = NULL;
+
+ return imap_copy_msgs(folder, dest, &msglist);
+}
+
+static gint imap_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
+{
+ MsgInfo *msginfo;
+ GSList *file_list;
+ gint ret;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msglist != NULL, -1);
+
+ msginfo = (MsgInfo *)msglist->data;
+ g_return_val_if_fail(msginfo->folder != NULL, -1);
+
+ if (folder == msginfo->folder->folder)
+ return imap_do_copy_msgs(folder, dest, msglist, FALSE);
+
+ file_list = procmsg_get_message_file_list(msglist);
+ g_return_val_if_fail(file_list != NULL, -1);
+
+ ret = imap_add_msgs(folder, dest, file_list, FALSE, NULL);
+
+ procmsg_message_file_list_free(file_list);
+
+ return ret;
+}
+
+static gint imap_remove_msgs_by_seq_set(Folder *folder, FolderItem *item,
+ GSList *seq_list)
+{
+ gint ok;
+ IMAPSession *session;
+ GSList *cur;
+
+ g_return_val_if_fail(seq_list != NULL, -1);
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ for (cur = seq_list; cur != NULL; cur = cur->next) {
+ gchar *seq_set = (gchar *)cur->data;
+
+ ok = imap_set_message_flags(session, seq_set, IMAP_FLAG_DELETED,
+ TRUE);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("can't set deleted flags: %s\n"),
+ seq_set);
+ return ok;
+ }
+ }
+
+ ok = imap_cmd_expunge(session);
+ if (ok != IMAP_SUCCESS)
+ log_warning(_("can't expunge\n"));
+
+ item->updated = TRUE;
+
+ return ok;
+}
+
+static gint imap_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo)
+{
+ GSList msglist;
+
+ g_return_val_if_fail(msginfo != NULL, -1);
+
+ msglist.data = msginfo;
+ msglist.next = NULL;
+
+ return imap_remove_msgs(folder, item, &msglist);
+}
+
+static gint imap_remove_msgs(Folder *folder, FolderItem *item, GSList *msglist)
+{
+ gint ok;
+ IMAPSession *session;
+ GSList *seq_list, *cur;
+ gchar *dir;
+ gboolean dir_exist;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
+ g_return_val_if_fail(item != NULL, -1);
+ g_return_val_if_fail(msglist != NULL, -1);
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ ok = imap_select(session, IMAP_FOLDER(folder), item->path,
+ NULL, NULL, NULL, NULL);
+ if (ok != IMAP_SUCCESS)
+ return ok;
+
+ seq_list = imap_get_seq_set_from_msglist(msglist);
+ ok = imap_remove_msgs_by_seq_set(folder, item, seq_list);
+ imap_seq_set_free(seq_list);
+ if (ok != IMAP_SUCCESS)
+ return ok;
+
+ dir = folder_item_get_path(item);
+ dir_exist = is_dir_exist(dir);
+ for (cur = msglist; cur != NULL; cur = cur->next) {
+ MsgInfo *msginfo = (MsgInfo *)cur->data;
+ guint32 uid = msginfo->msgnum;
+
+ if (dir_exist)
+ remove_numbered_files(dir, uid, uid);
+ item->total--;
+ if (MSG_IS_NEW(msginfo->flags))
+ item->new--;
+ if (MSG_IS_UNREAD(msginfo->flags))
+ item->unread--;
+ MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
+ }
+ g_free(dir);
+
+ return IMAP_SUCCESS;
+}
+
+static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
+{
+ gint ok;
+ IMAPSession *session;
+ gchar *dir;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(item != NULL, -1);
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ ok = imap_select(session, IMAP_FOLDER(folder), item->path,
+ NULL, NULL, NULL, NULL);
+ if (ok != IMAP_SUCCESS)
+ return ok;
+
+ imap_cmd_gen_send(session, "STORE 1:* +FLAGS.SILENT (\\Deleted)");
+ ok = imap_cmd_ok(session, NULL);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("can't set deleted flags: 1:*\n"));
+ return ok;
+ }
+
+ ok = imap_cmd_expunge(session);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("can't expunge\n"));
+ return ok;
+ }
+
+ item->new = item->unread = item->total = 0;
+ item->updated = TRUE;
+
+ dir = folder_item_get_path(item);
+ if (is_dir_exist(dir))
+ remove_all_numbered_files(dir);
+ g_free(dir);
+
+ return IMAP_SUCCESS;
+}
+
+static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
+ MsgInfo *msginfo)
+{
+ /* TODO: properly implement this method */
+ return FALSE;
+}
+
+static gint imap_close(Folder *folder, FolderItem *item)
+{
+ gint ok;
+ IMAPSession *session;
+
+ g_return_val_if_fail(folder != NULL, -1);
+
+ if (!item->path) return 0;
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ if (session->mbox) {
+ if (strcmp2(session->mbox, item->path) != 0) return -1;
+
+ ok = imap_cmd_close(session);
+ if (ok != IMAP_SUCCESS)
+ log_warning(_("can't close folder\n"));
+
+ g_free(session->mbox);
+ session->mbox = NULL;
+
+ return ok;
+ } else
+ return 0;
+}
+
+static gint imap_scan_folder(Folder *folder, FolderItem *item)
+{
+ IMAPSession *session;
+ gint messages, recent, unseen;
+ guint32 uid_next, uid_validity;
+ gint ok;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(item != NULL, -1);
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ ok = imap_status(session, IMAP_FOLDER(folder), item->path,
+ &messages, &recent, &uid_next, &uid_validity, &unseen);
+ if (ok != IMAP_SUCCESS) return -1;
+
+ item->new = unseen > 0 ? recent : 0;
+ item->unread = unseen;
+ item->total = messages;
+ item->last_num = (messages > 0 && uid_next > 0) ? uid_next - 1 : 0;
+ /* item->mtime = uid_validity; */
+ item->updated = TRUE;
+
+ return 0;
+}
+
+static gint imap_scan_tree(Folder *folder)
+{
+ FolderItem *item = NULL;
+ IMAPSession *session;
+ gchar *root_folder = NULL;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(folder->account != NULL, -1);
+
+ session = imap_session_get(folder);
+ if (!session) {
+ if (!folder->node) {
+ folder_tree_destroy(folder);
+ item = folder_item_new(folder->name, NULL);
+ item->folder = folder;
+ folder->node = item->node = g_node_new(item);
+ }
+ return -1;
+ }
+
+ if (folder->account->imap_dir && *folder->account->imap_dir) {
+ gchar *real_path;
+ GPtrArray *argbuf;
+ gint ok;
+
+ Xstrdup_a(root_folder, folder->account->imap_dir, return -1);
+ extract_quote(root_folder, '"');
+ subst_char(root_folder,
+ imap_get_path_separator(IMAP_FOLDER(folder),
+ root_folder),
+ '/');
+ strtailchomp(root_folder, '/');
+ real_path = imap_get_real_path
+ (IMAP_FOLDER(folder), root_folder);
+ debug_print("IMAP root directory: %s\n", real_path);
+
+ /* check if root directory exist */
+ argbuf = g_ptr_array_new();
+ ok = imap_cmd_list(session, NULL, real_path, argbuf);
+ if (ok != IMAP_SUCCESS ||
+ search_array_str(argbuf, "LIST ") == NULL) {
+ log_warning(_("root folder %s not exist\n"), real_path);
+ g_ptr_array_free(argbuf, TRUE);
+ g_free(real_path);
+ return -1;
+ }
+ g_ptr_array_free(argbuf, TRUE);
+ g_free(real_path);
+ }
+
+ if (folder->node)
+ item = FOLDER_ITEM(folder->node->data);
+ if (!item || ((item->path || root_folder) &&
+ strcmp2(item->path, root_folder) != 0)) {
+ folder_tree_destroy(folder);
+ item = folder_item_new(folder->name, root_folder);
+ item->folder = folder;
+ folder->node = item->node = g_node_new(item);
+ }
+
+ imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
+ imap_create_missing_folders(folder);
+
+ return 0;
+}
+
+static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
+{
+ Folder *folder;
+ IMAPFolder *imapfolder;
+ FolderItem *new_item;
+ GSList *item_list, *cur;
+ GNode *node;
+ gchar *real_path;
+ gchar *wildcard_path;
+ gchar separator;
+ gchar wildcard[3];
+
+ g_return_val_if_fail(item != NULL, -1);
+ g_return_val_if_fail(item->folder != NULL, -1);
+ g_return_val_if_fail(item->no_sub == FALSE, -1);
+
+ folder = item->folder;
+ imapfolder = IMAP_FOLDER(folder);
+
+ separator = imap_get_path_separator(imapfolder, item->path);
+
+ if (folder->ui_func)
+ folder->ui_func(folder, item, folder->ui_func_data);
+
+ if (item->path) {
+ wildcard[0] = separator;
+ wildcard[1] = '%';
+ wildcard[2] = '\0';
+ real_path = imap_get_real_path(imapfolder, item->path);
+ } else {
+ wildcard[0] = '%';
+ wildcard[1] = '\0';
+ real_path = g_strdup("");
+ }
+
+ Xstrcat_a(wildcard_path, real_path, wildcard,
+ {g_free(real_path); return IMAP_ERROR;});
+ QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
+
+ imap_cmd_gen_send(session, "LIST \"\" %s", wildcard_path);
+
+ strtailchomp(real_path, separator);
+ item_list = imap_parse_list(session, real_path, NULL);
+ g_free(real_path);
+
+ node = item->node->children;
+ while (node != NULL) {
+ FolderItem *old_item = FOLDER_ITEM(node->data);
+ GNode *next = node->next;
+
+ new_item = NULL;
+
+ for (cur = item_list; cur != NULL; cur = cur->next) {
+ FolderItem *cur_item = FOLDER_ITEM(cur->data);
+ if (!strcmp2(old_item->path, cur_item->path)) {
+ new_item = cur_item;
+ break;
+ }
+ }
+ if (!new_item) {
+ debug_print("folder '%s' not found. removing...\n",
+ old_item->path);
+ folder_item_remove(old_item);
+ } else {
+ old_item->no_sub = new_item->no_sub;
+ old_item->no_select = new_item->no_select;
+ if (old_item->no_select == TRUE)
+ old_item->new = old_item->unread =
+ old_item->total = 0;
+ if (old_item->no_sub == TRUE && node->children) {
+ debug_print("folder '%s' doesn't have "
+ "subfolders. removing...\n",
+ old_item->path);
+ folder_item_remove_children(old_item);
+ }
+ }
+
+ node = next;
+ }
+
+ for (cur = item_list; cur != NULL; cur = cur->next) {
+ FolderItem *cur_item = FOLDER_ITEM(cur->data);
+ new_item = NULL;
+ for (node = item->node->children; node != NULL;
+ node = node->next) {
+ if (!strcmp2(FOLDER_ITEM(node->data)->path,
+ cur_item->path)) {
+ new_item = FOLDER_ITEM(node->data);
+ folder_item_destroy(cur_item);
+ cur_item = NULL;
+ break;
+ }
+ }
+ if (!new_item) {
+ new_item = cur_item;
+ debug_print("new folder '%s' found.\n", new_item->path);
+ folder_item_append(item, new_item);
+ }
+
+ if (!strcmp(new_item->path, "INBOX")) {
+ new_item->stype = F_INBOX;
+ folder->inbox = new_item;
+ } else if (!item->parent || item->stype == F_INBOX) {
+ const gchar *base;
+
+ base = g_basename(new_item->path);
+
+ if (!folder->outbox &&
+ !g_ascii_strcasecmp(base, "Sent")) {
+ new_item->stype = F_OUTBOX;
+ folder->outbox = new_item;
+ } else if (!folder->draft &&
+ !g_ascii_strcasecmp(base, "Drafts")) {
+ new_item->stype = F_DRAFT;
+ folder->draft = new_item;
+ } else if (!folder->queue &&
+ !g_ascii_strcasecmp(base, "Queue")) {
+ new_item->stype = F_QUEUE;
+ folder->queue = new_item;
+ } else if (!folder->trash &&
+ !g_ascii_strcasecmp(base, "Trash")) {
+ new_item->stype = F_TRASH;
+ folder->trash = new_item;
+ }
+ }
+
+#if 0
+ if (new_item->no_select == FALSE)
+ imap_scan_folder(folder, new_item);
+#endif
+ if (new_item->no_sub == FALSE)
+ imap_scan_tree_recursive(session, new_item);
+ }
+
+ g_slist_free(item_list);
+
+ return IMAP_SUCCESS;
+}
+
+static GSList *imap_parse_list(IMAPSession *session, const gchar *real_path,
+ gchar *separator)
+{
+ gchar buf[IMAPBUFSIZE];
+ gchar flags[256];
+ gchar separator_str[16];
+ gchar *p;
+ const gchar *name;
+ gchar *loc_name, *loc_path;
+ GSList *item_list = NULL;
+ GString *str;
+ FolderItem *new_item;
+
+ debug_print("getting list of %s ...\n",
+ *real_path ? real_path : "\"\"");
+
+ str = g_string_new(NULL);
+
+ for (;;) {
+ if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
+ log_warning(_("error occurred while getting LIST.\n"));
+ break;
+ }
+ strretchomp(buf);
+ if (buf[0] != '*' || buf[1] != ' ') {
+ log_print("IMAP4< %s\n", buf);
+ if (sscanf(buf, "%*d %16s", buf) < 1 ||
+ strcmp(buf, "OK") != 0)
+ log_warning(_("error occurred while getting LIST.\n"));
+
+ break;
+ }
+ debug_print("IMAP4< %s\n", buf);
+
+ g_string_assign(str, buf);
+ p = str->str + 2;
+ if (strncmp(p, "LIST ", 5) != 0) continue;
+ p += 5;
+
+ if (*p != '(') continue;
+ p++;
+ p = strchr_cpy(p, ')', flags, sizeof(flags));
+ if (!p) continue;
+ while (*p == ' ') p++;
+
+ p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
+ if (!p) continue;
+ extract_quote(separator_str, '"');
+ if (!strcmp(separator_str, "NIL"))
+ separator_str[0] = '\0';
+ if (separator)
+ *separator = separator_str[0];
+
+ buf[0] = '\0';
+ while (*p == ' ') p++;
+ if (*p == '{' || *p == '"')
+ p = imap_parse_atom(session, p, buf, sizeof(buf), str);
+ else
+ strncpy2(buf, p, sizeof(buf));
+ strtailchomp(buf, separator_str[0]);
+ if (buf[0] == '\0') continue;
+ if (!strcmp(buf, real_path)) continue;
+
+ if (separator_str[0] != '\0')
+ subst_char(buf, separator_str[0], '/');
+ name = g_basename(buf);
+ if (name[0] == '.') continue;
+
+ loc_name = imap_modified_utf7_to_utf8(name);
+ loc_path = imap_modified_utf7_to_utf8(buf);
+ new_item = folder_item_new(loc_name, loc_path);
+ if (strcasestr(flags, "\\Noinferiors") != NULL)
+ new_item->no_sub = TRUE;
+ if (strcmp(buf, "INBOX") != 0 &&
+ strcasestr(flags, "\\Noselect") != NULL)
+ new_item->no_select = TRUE;
+
+ item_list = g_slist_append(item_list, new_item);
+
+ debug_print("folder '%s' found.\n", loc_path);
+ g_free(loc_path);
+ g_free(loc_name);
+ }
+
+ g_string_free(str, TRUE);
+
+ return item_list;
+}
+
+static gint imap_create_tree(Folder *folder)
+{
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(folder->node != NULL, -1);
+ g_return_val_if_fail(folder->node->data != NULL, -1);
+ g_return_val_if_fail(folder->account != NULL, -1);
+
+ imap_scan_tree(folder);
+ imap_create_missing_folders(folder);
+
+ return 0;
+}
+
+static void imap_create_missing_folders(Folder *folder)
+{
+ g_return_if_fail(folder != NULL);
+
+ if (!folder->inbox)
+ folder->inbox = imap_create_special_folder
+ (folder, F_INBOX, "INBOX");
+#if 0
+ if (!folder->outbox)
+ folder->outbox = imap_create_special_folder
+ (folder, F_OUTBOX, "Sent");
+ if (!folder->draft)
+ folder->draft = imap_create_special_folder
+ (folder, F_DRAFT, "Drafts");
+ if (!folder->queue)
+ folder->queue = imap_create_special_folder
+ (folder, F_QUEUE, "Queue");
+#endif
+ if (!folder->trash)
+ folder->trash = imap_create_special_folder
+ (folder, F_TRASH, "Trash");
+}
+
+static FolderItem *imap_create_special_folder(Folder *folder,
+ SpecialFolderItemType stype,
+ const gchar *name)
+{
+ FolderItem *item;
+ FolderItem *new_item;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(folder->node != NULL, NULL);
+ g_return_val_if_fail(folder->node->data != NULL, NULL);
+ g_return_val_if_fail(folder->account != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ item = FOLDER_ITEM(folder->node->data);
+ new_item = imap_create_folder(folder, item, name);
+
+ if (!new_item) {
+ g_warning(_("Can't create '%s'\n"), name);
+ if (!folder->inbox) return NULL;
+
+ new_item = imap_create_folder(folder, folder->inbox, name);
+ if (!new_item)
+ g_warning(_("Can't create '%s' under INBOX\n"), name);
+ else
+ new_item->stype = stype;
+ } else
+ new_item->stype = stype;
+
+ return new_item;
+}
+
+static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
+ const gchar *name)
+{
+ gchar *dirpath, *imap_path;
+ IMAPSession *session;
+ FolderItem *new_item;
+ gchar separator;
+ gchar *new_name;
+ const gchar *p;
+ gint ok;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(folder->account != NULL, NULL);
+ g_return_val_if_fail(parent != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ session = imap_session_get(folder);
+ if (!session) return NULL;
+
+ if (!parent->parent && strcmp(name, "INBOX") == 0)
+ dirpath = g_strdup(name);
+ else if (parent->path)
+ dirpath = g_strconcat(parent->path, "/", name, NULL);
+ else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
+ dirpath = g_strdup(name);
+ else if (folder->account->imap_dir && *folder->account->imap_dir) {
+ gchar *imap_dir;
+
+ Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
+ strtailchomp(imap_dir, '/');
+ dirpath = g_strconcat(imap_dir, "/", name, NULL);
+ } else
+ dirpath = g_strdup(name);
+
+ /* keep trailing directory separator to create a folder that contains
+ sub folder */
+ imap_path = imap_utf8_to_modified_utf7(dirpath);
+ strtailchomp(dirpath, '/');
+ Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
+ strtailchomp(new_name, '/');
+ separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
+ imap_path_separator_subst(imap_path, separator);
+ subst_char(new_name, '/', separator);
+
+ if (strcmp(name, "INBOX") != 0) {
+ GPtrArray *argbuf;
+ gint i;
+ gboolean exist = FALSE;
+
+ argbuf = g_ptr_array_new();
+ ok = imap_cmd_list(session, NULL, imap_path, argbuf);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("can't create mailbox: LIST failed\n"));
+ g_free(imap_path);
+ g_free(dirpath);
+ g_ptr_array_free(argbuf, TRUE);
+ return NULL;
+ }
+
+ for (i = 0; i < argbuf->len; i++) {
+ gchar *str;
+ str = g_ptr_array_index(argbuf, i);
+ if (!strncmp(str, "LIST ", 5)) {
+ exist = TRUE;
+ break;
+ }
+ }
+ g_ptr_array_free(argbuf, TRUE);
+
+ if (!exist) {
+ ok = imap_cmd_create(session, imap_path);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("can't create mailbox\n"));
+ g_free(imap_path);
+ g_free(dirpath);
+ return NULL;
+ }
+ }
+ }
+
+ new_item = folder_item_new(new_name, dirpath);
+ folder_item_append(parent, new_item);
+ g_free(imap_path);
+ g_free(dirpath);
+
+ dirpath = folder_item_get_path(new_item);
+ if (!is_dir_exist(dirpath))
+ make_dir_hier(dirpath);
+ g_free(dirpath);
+
+ return new_item;
+}
+
+static gint imap_rename_folder_real(Folder *folder, FolderItem *item,
+ FolderItem *new_parent, const gchar *name)
+{
+ gchar *newpath;
+ gchar *real_oldpath;
+ gchar *real_newpath;
+ gchar *paths[2];
+ gchar *old_cache_dir;
+ gchar *new_cache_dir;
+ IMAPSession *session;
+ gchar separator;
+ gint ok;
+ gint exists, recent, unseen;
+ guint32 uid_validity;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(item != NULL, -1);
+ g_return_val_if_fail(folder == item->folder, -1);
+ g_return_val_if_fail(item->path != NULL, -1);
+ g_return_val_if_fail(new_parent != NULL || name != NULL, -1);
+ if (new_parent) {
+ g_return_val_if_fail(item != new_parent, -1);
+ g_return_val_if_fail(item->parent != new_parent, -1);
+ g_return_val_if_fail(item->folder == new_parent->folder, -1);
+ if (g_node_is_ancestor(item->node, new_parent->node)) {
+ g_warning("folder to be moved is ancestor of new parent\n");
+ return -1;
+ }
+ }
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
+
+ g_free(session->mbox);
+ session->mbox = NULL;
+ ok = imap_cmd_examine(session, "INBOX",
+ &exists, &recent, &unseen, &uid_validity);
+ if (ok != IMAP_SUCCESS) {
+ g_free(real_oldpath);
+ return -1;
+ }
+
+ separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
+ if (new_parent) {
+ if (name) {
+ newpath = g_strconcat(new_parent->path,
+ G_DIR_SEPARATOR_S, name, NULL);
+ } else {
+ gchar *name_;
+
+ name_ = g_path_get_basename(item->path);
+ newpath = g_strconcat(new_parent->path,
+ G_DIR_SEPARATOR_S, name_, NULL);
+ AUTORELEASE_STR(name_, );
+ name = name_;
+ }
+ } else {
+ if (strchr(item->path, G_DIR_SEPARATOR)) {
+ gchar *dirpath;
+
+ dirpath = g_dirname(item->path);
+ newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name,
+ NULL);
+ g_free(dirpath);
+ } else
+ newpath = g_strdup(name);
+ }
+
+ real_newpath = imap_utf8_to_modified_utf7(newpath);
+ imap_path_separator_subst(real_newpath, separator);
+
+ ok = imap_cmd_rename(session, real_oldpath, real_newpath);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("can't rename mailbox: %s to %s\n"),
+ real_oldpath, real_newpath);
+ g_free(real_oldpath);
+ g_free(newpath);
+ g_free(real_newpath);
+ return -1;
+ }
+
+ if (new_parent) {
+ g_node_unlink(item->node);
+ g_node_append(new_parent->node, item->node);
+ item->parent = new_parent;
+ }
+
+ g_free(item->name);
+ item->name = g_strdup(name);
+
+ old_cache_dir = folder_item_get_path(item);
+
+ paths[0] = g_strdup(item->path);
+ paths[1] = newpath;
+ g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+ imap_rename_folder_func, paths);
+
+ if (is_dir_exist(old_cache_dir)) {
+ new_cache_dir = folder_item_get_path(item);
+ if (g_rename(old_cache_dir, new_cache_dir) < 0) {
+ FILE_OP_ERROR(old_cache_dir, "rename");
+ }
+ g_free(new_cache_dir);
+ }
+
+ g_free(old_cache_dir);
+ g_free(paths[0]);
+ g_free(newpath);
+ g_free(real_oldpath);
+ g_free(real_newpath);
+
+ return 0;
+}
+
+static gint imap_rename_folder(Folder *folder, FolderItem *item,
+ const gchar *name)
+{
+ return imap_rename_folder_real(folder, item, NULL, name);
+}
+
+static gint imap_move_folder(Folder *folder, FolderItem *item,
+ FolderItem *new_parent)
+{
+ return imap_rename_folder_real(folder, item, new_parent, NULL);
+}
+
+static gint imap_remove_folder(Folder *folder, FolderItem *item)
+{
+ gint ok;
+ IMAPSession *session;
+ gchar *path;
+ gchar *cache_dir;
+ gint exists, recent, unseen;
+ guint32 uid_validity;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(item != NULL, -1);
+ g_return_val_if_fail(item->path != NULL, -1);
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
+
+ ok = imap_cmd_examine(session, "INBOX",
+ &exists, &recent, &unseen, &uid_validity);
+ if (ok != IMAP_SUCCESS) {
+ g_free(path);
+ return -1;
+ }
+
+ ok = imap_cmd_delete(session, path);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("can't delete mailbox\n"));
+ g_free(path);
+ return -1;
+ }
+
+ g_free(path);
+ cache_dir = folder_item_get_path(item);
+ if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
+ g_warning("can't remove directory '%s'\n", cache_dir);
+ g_free(cache_dir);
+ folder_item_remove(item);
+
+ return 0;
+}
+
+static GSList *imap_get_uncached_messages(IMAPSession *session,
+ FolderItem *item,
+ guint32 first_uid, guint32 last_uid,
+ gboolean update_count)
+{
+ gchar *tmp;
+ GSList *newlist = NULL;
+ GSList *llast = NULL;
+ GString *str;
+ MsgInfo *msginfo;
+ gchar seq_set[22];
+
+ g_return_val_if_fail(session != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(item->folder != NULL, NULL);
+ g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, NULL);
+ g_return_val_if_fail(first_uid <= last_uid, NULL);
+
+ if (first_uid == 0 && last_uid == 0)
+ strcpy(seq_set, "1:*");
+ else
+ g_snprintf(seq_set, sizeof(seq_set), "%u:%u",
+ first_uid, last_uid);
+ if (imap_cmd_envelope(session, seq_set) != IMAP_SUCCESS) {
+ log_warning(_("can't get envelope\n"));
+ return NULL;
+ }
+
+ str = g_string_new(NULL);
+
+ for (;;) {
+ if (sock_getline(SESSION(session)->sock, &tmp) < 0) {
+ log_warning(_("error occurred while getting envelope.\n"));
+ g_string_free(str, TRUE);
+ return newlist;
+ }
+ strretchomp(tmp);
+ if (tmp[0] != '*' || tmp[1] != ' ') {
+ log_print("IMAP4< %s\n", tmp);
+ g_free(tmp);
+ break;
+ }
+ if (strstr(tmp, "FETCH") == NULL) {
+ log_print("IMAP4< %s\n", tmp);
+ g_free(tmp);
+ continue;
+ }
+ log_print("IMAP4< %s\n", tmp);
+ g_string_assign(str, tmp);
+ g_free(tmp);
+
+ msginfo = imap_parse_envelope(session, item, str);
+ if (!msginfo) {
+ log_warning(_("can't parse envelope: %s\n"), str->str);
+ continue;
+ }
+ if (update_count) {
+ if (MSG_IS_NEW(msginfo->flags))
+ item->new++;
+ if (MSG_IS_UNREAD(msginfo->flags))
+ item->unread++;
+ }
+ 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);
+ }
+
+ msginfo->folder = item;
+
+ if (!newlist)
+ llast = newlist = g_slist_append(newlist, msginfo);
+ else {
+ llast = g_slist_append(llast, msginfo);
+ llast = llast->next;
+ }
+
+ if (update_count)
+ item->total++;
+ }
+
+ g_string_free(str, TRUE);
+
+ session_set_access_time(SESSION(session));
+
+ return newlist;
+}
+
+static void imap_delete_cached_message(FolderItem *item, guint32 uid)
+{
+ gchar *dir;
+ gchar *file;
+
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+ g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP);
+
+ dir = folder_item_get_path(item);
+ file = g_strdup_printf("%s%c%u", dir, G_DIR_SEPARATOR, uid);
+
+ debug_print("Deleting cached message: %s\n", file);
+
+ g_unlink(file);
+
+ g_free(file);
+ g_free(dir);
+}
+
+static GSList *imap_delete_cached_messages(GSList *mlist, FolderItem *item,
+ guint32 first_uid, guint32 last_uid)
+{
+ GSList *cur, *next;
+ MsgInfo *msginfo;
+ gchar *dir;
+
+ g_return_val_if_fail(item != NULL, mlist);
+ g_return_val_if_fail(item->folder != NULL, mlist);
+ g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, mlist);
+
+ if (first_uid == 0 && last_uid == 0)
+ return mlist;
+
+ debug_print("Deleting cached messages %u - %u ... ",
+ first_uid, last_uid);
+
+ dir = folder_item_get_path(item);
+ if (is_dir_exist(dir))
+ remove_numbered_files(dir, first_uid, last_uid);
+ g_free(dir);
+
+ for (cur = mlist; cur != NULL; ) {
+ next = cur->next;
+
+ msginfo = (MsgInfo *)cur->data;
+ if (msginfo != NULL && first_uid <= msginfo->msgnum &&
+ msginfo->msgnum <= last_uid) {
+ procmsg_msginfo_free(msginfo);
+ mlist = g_slist_remove(mlist, msginfo);
+ }
+
+ cur = next;
+ }
+
+ debug_print("done.\n");
+
+ return mlist;
+}
+
+static void imap_delete_all_cached_messages(FolderItem *item)
+{
+ gchar *dir;
+
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+ g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP);
+
+ debug_print("Deleting all cached messages... ");
+
+ dir = folder_item_get_path(item);
+ if (is_dir_exist(dir))
+ remove_all_numbered_files(dir);
+ g_free(dir);
+
+ debug_print("done.\n");
+}
+
+#if USE_SSL
+static SockInfo *imap_open(const gchar *server, gushort port,
+ SSLType ssl_type)
+#else
+static SockInfo *imap_open(const gchar *server, gushort port)
+#endif
+{
+ SockInfo *sock;
+
+ if ((sock = sock_connect(server, port)) == NULL) {
+ log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
+ server, port);
+ return NULL;
+ }
+
+#if USE_SSL
+ if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
+ log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
+ server, port);
+ sock_close(sock);
+ return NULL;
+ }
+#endif
+
+ return sock;
+}
+
+static GList *imap_parse_namespace_str(gchar *str)
+{
+ guchar *p = str;
+ gchar *name;
+ gchar *separator;
+ IMAPNameSpace *namespace;
+ GList *ns_list = NULL;
+
+ while (*p != '\0') {
+ /* parse ("#foo" "/") */
+
+ while (*p && *p != '(') p++;
+ if (*p == '\0') break;
+ p++;
+
+ while (*p && *p != '"') p++;
+ if (*p == '\0') break;
+ p++;
+ name = p;
+
+ while (*p && *p != '"') p++;
+ if (*p == '\0') break;
+ *p = '\0';
+ p++;
+
+ while (*p && g_ascii_isspace(*p)) p++;
+ if (*p == '\0') break;
+ if (strncmp(p, "NIL", 3) == 0)
+ separator = NULL;
+ else if (*p == '"') {
+ p++;
+ separator = p;
+ while (*p && *p != '"') p++;
+ if (*p == '\0') break;
+ *p = '\0';
+ p++;
+ } else break;
+
+ while (*p && *p != ')') p++;
+ if (*p == '\0') break;
+ p++;
+
+ namespace = g_new(IMAPNameSpace, 1);
+ namespace->name = g_strdup(name);
+ namespace->separator = separator ? separator[0] : '\0';
+ ns_list = g_list_append(ns_list, namespace);
+ }
+
+ return ns_list;
+}
+
+static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
+{
+ gchar *ns_str = NULL;
+ gchar **str_array;
+
+ g_return_if_fail(session != NULL);
+ g_return_if_fail(folder != NULL);
+
+ if (folder->ns_personal != NULL ||
+ folder->ns_others != NULL ||
+ folder->ns_shared != NULL)
+ return;
+
+ if (imap_cmd_namespace(session, &ns_str) != IMAP_SUCCESS) {
+ log_warning(_("can't get namespace\n"));
+ imap_get_namespace_by_list(session, folder);
+ return;
+ }
+
+ str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
+ if (str_array[0])
+ folder->ns_personal = imap_parse_namespace_str(str_array[0]);
+ if (str_array[0] && str_array[1])
+ folder->ns_others = imap_parse_namespace_str(str_array[1]);
+ if (str_array[0] && str_array[1] && str_array[2])
+ folder->ns_shared = imap_parse_namespace_str(str_array[2]);
+ g_strfreev(str_array);
+ g_free(ns_str);
+}
+
+static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
+{
+ GSList *item_list, *cur;
+ gchar separator = '\0';
+ IMAPNameSpace *namespace;
+
+ g_return_if_fail(session != NULL);
+ g_return_if_fail(folder != NULL);
+
+ if (folder->ns_personal != NULL ||
+ folder->ns_others != NULL ||
+ folder->ns_shared != NULL)
+ return;
+
+ imap_cmd_gen_send(session, "LIST \"\" \"\"");
+ item_list = imap_parse_list(session, "", &separator);
+ for (cur = item_list; cur != NULL; cur = cur->next)
+ folder_item_destroy(FOLDER_ITEM(cur->data));
+ g_slist_free(item_list);
+
+ namespace = g_new(IMAPNameSpace, 1);
+ namespace->name = g_strdup("");
+ namespace->separator = separator;
+ folder->ns_personal = g_list_append(NULL, namespace);
+}
+
+static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
+ const gchar *path)
+{
+ IMAPNameSpace *namespace = NULL;
+ gchar *tmp_path, *name;
+
+ if (!path) path = "";
+
+ for (; ns_list != NULL; ns_list = ns_list->next) {
+ IMAPNameSpace *tmp_ns = ns_list->data;
+
+ Xstrcat_a(tmp_path, path, "/", return namespace);
+ Xstrdup_a(name, tmp_ns->name, return namespace);
+ if (tmp_ns->separator && tmp_ns->separator != '/') {
+ subst_char(tmp_path, tmp_ns->separator, '/');
+ subst_char(name, tmp_ns->separator, '/');
+ }
+ if (strncmp(tmp_path, name, strlen(name)) == 0)
+ namespace = tmp_ns;
+ }
+
+ return namespace;
+}
+
+static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
+ const gchar *path)
+{
+ IMAPNameSpace *namespace;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+
+ namespace = imap_find_namespace_from_list(folder->ns_personal, path);
+ if (namespace) return namespace;
+ namespace = imap_find_namespace_from_list(folder->ns_others, path);
+ if (namespace) return namespace;
+ namespace = imap_find_namespace_from_list(folder->ns_shared, path);
+ if (namespace) return namespace;
+
+ return NULL;
+}
+
+static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
+{
+ IMAPNameSpace *namespace;
+ gchar separator = '/';
+
+ namespace = imap_find_namespace(folder, path);
+ if (namespace && namespace->separator)
+ separator = namespace->separator;
+
+ return separator;
+}
+
+static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
+{
+ gchar *real_path;
+ gchar separator;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(path != NULL, NULL);
+
+ real_path = imap_utf8_to_modified_utf7(path);
+ separator = imap_get_path_separator(folder, path);
+ imap_path_separator_subst(real_path, separator);
+
+ return real_path;
+}
+
+static gchar *imap_parse_atom(IMAPSession *session, gchar *src,
+ gchar *dest, gint dest_len, GString *str)
+{
+ gchar *cur_pos = src;
+ gchar *nextline;
+
+ g_return_val_if_fail(str != NULL, cur_pos);
+
+ /* read the next line if the current response buffer is empty */
+ while (g_ascii_isspace(*cur_pos)) cur_pos++;
+ while (*cur_pos == '\0') {
+ if (sock_getline(SESSION(session)->sock, &nextline) < 0)
+ return cur_pos;
+ g_string_assign(str, nextline);
+ cur_pos = str->str;
+ strretchomp(nextline);
+ /* log_print("IMAP4< %s\n", nextline); */
+ debug_print("IMAP4< %s\n", nextline);
+ g_free(nextline);
+
+ while (g_ascii_isspace(*cur_pos)) cur_pos++;
+ }
+
+ if (!strncmp(cur_pos, "NIL", 3)) {
+ *dest = '\0';
+ cur_pos += 3;
+ } else if (*cur_pos == '\"') {
+ gchar *p;
+
+ p = get_quoted(cur_pos, '\"', dest, dest_len);
+ cur_pos = p ? p : cur_pos + 2;
+ } else if (*cur_pos == '{') {
+ gchar buf[32];
+ gint len;
+ gint block_len = 0;
+
+ cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
+ len = atoi(buf);
+ g_return_val_if_fail(len >= 0, cur_pos);
+
+ g_string_truncate(str, 0);
+ cur_pos = str->str;
+
+ do {
+ gint cur_len;
+
+ cur_len = sock_getline(SESSION(session)->sock,
+ &nextline);
+ if (cur_len < 0)
+ return cur_pos;
+ block_len += cur_len;
+ subst_null(nextline, cur_len, ' ');
+ g_string_append(str, nextline);
+ cur_pos = str->str;
+ strretchomp(nextline);
+ /* log_print("IMAP4< %s\n", nextline); */
+ debug_print("IMAP4< %s\n", nextline);
+ g_free(nextline);
+ } while (block_len < len);
+
+ memcpy(dest, cur_pos, MIN(len, dest_len - 1));
+ dest[MIN(len, dest_len - 1)] = '\0';
+ cur_pos += len;
+ }
+
+ return cur_pos;
+}
+
+static gchar *imap_get_header(IMAPSession *session, gchar *cur_pos,
+ gchar **headers, GString *str)
+{
+ gchar *nextline;
+ gchar buf[32];
+ gint len;
+ gint block_len = 0;
+
+ *headers = NULL;
+
+ g_return_val_if_fail(str != NULL, cur_pos);
+
+ while (g_ascii_isspace(*cur_pos)) cur_pos++;
+
+ g_return_val_if_fail(*cur_pos == '{', cur_pos);
+
+ cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
+ len = atoi(buf);
+ g_return_val_if_fail(len >= 0, cur_pos);
+
+ g_string_truncate(str, 0);
+ cur_pos = str->str;
+
+ do {
+ gint cur_len;
+
+ cur_len = sock_getline(SESSION(session)->sock, &nextline);
+ if (cur_len < 0)
+ return cur_pos;
+ block_len += cur_len;
+ subst_null(nextline, cur_len, ' ');
+ g_string_append(str, nextline);
+ cur_pos = str->str;
+ /* strretchomp(nextline); */
+ /* debug_print("IMAP4< %s\n", nextline); */
+ g_free(nextline);
+ } while (block_len < len);
+
+ debug_print("IMAP4< [contents of RFC822.HEADER]\n");
+
+ *headers = g_strndup(cur_pos, len);
+ cur_pos += len;
+
+ while (g_ascii_isspace(*cur_pos)) cur_pos++;
+ while (*cur_pos == '\0') {
+ if (sock_getline(SESSION(session)->sock, &nextline) < 0)
+ return cur_pos;
+ g_string_assign(str, nextline);
+ cur_pos = str->str;
+ strretchomp(nextline);
+ debug_print("IMAP4< %s\n", nextline);
+ g_free(nextline);
+
+ while (g_ascii_isspace(*cur_pos)) cur_pos++;
+ }
+
+ return cur_pos;
+}
+
+static MsgFlags imap_parse_flags(const gchar *flag_str)
+{
+ const gchar *p = flag_str;
+ MsgFlags flags = {0, 0};
+
+ flags.perm_flags = MSG_UNREAD;
+
+ while ((p = strchr(p, '\\')) != NULL) {
+ p++;
+
+ if (g_ascii_strncasecmp(p, "Recent", 6) == 0 &&
+ MSG_IS_UNREAD(flags)) {
+ MSG_SET_PERM_FLAGS(flags, MSG_NEW);
+ } else if (g_ascii_strncasecmp(p, "Seen", 4) == 0) {
+ MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
+ } else if (g_ascii_strncasecmp(p, "Deleted", 7) == 0) {
+ MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
+ } else if (g_ascii_strncasecmp(p, "Flagged", 7) == 0) {
+ MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
+ } else if (g_ascii_strncasecmp(p, "Answered", 8) == 0) {
+ MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
+ }
+ }
+
+ return flags;
+}
+
+static IMAPFlags imap_parse_imap_flags(const gchar *flag_str)
+{
+ const gchar *p = flag_str;
+ IMAPFlags flags = 0;
+
+ while ((p = strchr(p, '\\')) != NULL) {
+ p++;
+
+ if (g_ascii_strncasecmp(p, "Seen", 4) == 0) {
+ flags |= IMAP_FLAG_SEEN;
+ } else if (g_ascii_strncasecmp(p, "Deleted", 7) == 0) {
+ flags |= IMAP_FLAG_DELETED;
+ } else if (g_ascii_strncasecmp(p, "Flagged", 7) == 0) {
+ flags |= IMAP_FLAG_FLAGGED;
+ } else if (g_ascii_strncasecmp(p, "Answered", 8) == 0) {
+ flags |= IMAP_FLAG_ANSWERED;
+ }
+ }
+
+ return flags;
+}
+
+static MsgInfo *imap_parse_envelope(IMAPSession *session, FolderItem *item,
+ GString *line_str)
+{
+ gchar buf[IMAPBUFSIZE];
+ MsgInfo *msginfo = NULL;
+ gchar *cur_pos;
+ gint msgnum;
+ guint32 uid = 0;
+ size_t size = 0;
+ MsgFlags flags = {0, 0}, imap_flags = {0, 0};
+
+ g_return_val_if_fail(line_str != NULL, NULL);
+ g_return_val_if_fail(line_str->str[0] == '*' &&
+ line_str->str[1] == ' ', NULL);
+
+ MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
+ if (item->stype == F_QUEUE) {
+ MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
+ } else if (item->stype == F_DRAFT) {
+ MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
+ }
+
+ cur_pos = line_str->str + 2;
+
+#define PARSE_ONE_ELEMENT(ch) \
+{ \
+ cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
+ if (cur_pos == NULL) { \
+ g_warning("cur_pos == NULL\n"); \
+ procmsg_msginfo_free(msginfo); \
+ return NULL; \
+ } \
+}
+
+ PARSE_ONE_ELEMENT(' ');
+ msgnum = atoi(buf);
+
+ PARSE_ONE_ELEMENT(' ');
+ g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
+
+ g_return_val_if_fail(*cur_pos == '(', NULL);
+ cur_pos++;
+
+ while (*cur_pos != '\0' && *cur_pos != ')') {
+ while (*cur_pos == ' ') cur_pos++;
+
+ if (!strncmp(cur_pos, "UID ", 4)) {
+ cur_pos += 4;
+ uid = strtoul(cur_pos, &cur_pos, 10);
+ } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
+ cur_pos += 6;
+ if (*cur_pos != '(') {
+ g_warning("*cur_pos != '('\n");
+ procmsg_msginfo_free(msginfo);
+ return NULL;
+ }
+ cur_pos++;
+ PARSE_ONE_ELEMENT(')');
+ imap_flags = imap_parse_flags(buf);
+ } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
+ cur_pos += 12;
+ size = strtol(cur_pos, &cur_pos, 10);
+ } else if (!strncmp(cur_pos, "RFC822.HEADER ", 14)) {
+ gchar *headers;
+
+ cur_pos += 14;
+ cur_pos = imap_get_header(session, cur_pos, &headers,
+ line_str);
+ msginfo = procheader_parse_str(headers, flags, FALSE);
+ g_free(headers);
+ } else {
+ g_warning("invalid FETCH response: %s\n", cur_pos);
+ break;
+ }
+ }
+
+#undef PARSE_ONE_ELEMENT
+
+ if (msginfo) {
+ msginfo->msgnum = uid;
+ msginfo->size = size;
+ msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
+ msginfo->flags.perm_flags = imap_flags.perm_flags;
+ }
+
+ return msginfo;
+}
+
+static gint imap_msg_list_change_perm_flags(GSList *msglist, MsgPermFlags flags,
+ gboolean is_set)
+{
+ Folder *folder;
+ IMAPSession *session;
+ IMAPFlags iflags = 0;
+ MsgInfo *msginfo;
+ GSList *seq_list, *cur;
+ gint ok = IMAP_SUCCESS;
+
+ if (msglist == NULL) return IMAP_SUCCESS;
+
+ msginfo = (MsgInfo *)msglist->data;
+ g_return_val_if_fail(msginfo != NULL, -1);
+
+ g_return_val_if_fail(MSG_IS_IMAP(msginfo->flags), -1);
+ g_return_val_if_fail(msginfo->folder != NULL, -1);
+ g_return_val_if_fail(msginfo->folder->folder != NULL, -1);
+
+ folder = msginfo->folder->folder;
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
+
+ session = imap_session_get(folder);
+ if (!session) return -1;
+
+ ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
+ NULL, NULL, NULL, NULL);
+ if (ok != IMAP_SUCCESS)
+ return ok;
+
+ seq_list = imap_get_seq_set_from_msglist(msglist);
+
+ if (flags & MSG_MARKED) iflags |= IMAP_FLAG_FLAGGED;
+ if (flags & MSG_REPLIED) iflags |= IMAP_FLAG_ANSWERED;
+
+ for (cur = seq_list; cur != NULL; cur = cur->next) {
+ gchar *seq_set = (gchar *)cur->data;
+
+ if (iflags) {
+ ok = imap_set_message_flags(session, seq_set, iflags,
+ is_set);
+ if (ok != IMAP_SUCCESS) break;
+ }
+
+ if (flags & MSG_UNREAD) {
+ ok = imap_set_message_flags(session, seq_set,
+ IMAP_FLAG_SEEN, !is_set);
+ if (ok != IMAP_SUCCESS) break;
+ }
+ }
+
+ imap_seq_set_free(seq_list);
+
+ return ok;
+}
+
+gint imap_msg_set_perm_flags(MsgInfo *msginfo, MsgPermFlags flags)
+{
+ GSList msglist;
+
+ msglist.data = msginfo;
+ msglist.next = NULL;
+
+ return imap_msg_list_change_perm_flags(&msglist, flags, TRUE);
+}
+
+gint imap_msg_unset_perm_flags(MsgInfo *msginfo, MsgPermFlags flags)
+{
+ GSList msglist;
+
+ msglist.data = msginfo;
+ msglist.next = NULL;
+
+ return imap_msg_list_change_perm_flags(&msglist, flags, FALSE);
+}
+
+gint imap_msg_list_set_perm_flags(GSList *msglist, MsgPermFlags flags)
+{
+ return imap_msg_list_change_perm_flags(msglist, flags, TRUE);
+}
+
+gint imap_msg_list_unset_perm_flags(GSList *msglist, MsgPermFlags flags)
+{
+ return imap_msg_list_change_perm_flags(msglist, flags, FALSE);
+}
+
+static gchar *imap_get_flag_str(IMAPFlags flags)
+{
+ GString *str;
+ gchar *ret;
+
+ str = g_string_new(NULL);
+
+ if (IMAP_IS_SEEN(flags)) g_string_append(str, "\\Seen ");
+ if (IMAP_IS_ANSWERED(flags)) g_string_append(str, "\\Answered ");
+ if (IMAP_IS_FLAGGED(flags)) g_string_append(str, "\\Flagged ");
+ if (IMAP_IS_DELETED(flags)) g_string_append(str, "\\Deleted ");
+ if (IMAP_IS_DRAFT(flags)) g_string_append(str, "\\Draft");
+
+ if (str->len > 0 && str->str[str->len - 1] == ' ')
+ g_string_truncate(str, str->len - 1);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+
+ return ret;
+}
+
+static gint imap_set_message_flags(IMAPSession *session,
+ const gchar *seq_set,
+ IMAPFlags flags,
+ gboolean is_set)
+{
+ gchar *cmd;
+ gchar *flag_str;
+ gint ok;
+
+ flag_str = imap_get_flag_str(flags);
+ cmd = g_strconcat(is_set ? "+FLAGS.SILENT (" : "-FLAGS.SILENT (",
+ flag_str, ")", NULL);
+ g_free(flag_str);
+
+ ok = imap_cmd_store(session, seq_set, cmd);
+ g_free(cmd);
+
+ return ok;
+}
+
+static gint imap_select(IMAPSession *session, IMAPFolder *folder,
+ const gchar *path,
+ gint *exists, gint *recent, gint *unseen,
+ guint32 *uid_validity)
+{
+ gchar *real_path;
+ gint ok;
+ gint exists_, recent_, unseen_, uid_validity_;
+
+ if (!exists || !recent || !unseen || !uid_validity) {
+ if (session->mbox && strcmp(session->mbox, path) == 0)
+ return IMAP_SUCCESS;
+ exists = &exists_;
+ recent = &recent_;
+ unseen = &unseen_;
+ uid_validity = &uid_validity_;
+ }
+
+ g_free(session->mbox);
+ session->mbox = NULL;
+
+ real_path = imap_get_real_path(folder, path);
+ ok = imap_cmd_select(session, real_path,
+ exists, recent, unseen, uid_validity);
+ if (ok != IMAP_SUCCESS)
+ log_warning(_("can't select folder: %s\n"), real_path);
+ else
+ session->mbox = g_strdup(path);
+ g_free(real_path);
+
+ return ok;
+}
+
+#define THROW(err) { ok = err; goto catch; }
+
+static gint imap_status(IMAPSession *session, IMAPFolder *folder,
+ const gchar *path,
+ gint *messages, gint *recent,
+ guint32 *uid_next, guint32 *uid_validity,
+ gint *unseen)
+{
+ gchar *real_path;
+ gchar *real_path_;
+ gint ok;
+ GPtrArray *argbuf = NULL;
+ gchar *str;
+
+ if (messages && recent && uid_next && uid_validity && unseen) {
+ *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
+ argbuf = g_ptr_array_new();
+ }
+
+ real_path = imap_get_real_path(folder, path);
+ QUOTE_IF_REQUIRED(real_path_, real_path);
+ imap_cmd_gen_send(session, "STATUS %s "
+ "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
+ real_path_);
+
+ ok = imap_cmd_ok(session, argbuf);
+ if (ok != IMAP_SUCCESS || !argbuf) THROW(ok);
+
+ str = search_array_str(argbuf, "STATUS");
+ if (!str) THROW(IMAP_ERROR);
+
+ str = strchr(str, '(');
+ if (!str) THROW(IMAP_ERROR);
+ str++;
+ while (*str != '\0' && *str != ')') {
+ while (*str == ' ') str++;
+
+ if (!strncmp(str, "MESSAGES ", 9)) {
+ str += 9;
+ *messages = strtol(str, &str, 10);
+ } else if (!strncmp(str, "RECENT ", 7)) {
+ str += 7;
+ *recent = strtol(str, &str, 10);
+ } else if (!strncmp(str, "UIDNEXT ", 8)) {
+ str += 8;
+ *uid_next = strtoul(str, &str, 10);
+ } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
+ str += 12;
+ *uid_validity = strtoul(str, &str, 10);
+ } else if (!strncmp(str, "UNSEEN ", 7)) {
+ str += 7;
+ *unseen = strtol(str, &str, 10);
+ } else {
+ g_warning("invalid STATUS response: %s\n", str);
+ break;
+ }
+ }
+
+catch:
+ g_free(real_path);
+ if (argbuf) {
+ ptr_array_free_strings(argbuf);
+ g_ptr_array_free(argbuf, TRUE);
+ }
+
+ return ok;
+}
+
+#undef THROW
+
+static gboolean imap_has_capability(IMAPSession *session,
+ const gchar *capability)
+{
+ gchar **p;
+
+ for (p = session->capability; *p != NULL; ++p) {
+ if (!g_ascii_strcasecmp(*p, capability))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void imap_capability_free(IMAPSession *session)
+{
+ if (session->capability) {
+ g_strfreev(session->capability);
+ session->capability = NULL;
+ }
+}
+
+
+/* low-level IMAP4rev1 commands */
+
+#define THROW(err) { ok = err; goto catch; }
+
+static gint imap_cmd_capability(IMAPSession *session)
+{
+ gint ok;
+ GPtrArray *argbuf;
+ gchar *capability;
+
+ argbuf = g_ptr_array_new();
+
+ imap_cmd_gen_send(session, "CAPABILITY");
+ if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
+
+ capability = search_array_str(argbuf, "CAPABILITY ");
+ if (!capability) THROW(IMAP_ERROR);
+
+ capability += strlen("CAPABILITY ");
+
+ imap_capability_free(session);
+ session->capability = g_strsplit(capability, " ", -1);
+
+catch:
+ ptr_array_free_strings(argbuf);
+ g_ptr_array_free(argbuf, TRUE);
+
+ return ok;
+}
+
+#undef THROW
+
+static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user,
+ const gchar *pass, IMAPAuthType type)
+{
+ gchar *auth_type;
+ gint ok;
+ gchar *buf = NULL;
+ gchar *challenge;
+ gint challenge_len;
+ gchar hexdigest[33];
+ gchar *response;
+ gchar *response64;
+
+ g_return_val_if_fail((type == 0 || type == IMAP_AUTH_CRAM_MD5),
+ IMAP_ERROR);
+
+ auth_type = "CRAM-MD5";
+
+ imap_cmd_gen_send(session, "AUTHENTICATE %s", auth_type);
+ ok = imap_cmd_gen_recv(session, &buf);
+ if (ok != IMAP_SUCCESS || buf[0] != '+' || buf[1] != ' ') {
+ g_free(buf);
+ return IMAP_ERROR;
+ }
+
+ challenge = g_malloc(strlen(buf + 2) + 1);
+ challenge_len = base64_decode(challenge, buf + 2, -1);
+ challenge[challenge_len] = '\0';
+ g_free(buf);
+ log_print("IMAP< [Decoded: %s]\n", challenge);
+
+ md5_hex_hmac(hexdigest, challenge, challenge_len, pass, strlen(pass));
+ g_free(challenge);
+
+ response = g_strdup_printf("%s %s", user, hexdigest);
+ log_print("IMAP> [Encoded: %s]\n", response);
+ response64 = g_malloc((strlen(response) + 3) * 2 + 1);
+ base64_encode(response64, response, strlen(response));
+ g_free(response);
+
+ log_print("IMAP> %s\n", response64);
+ sock_puts(SESSION(session)->sock, response64);
+ ok = imap_cmd_ok(session, NULL);
+ if (ok != IMAP_SUCCESS)
+ log_warning(_("IMAP4 authentication failed.\n"));
+
+ return ok;
+}
+
+static gint imap_cmd_login(IMAPSession *session,
+ const gchar *user, const gchar *pass)
+{
+ gchar *user_, *pass_;
+ gint ok;
+
+ QUOTE_IF_REQUIRED(user_, user);
+ QUOTE_IF_REQUIRED(pass_, pass);
+ imap_cmd_gen_send(session, "LOGIN %s %s", user_, pass_);
+
+ ok = imap_cmd_ok(session, NULL);
+ if (ok != IMAP_SUCCESS)
+ log_warning(_("IMAP4 login failed.\n"));
+
+ return ok;
+}
+
+static gint imap_cmd_logout(IMAPSession *session)
+{
+ imap_cmd_gen_send(session, "LOGOUT");
+ return imap_cmd_ok(session, NULL);
+}
+
+static gint imap_cmd_noop(IMAPSession *session)
+{
+ imap_cmd_gen_send(session, "NOOP");
+ return imap_cmd_ok(session, NULL);
+}
+
+#if USE_SSL
+static gint imap_cmd_starttls(IMAPSession *session)
+{
+ imap_cmd_gen_send(session, "STARTTLS");
+ return imap_cmd_ok(session, NULL);
+}
+#endif
+
+#define THROW(err) { ok = err; goto catch; }
+
+static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
+{
+ gint ok;
+ GPtrArray *argbuf;
+ gchar *str;
+
+ argbuf = g_ptr_array_new();
+
+ imap_cmd_gen_send(session, "NAMESPACE");
+ if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
+
+ str = search_array_str(argbuf, "NAMESPACE");
+ if (!str) THROW(IMAP_ERROR);
+
+ *ns_str = g_strdup(str);
+
+catch:
+ ptr_array_free_strings(argbuf);
+ g_ptr_array_free(argbuf, TRUE);
+
+ return ok;
+}
+
+#undef THROW
+
+static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
+ const gchar *mailbox, GPtrArray *argbuf)
+{
+ gchar *ref_, *mailbox_;
+
+ if (!ref) ref = "\"\"";
+ if (!mailbox) mailbox = "\"\"";
+
+ QUOTE_IF_REQUIRED(ref_, ref);
+ QUOTE_IF_REQUIRED(mailbox_, mailbox);
+ imap_cmd_gen_send(session, "LIST %s %s", ref_, mailbox_);
+
+ return imap_cmd_ok(session, argbuf);
+}
+
+#define THROW goto catch
+
+static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
+ gboolean examine,
+ gint *exists, gint *recent, gint *unseen,
+ guint32 *uid_validity)
+{
+ gint ok;
+ gchar *resp_str;
+ GPtrArray *argbuf;
+ gchar *select_cmd;
+ gchar *folder_;
+ guint uid_validity_;
+
+ *exists = *recent = *unseen = *uid_validity = 0;
+ argbuf = g_ptr_array_new();
+
+ if (examine)
+ select_cmd = "EXAMINE";
+ else
+ select_cmd = "SELECT";
+
+ QUOTE_IF_REQUIRED(folder_, folder);
+ imap_cmd_gen_send(session, "%s %s", select_cmd, folder_);
+
+ if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
+
+ resp_str = search_array_contain_str(argbuf, "EXISTS");
+ if (resp_str) {
+ if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
+ g_warning("imap_cmd_select(): invalid EXISTS line.\n");
+ THROW;
+ }
+ }
+
+ resp_str = search_array_contain_str(argbuf, "RECENT");
+ if (resp_str) {
+ if (sscanf(resp_str, "%d RECENT", recent) != 1) {
+ g_warning("imap_cmd_select(): invalid RECENT line.\n");
+ THROW;
+ }
+ }
+
+ resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
+ if (resp_str) {
+ if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", &uid_validity_)
+ != 1) {
+ g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
+ THROW;
+ }
+ *uid_validity = uid_validity_;
+ }
+
+ resp_str = search_array_contain_str(argbuf, "UNSEEN");
+ if (resp_str) {
+ if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
+ g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
+ THROW;
+ }
+ }
+
+catch:
+ ptr_array_free_strings(argbuf);
+ g_ptr_array_free(argbuf, TRUE);
+
+ return ok;
+}
+
+static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
+ gint *exists, gint *recent, gint *unseen,
+ guint32 *uid_validity)
+{
+ return imap_cmd_do_select(session, folder, FALSE,
+ exists, recent, unseen, uid_validity);
+}
+
+static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
+ gint *exists, gint *recent, gint *unseen,
+ guint32 *uid_validity)
+{
+ return imap_cmd_do_select(session, folder, TRUE,
+ exists, recent, unseen, uid_validity);
+}
+
+#undef THROW
+
+static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
+{
+ gchar *folder_;
+
+ QUOTE_IF_REQUIRED(folder_, folder);
+ imap_cmd_gen_send(session, "CREATE %s", folder_);
+
+ return imap_cmd_ok(session, NULL);
+}
+
+static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
+ const gchar *new_folder)
+{
+ gchar *old_folder_, *new_folder_;
+
+ QUOTE_IF_REQUIRED(old_folder_, old_folder);
+ QUOTE_IF_REQUIRED(new_folder_, new_folder);
+ imap_cmd_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
+
+ return imap_cmd_ok(session, NULL);
+}
+
+static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
+{
+ gchar *folder_;
+
+ QUOTE_IF_REQUIRED(folder_, folder);
+ imap_cmd_gen_send(session, "DELETE %s", folder_);
+
+ return imap_cmd_ok(session, NULL);
+}
+
+#define THROW(err) { ok = err; goto catch; }
+
+static gint imap_cmd_search(IMAPSession *session, const gchar *criteria,
+ GArray **result)
+{
+ gint ok;
+ GPtrArray *argbuf;
+ GArray *array;
+ gchar *str;
+ gchar *p, *ep;
+ gint i;
+ guint32 uid;
+
+ g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
+ g_return_val_if_fail(result != NULL, IMAP_ERROR);
+
+ argbuf = g_ptr_array_new();
+
+ imap_cmd_gen_send(session, "UID SEARCH %s", criteria);
+ if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
+
+ array = g_array_new(FALSE, FALSE, sizeof(guint32));
+
+ for (i = 0; i < argbuf->len; i++) {
+ str = g_ptr_array_index(argbuf, i);
+ if (strncmp(str, "SEARCH", 6) != 0)
+ continue;
+
+ p = str + 6;
+ while (*p != '\0') {
+ uid = strtoul(p, &ep, 10);
+ if (p < ep && uid > 0) {
+ g_array_append_val(array, uid);
+ p = ep;
+ } else
+ break;
+ }
+ }
+
+ *result = array;
+
+catch:
+ ptr_array_free_strings(argbuf);
+ g_ptr_array_free(argbuf, TRUE);
+
+ return ok;
+}
+
+static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
+ const gchar *filename)
+{
+ gint ok;
+ gchar *buf;
+ gchar *cur_pos;
+ gchar size_str[32];
+ glong size_num;
+ gint ret;
+
+ g_return_val_if_fail(filename != NULL, IMAP_ERROR);
+
+ imap_cmd_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
+
+ while ((ok = imap_cmd_gen_recv(session, &buf)) == IMAP_SUCCESS) {
+ if (buf[0] != '*' || buf[1] != ' ') {
+ g_free(buf);
+ return IMAP_ERROR;
+ }
+ if (strstr(buf, "FETCH") != NULL) break;
+ g_free(buf);
+ }
+ if (ok != IMAP_SUCCESS)
+ return ok;
+
+#define RETURN_ERROR_IF_FAIL(cond) \
+ if (!(cond)) { \
+ g_free(buf); \
+ return IMAP_ERROR; \
+ }
+
+ cur_pos = strchr(buf, '{');
+ RETURN_ERROR_IF_FAIL(cur_pos != NULL);
+ cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
+ RETURN_ERROR_IF_FAIL(cur_pos != NULL);
+ size_num = atol(size_str);
+ RETURN_ERROR_IF_FAIL(size_num >= 0);
+
+ RETURN_ERROR_IF_FAIL(*cur_pos == '\0');
+
+#undef RETURN_ERROR_IF_FAIL
+
+ g_free(buf);
+
+ if ((ret = recv_bytes_write_to_file(SESSION(session)->sock,
+ size_num, filename)) != 0) {
+ if (ret == -2)
+ return IMAP_SOCKET;
+ }
+
+ if (imap_cmd_gen_recv(session, &buf) != IMAP_SUCCESS)
+ return IMAP_ERROR;
+
+ if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
+ g_free(buf);
+ return IMAP_ERROR;
+ }
+ g_free(buf);
+
+ ok = imap_cmd_ok(session, NULL);
+
+ if (ret != 0)
+ return IMAP_ERROR;
+
+ return ok;
+}
+
+static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
+ const gchar *file, IMAPFlags flags,
+ guint32 *new_uid)
+{
+ gint ok;
+ gint size;
+ gchar *destfolder_;
+ gchar *flag_str;
+ guint new_uid_;
+ gchar *ret = NULL;
+ gchar buf[BUFFSIZE];
+ FILE *fp;
+ GPtrArray *argbuf;
+ gchar *resp_str;
+
+ g_return_val_if_fail(file != NULL, IMAP_ERROR);
+
+ size = get_file_size_as_crlf(file);
+ if ((fp = g_fopen(file, "rb")) == NULL) {
+ FILE_OP_ERROR(file, "fopen");
+ return -1;
+ }
+ QUOTE_IF_REQUIRED(destfolder_, destfolder);
+ flag_str = imap_get_flag_str(flags);
+ imap_cmd_gen_send(session, "APPEND %s (%s) {%d}",
+ destfolder_, flag_str, size);
+ g_free(flag_str);
+
+ ok = imap_cmd_gen_recv(session, &ret);
+ if (ok != IMAP_SUCCESS || ret[0] != '+' || ret[1] != ' ') {
+ log_warning(_("can't append %s to %s\n"), file, destfolder_);
+ g_free(ret);
+ fclose(fp);
+ return IMAP_ERROR;
+ }
+ g_free(ret);
+
+ log_print("IMAP4> %s\n", _("(sending file...)"));
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ strretchomp(buf);
+ if (sock_puts(SESSION(session)->sock, buf) < 0) {
+ fclose(fp);
+ return -1;
+ }
+ }
+
+ if (ferror(fp)) {
+ FILE_OP_ERROR(file, "fgets");
+ fclose(fp);
+ return -1;
+ }
+
+ sock_puts(SESSION(session)->sock, "");
+
+ fclose(fp);
+
+ if (new_uid != NULL)
+ *new_uid = 0;
+
+ if (new_uid != NULL && session->uidplus) {
+ argbuf = g_ptr_array_new();
+
+ ok = imap_cmd_ok(session, argbuf);
+ if (ok != IMAP_SUCCESS)
+ log_warning(_("can't append message to %s\n"),
+ destfolder_);
+ else if (argbuf->len > 0) {
+ resp_str = g_ptr_array_index(argbuf, argbuf->len - 1);
+ if (resp_str &&
+ sscanf(resp_str, "%*u OK [APPENDUID %*u %u]",
+ &new_uid_) == 1) {
+ *new_uid = new_uid_;
+ }
+ }
+
+ ptr_array_free_strings(argbuf);
+ g_ptr_array_free(argbuf, TRUE);
+ } else
+ ok = imap_cmd_ok(session, NULL);
+
+ return ok;
+}
+
+static gint imap_cmd_copy(IMAPSession *session, const gchar *seq_set,
+ const gchar *destfolder)
+{
+ gint ok;
+ gchar *destfolder_;
+
+ g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
+
+ QUOTE_IF_REQUIRED(destfolder_, destfolder);
+ imap_cmd_gen_send(session, "UID COPY %s %s", seq_set, destfolder_);
+
+ ok = imap_cmd_ok(session, NULL);
+ if (ok != IMAP_SUCCESS) {
+ log_warning(_("can't copy %s to %s\n"), seq_set, destfolder_);
+ return -1;
+ }
+
+ return ok;
+}
+
+gint imap_cmd_envelope(IMAPSession *session, const gchar *seq_set)
+{
+ imap_cmd_gen_send
+ (session, "UID FETCH %s (UID FLAGS RFC822.SIZE RFC822.HEADER)",
+ seq_set);
+
+ return IMAP_SUCCESS;
+}
+
+static gint imap_cmd_store(IMAPSession *session, const gchar *seq_set,
+ const gchar *sub_cmd)
+{
+ gint ok;
+
+ imap_cmd_gen_send(session, "UID STORE %s %s", seq_set, sub_cmd);
+
+ if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
+ log_warning(_("error while imap command: STORE %s %s\n"),
+ seq_set, sub_cmd);
+ return ok;
+ }
+
+ return IMAP_SUCCESS;
+}
+
+static gint imap_cmd_expunge(IMAPSession *session)
+{
+ gint ok;
+
+ imap_cmd_gen_send(session, "EXPUNGE");
+ if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
+ log_warning(_("error while imap command: EXPUNGE\n"));
+ return ok;
+ }
+
+ return IMAP_SUCCESS;
+}
+
+static gint imap_cmd_close(IMAPSession *session)
+{
+ gint ok;
+
+ imap_cmd_gen_send(session, "CLOSE");
+ if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS)
+ log_warning(_("error while imap command: CLOSE\n"));
+
+ return ok;
+}
+
+static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
+{
+ gint ok;
+ gchar *buf;
+ gint cmd_num;
+ gchar cmd_status[IMAPBUFSIZE + 1];
+
+ while ((ok = imap_cmd_gen_recv(session, &buf)) == IMAP_SUCCESS) {
+ if (buf[0] == '*' && buf[1] == ' ') {
+ if (argbuf) {
+ g_memmove(buf, buf + 2, strlen(buf + 2) + 1);
+ g_ptr_array_add(argbuf, buf);
+ } else
+ g_free(buf);
+ continue;
+ }
+
+ if (sscanf(buf, "%d %" Xstr(IMAPBUFSIZE) "s",
+ &cmd_num, cmd_status) < 2) {
+ g_free(buf);
+ return IMAP_ERROR;
+ } else if (cmd_num == session->cmd_count &&
+ !strcmp(cmd_status, "OK")) {
+ if (argbuf)
+ g_ptr_array_add(argbuf, buf);
+ else
+ g_free(buf);
+ return IMAP_SUCCESS;
+ } else {
+ g_free(buf);
+ return IMAP_ERROR;
+ }
+ }
+
+ return ok;
+}
+
+static void imap_cmd_gen_send(IMAPSession *session, const gchar *format, ...)
+{
+ gchar buf[IMAPBUFSIZE];
+ gchar tmp[IMAPBUFSIZE];
+ gchar *p;
+ va_list args;
+
+ va_start(args, format);
+ g_vsnprintf(tmp, sizeof(tmp), format, args);
+ va_end(args);
+
+ session->cmd_count++;
+
+ g_snprintf(buf, sizeof(buf), "%d %s\r\n", session->cmd_count, tmp);
+ if (!g_ascii_strncasecmp(tmp, "LOGIN ", 6) &&
+ (p = strchr(tmp + 6, ' '))) {
+ *p = '\0';
+ log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
+ } else
+ log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
+
+ sock_write_all(SESSION(session)->sock, buf, strlen(buf));
+}
+
+static gint imap_cmd_gen_recv(IMAPSession *session, gchar **ret)
+{
+ if (sock_getline(SESSION(session)->sock, ret) < 0)
+ return IMAP_SOCKET;
+
+ strretchomp(*ret);
+
+ log_print("IMAP4< %s\n", *ret);
+
+ session_set_access_time(SESSION(session));
+
+ return IMAP_SUCCESS;
+}
+
+
+/* misc utility functions */
+
+static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
+{
+ gchar *tmp;
+
+ dest[0] = '\0';
+ tmp = strchr(src, ch);
+ if (!tmp)
+ return NULL;
+
+ memcpy(dest, src, MIN(tmp - src, len - 1));
+ dest[MIN(tmp - src, len - 1)] = '\0';
+
+ return tmp + 1;
+}
+
+static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
+{
+ const gchar *p = src;
+ gint n = 0;
+
+ g_return_val_if_fail(*p == ch, NULL);
+
+ *dest = '\0';
+ p++;
+
+ while (*p != '\0' && *p != ch) {
+ if (n < len - 1) {
+ if (*p == '\\' && *(p + 1) != '\0')
+ p++;
+ *dest++ = *p++;
+ } else
+ p++;
+ n++;
+ }
+
+ *dest = '\0';
+ return (gchar *)(*p == ch ? p + 1 : p);
+}
+
+static gchar *search_array_contain_str(GPtrArray *array, gchar *str)
+{
+ gint i;
+
+ for (i = 0; i < array->len; i++) {
+ gchar *tmp;
+
+ tmp = g_ptr_array_index(array, i);
+ if (strstr(tmp, str) != NULL)
+ return tmp;
+ }
+
+ return NULL;
+}
+
+static gchar *search_array_str(GPtrArray *array, gchar *str)
+{
+ gint i;
+ gint len;
+
+ len = strlen(str);
+
+ for (i = 0; i < array->len; i++) {
+ gchar *tmp;
+
+ tmp = g_ptr_array_index(array, i);
+ if (!strncmp(tmp, str, len))
+ return tmp;
+ }
+
+ return NULL;
+}
+
+static void imap_path_separator_subst(gchar *str, gchar separator)
+{
+ gchar *p;
+ gboolean in_escape = FALSE;
+
+ if (!separator || separator == '/') return;
+
+ for (p = str; *p != '\0'; p++) {
+ if (*p == '/' && !in_escape)
+ *p = separator;
+ else if (*p == '&' && *(p + 1) != '-' && !in_escape)
+ in_escape = TRUE;
+ else if (*p == '-' && in_escape)
+ in_escape = FALSE;
+ }
+}
+
+static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
+{
+ static iconv_t cd = (iconv_t)-1;
+ static gboolean iconv_ok = TRUE;
+ GString *norm_utf7;
+ gchar *norm_utf7_p;
+ size_t norm_utf7_len;
+ const gchar *p;
+ gchar *to_str, *to_p;
+ size_t to_len;
+ gboolean in_escape = FALSE;
+
+ if (!iconv_ok) return g_strdup(mutf7_str);
+
+ if (cd == (iconv_t)-1) {
+ cd = iconv_open(CS_INTERNAL, CS_UTF_7);
+ if (cd == (iconv_t)-1) {
+ g_warning("iconv cannot convert UTF-7 to %s\n",
+ CS_INTERNAL);
+ iconv_ok = FALSE;
+ return g_strdup(mutf7_str);
+ }
+ }
+
+ /* modified UTF-7 to normal UTF-7 conversion */
+ norm_utf7 = g_string_new(NULL);
+
+ for (p = mutf7_str; *p != '\0'; p++) {
+ /* replace: '&' -> '+',
+ "&-" -> '&',
+ "+" -> "+-",
+ escaped ',' -> '/' */
+ if (!in_escape && *p == '&') {
+ if (*(p + 1) != '-') {
+ g_string_append_c(norm_utf7, '+');
+ in_escape = TRUE;
+ } else {
+ g_string_append_c(norm_utf7, '&');
+ p++;
+ }
+ } else if (!in_escape && *p == '+') {
+ g_string_append(norm_utf7, "+-");
+ } else if (in_escape && *p == ',') {
+ g_string_append_c(norm_utf7, '/');
+ } else if (in_escape && *p == '-') {
+ g_string_append_c(norm_utf7, '-');
+ in_escape = FALSE;
+ } else {
+ g_string_append_c(norm_utf7, *p);
+ }
+ }
+
+ /* somehow iconv() returns error when the last of the string is "+-" */
+ g_string_append_c(norm_utf7, '\n');
+ norm_utf7_p = norm_utf7->str;
+ norm_utf7_len = norm_utf7->len;
+ to_len = strlen(mutf7_str) * 5;
+ to_p = to_str = g_malloc(to_len + 1);
+
+ if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
+ &to_p, &to_len) == -1) {
+ g_warning(_("iconv cannot convert UTF-7 to %s\n"), CS_INTERNAL);
+ g_string_free(norm_utf7, TRUE);
+ g_free(to_str);
+ return g_strdup(mutf7_str);
+ }
+
+ /* second iconv() call for flushing */
+ iconv(cd, NULL, NULL, &to_p, &to_len);
+ g_string_free(norm_utf7, TRUE);
+ *to_p = '\0';
+ strretchomp(to_str);
+
+ return to_str;
+}
+
+static gchar *imap_utf8_to_modified_utf7(const gchar *from)
+{
+ static iconv_t cd = (iconv_t)-1;
+ static gboolean iconv_ok = TRUE;
+ gchar *norm_utf7, *norm_utf7_p;
+ size_t from_len, norm_utf7_len;
+ GString *to_str;
+ gchar *from_tmp, *to, *p;
+ gboolean in_escape = FALSE;
+
+ if (!iconv_ok) return g_strdup(from);
+
+ if (cd == (iconv_t)-1) {
+ cd = iconv_open(CS_UTF_7, CS_INTERNAL);
+ if (cd == (iconv_t)-1) {
+ g_warning(_("iconv cannot convert %s to UTF-7\n"),
+ CS_INTERNAL);
+ iconv_ok = FALSE;
+ return g_strdup(from);
+ }
+ }
+
+ /* UTF-8 to normal UTF-7 conversion */
+ Xstrdup_a(from_tmp, from, return g_strdup(from));
+ from_len = strlen(from);
+ norm_utf7_len = from_len * 5;
+ Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
+ norm_utf7_p = norm_utf7;
+
+ while (from_len > 0) {
+ if (*from_tmp == '+') {
+ *norm_utf7_p++ = '+';
+ *norm_utf7_p++ = '-';
+ norm_utf7_len -= 2;
+ from_tmp++;
+ from_len--;
+ } else if (g_ascii_isprint(*from_tmp)) {
+ /* printable ascii char */
+ *norm_utf7_p = *from_tmp;
+ norm_utf7_p++;
+ norm_utf7_len--;
+ from_tmp++;
+ from_len--;
+ } else {
+ size_t conv_len = 0;
+
+ /* unprintable char: convert to UTF-7 */
+ p = from_tmp;
+ while (!g_ascii_isprint(*p) && conv_len < from_len) {
+ conv_len += g_utf8_skip[*(guchar *)p];
+ p += g_utf8_skip[*(guchar *)p];
+ }
+
+ from_len -= conv_len;
+ if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
+ &conv_len,
+ &norm_utf7_p, &norm_utf7_len) == -1) {
+ g_warning("iconv cannot convert %s to UTF-7\n",
+ CS_INTERNAL);
+ return g_strdup(from);
+ }
+
+ /* second iconv() call for flushing */
+ iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
+ }
+ }
+
+ *norm_utf7_p = '\0';
+ to_str = g_string_new(NULL);
+ for (p = norm_utf7; p < norm_utf7_p; p++) {
+ /* replace: '&' -> "&-",
+ '+' -> '&',
+ "+-" -> '+',
+ BASE64 '/' -> ',' */
+ if (!in_escape && *p == '&') {
+ g_string_append(to_str, "&-");
+ } else if (!in_escape && *p == '+') {
+ if (*(p + 1) == '-') {
+ g_string_append_c(to_str, '+');
+ p++;
+ } else {
+ g_string_append_c(to_str, '&');
+ in_escape = TRUE;
+ }
+ } else if (in_escape && *p == '/') {
+ g_string_append_c(to_str, ',');
+ } else if (in_escape && *p == '-') {
+ g_string_append_c(to_str, '-');
+ in_escape = FALSE;
+ } else {
+ g_string_append_c(to_str, *p);
+ }
+ }
+
+ if (in_escape) {
+ in_escape = FALSE;
+ g_string_append_c(to_str, '-');
+ }
+
+ to = to_str->str;
+ g_string_free(to_str, FALSE);
+
+ return to;
+}
+
+static GSList *imap_get_seq_set_from_msglist(GSList *msglist)
+{
+ GString *str;
+ GSList *sorted_list, *cur;
+ guint first, last, next;
+ gchar *ret_str;
+ GSList *ret_list = NULL;
+
+ if (msglist == NULL)
+ return NULL;
+
+ str = g_string_sized_new(256);
+
+ sorted_list = g_slist_copy(msglist);
+ sorted_list = procmsg_sort_msg_list(sorted_list, SORT_BY_NUMBER,
+ SORT_ASCENDING);
+
+ first = ((MsgInfo *)sorted_list->data)->msgnum;
+
+ for (cur = sorted_list; cur != NULL; cur = cur->next) {
+ last = ((MsgInfo *)cur->data)->msgnum;
+ if (cur->next)
+ next = ((MsgInfo *)cur->next->data)->msgnum;
+ else
+ next = 0;
+
+ if (last + 1 != next || next == 0) {
+ if (str->len > 0)
+ g_string_append_c(str, ',');
+ if (first == last)
+ g_string_sprintfa(str, "%u", first);
+ else
+ g_string_sprintfa(str, "%u:%u", first, last);
+
+ first = next;
+
+ if (str->len > IMAP_CMD_LIMIT) {
+ ret_str = g_strdup(str->str);
+ ret_list = g_slist_append(ret_list, ret_str);
+ g_string_truncate(str, 0);
+ }
+ }
+ }
+
+ if (str->len > 0) {
+ ret_str = g_strdup(str->str);
+ ret_list = g_slist_append(ret_list, ret_str);
+ }
+
+ g_slist_free(sorted_list);
+ g_string_free(str, TRUE);
+
+ return ret_list;
+}
+
+static void imap_seq_set_free(GSList *seq_list)
+{
+ slist_free_strings(seq_list);
+ g_slist_free(seq_list);
+}
+
+static GHashTable *imap_get_uid_table(GArray *array)
+{
+ GHashTable *table;
+ gint i;
+ guint32 uid;
+
+ g_return_val_if_fail(array != NULL, NULL);
+
+ table = g_hash_table_new(NULL, g_direct_equal);
+
+ for (i = 0; i < array->len; i++) {
+ uid = g_array_index(array, guint32, i);
+ g_hash_table_insert(table, GUINT_TO_POINTER(uid),
+ GINT_TO_POINTER(i + 1));
+ }
+
+ return table;
+}
+
+static gboolean imap_rename_folder_func(GNode *node, gpointer data)
+{
+ FolderItem *item = node->data;
+ gchar **paths = data;
+ const gchar *oldpath = paths[0];
+ const gchar *newpath = paths[1];
+ gchar *base;
+ gchar *new_itempath;
+ gint oldpathlen;
+
+ oldpathlen = strlen(oldpath);
+ if (strncmp(oldpath, item->path, oldpathlen) != 0) {
+ g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
+ return TRUE;
+ }
+
+ base = item->path + oldpathlen;
+ while (*base == G_DIR_SEPARATOR) base++;
+ if (*base == '\0')
+ new_itempath = g_strdup(newpath);
+ else
+ new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
+ NULL);
+ g_free(item->path);
+ item->path = new_itempath;
+
+ return FALSE;
+}
diff --git a/libsylph/imap.h b/libsylph/imap.h
new file mode 100644
index 00000000..c726c875
--- /dev/null
+++ b/libsylph/imap.h
@@ -0,0 +1,114 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __IMAP_H__
+#define __IMAP_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <time.h>
+
+#include "folder.h"
+#include "session.h"
+#include "procmsg.h"
+
+typedef struct _IMAPFolder IMAPFolder;
+typedef struct _IMAPSession IMAPSession;
+typedef struct _IMAPNameSpace IMAPNameSpace;
+
+#define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
+#define IMAP_SESSION(obj) ((IMAPSession *)obj)
+
+#include "prefs_account.h"
+
+typedef enum
+{
+ IMAP_AUTH_LOGIN = 1 << 0,
+ IMAP_AUTH_CRAM_MD5 = 1 << 1
+} IMAPAuthType;
+
+struct _IMAPFolder
+{
+ RemoteFolder rfolder;
+
+ /* list of IMAPNameSpace */
+ GList *ns_personal;
+ GList *ns_others;
+ GList *ns_shared;
+};
+
+struct _IMAPSession
+{
+ Session session;
+
+ gboolean authenticated;
+
+ gchar **capability;
+ gboolean uidplus;
+
+ gchar *mbox;
+ guint cmd_count;
+};
+
+struct _IMAPNameSpace
+{
+ gchar *name;
+ gchar separator;
+};
+
+#define IMAP_SUCCESS 0
+#define IMAP_SOCKET 2
+#define IMAP_AUTHFAIL 3
+#define IMAP_PROTOCOL 4
+#define IMAP_SYNTAX 5
+#define IMAP_IOERR 6
+#define IMAP_ERROR 7
+
+#define IMAPBUFSIZE 8192
+
+typedef enum
+{
+ IMAP_FLAG_SEEN = 1 << 0,
+ IMAP_FLAG_ANSWERED = 1 << 1,
+ IMAP_FLAG_FLAGGED = 1 << 2,
+ IMAP_FLAG_DELETED = 1 << 3,
+ IMAP_FLAG_DRAFT = 1 << 4
+} IMAPFlags;
+
+#define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
+#define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
+#define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
+#define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
+#define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
+
+FolderClass *imap_get_class (void);
+
+gint imap_msg_set_perm_flags (MsgInfo *msginfo,
+ MsgPermFlags flags);
+gint imap_msg_unset_perm_flags (MsgInfo *msginfo,
+ MsgPermFlags flags);
+gint imap_msg_list_set_perm_flags (GSList *msglist,
+ MsgPermFlags flags);
+gint imap_msg_list_unset_perm_flags (GSList *msglist,
+ MsgPermFlags flags);
+
+#endif /* __IMAP_H__ */
diff --git a/libsylph/md5.c b/libsylph/md5.c
new file mode 100644
index 00000000..54585971
--- /dev/null
+++ b/libsylph/md5.c
@@ -0,0 +1,433 @@
+/* md5.c - MD5 Message-Digest Algorithm
+ * Copyright (C) 1995, 1996, 1998, 1999 Free Software Foundation, Inc.
+ *
+ * according to the definition of MD5 in RFC 1321 from April 1992.
+ * NOTE: This is *not* the same file as the one from glibc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995. */
+/* heavily modified for GnuPG by <werner.koch@guug.de> */
+/* modified again for Sylpheed by <wk@gnupg.org> 2001-02-11 */
+
+
+/* Test values:
+ * "" D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E
+ * "a" 0C C1 75 B9 C0 F1 B6 A8 31 C3 99 E2 69 77 26 61
+ * "abc 90 01 50 98 3C D2 4F B0 D6 96 3F 7D 28 E1 7F 72
+ * "message digest" F9 6B 69 7D 7C B7 93 8D 52 5A 2F 31 AA F1 61 D0
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "utils.h"
+#include "md5.h"
+
+
+/****************
+ * Rotate a 32 bit integer by n bytes
+ */
+#if defined(__GNUC__) && defined(__i386__)
+static inline u32
+rol( u32 x, int n)
+{
+ __asm__("roll %%cl,%0"
+ :"=r" (x)
+ :"0" (x),"c" (n));
+ return x;
+}
+#else
+#define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) )
+#endif
+
+
+void
+md5_init(MD5_CONTEXT *ctx)
+{
+ ctx->A = 0x67452301;
+ ctx->B = 0xefcdab89;
+ ctx->C = 0x98badcfe;
+ ctx->D = 0x10325476;
+
+ ctx->nblocks = 0;
+ ctx->count = 0;
+ ctx->finalized = 0;
+}
+
+/* These are the four functions used in the four steps of the MD5 algorithm
+ and defined in the RFC 1321. The first function is a little bit optimized
+ (as found in Colin Plumbs public domain implementation). */
+/* #define FF(b, c, d) ((b & c) | (~b & d)) */
+#define FF(b, c, d) (d ^ (b & (c ^ d)))
+#define FG(b, c, d) FF (d, b, c)
+#define FH(b, c, d) (b ^ c ^ d)
+#define FI(b, c, d) (c ^ (b | ~d))
+
+
+/****************
+ * transform n*64 bytes
+ */
+static void
+transform(MD5_CONTEXT *ctx, const unsigned char *data)
+{
+ u32 correct_words[16];
+ u32 A = ctx->A;
+ u32 B = ctx->B;
+ u32 C = ctx->C;
+ u32 D = ctx->D;
+ u32 *cwp = correct_words;
+
+#ifdef BIG_ENDIAN_HOST
+ {
+ int i;
+ unsigned char *p2, *p1;
+
+ for (i = 0, p1 = data, p2 = (unsigned char*)correct_words;
+ i < 16; i++, p2 += 4) {
+ p2[3] = *p1++;
+ p2[2] = *p1++;
+ p2[1] = *p1++;
+ p2[0] = *p1++;
+ }
+ }
+#else
+ memcpy(correct_words, data, 64);
+#endif
+
+
+#define OP(a, b, c, d, s, T) \
+ do { \
+ a += FF (b, c, d) + (*cwp++) + T; \
+ a = rol(a, s); \
+ a += b; \
+ } while (0)
+
+ /* Before we start, one word about the strange constants.
+ They are defined in RFC 1321 as
+
+ T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
+ */
+
+ /* Round 1. */
+ OP (A, B, C, D, 7, 0xd76aa478);
+ OP (D, A, B, C, 12, 0xe8c7b756);
+ OP (C, D, A, B, 17, 0x242070db);
+ OP (B, C, D, A, 22, 0xc1bdceee);
+ OP (A, B, C, D, 7, 0xf57c0faf);
+ OP (D, A, B, C, 12, 0x4787c62a);
+ OP (C, D, A, B, 17, 0xa8304613);
+ OP (B, C, D, A, 22, 0xfd469501);
+ OP (A, B, C, D, 7, 0x698098d8);
+ OP (D, A, B, C, 12, 0x8b44f7af);
+ OP (C, D, A, B, 17, 0xffff5bb1);
+ OP (B, C, D, A, 22, 0x895cd7be);
+ OP (A, B, C, D, 7, 0x6b901122);
+ OP (D, A, B, C, 12, 0xfd987193);
+ OP (C, D, A, B, 17, 0xa679438e);
+ OP (B, C, D, A, 22, 0x49b40821);
+
+#undef OP
+#define OP(f, a, b, c, d, k, s, T) \
+ do { \
+ a += f (b, c, d) + correct_words[k] + T; \
+ a = rol(a, s); \
+ a += b; \
+ } while (0)
+
+ /* Round 2. */
+ OP (FG, A, B, C, D, 1, 5, 0xf61e2562);
+ OP (FG, D, A, B, C, 6, 9, 0xc040b340);
+ OP (FG, C, D, A, B, 11, 14, 0x265e5a51);
+ OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa);
+ OP (FG, A, B, C, D, 5, 5, 0xd62f105d);
+ OP (FG, D, A, B, C, 10, 9, 0x02441453);
+ OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);
+ OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8);
+ OP (FG, A, B, C, D, 9, 5, 0x21e1cde6);
+ OP (FG, D, A, B, C, 14, 9, 0xc33707d6);
+ OP (FG, C, D, A, B, 3, 14, 0xf4d50d87);
+ OP (FG, B, C, D, A, 8, 20, 0x455a14ed);
+ OP (FG, A, B, C, D, 13, 5, 0xa9e3e905);
+ OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8);
+ OP (FG, C, D, A, B, 7, 14, 0x676f02d9);
+ OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
+
+ /* Round 3. */
+ OP (FH, A, B, C, D, 5, 4, 0xfffa3942);
+ OP (FH, D, A, B, C, 8, 11, 0x8771f681);
+ OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);
+ OP (FH, B, C, D, A, 14, 23, 0xfde5380c);
+ OP (FH, A, B, C, D, 1, 4, 0xa4beea44);
+ OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9);
+ OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60);
+ OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);
+ OP (FH, A, B, C, D, 13, 4, 0x289b7ec6);
+ OP (FH, D, A, B, C, 0, 11, 0xeaa127fa);
+ OP (FH, C, D, A, B, 3, 16, 0xd4ef3085);
+ OP (FH, B, C, D, A, 6, 23, 0x04881d05);
+ OP (FH, A, B, C, D, 9, 4, 0xd9d4d039);
+ OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);
+ OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);
+ OP (FH, B, C, D, A, 2, 23, 0xc4ac5665);
+
+ /* Round 4. */
+ OP (FI, A, B, C, D, 0, 6, 0xf4292244);
+ OP (FI, D, A, B, C, 7, 10, 0x432aff97);
+ OP (FI, C, D, A, B, 14, 15, 0xab9423a7);
+ OP (FI, B, C, D, A, 5, 21, 0xfc93a039);
+ OP (FI, A, B, C, D, 12, 6, 0x655b59c3);
+ OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92);
+ OP (FI, C, D, A, B, 10, 15, 0xffeff47d);
+ OP (FI, B, C, D, A, 1, 21, 0x85845dd1);
+ OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f);
+ OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
+ OP (FI, C, D, A, B, 6, 15, 0xa3014314);
+ OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);
+ OP (FI, A, B, C, D, 4, 6, 0xf7537e82);
+ OP (FI, D, A, B, C, 11, 10, 0xbd3af235);
+ OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb);
+ OP (FI, B, C, D, A, 9, 21, 0xeb86d391);
+
+ /* Put checksum in context given as argument. */
+ ctx->A += A;
+ ctx->B += B;
+ ctx->C += C;
+ ctx->D += D;
+}
+
+
+
+/* The routine updates the message-digest context to
+ * account for the presence of each of the characters inBuf[0..inLen-1]
+ * in the message whose digest is being computed.
+ */
+void
+md5_update(MD5_CONTEXT *hd, const unsigned char *inbuf, size_t inlen)
+{
+ if (hd->count == 64) { /* flush the buffer */
+ transform( hd, hd->buf );
+ hd->count = 0;
+ hd->nblocks++;
+ }
+ if (!inbuf)
+ return;
+ if (hd->count) {
+ for (; inlen && hd->count < 64; inlen--)
+ hd->buf[hd->count++] = *inbuf++;
+ md5_update(hd, NULL, 0);
+ if (!inlen)
+ return;
+ }
+
+ while (inlen >= 64) {
+ transform(hd, inbuf);
+ hd->count = 0;
+ hd->nblocks++;
+ inlen -= 64;
+ inbuf += 64;
+ }
+
+ for (; inlen && hd->count < 64; inlen--)
+ hd->buf[hd->count++] = *inbuf++;
+}
+
+
+
+/* The routine final terminates the message-digest computation and
+ * ends with the desired message digest in mdContext->digest[0...15].
+ * The handle is prepared for a new MD5 cycle.
+ * Returns 16 bytes representing the digest.
+ */
+
+static void
+do_final(MD5_CONTEXT *hd)
+{
+ u32 t, msb, lsb;
+ unsigned char *p;
+
+ md5_update(hd, NULL, 0); /* flush */
+
+ msb = 0;
+ t = hd->nblocks;
+ if ((lsb = t << 6) < t) /* multiply by 64 to make a byte count */
+ msb++;
+ msb += t >> 26;
+ t = lsb;
+ if ((lsb = t + hd->count) < t) /* add the count */
+ msb++;
+ t = lsb;
+ if ((lsb = t << 3) < t) /* multiply by 8 to make a bit count */
+ msb++;
+ msb += t >> 29;
+
+ if (hd->count < 56) { /* enough room */
+ hd->buf[hd->count++] = 0x80; /* pad */
+ while(hd->count < 56)
+ hd->buf[hd->count++] = 0; /* pad */
+ } else { /* need one extra block */
+ hd->buf[hd->count++] = 0x80; /* pad character */
+ while (hd->count < 64)
+ hd->buf[hd->count++] = 0;
+ md5_update(hd, NULL, 0); /* flush */
+ memset(hd->buf, 0, 56); /* fill next block with zeroes */
+ }
+
+ /* append the 64 bit count */
+ hd->buf[56] = lsb ;
+ hd->buf[57] = lsb >> 8;
+ hd->buf[58] = lsb >> 16;
+ hd->buf[59] = lsb >> 24;
+ hd->buf[60] = msb ;
+ hd->buf[61] = msb >> 8;
+ hd->buf[62] = msb >> 16;
+ hd->buf[63] = msb >> 24;
+ transform(hd, hd->buf);
+
+ p = hd->buf;
+#ifdef BIG_ENDIAN_HOST
+#define X(a) do { *p++ = hd->a ; *p++ = hd->a >> 8; \
+ *p++ = hd->a >> 16; *p++ = hd->a >> 24; } while(0)
+#else /* little endian */
+ /*#define X(a) do { *(u32*)p = hd->##a ; p += 4; } while(0)*/
+ /* Unixware's cpp doesn't like the above construct so we do it his way:
+ * (reported by Allan Clark) */
+#define X(a) do { *(u32*)p = (*hd).a ; p += 4; } while(0)
+#endif
+ X(A);
+ X(B);
+ X(C);
+ X(D);
+#undef X
+ hd->finalized = 1;
+}
+
+void
+md5_final(unsigned char *digest, MD5_CONTEXT *ctx)
+{
+ if (!ctx->finalized)
+ do_final(ctx);
+ memcpy(digest, ctx->buf, 16);
+}
+
+/*
+ * Creates a MD5 digest in hex fomrat (lowercase letters) from the
+ * string S. hextdigest but be buffer of at lease 33 bytes!
+ */
+void
+md5_hex_digest(char *hexdigest, const unsigned char *s)
+{
+ int i;
+ MD5_CONTEXT context;
+ unsigned char digest[16];
+
+ md5_init(&context);
+ md5_update(&context, s, strlen(s));
+ md5_final(digest, &context);
+
+ for (i = 0; i < 16; i++)
+ sprintf(hexdigest + 2 * i, "%02x", digest[i]);
+}
+
+
+/*
+** Function: md5_hmac
+** taken from the file rfc2104.txt
+** written by Martin Schaaf <mascha@ma-scha.de>
+*/
+void
+md5_hmac(unsigned char *digest,
+ const unsigned char* text, int text_len,
+ const unsigned char* key, int key_len)
+{
+ MD5_CONTEXT context;
+ unsigned char k_ipad[64]; /* inner padding -
+ * key XORd with ipad
+ */
+ unsigned char k_opad[64]; /* outer padding -
+ * key XORd with opad
+ */
+ /* unsigned char tk[16]; */
+ int i;
+
+ /* start out by storing key in pads */
+ memset(k_ipad, 0, sizeof k_ipad);
+ memset(k_opad, 0, sizeof k_opad);
+ if (key_len > 64) {
+ /* if key is longer than 64 bytes reset it to key=MD5(key) */
+ MD5_CONTEXT tctx;
+
+ md5_init(&tctx);
+ md5_update(&tctx, key, key_len);
+ md5_final(k_ipad, &tctx);
+ md5_final(k_opad, &tctx);
+ } else {
+ memcpy(k_ipad, key, key_len);
+ memcpy(k_opad, key, key_len);
+ }
+
+ /*
+ * the HMAC_MD5 transform looks like:
+ *
+ * MD5(K XOR opad, MD5(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected
+ */
+
+
+ /* XOR key with ipad and opad values */
+ for (i = 0; i < 64; i++) {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+
+ /*
+ * perform inner MD5
+ */
+ md5_init(&context); /* init context for 1st
+ * pass */
+ md5_update(&context, k_ipad, 64); /* start with inner pad */
+ md5_update(&context, text, text_len); /* then text of datagram */
+ md5_final(digest, &context); /* finish up 1st pass */
+ /*
+ * perform outer MD5
+ */
+ md5_init(&context); /* init context for 2nd
+ * pass */
+ md5_update(&context, k_opad, 64); /* start with outer pad */
+ md5_update(&context, digest, 16); /* then results of 1st
+ * hash */
+ md5_final(digest, &context); /* finish up 2nd pass */
+}
+
+
+void
+md5_hex_hmac(char *hexdigest,
+ const unsigned char* text, int text_len,
+ const unsigned char* key, int key_len)
+{
+ unsigned char digest[16];
+ int i;
+
+ md5_hmac(digest, text, text_len, key, key_len);
+ for (i = 0; i < 16; i++)
+ sprintf(hexdigest + 2 * i, "%02x", digest[i]);
+}
diff --git a/libsylph/md5.h b/libsylph/md5.h
new file mode 100644
index 00000000..84894b2c
--- /dev/null
+++ b/libsylph/md5.h
@@ -0,0 +1,49 @@
+/* md5.h - MD5 Message-Digest Algorithm
+ * Copyright (C) 1995, 1996, 1998, 1999 Free Software Foundation, Inc.
+ *
+ * according to the definition of MD5 in RFC 1321 from April 1992.
+ * NOTE: This is *not* the same file as the one from glibc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _MD5_HDR_
+#define _MD5_HDR_
+
+#include "utils.h"
+
+typedef struct { /* Hmm, should be private */
+ u32 A,B,C,D;
+ u32 nblocks;
+ unsigned char buf[64];
+ int count;
+ int finalized;
+} MD5_CONTEXT;
+
+void md5_init(MD5_CONTEXT *ctx);
+void md5_update(MD5_CONTEXT *hd, const unsigned char *inbuf, size_t inlen);
+void md5_final(unsigned char *digest, MD5_CONTEXT *ctx);
+
+void md5_hex_digest(char *hexdigest, const unsigned char *s);
+
+void md5_hmac(unsigned char *digest,
+ const unsigned char* text, int text_len,
+ const unsigned char* key, int key_len);
+void md5_hex_hmac(char *hexdigest,
+ const unsigned char* text, int text_len,
+ const unsigned char* key, int key_len);
+
+#endif /* _MD5_HDR_ */
+
diff --git a/libsylph/mh.c b/libsylph/mh.c
new file mode 100644
index 00000000..40554867
--- /dev/null
+++ b/libsylph/mh.c
@@ -0,0 +1,1431 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#undef MEASURE_TIME
+
+#include "folder.h"
+#include "mh.h"
+#include "procmsg.h"
+#include "procheader.h"
+#include "utils.h"
+#include "prefs_common.h"
+
+static void mh_folder_init (Folder *folder,
+ const gchar *name,
+ const gchar *path);
+
+static Folder *mh_folder_new (const gchar *name,
+ const gchar *path);
+static void mh_folder_destroy (Folder *folder);
+
+static GSList *mh_get_msg_list (Folder *folder,
+ FolderItem *item,
+ gboolean use_cache);
+static gchar *mh_fetch_msg (Folder *folder,
+ FolderItem *item,
+ gint num);
+static MsgInfo *mh_get_msginfo (Folder *folder,
+ FolderItem *item,
+ gint num);
+static gint mh_add_msg (Folder *folder,
+ FolderItem *dest,
+ const gchar *file,
+ MsgFlags *flags,
+ gboolean remove_source);
+static gint mh_add_msgs (Folder *folder,
+ FolderItem *dest,
+ GSList *file_list,
+ gboolean remove_source,
+ gint *first);
+static gint mh_move_msg (Folder *folder,
+ FolderItem *dest,
+ MsgInfo *msginfo);
+static gint mh_move_msgs (Folder *folder,
+ FolderItem *dest,
+ GSList *msglist);
+static gint mh_copy_msg (Folder *folder,
+ FolderItem *dest,
+ MsgInfo *msginfo);
+static gint mh_copy_msgs (Folder *folder,
+ FolderItem *dest,
+ GSList *msglist);
+static gint mh_remove_msg (Folder *folder,
+ FolderItem *item,
+ MsgInfo *msginfo);
+static gint mh_remove_all_msg (Folder *folder,
+ FolderItem *item);
+static gboolean mh_is_msg_changed (Folder *folder,
+ FolderItem *item,
+ MsgInfo *msginfo);
+static gint mh_close (Folder *folder,
+ FolderItem *item);
+
+static gint mh_scan_folder_full (Folder *folder,
+ FolderItem *item,
+ gboolean count_sum);
+static gint mh_scan_folder (Folder *folder,
+ FolderItem *item);
+static gint mh_scan_tree (Folder *folder);
+
+static gint mh_create_tree (Folder *folder);
+static FolderItem *mh_create_folder (Folder *folder,
+ FolderItem *parent,
+ const gchar *name);
+static gint mh_rename_folder (Folder *folder,
+ FolderItem *item,
+ const gchar *name);
+static gint mh_move_folder (Folder *folder,
+ FolderItem *item,
+ FolderItem *new_parent);
+static gint mh_remove_folder (Folder *folder,
+ FolderItem *item);
+
+static gchar *mh_get_new_msg_filename (FolderItem *dest);
+
+static gint mh_do_move_msgs (Folder *folder,
+ FolderItem *dest,
+ GSList *msglist);
+
+static time_t mh_get_mtime (FolderItem *item);
+static GSList *mh_get_uncached_msgs (GHashTable *msg_table,
+ FolderItem *item);
+static MsgInfo *mh_parse_msg (const gchar *file,
+ FolderItem *item);
+static void mh_remove_missing_folder_items (Folder *folder);
+static void mh_scan_tree_recursive (FolderItem *item);
+
+static gboolean mh_rename_folder_func (GNode *node,
+ gpointer data);
+
+static FolderClass mh_class =
+{
+ F_MH,
+
+ mh_folder_new,
+ mh_folder_destroy,
+
+ mh_scan_tree,
+ mh_create_tree,
+
+ mh_get_msg_list,
+ mh_fetch_msg,
+ mh_get_msginfo,
+ mh_add_msg,
+ mh_add_msgs,
+ mh_move_msg,
+ mh_move_msgs,
+ mh_copy_msg,
+ mh_copy_msgs,
+ mh_remove_msg,
+ NULL,
+ mh_remove_all_msg,
+ mh_is_msg_changed,
+ mh_close,
+ mh_scan_folder,
+
+ mh_create_folder,
+ mh_rename_folder,
+ mh_move_folder,
+ mh_remove_folder,
+};
+
+
+FolderClass *mh_get_class(void)
+{
+ return &mh_class;
+}
+
+static Folder *mh_folder_new(const gchar *name, const gchar *path)
+{
+ Folder *folder;
+
+ folder = (Folder *)g_new0(MHFolder, 1);
+ mh_folder_init(folder, name, path);
+
+ return folder;
+}
+
+static void mh_folder_destroy(Folder *folder)
+{
+ folder_local_folder_destroy(LOCAL_FOLDER(folder));
+}
+
+static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path)
+{
+ folder->klass = mh_get_class();
+ folder_local_folder_init(folder, name, path);
+}
+
+static GSList *mh_get_msg_list(Folder *folder, FolderItem *item,
+ gboolean use_cache)
+{
+ GSList *mlist;
+ GHashTable *msg_table;
+ time_t cur_mtime;
+#ifdef MEASURE_TIME
+ GTimer *timer;
+#endif
+
+ g_return_val_if_fail(item != NULL, NULL);
+
+#ifdef MEASURE_TIME
+ timer = g_timer_new();
+#endif
+
+ cur_mtime = mh_get_mtime(item);
+
+ if (use_cache && item->mtime == cur_mtime) {
+ debug_print("Folder is not modified.\n");
+ mlist = procmsg_read_cache(item, FALSE);
+ if (!mlist) {
+ mlist = mh_get_uncached_msgs(NULL, item);
+ if (mlist)
+ item->cache_dirty = TRUE;
+ }
+ } else if (use_cache) {
+ GSList *newlist, *cur, *next;
+ gboolean strict_cache_check = prefs_common.strict_cache_check;
+
+ if (item->stype == F_QUEUE || item->stype == F_DRAFT)
+ strict_cache_check = TRUE;
+
+ mlist = procmsg_read_cache(item, strict_cache_check);
+ msg_table = procmsg_msg_hash_table_create(mlist);
+ newlist = mh_get_uncached_msgs(msg_table, item);
+ if (newlist)
+ item->cache_dirty = TRUE;
+ if (msg_table)
+ g_hash_table_destroy(msg_table);
+
+ if (!strict_cache_check) {
+ /* remove nonexistent messages */
+ for (cur = mlist; cur != NULL; cur = next) {
+ MsgInfo *msginfo = (MsgInfo *)cur->data;
+ next = cur->next;
+ if (!MSG_IS_CACHED(msginfo->flags)) {
+ debug_print("removing nonexistent message %d from cache\n", msginfo->msgnum);
+ mlist = g_slist_remove(mlist, msginfo);
+ procmsg_msginfo_free(msginfo);
+ item->cache_dirty = TRUE;
+ item->mark_dirty = TRUE;
+ }
+ }
+ }
+
+ mlist = g_slist_concat(mlist, newlist);
+ } else {
+ mlist = mh_get_uncached_msgs(NULL, item);
+ item->cache_dirty = TRUE;
+ }
+
+ item->mtime = cur_mtime;
+
+ procmsg_set_flags(mlist, item);
+
+ mlist = procmsg_sort_msg_list(mlist, item->sort_key, item->sort_type);
+
+#ifdef MEASURE_TIME
+ g_timer_stop(timer);
+ g_print("%s: %s: elapsed time: %f sec\n",
+ G_STRFUNC, item->path, g_timer_elapsed(timer, NULL));
+ g_timer_destroy(timer);
+#endif
+ debug_print("cache_dirty: %d, mark_dirty: %d\n",
+ item->cache_dirty, item->mark_dirty);
+
+ return mlist;
+}
+
+static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num)
+{
+ gchar *path;
+ gchar *file;
+
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(num > 0, NULL);
+
+ if (item->last_num < 0 || num > item->last_num) {
+ mh_scan_folder(folder, item);
+ if (item->last_num < 0) return NULL;
+ }
+
+ g_return_val_if_fail(num <= item->last_num, NULL);
+
+ path = folder_item_get_path(item);
+ file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
+ g_free(path);
+ if (!is_file_exist(file)) {
+ g_free(file);
+ return NULL;
+ }
+
+ return file;
+}
+
+static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num)
+{
+ MsgInfo *msginfo;
+ gchar *file;
+
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(num > 0, NULL);
+
+ file = mh_fetch_msg(folder, item, num);
+ if (!file) return NULL;
+
+ msginfo = mh_parse_msg(file, item);
+ if (msginfo)
+ msginfo->msgnum = num;
+
+ g_free(file);
+
+ return msginfo;
+}
+
+static gchar *mh_get_new_msg_filename(FolderItem *dest)
+{
+ gchar *destfile;
+ gchar *destpath;
+
+ destpath = folder_item_get_path(dest);
+ g_return_val_if_fail(destpath != NULL, NULL);
+
+ if (!is_dir_exist(destpath))
+ make_dir_hier(destpath);
+
+ for (;;) {
+ destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
+ dest->last_num + 1);
+ if (is_file_entry_exist(destfile)) {
+ dest->last_num++;
+ g_free(destfile);
+ } else
+ break;
+ }
+
+ g_free(destpath);
+
+ return destfile;
+}
+
+#define SET_DEST_MSG_FLAGS(fp, dest, n, fl) \
+{ \
+ MsgInfo newmsginfo; \
+ \
+ newmsginfo.msgnum = n; \
+ newmsginfo.flags = fl; \
+ if (dest->stype == F_OUTBOX || \
+ dest->stype == F_QUEUE || \
+ dest->stype == F_DRAFT || \
+ dest->stype == F_TRASH) \
+ MSG_UNSET_PERM_FLAGS(newmsginfo.flags, \
+ MSG_NEW|MSG_UNREAD|MSG_DELETED); \
+ \
+ if (fp) \
+ procmsg_write_flags(&newmsginfo, fp); \
+ else if (dest->opened) \
+ procmsg_add_flags(dest, n, newmsginfo.flags); \
+}
+
+static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
+ MsgFlags *flags, gboolean remove_source)
+{
+ GSList file_list;
+ MsgFileInfo fileinfo;
+
+ g_return_val_if_fail(file != NULL, -1);
+
+ fileinfo.file = (gchar *)file;
+ fileinfo.flags = flags;
+ file_list.data = &fileinfo;
+ file_list.next = NULL;
+
+ return mh_add_msgs(folder, dest, &file_list, remove_source, NULL);
+}
+
+static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
+ gboolean remove_source, gint *first)
+{
+ gchar *destfile;
+ GSList *cur;
+ MsgFileInfo *fileinfo;
+ gint first_ = 0;
+ FILE *fp;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(file_list != NULL, -1);
+
+ if (dest->last_num < 0) {
+ mh_scan_folder(folder, dest);
+ if (dest->last_num < 0) return -1;
+ }
+
+ if ((((MsgFileInfo *)file_list->data)->flags == NULL &&
+ file_list->next == NULL) || dest->opened)
+ fp = NULL;
+ else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL)
+ g_warning("Can't open mark file.\n");
+
+ for (cur = file_list; cur != NULL; cur = cur->next) {
+ fileinfo = (MsgFileInfo *)cur->data;
+
+ destfile = mh_get_new_msg_filename(dest);
+ if (destfile == NULL) return -1;
+ if (first_ == 0 || first_ > dest->last_num + 1)
+ first_ = dest->last_num + 1;
+
+#ifdef G_OS_UNIX
+ if (link(fileinfo->file, destfile) < 0) {
+#endif
+ if (copy_file(fileinfo->file, destfile, TRUE) < 0) {
+ g_warning(_("can't copy message %s to %s\n"),
+ fileinfo->file, destfile);
+ g_free(destfile);
+ return -1;
+ }
+#ifdef G_OS_UNIX
+ }
+#endif
+
+ g_free(destfile);
+ dest->last_num++;
+ dest->total++;
+ dest->updated = TRUE;
+
+ if (fileinfo->flags) {
+ if (MSG_IS_RECEIVED(*fileinfo->flags)) {
+ if (dest->unmarked_num == 0)
+ dest->new = 0;
+ dest->unmarked_num++;
+ procmsg_add_mark_queue(dest, dest->last_num,
+ *fileinfo->flags);
+ } else {
+ SET_DEST_MSG_FLAGS(fp, dest, dest->last_num,
+ *fileinfo->flags);
+ }
+ if (MSG_IS_NEW(*fileinfo->flags))
+ dest->new++;
+ if (MSG_IS_UNREAD(*fileinfo->flags))
+ dest->unread++;
+ } else {
+ if (dest->unmarked_num == 0)
+ dest->new = 0;
+ dest->unmarked_num++;
+ dest->new++;
+ dest->unread++;
+ }
+ }
+
+ if (fp) fclose(fp);
+
+ if (first)
+ *first = first_;
+
+ if (remove_source) {
+ for (cur = file_list; cur != NULL; cur = cur->next) {
+ fileinfo = (MsgFileInfo *)cur->data;
+ if (g_unlink(fileinfo->file) < 0)
+ FILE_OP_ERROR(fileinfo->file, "unlink");
+ }
+ }
+
+ return dest->last_num;
+}
+
+static gint mh_do_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
+{
+ FolderItem *src;
+ gchar *srcfile;
+ gchar *destfile;
+ FILE *fp;
+ GSList *cur;
+ MsgInfo *msginfo;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msglist != NULL, -1);
+
+ if (dest->last_num < 0) {
+ mh_scan_folder(folder, dest);
+ if (dest->last_num < 0) return -1;
+ }
+
+ if (dest->opened)
+ fp = NULL;
+ else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL)
+ g_warning(_("Can't open mark file.\n"));
+
+ for (cur = msglist; cur != NULL; cur = cur->next) {
+ msginfo = (MsgInfo *)cur->data;
+ src = msginfo->folder;
+
+ if (src == dest) {
+ g_warning(_("the src folder is identical to the dest.\n"));
+ continue;
+ }
+ debug_print("Moving message %s%c%d to %s ...\n",
+ src->path, G_DIR_SEPARATOR, msginfo->msgnum,
+ dest->path);
+
+ destfile = mh_get_new_msg_filename(dest);
+ if (!destfile) break;
+ srcfile = procmsg_get_message_file(msginfo);
+
+ if (move_file(srcfile, destfile, FALSE) < 0) {
+ g_free(srcfile);
+ g_free(destfile);
+ break;
+ }
+
+ g_free(srcfile);
+ g_free(destfile);
+ src->total--;
+ src->updated = TRUE;
+ dest->last_num++;
+ dest->total++;
+ dest->updated = TRUE;
+
+ if (fp) {
+ SET_DEST_MSG_FLAGS(fp, dest, dest->last_num,
+ msginfo->flags);
+ }
+
+ if (MSG_IS_NEW(msginfo->flags)) {
+ src->new--;
+ dest->new++;
+ }
+ if (MSG_IS_UNREAD(msginfo->flags)) {
+ src->unread--;
+ dest->unread++;
+ }
+
+ MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
+ }
+
+ if (fp) fclose(fp);
+
+ return dest->last_num;
+}
+
+static gint mh_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
+{
+ GSList msglist;
+
+ g_return_val_if_fail(msginfo != NULL, -1);
+
+ msglist.data = msginfo;
+ msglist.next = NULL;
+
+ return mh_move_msgs(folder, dest, &msglist);
+}
+
+static gint mh_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
+{
+ MsgInfo *msginfo;
+ GSList *file_list;
+ gint ret = 0;
+ gint first;
+
+ msginfo = (MsgInfo *)msglist->data;
+ if (folder == msginfo->folder->folder)
+ return mh_do_move_msgs(folder, dest, msglist);
+
+ file_list = procmsg_get_message_file_list(msglist);
+ g_return_val_if_fail(file_list != NULL, -1);
+
+ ret = mh_add_msgs(folder, dest, file_list, FALSE, &first);
+
+ procmsg_message_file_list_free(file_list);
+
+ if (ret != -1)
+ ret = folder_item_remove_msgs(msginfo->folder, msglist);
+
+ return ret;
+}
+
+static gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
+{
+ GSList msglist;
+
+ g_return_val_if_fail(msginfo != NULL, -1);
+
+ msglist.data = msginfo;
+ msglist.next = NULL;
+
+ return mh_copy_msgs(folder, dest, &msglist);
+}
+
+static gint mh_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
+{
+ gchar *srcfile;
+ gchar *destfile;
+ FILE *fp;
+ GSList *cur;
+ MsgInfo *msginfo;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(msglist != NULL, -1);
+
+ if (dest->last_num < 0) {
+ mh_scan_folder(folder, dest);
+ if (dest->last_num < 0) return -1;
+ }
+
+ if (dest->opened)
+ fp = NULL;
+ else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL)
+ g_warning(_("Can't open mark file.\n"));
+
+ for (cur = msglist; cur != NULL; cur = cur->next) {
+ msginfo = (MsgInfo *)cur->data;
+
+ if (msginfo->folder == dest) {
+ g_warning(_("the src folder is identical to the dest.\n"));
+ continue;
+ }
+ debug_print(_("Copying message %s%c%d to %s ...\n"),
+ msginfo->folder->path, G_DIR_SEPARATOR,
+ msginfo->msgnum, dest->path);
+
+ destfile = mh_get_new_msg_filename(dest);
+ if (!destfile) break;
+ srcfile = procmsg_get_message_file(msginfo);
+
+ if (copy_file(srcfile, destfile, TRUE) < 0) {
+ FILE_OP_ERROR(srcfile, "copy");
+ g_free(srcfile);
+ g_free(destfile);
+ break;
+ }
+
+ g_free(srcfile);
+ g_free(destfile);
+ dest->last_num++;
+ dest->total++;
+ dest->updated = TRUE;
+
+ if (fp) {
+ SET_DEST_MSG_FLAGS(fp, dest, dest->last_num,
+ msginfo->flags);
+ }
+
+ if (MSG_IS_NEW(msginfo->flags))
+ dest->new++;
+ if (MSG_IS_UNREAD(msginfo->flags))
+ dest->unread++;
+ }
+
+ if (fp) fclose(fp);
+
+ return dest->last_num;
+}
+
+static gint mh_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo)
+{
+ gchar *file;
+
+ g_return_val_if_fail(item != NULL, -1);
+
+ file = mh_fetch_msg(folder, item, msginfo->msgnum);
+ g_return_val_if_fail(file != NULL, -1);
+
+ if (g_unlink(file) < 0) {
+ FILE_OP_ERROR(file, "unlink");
+ g_free(file);
+ return -1;
+ }
+ g_free(file);
+
+ item->total--;
+ item->updated = TRUE;
+ if (MSG_IS_NEW(msginfo->flags))
+ item->new--;
+ if (MSG_IS_UNREAD(msginfo->flags))
+ item->unread--;
+ MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
+
+ if (msginfo->msgnum == item->last_num)
+ mh_scan_folder_full(folder, item, FALSE);
+
+ return 0;
+}
+
+static gint mh_remove_all_msg(Folder *folder, FolderItem *item)
+{
+ gchar *path;
+ gint val;
+
+ g_return_val_if_fail(item != NULL, -1);
+
+ path = folder_item_get_path(item);
+ g_return_val_if_fail(path != NULL, -1);
+ val = remove_all_numbered_files(path);
+ g_free(path);
+ if (val == 0) {
+ item->new = item->unread = item->total = 0;
+ item->last_num = 0;
+ item->updated = TRUE;
+ }
+
+ return val;
+}
+
+static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item,
+ MsgInfo *msginfo)
+{
+ struct stat s;
+
+ if (g_stat(itos(msginfo->msgnum), &s) < 0 ||
+ msginfo->size != s.st_size ||
+ msginfo->mtime != s.st_mtime)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gint mh_close(Folder *folder, FolderItem *item)
+{
+ return 0;
+}
+
+static gint mh_scan_folder_full(Folder *folder, FolderItem *item,
+ gboolean count_sum)
+{
+ gchar *path;
+ DIR *dp;
+ struct dirent *d;
+ gint max = 0;
+ gint num;
+ gint n_msg = 0;
+
+ g_return_val_if_fail(item != NULL, -1);
+
+ debug_print("mh_scan_folder(): Scanning %s ...\n", item->path);
+
+ path = folder_item_get_path(item);
+ g_return_val_if_fail(path != NULL, -1);
+ if (change_dir(path) < 0) {
+ g_free(path);
+ return -1;
+ }
+ g_free(path);
+
+ if ((dp = opendir(".")) == NULL) {
+ FILE_OP_ERROR(item->path, "opendir");
+ return -1;
+ }
+
+ if (folder->ui_func)
+ folder->ui_func(folder, item, folder->ui_func_data);
+
+ while ((d = readdir(dp)) != NULL) {
+ if ((num = to_number(d->d_name)) > 0 &&
+ dirent_is_regular_file(d)) {
+ n_msg++;
+ if (max < num)
+ max = num;
+ }
+ }
+
+ closedir(dp);
+
+ if (n_msg == 0)
+ item->new = item->unread = item->total = 0;
+ else if (count_sum) {
+ gint new, unread, total, min, max_;
+
+ procmsg_get_mark_sum
+ (item, &new, &unread, &total, &min, &max_, 0);
+
+ if (n_msg > total) {
+ item->unmarked_num = new = n_msg - total;
+ unread += n_msg - total;
+ } else
+ item->unmarked_num = 0;
+
+ item->new = new;
+ item->unread = unread;
+ item->total = n_msg;
+ }
+
+ item->updated = TRUE;
+
+ debug_print(_("Last number in dir %s = %d\n"), item->path, max);
+ item->last_num = max;
+
+ return 0;
+}
+
+static gint mh_scan_folder(Folder *folder, FolderItem *item)
+{
+ return mh_scan_folder_full(folder, item, TRUE);
+}
+
+static gint mh_scan_tree(Folder *folder)
+{
+ FolderItem *item;
+ gchar *rootpath;
+
+ g_return_val_if_fail(folder != NULL, -1);
+
+ if (!folder->node) {
+ item = folder_item_new(folder->name, NULL);
+ item->folder = folder;
+ folder->node = item->node = g_node_new(item);
+ } else
+ item = FOLDER_ITEM(folder->node->data);
+
+ rootpath = folder_item_get_path(item);
+ if (change_dir(rootpath) < 0) {
+ g_free(rootpath);
+ return -1;
+ }
+ g_free(rootpath);
+
+ mh_create_tree(folder);
+ mh_remove_missing_folder_items(folder);
+ mh_scan_tree_recursive(item);
+
+ return 0;
+}
+
+#define MAKE_DIR_IF_NOT_EXIST(dir) \
+{ \
+ if (!is_dir_exist(dir)) { \
+ if (is_file_exist(dir)) { \
+ g_warning(_("File `%s' already exists.\n" \
+ "Can't create folder."), dir); \
+ return -1; \
+ } \
+ if (make_dir(dir) < 0) \
+ return -1; \
+ } \
+}
+
+static gint mh_create_tree(Folder *folder)
+{
+ gchar *rootpath;
+
+ g_return_val_if_fail(folder != NULL, -1);
+
+ CHDIR_RETURN_VAL_IF_FAIL(get_mail_base_dir(), -1);
+ rootpath = LOCAL_FOLDER(folder)->rootpath;
+ MAKE_DIR_IF_NOT_EXIST(rootpath);
+ CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1);
+ MAKE_DIR_IF_NOT_EXIST(INBOX_DIR);
+ MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR);
+ MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR);
+ MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR);
+ MAKE_DIR_IF_NOT_EXIST(TRASH_DIR);
+
+ return 0;
+}
+
+#undef MAKE_DIR_IF_NOT_EXIST
+
+static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent,
+ const gchar *name)
+{
+ gchar *path;
+ gchar *fs_name;
+ gchar *fullpath;
+ FolderItem *new_item;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(parent != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ path = folder_item_get_path(parent);
+ fs_name = g_filename_from_utf8(name, -1, NULL, NULL, NULL);
+ fullpath = g_strconcat(path, G_DIR_SEPARATOR_S,
+ fs_name ? fs_name : name, NULL);
+ g_free(fs_name);
+ g_free(path);
+
+ if (make_dir(fullpath) < 0) {
+ g_free(fullpath);
+ return NULL;
+ }
+
+ g_free(fullpath);
+
+ if (parent->path)
+ path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name,
+ NULL);
+ else
+ path = g_strdup(name);
+ new_item = folder_item_new(name, path);
+ folder_item_append(parent, new_item);
+ g_free(path);
+
+ return new_item;
+}
+
+static gint mh_move_folder_real(Folder *folder, FolderItem *item,
+ FolderItem *new_parent, const gchar *name)
+{
+ gchar *oldpath;
+ gchar *newpath;
+ gchar *dirname;
+ gchar *new_dir;
+ gchar *name_;
+ gchar *utf8_name;
+ gchar *paths[2];
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(item != NULL, -1);
+ g_return_val_if_fail(folder == item->folder, -1);
+ g_return_val_if_fail(item->path != NULL, -1);
+ g_return_val_if_fail(new_parent != NULL || name != NULL, -1);
+ if (new_parent) {
+ g_return_val_if_fail(item != new_parent, -1);
+ g_return_val_if_fail(item->parent != new_parent, -1);
+ g_return_val_if_fail(item->folder == new_parent->folder, -1);
+ if (g_node_is_ancestor(item->node, new_parent->node)) {
+ g_warning("folder to be moved is ancestor of new parent\n");
+ return -1;
+ }
+ }
+
+ oldpath = folder_item_get_path(item);
+ if (new_parent) {
+ if (name) {
+ name_ = g_filename_from_utf8(name, -1, NULL, NULL,
+ NULL);
+ if (!name_)
+ name_ = g_strdup(name);
+ utf8_name = g_strdup(name);
+ } else {
+ name_ = g_path_get_basename(oldpath);
+ utf8_name = g_filename_to_utf8(name_, -1, NULL, NULL,
+ NULL);
+ if (!utf8_name)
+ utf8_name = g_strdup(name_);
+ }
+ new_dir = folder_item_get_path(new_parent);
+ newpath = g_strconcat(new_dir, G_DIR_SEPARATOR_S, name_, NULL);
+ g_free(new_dir);
+ } else {
+ name_ = g_filename_from_utf8(name, -1, NULL, NULL, NULL);
+ utf8_name = g_strdup(name);
+ dirname = g_dirname(oldpath);
+ newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S,
+ name_ ? name_ : name, NULL);
+ g_free(dirname);
+ }
+ g_free(name_);
+
+ if (is_file_entry_exist(newpath)) {
+ g_warning("%s already exists\n", newpath);
+ g_free(oldpath);
+ g_free(newpath);
+ g_free(utf8_name);
+ return -1;
+ }
+
+ debug_print("mh_move_folder: rename(%s, %s)\n", oldpath, newpath);
+
+ if (g_rename(oldpath, newpath) < 0) {
+ FILE_OP_ERROR(oldpath, "rename");
+ g_free(oldpath);
+ g_free(newpath);
+ g_free(utf8_name);
+ return -1;
+ }
+
+ g_free(oldpath);
+ g_free(newpath);
+
+ if (new_parent) {
+ g_node_unlink(item->node);
+ g_node_append(new_parent->node, item->node);
+ item->parent = new_parent;
+ if (new_parent->path != NULL) {
+ newpath = g_strconcat(new_parent->path,
+ G_DIR_SEPARATOR_S, utf8_name,
+ NULL);
+ g_free(utf8_name);
+ } else
+ newpath = utf8_name;
+ } else {
+ if (strchr(item->path, G_DIR_SEPARATOR) != NULL) {
+ dirname = g_dirname(item->path);
+ newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S,
+ utf8_name, NULL);
+ g_free(dirname);
+ g_free(utf8_name);
+ } else
+ newpath = utf8_name;
+ }
+
+ if (name) {
+ g_free(item->name);
+ item->name = g_strdup(name);
+ }
+
+ paths[0] = g_strdup(item->path);
+ paths[1] = newpath;
+ g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+ mh_rename_folder_func, paths);
+
+ g_free(paths[0]);
+ g_free(paths[1]);
+
+ return 0;
+}
+
+static gint mh_move_folder(Folder *folder, FolderItem *item,
+ FolderItem *new_parent)
+{
+ return mh_move_folder_real(folder, item, new_parent, NULL);
+}
+
+static gint mh_rename_folder(Folder *folder, FolderItem *item,
+ const gchar *name)
+{
+ return mh_move_folder_real(folder, item, NULL, name);
+}
+
+static gint mh_remove_folder(Folder *folder, FolderItem *item)
+{
+ gchar *path;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(item != NULL, -1);
+ g_return_val_if_fail(item->path != NULL, -1);
+
+ path = folder_item_get_path(item);
+ if (remove_dir_recursive(path) < 0) {
+ g_warning("can't remove directory `%s'\n", path);
+ g_free(path);
+ return -1;
+ }
+
+ g_free(path);
+ folder_item_remove(item);
+ return 0;
+}
+
+
+static time_t mh_get_mtime(FolderItem *item)
+{
+ gchar *path;
+ struct stat s;
+
+ path = folder_item_get_path(item);
+ if (g_stat(path, &s) < 0) {
+ FILE_OP_ERROR(path, "stat");
+ return -1;
+ } else {
+ return MAX(s.st_mtime, s.st_ctime);
+ }
+}
+
+static GSList *mh_get_uncached_msgs(GHashTable *msg_table, FolderItem *item)
+{
+ gchar *path;
+ DIR *dp;
+ struct dirent *d;
+ GSList *newlist = NULL;
+ GSList *last = NULL;
+ MsgInfo *msginfo;
+ gint n_newmsg = 0;
+ gint num;
+
+ g_return_val_if_fail(item != NULL, NULL);
+
+ path = folder_item_get_path(item);
+ g_return_val_if_fail(path != NULL, NULL);
+ if (change_dir(path) < 0) {
+ g_free(path);
+ return NULL;
+ }
+ g_free(path);
+
+ if ((dp = opendir(".")) == NULL) {
+ FILE_OP_ERROR(item->path, "opendir");
+ return NULL;
+ }
+
+ debug_print("Searching uncached messages...\n");
+
+ if (msg_table) {
+ while ((d = readdir(dp)) != NULL) {
+ if ((num = to_number(d->d_name)) <= 0) continue;
+
+ msginfo = g_hash_table_lookup
+ (msg_table, GUINT_TO_POINTER(num));
+
+ if (msginfo) {
+ MSG_SET_TMP_FLAGS(msginfo->flags, MSG_CACHED);
+ } else {
+ /* not found in the cache (uncached message) */
+ msginfo = mh_parse_msg(d->d_name, item);
+ if (!msginfo) continue;
+
+ if (!newlist)
+ last = newlist =
+ g_slist_append(NULL, msginfo);
+ else {
+ last = g_slist_append(last, msginfo);
+ last = last->next;
+ }
+ n_newmsg++;
+ }
+ }
+ } else {
+ /* discard all previous cache */
+ while ((d = readdir(dp)) != NULL) {
+ if (to_number(d->d_name) <= 0) continue;
+
+ msginfo = mh_parse_msg(d->d_name, item);
+ if (!msginfo) continue;
+
+ if (!newlist)
+ last = newlist = g_slist_append(NULL, msginfo);
+ else {
+ last = g_slist_append(last, msginfo);
+ last = last->next;
+ }
+ n_newmsg++;
+ }
+ }
+
+ closedir(dp);
+
+ if (n_newmsg)
+ debug_print("%d uncached message(s) found.\n", n_newmsg);
+ else
+ debug_print("done.\n");
+
+ /* sort new messages in numerical order */
+ if (newlist && item->sort_key == SORT_BY_NONE) {
+ debug_print("Sorting uncached messages in numerical order...\n");
+ newlist = g_slist_sort
+ (newlist, (GCompareFunc)procmsg_cmp_msgnum_for_sort);
+ debug_print("done.\n");
+ }
+
+ return newlist;
+}
+
+static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item)
+{
+ MsgInfo *msginfo;
+ MsgFlags flags;
+
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(file != NULL, NULL);
+
+ flags.perm_flags = MSG_NEW|MSG_UNREAD;
+ flags.tmp_flags = 0;
+
+ if (item->stype == F_QUEUE) {
+ MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
+ } else if (item->stype == F_DRAFT) {
+ MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
+ }
+
+ msginfo = procheader_parse_file(file, flags, FALSE);
+ if (!msginfo) return NULL;
+
+ msginfo->msgnum = atoi(file);
+ msginfo->folder = item;
+
+ return msginfo;
+}
+
+#if 0
+static gboolean mh_is_maildir_one(const gchar *path, const gchar *dir)
+{
+ gchar *entry;
+ gboolean result;
+
+ entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir, NULL);
+ result = is_dir_exist(entry);
+ g_free(entry);
+
+ return result;
+}
+
+/*
+ * check whether PATH is a Maildir style mailbox.
+ * This is the case if the 3 subdir: new, cur, tmp are existing.
+ * This functon assumes that entry is an directory
+ */
+static gboolean mh_is_maildir(const gchar *path)
+{
+ return mh_is_maildir_one(path, "new") &&
+ mh_is_maildir_one(path, "cur") &&
+ mh_is_maildir_one(path, "tmp");
+}
+#endif
+
+static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data)
+{
+ FolderItem *item;
+ gchar *path;
+
+ g_return_val_if_fail(node->data != NULL, FALSE);
+
+ if (G_NODE_IS_ROOT(node))
+ return FALSE;
+
+ item = FOLDER_ITEM(node->data);
+
+ path = folder_item_get_path(item);
+ if (!is_dir_exist(path)) {
+ debug_print("folder '%s' not found. removing...\n", path);
+ folder_item_remove(item);
+ }
+ g_free(path);
+
+ return FALSE;
+}
+
+static void mh_remove_missing_folder_items(Folder *folder)
+{
+ g_return_if_fail(folder != NULL);
+
+ debug_print("searching missing folders...\n");
+
+ g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+ mh_remove_missing_folder_items_func, folder);
+}
+
+static void mh_scan_tree_recursive(FolderItem *item)
+{
+ Folder *folder;
+#ifdef G_OS_WIN32
+ GDir *dir;
+#else
+ DIR *dp;
+ struct dirent *d;
+#endif
+ const gchar *dir_name;
+ struct stat s;
+ gchar *fs_path;
+ gchar *entry;
+ gchar *utf8entry;
+ gchar *utf8name;
+ gint n_msg = 0;
+
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+
+ folder = item->folder;
+
+ fs_path = item->path ?
+ g_filename_from_utf8(item->path, -1, NULL, NULL, NULL)
+ : g_strdup(".");
+ if (!fs_path)
+ fs_path = g_strdup(item->path);
+#ifdef G_OS_WIN32
+ dir = g_dir_open(fs_path, 0, NULL);
+ if (!dir) {
+ g_warning("failed to open directory: %s\n", fs_path);
+ g_free(fs_path);
+ return;
+ }
+#else
+ dp = opendir(fs_path);
+ if (!dp) {
+ FILE_OP_ERROR(fs_path, "opendir");
+ g_free(fs_path);
+ return;
+ }
+#endif
+ g_free(fs_path);
+
+ debug_print("scanning %s ...\n",
+ item->path ? item->path
+ : LOCAL_FOLDER(item->folder)->rootpath);
+ if (folder->ui_func)
+ folder->ui_func(folder, item, folder->ui_func_data);
+
+#ifdef G_OS_WIN32
+ while ((dir_name = g_dir_read_name(dir)) != NULL) {
+#else
+ while ((d = readdir(dp)) != NULL) {
+ dir_name = d->d_name;
+#endif
+ if (dir_name[0] == '.') continue;
+
+ utf8name = g_filename_to_utf8(dir_name, -1, NULL, NULL, NULL);
+ if (!utf8name)
+ utf8name = g_strdup(dir_name);
+
+ if (item->path)
+ utf8entry = g_strconcat(item->path, G_DIR_SEPARATOR_S,
+ utf8name, NULL);
+ else
+ utf8entry = g_strdup(utf8name);
+ entry = g_filename_from_utf8(utf8entry, -1, NULL, NULL, NULL);
+ if (!entry)
+ entry = g_strdup(utf8entry);
+
+ if (
+#if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
+ d->d_type == DT_DIR ||
+ (d->d_type == DT_UNKNOWN &&
+#endif
+ g_stat(entry, &s) == 0 && S_ISDIR(s.st_mode)
+#if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
+ )
+#endif
+ ) {
+ FolderItem *new_item = NULL;
+ GNode *node;
+
+#if 0
+ if (mh_is_maildir(entry)) {
+ g_free(entry);
+ g_free(utf8entry);
+ g_free(utf8name);
+ continue;
+ }
+#endif
+
+#ifndef G_OS_WIN32
+ if (g_utf8_validate(utf8name, -1, NULL) == FALSE) {
+ g_warning(_("Directory name\n"
+ "'%s' is not a valid UTF-8 string.\n"
+ "Maybe the locale encoding is used for filename.\n"
+ "If that is the case, you must set the following environmental variable\n"
+ "(see README for detail):\n"
+ "\n"
+ "\tG_FILENAME_ENCODING=@locale\n"),
+ utf8name);
+ g_free(entry);
+ g_free(utf8entry);
+ g_free(utf8name);
+ continue;
+ }
+#endif /* G_OS_WIN32 */
+
+ node = item->node;
+ for (node = node->children; node != NULL; node = node->next) {
+ FolderItem *cur_item = FOLDER_ITEM(node->data);
+ if (!strcmp2(cur_item->path, utf8entry)) {
+ new_item = cur_item;
+ break;
+ }
+ }
+ if (!new_item) {
+ debug_print("new folder '%s' found.\n", entry);
+ new_item = folder_item_new(utf8name, utf8entry);
+ folder_item_append(item, new_item);
+ }
+
+ if (!item->path) {
+ if (!folder->inbox &&
+ !strcmp(dir_name, INBOX_DIR)) {
+ new_item->stype = F_INBOX;
+ folder->inbox = new_item;
+ } else if (!folder->outbox &&
+ !strcmp(dir_name, OUTBOX_DIR)) {
+ new_item->stype = F_OUTBOX;
+ folder->outbox = new_item;
+ } else if (!folder->draft &&
+ !strcmp(dir_name, DRAFT_DIR)) {
+ new_item->stype = F_DRAFT;
+ folder->draft = new_item;
+ } else if (!folder->queue &&
+ !strcmp(dir_name, QUEUE_DIR)) {
+ new_item->stype = F_QUEUE;
+ folder->queue = new_item;
+ } else if (!folder->trash &&
+ !strcmp(dir_name, TRASH_DIR)) {
+ new_item->stype = F_TRASH;
+ folder->trash = new_item;
+ }
+ }
+
+ mh_scan_tree_recursive(new_item);
+ } else if (to_number(dir_name) > 0) n_msg++;
+
+ g_free(entry);
+ g_free(utf8entry);
+ g_free(utf8name);
+ }
+
+#ifdef G_OS_WIN32
+ g_dir_close(dir);
+#else
+ closedir(dp);
+#endif
+
+ if (item->path) {
+ gint new, unread, total, min, max;
+
+ procmsg_get_mark_sum
+ (item, &new, &unread, &total, &min, &max, 0);
+ if (n_msg > total) {
+ new += n_msg - total;
+ unread += n_msg - total;
+ }
+ item->new = new;
+ item->unread = unread;
+ item->total = n_msg;
+ item->updated = TRUE;
+ }
+}
+
+static gboolean mh_rename_folder_func(GNode *node, gpointer data)
+{
+ FolderItem *item = node->data;
+ gchar **paths = data;
+ const gchar *oldpath = paths[0];
+ const gchar *newpath = paths[1];
+ gchar *base;
+ gchar *new_itempath;
+ gint oldpathlen;
+
+ oldpathlen = strlen(oldpath);
+ if (strncmp(oldpath, item->path, oldpathlen) != 0) {
+ g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
+ return TRUE;
+ }
+
+ base = item->path + oldpathlen;
+ while (*base == G_DIR_SEPARATOR) base++;
+ if (*base == '\0')
+ new_itempath = g_strdup(newpath);
+ else
+ new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
+ NULL);
+ g_free(item->path);
+ item->path = new_itempath;
+
+ return FALSE;
+}
diff --git a/libsylph/mh.h b/libsylph/mh.h
new file mode 100644
index 00000000..160259c1
--- /dev/null
+++ b/libsylph/mh.h
@@ -0,0 +1,38 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MH_H__
+#define __MH_H__
+
+#include <glib.h>
+
+#include "folder.h"
+
+typedef struct _MHFolder MHFolder;
+
+#define MH_FOLDER(obj) ((MHFolder *)obj)
+
+struct _MHFolder
+{
+ LocalFolder lfolder;
+};
+
+FolderClass *mh_get_class (void);
+
+#endif /* __MH_H__ */
diff --git a/libsylph/news.c b/libsylph/news.c
new file mode 100644
index 00000000..28181b32
--- /dev/null
+++ b/libsylph/news.c
@@ -0,0 +1,1062 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "news.h"
+#include "nntp.h"
+#include "socket.h"
+#include "recv.h"
+#include "procmsg.h"
+#include "procheader.h"
+#include "folder.h"
+#include "session.h"
+#include "codeconv.h"
+#include "utils.h"
+#include "prefs_common.h"
+#include "prefs_account.h"
+#if USE_SSL
+# include "ssl.h"
+#endif
+
+#define NNTP_PORT 119
+#if USE_SSL
+#define NNTPS_PORT 563
+#endif
+
+static void news_folder_init (Folder *folder,
+ const gchar *name,
+ const gchar *path);
+
+static Folder *news_folder_new (const gchar *name,
+ const gchar *folder);
+static void news_folder_destroy (Folder *folder);
+
+static GSList *news_get_article_list (Folder *folder,
+ FolderItem *item,
+ gboolean use_cache);
+static gchar *news_fetch_msg (Folder *folder,
+ FolderItem *item,
+ gint num);
+static MsgInfo *news_get_msginfo (Folder *folder,
+ FolderItem *item,
+ gint num);
+
+static gint news_close (Folder *folder,
+ FolderItem *item);
+
+static gint news_scan_group (Folder *folder,
+ FolderItem *item);
+
+#if USE_SSL
+static Session *news_session_new (const gchar *server,
+ gushort port,
+ const gchar *userid,
+ const gchar *passwd,
+ SSLType ssl_type);
+#else
+static Session *news_session_new (const gchar *server,
+ gushort port,
+ const gchar *userid,
+ const gchar *passwd);
+#endif
+
+static gint news_get_article_cmd (NNTPSession *session,
+ const gchar *cmd,
+ gint num,
+ gchar *filename);
+static gint news_get_article (NNTPSession *session,
+ gint num,
+ gchar *filename);
+#if 0
+static gint news_get_header (NNTPSession *session,
+ gint num,
+ gchar *filename);
+#endif
+
+static gint news_select_group (NNTPSession *session,
+ const gchar *group,
+ gint *num,
+ gint *first,
+ gint *last);
+static GSList *news_get_uncached_articles(NNTPSession *session,
+ FolderItem *item,
+ gint cache_last,
+ gint *rfirst,
+ gint *rlast);
+static MsgInfo *news_parse_xover (const gchar *xover_str);
+static gchar *news_parse_xhdr (const gchar *xhdr_str,
+ MsgInfo *msginfo);
+static GSList *news_delete_old_articles (GSList *alist,
+ FolderItem *item,
+ gint first);
+static void news_delete_all_articles (FolderItem *item);
+static void news_delete_expired_caches (GSList *alist,
+ FolderItem *item);
+
+static FolderClass news_class =
+{
+ F_NEWS,
+
+ news_folder_new,
+ news_folder_destroy,
+
+ NULL,
+ NULL,
+
+ news_get_article_list,
+ news_fetch_msg,
+ news_get_msginfo,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ news_close,
+ news_scan_group,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+
+FolderClass *news_get_class(void)
+{
+ return &news_class;
+}
+
+static Folder *news_folder_new(const gchar *name, const gchar *path)
+{
+ Folder *folder;
+
+ folder = (Folder *)g_new0(NewsFolder, 1);
+ news_folder_init(folder, name, path);
+
+ return folder;
+}
+
+static void news_folder_destroy(Folder *folder)
+{
+ gchar *dir;
+
+ dir = folder_get_path(folder);
+ if (is_dir_exist(dir))
+ remove_dir_recursive(dir);
+ g_free(dir);
+
+ folder_remote_folder_destroy(REMOTE_FOLDER(folder));
+}
+
+static void news_folder_init(Folder *folder, const gchar *name,
+ const gchar *path)
+{
+ folder->klass = news_get_class();
+ folder_remote_folder_init(folder, name, path);
+}
+
+#if USE_SSL
+static Session *news_session_new(const gchar *server, gushort port,
+ const gchar *userid, const gchar *passwd,
+ SSLType ssl_type)
+#else
+static Session *news_session_new(const gchar *server, gushort port,
+ const gchar *userid, const gchar *passwd)
+#endif
+{
+ gchar buf[NNTPBUFSIZE];
+ Session *session;
+
+ g_return_val_if_fail(server != NULL, NULL);
+
+ log_message(_("creating NNTP connection to %s:%d ...\n"), server, port);
+
+#if USE_SSL
+ session = nntp_session_new(server, port, buf, userid, passwd, ssl_type);
+#else
+ session = nntp_session_new(server, port, buf, userid, passwd);
+#endif
+
+ return session;
+}
+
+static Session *news_session_new_for_folder(Folder *folder)
+{
+ Session *session;
+ PrefsAccount *ac;
+ const gchar *userid = NULL;
+ gchar *passwd = NULL;
+ gushort port;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(folder->account != NULL, NULL);
+
+ ac = folder->account;
+ if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
+ userid = ac->userid;
+ if (ac->passwd && ac->passwd[0])
+ passwd = g_strdup(ac->passwd);
+ else
+ passwd = input_query_password(ac->nntp_server, userid);
+ }
+
+#if USE_SSL
+ port = ac->set_nntpport ? ac->nntpport
+ : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT;
+ session = news_session_new(ac->nntp_server, port, userid, passwd,
+ ac->ssl_nntp);
+#else
+ port = ac->set_nntpport ? ac->nntpport : NNTP_PORT;
+ session = news_session_new(ac->nntp_server, port, userid, passwd);
+#endif
+
+ g_free(passwd);
+
+ return session;
+}
+
+static NNTPSession *news_session_get(Folder *folder)
+{
+ RemoteFolder *rfolder = REMOTE_FOLDER(folder);
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
+ g_return_val_if_fail(folder->account != NULL, NULL);
+
+ if (!prefs_common.online_mode)
+ return NULL;
+
+ if (!rfolder->session) {
+ rfolder->session = news_session_new_for_folder(folder);
+ return NNTP_SESSION(rfolder->session);
+ }
+
+ if (time(NULL) - rfolder->session->last_access_time <
+ SESSION_TIMEOUT_INTERVAL) {
+ return NNTP_SESSION(rfolder->session);
+ }
+
+ if (nntp_mode(NNTP_SESSION(rfolder->session), FALSE)
+ != NN_SUCCESS) {
+ log_warning(_("NNTP connection to %s:%d has been"
+ " disconnected. Reconnecting...\n"),
+ folder->account->nntp_server,
+ folder->account->set_nntpport ?
+ folder->account->nntpport : NNTP_PORT);
+ session_destroy(rfolder->session);
+ rfolder->session = news_session_new_for_folder(folder);
+ }
+
+ if (rfolder->session)
+ session_set_access_time(rfolder->session);
+
+ return NNTP_SESSION(rfolder->session);
+}
+
+static GSList *news_get_article_list(Folder *folder, FolderItem *item,
+ gboolean use_cache)
+{
+ GSList *alist;
+ NNTPSession *session;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
+
+ session = news_session_get(folder);
+
+ if (!session) {
+ alist = procmsg_read_cache(item, FALSE);
+ item->last_num = procmsg_get_last_num_in_msg_list(alist);
+ } else if (use_cache) {
+ GSList *newlist;
+ gint cache_last;
+ gint first, last;
+
+ alist = procmsg_read_cache(item, FALSE);
+
+ cache_last = procmsg_get_last_num_in_msg_list(alist);
+ newlist = news_get_uncached_articles
+ (session, item, cache_last, &first, &last);
+ if (newlist)
+ item->cache_dirty = TRUE;
+ if (first == 0 && last == 0) {
+ news_delete_all_articles(item);
+ procmsg_msg_list_free(alist);
+ alist = NULL;
+ item->cache_dirty = TRUE;
+ } else {
+ alist = news_delete_old_articles(alist, item, first);
+ news_delete_expired_caches(alist, item);
+ }
+
+ alist = g_slist_concat(alist, newlist);
+
+ item->last_num = last;
+ } else {
+ gint last;
+
+ alist = news_get_uncached_articles
+ (session, item, 0, NULL, &last);
+ news_delete_all_articles(item);
+ item->last_num = last;
+ item->cache_dirty = TRUE;
+ }
+
+ procmsg_set_flags(alist, item);
+
+ alist = procmsg_sort_msg_list(alist, item->sort_key, item->sort_type);
+
+ debug_print("cache_dirty: %d, mark_dirty: %d\n",
+ item->cache_dirty, item->mark_dirty);
+
+ return alist;
+}
+
+static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
+{
+ gchar *path, *filename;
+ NNTPSession *session;
+ gint ok;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+
+ path = folder_item_get_path(item);
+ if (!is_dir_exist(path))
+ make_dir_hier(path);
+ filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
+ g_free(path);
+
+ if (is_file_exist(filename)) {
+ debug_print(_("article %d has been already cached.\n"), num);
+ return filename;
+ }
+
+ session = news_session_get(folder);
+ if (!session) {
+ g_free(filename);
+ return NULL;
+ }
+
+ ok = news_select_group(session, item->path, NULL, NULL, NULL);
+ if (ok != NN_SUCCESS) {
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(folder)->session = NULL;
+ }
+ g_free(filename);
+ return NULL;
+ }
+
+ debug_print(_("getting article %d...\n"), num);
+ ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session),
+ num, filename);
+ if (ok != NN_SUCCESS) {
+ g_warning(_("can't read article %d\n"), num);
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(folder)->session = NULL;
+ }
+ g_free(filename);
+ return NULL;
+ }
+
+ return filename;
+}
+
+static MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num)
+{
+ MsgInfo *msginfo;
+ MsgFlags flags = {0, 0};
+ gchar *file;
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+
+ file = news_fetch_msg(folder, item, num);
+ if (!file) return NULL;
+
+ msginfo = procheader_parse_file(file, flags, FALSE);
+
+ g_free(file);
+
+ return msginfo;
+}
+
+static gint news_close(Folder *folder, FolderItem *item)
+{
+ return 0;
+}
+
+static gint news_scan_group(Folder *folder, FolderItem *item)
+{
+ NNTPSession *session;
+ gint num = 0, first = 0, last = 0;
+ gint new = 0, unread = 0, total = 0;
+ gint min = 0, max = 0;
+ gint ok;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(item != NULL, -1);
+
+ session = news_session_get(folder);
+ if (!session) return -1;
+
+ ok = news_select_group(session, item->path, &num, &first, &last);
+ if (ok != NN_SUCCESS) {
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(folder)->session = NULL;
+ }
+ return -1;
+ }
+
+ if (num == 0) {
+ item->new = item->unread = item->total = item->last_num = 0;
+ return 0;
+ }
+
+ procmsg_get_mark_sum(item, &new, &unread, &total, &min, &max, first);
+
+ if (max < first || last < min)
+ new = unread = total = num;
+ else {
+ if (min < first)
+ min = first;
+
+ if (last < max)
+ max = last;
+ else if (max < last) {
+ new += last - max;
+ unread += last - max;
+ }
+
+ if (new > num) new = num;
+ if (unread > num) unread = num;
+ }
+
+ item->new = new;
+ item->unread = unread;
+ item->total = num;
+ item->last_num = last;
+
+ return 0;
+}
+
+static NewsGroupInfo *news_group_info_new(const gchar *name,
+ gint first, gint last, gchar type)
+{
+ NewsGroupInfo *ginfo;
+
+ ginfo = g_new(NewsGroupInfo, 1);
+ ginfo->name = g_strdup(name);
+ ginfo->first = first;
+ ginfo->last = last;
+ ginfo->type = type;
+
+ return ginfo;
+}
+
+static void news_group_info_free(NewsGroupInfo *ginfo)
+{
+ g_free(ginfo->name);
+ g_free(ginfo);
+}
+
+static gint news_group_info_compare(NewsGroupInfo *ginfo1,
+ NewsGroupInfo *ginfo2)
+{
+ return g_ascii_strcasecmp(ginfo1->name, ginfo2->name);
+}
+
+GSList *news_get_group_list(Folder *folder)
+{
+ gchar *path, *filename;
+ FILE *fp;
+ GSList *list = NULL;
+ GSList *last = NULL;
+ gchar buf[NNTPBUFSIZE];
+
+ g_return_val_if_fail(folder != NULL, NULL);
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
+
+ path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
+ if (!is_dir_exist(path))
+ make_dir_hier(path);
+ filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
+ g_free(path);
+
+ if ((fp = g_fopen(filename, "rb")) == NULL) {
+ NNTPSession *session;
+ gint ok;
+
+ session = news_session_get(folder);
+ if (!session) {
+ g_free(filename);
+ return NULL;
+ }
+
+ ok = nntp_list(session);
+ if (ok != NN_SUCCESS) {
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(folder)->session = NULL;
+ }
+ g_free(filename);
+ return NULL;
+ }
+ if (recv_write_to_file(SESSION(session)->sock, filename) < 0) {
+ log_warning(_("can't retrieve newsgroup list\n"));
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(folder)->session = NULL;
+ g_free(filename);
+ return NULL;
+ }
+
+ if ((fp = g_fopen(filename, "rb")) == NULL) {
+ FILE_OP_ERROR(filename, "fopen");
+ g_free(filename);
+ return NULL;
+ }
+ }
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ gchar *p = buf;
+ gchar *name;
+ gint last_num;
+ gint first_num;
+ gchar type;
+ NewsGroupInfo *ginfo;
+
+ p = strchr(p, ' ');
+ if (!p) continue;
+ *p = '\0';
+ p++;
+ name = buf;
+
+ if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3)
+ continue;
+
+ ginfo = news_group_info_new(name, first_num, last_num, type);
+
+ if (!last)
+ last = list = g_slist_append(NULL, ginfo);
+ else {
+ last = g_slist_append(last, ginfo);
+ last = last->next;
+ }
+ }
+
+ fclose(fp);
+ g_free(filename);
+
+ list = g_slist_sort(list, (GCompareFunc)news_group_info_compare);
+
+ return list;
+}
+
+void news_group_list_free(GSList *group_list)
+{
+ GSList *cur;
+
+ if (!group_list) return;
+
+ for (cur = group_list; cur != NULL; cur = cur->next)
+ news_group_info_free((NewsGroupInfo *)cur->data);
+ g_slist_free(group_list);
+}
+
+void news_remove_group_list_cache(Folder *folder)
+{
+ gchar *path, *filename;
+
+ g_return_if_fail(folder != NULL);
+ g_return_if_fail(FOLDER_TYPE(folder) == F_NEWS);
+
+ path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
+ filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
+ g_free(path);
+
+ if (is_file_exist(filename)) {
+ if (remove(filename) < 0)
+ FILE_OP_ERROR(filename, "remove");
+ }
+ g_free(filename);
+}
+
+gint news_post(Folder *folder, const gchar *file)
+{
+ FILE *fp;
+ gint ok;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1);
+ g_return_val_if_fail(file != NULL, -1);
+
+ if ((fp = g_fopen(file, "rb")) == NULL) {
+ FILE_OP_ERROR(file, "fopen");
+ return -1;
+ }
+
+ ok = news_post_stream(folder, fp);
+
+ fclose(fp);
+
+ return ok;
+}
+
+gint news_post_stream(Folder *folder, FILE *fp)
+{
+ NNTPSession *session;
+ gint ok;
+
+ g_return_val_if_fail(folder != NULL, -1);
+ g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1);
+ g_return_val_if_fail(fp != NULL, -1);
+
+ session = news_session_get(folder);
+ if (!session) return -1;
+
+ ok = nntp_post(session, fp);
+ if (ok != NN_SUCCESS) {
+ log_warning(_("can't post article.\n"));
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(folder)->session = NULL;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd,
+ gint num, gchar *filename)
+{
+ gchar *msgid;
+ gint ok;
+
+ ok = nntp_get_article(session, cmd, num, &msgid);
+ if (ok != NN_SUCCESS)
+ return ok;
+
+ debug_print("Message-Id = %s, num = %d\n", msgid, num);
+ g_free(msgid);
+
+ ok = recv_write_to_file(SESSION(session)->sock, filename);
+ if (ok < 0) {
+ log_warning(_("can't retrieve article %d\n"), num);
+ if (ok == -2)
+ return NN_SOCKET;
+ else
+ return NN_IOERR;
+ }
+
+ return NN_SUCCESS;
+}
+
+static gint news_get_article(NNTPSession *session, gint num, gchar *filename)
+{
+ return news_get_article_cmd(session, "ARTICLE", num, filename);
+}
+
+#if 0
+static gint news_get_header(NNTPSession *session, gint num, gchar *filename)
+{
+ return news_get_article_cmd(session, "HEAD", num, filename);
+}
+#endif
+
+/**
+ * news_select_group:
+ * @session: Active NNTP session.
+ * @group: Newsgroup name.
+ * @num: Estimated number of articles.
+ * @first: First article number.
+ * @last: Last article number.
+ *
+ * Select newsgroup @group with the GROUP command if it is not already
+ * selected in @session, or article numbers need to be returned.
+ *
+ * Return value: NNTP result code.
+ **/
+static gint news_select_group(NNTPSession *session, const gchar *group,
+ gint *num, gint *first, gint *last)
+{
+ gint ok;
+ gint num_, first_, last_;
+
+ if (!num || !first || !last) {
+ if (session->group &&
+ g_ascii_strcasecmp(session->group, group) == 0)
+ return NN_SUCCESS;
+ num = &num_;
+ first = &first_;
+ last = &last_;
+ }
+
+ g_free(session->group);
+ session->group = NULL;
+
+ ok = nntp_group(session, group, num, first, last);
+ if (ok == NN_SUCCESS)
+ session->group = g_strdup(group);
+ else
+ log_warning(_("can't select group: %s\n"), group);
+
+ return ok;
+}
+
+static GSList *news_get_uncached_articles(NNTPSession *session,
+ FolderItem *item, gint cache_last,
+ gint *rfirst, gint *rlast)
+{
+ gint ok;
+ gint num = 0, first = 0, last = 0, begin = 0, end = 0;
+ gchar buf[NNTPBUFSIZE];
+ GSList *newlist = NULL;
+ GSList *llast = NULL;
+ MsgInfo *msginfo;
+ gint max_articles;
+
+ if (rfirst) *rfirst = -1;
+ if (rlast) *rlast = -1;
+
+ g_return_val_if_fail(session != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(item->folder != NULL, NULL);
+ g_return_val_if_fail(item->folder->account != NULL, NULL);
+ g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, NULL);
+
+ ok = news_select_group(session, item->path, &num, &first, &last);
+ if (ok != NN_SUCCESS) {
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(item->folder)->session = NULL;
+ }
+ return NULL;
+ }
+
+ /* calculate getting overview range */
+ if (first > last) {
+ log_warning(_("invalid article range: %d - %d\n"),
+ first, last);
+ return NULL;
+ }
+
+ if (rfirst) *rfirst = first;
+ if (rlast) *rlast = last;
+
+ if (cache_last < first)
+ begin = first;
+ else if (last < cache_last)
+ begin = first;
+ else if (last == cache_last) {
+ debug_print(_("no new articles.\n"));
+ return NULL;
+ } else
+ begin = cache_last + 1;
+ end = last;
+
+ max_articles = item->folder->account->max_nntp_articles;
+ if (max_articles > 0 && end - begin + 1 > max_articles)
+ begin = end - max_articles + 1;
+
+ log_message(_("getting xover %d - %d in %s...\n"),
+ begin, end, item->path);
+ ok = nntp_xover(session, begin, end);
+ if (ok != NN_SUCCESS) {
+ log_warning(_("can't get xover\n"));
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(item->folder)->session = NULL;
+ }
+ return NULL;
+ }
+
+ for (;;) {
+ if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
+ log_warning(_("error occurred while getting xover.\n"));
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(item->folder)->session = NULL;
+ return newlist;
+ }
+
+ if (buf[0] == '.' && buf[1] == '\r') break;
+
+ msginfo = news_parse_xover(buf);
+ if (!msginfo) {
+ log_warning(_("invalid xover line: %s\n"), buf);
+ continue;
+ }
+
+ msginfo->folder = item;
+ msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
+ msginfo->flags.tmp_flags = MSG_NEWS;
+ msginfo->newsgroups = g_strdup(item->path);
+
+ if (!newlist)
+ llast = newlist = g_slist_append(newlist, msginfo);
+ else {
+ llast = g_slist_append(llast, msginfo);
+ llast = llast->next;
+ }
+ }
+
+ ok = nntp_xhdr(session, "to", begin, end);
+ if (ok != NN_SUCCESS) {
+ log_warning(_("can't get xhdr\n"));
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(item->folder)->session = NULL;
+ }
+ return newlist;
+ }
+
+ llast = newlist;
+
+ for (;;) {
+ if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
+ log_warning(_("error occurred while getting xhdr.\n"));
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(item->folder)->session = NULL;
+ return newlist;
+ }
+
+ if (buf[0] == '.' && buf[1] == '\r') break;
+ if (!llast) {
+ g_warning("llast == NULL\n");
+ continue;
+ }
+
+ msginfo = (MsgInfo *)llast->data;
+ msginfo->to = news_parse_xhdr(buf, msginfo);
+
+ llast = llast->next;
+ }
+
+ ok = nntp_xhdr(session, "cc", begin, end);
+ if (ok != NN_SUCCESS) {
+ log_warning(_("can't get xhdr\n"));
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(item->folder)->session = NULL;
+ }
+ return newlist;
+ }
+
+ llast = newlist;
+
+ for (;;) {
+ if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
+ log_warning(_("error occurred while getting xhdr.\n"));
+ session_destroy(SESSION(session));
+ REMOTE_FOLDER(item->folder)->session = NULL;
+ return newlist;
+ }
+
+ if (buf[0] == '.' && buf[1] == '\r') break;
+ if (!llast) {
+ g_warning("llast == NULL\n");
+ continue;
+ }
+
+ msginfo = (MsgInfo *)llast->data;
+ msginfo->cc = news_parse_xhdr(buf, msginfo);
+
+ llast = llast->next;
+ }
+
+ session_set_access_time(SESSION(session));
+
+ return newlist;
+}
+
+#define PARSE_ONE_PARAM(p, srcp) \
+{ \
+ p = strchr(srcp, '\t'); \
+ if (!p) return NULL; \
+ else \
+ *p++ = '\0'; \
+}
+
+static MsgInfo *news_parse_xover(const gchar *xover_str)
+{
+ MsgInfo *msginfo;
+ gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp;
+ gchar *p;
+ gint num, size_int, line_int;
+ gchar *xover_buf;
+
+ Xstrdup_a(xover_buf, xover_str, return NULL);
+
+ PARSE_ONE_PARAM(subject, xover_buf);
+ PARSE_ONE_PARAM(sender, subject);
+ PARSE_ONE_PARAM(date, sender);
+ PARSE_ONE_PARAM(msgid, date);
+ PARSE_ONE_PARAM(ref, msgid);
+ PARSE_ONE_PARAM(size, ref);
+ PARSE_ONE_PARAM(line, size);
+
+ tmp = strchr(line, '\t');
+ if (!tmp) tmp = strchr(line, '\r');
+ if (!tmp) tmp = strchr(line, '\n');
+ if (tmp) *tmp = '\0';
+
+ num = atoi(xover_str);
+ size_int = atoi(size);
+ line_int = atoi(line);
+
+ /* set MsgInfo */
+ msginfo = g_new0(MsgInfo, 1);
+ msginfo->msgnum = num;
+ msginfo->size = size_int;
+
+ msginfo->date = g_strdup(date);
+ msginfo->date_t = procheader_date_parse(NULL, date, 0);
+
+ msginfo->from = conv_unmime_header(sender, NULL);
+ msginfo->fromname = procheader_get_fromname(msginfo->from);
+
+ msginfo->subject = conv_unmime_header(subject, NULL);
+
+ extract_parenthesis(msgid, '<', '>');
+ remove_space(msgid);
+ if (*msgid != '\0')
+ msginfo->msgid = g_strdup(msgid);
+
+ eliminate_parenthesis(ref, '(', ')');
+ if ((p = strrchr(ref, '<')) != NULL) {
+ extract_parenthesis(p, '<', '>');
+ remove_space(p);
+ if (*p != '\0')
+ msginfo->inreplyto = g_strdup(p);
+ }
+
+ return msginfo;
+}
+
+static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo)
+{
+ gchar *p;
+ gchar *tmp;
+ gint num;
+
+ p = strchr(xhdr_str, ' ');
+ if (!p)
+ return NULL;
+ else
+ p++;
+
+ num = atoi(xhdr_str);
+ if (msginfo->msgnum != num) return NULL;
+
+ tmp = strchr(p, '\r');
+ if (!tmp) tmp = strchr(p, '\n');
+
+ if (tmp)
+ return g_strndup(p, tmp - p);
+ else
+ return g_strdup(p);
+}
+
+static GSList *news_delete_old_articles(GSList *alist, FolderItem *item,
+ gint first)
+{
+ GSList *cur, *next;
+ MsgInfo *msginfo;
+ gchar *dir;
+
+ g_return_val_if_fail(item != NULL, alist);
+ g_return_val_if_fail(item->folder != NULL, alist);
+ g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, alist);
+
+ if (first < 2) return alist;
+
+ debug_print("Deleting cached articles 1 - %d ...\n", first - 1);
+
+ dir = folder_item_get_path(item);
+ remove_numbered_files(dir, 1, first - 1);
+ g_free(dir);
+
+ for (cur = alist; cur != NULL; ) {
+ next = cur->next;
+
+ msginfo = (MsgInfo *)cur->data;
+ if (msginfo && msginfo->msgnum < first) {
+ procmsg_msginfo_free(msginfo);
+ alist = g_slist_remove(alist, msginfo);
+ item->cache_dirty = TRUE;
+ }
+
+ cur = next;
+ }
+
+ return alist;
+}
+
+static void news_delete_all_articles(FolderItem *item)
+{
+ gchar *dir;
+
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+ g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS);
+
+ debug_print("Deleting all cached articles...\n");
+
+ dir = folder_item_get_path(item);
+ remove_all_numbered_files(dir);
+ g_free(dir);
+}
+
+static void news_delete_expired_caches(GSList *alist, FolderItem *item)
+{
+ gchar *dir;
+
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+ g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS);
+
+ debug_print("Deleting expired cached articles...\n");
+
+ dir = folder_item_get_path(item);
+ remove_expired_files(dir, 24 * 7);
+ g_free(dir);
+}
diff --git a/libsylph/news.h b/libsylph/news.h
new file mode 100644
index 00000000..31628113
--- /dev/null
+++ b/libsylph/news.h
@@ -0,0 +1,59 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __NEWS_H__
+#define __NEWS_H__
+
+#include <glib.h>
+#include <stdio.h>
+
+#include "folder.h"
+
+typedef struct _NewsFolder NewsFolder;
+typedef struct _NewsGroupInfo NewsGroupInfo;
+
+#define NEWS_FOLDER(obj) ((NewsFolder *)obj)
+
+struct _NewsFolder
+{
+ RemoteFolder rfolder;
+
+ gboolean use_auth;
+};
+
+struct _NewsGroupInfo
+{
+ gchar *name;
+ guint first;
+ guint last;
+ gchar type;
+};
+
+FolderClass *news_get_class (void);
+
+GSList *news_get_group_list (Folder *folder);
+void news_group_list_free (GSList *group_list);
+void news_remove_group_list_cache (Folder *folder);
+
+gint news_post (Folder *folder,
+ const gchar *file);
+gint news_post_stream (Folder *folder,
+ FILE *fp);
+
+#endif /* __NEWS_H__ */
diff --git a/libsylph/nntp.c b/libsylph/nntp.c
new file mode 100644
index 00000000..7e36baaa
--- /dev/null
+++ b/libsylph/nntp.c
@@ -0,0 +1,431 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "nntp.h"
+#include "socket.h"
+#include "utils.h"
+#if USE_SSL
+# include "ssl.h"
+#endif
+
+static gint verbose = 1;
+
+static void nntp_session_destroy(Session *session);
+
+static gint nntp_ok (SockInfo *sock,
+ gchar *argbuf);
+
+static gint nntp_gen_send (SockInfo *sock,
+ const gchar *format,
+ ...);
+static gint nntp_gen_recv (SockInfo *sock,
+ gchar *buf,
+ gint size);
+static gint nntp_gen_command (NNTPSession *session,
+ gchar *argbuf,
+ const gchar *format,
+ ...);
+
+
+#if USE_SSL
+Session *nntp_session_new(const gchar *server, gushort port, gchar *buf,
+ const gchar *userid, const gchar *passwd,
+ SSLType ssl_type)
+#else
+Session *nntp_session_new(const gchar *server, gushort port, gchar *buf,
+ const gchar *userid, const gchar *passwd)
+#endif
+{
+ NNTPSession *session;
+ SockInfo *sock;
+
+ if ((sock = sock_connect(server, port)) == NULL) {
+ log_warning(_("Can't connect to NNTP server: %s:%d\n"),
+ server, port);
+ return NULL;
+ }
+
+#if USE_SSL
+ if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
+ sock_close(sock);
+ return NULL;
+ }
+#endif
+
+ if (nntp_ok(sock, buf) != NN_SUCCESS) {
+ sock_close(sock);
+ return NULL;
+ }
+
+ session = g_new0(NNTPSession, 1);
+
+ session_init(SESSION(session));
+
+ SESSION(session)->type = SESSION_NEWS;
+ SESSION(session)->server = g_strdup(server);
+ SESSION(session)->sock = sock;
+ SESSION(session)->last_access_time = time(NULL);
+ SESSION(session)->data = NULL;
+
+ SESSION(session)->destroy = nntp_session_destroy;
+
+ session->group = NULL;
+
+ if (userid && passwd) {
+ gint ok;
+
+ session->userid = g_strdup(userid);
+ session->passwd = g_strdup(passwd);
+
+ ok = nntp_gen_send(sock, "AUTHINFO USER %s", session->userid);
+ if (ok != NN_SUCCESS) {
+ session_destroy(SESSION(session));
+ return NULL;
+ }
+ ok = nntp_ok(sock, NULL);
+ if (ok == NN_AUTHCONT) {
+ ok = nntp_gen_send(sock, "AUTHINFO PASS %s",
+ session->passwd);
+ if (ok != NN_SUCCESS) {
+ session_destroy(SESSION(session));
+ return NULL;
+ }
+ ok = nntp_ok(sock, NULL);
+ if (ok != NN_SUCCESS)
+ session->auth_failed = TRUE;
+ }
+ if (ok == NN_SOCKET) {
+ session_destroy(SESSION(session));
+ return NULL;
+ }
+ }
+
+ session_set_access_time(SESSION(session));
+
+ return SESSION(session);
+}
+
+static void nntp_session_destroy(Session *session)
+{
+ NNTPSession *nntp_session = NNTP_SESSION(session);
+
+ g_return_if_fail(session != NULL);
+
+ g_free(nntp_session->group);
+ g_free(nntp_session->userid);
+ g_free(nntp_session->passwd);
+}
+
+gint nntp_group(NNTPSession *session, const gchar *group,
+ gint *num, gint *first, gint *last)
+{
+ gint ok;
+ gint resp;
+ gchar buf[NNTPBUFSIZE];
+
+ ok = nntp_gen_command(session, buf, "GROUP %s", group);
+
+ if (ok != NN_SUCCESS && ok != NN_SOCKET && ok != NN_AUTHREQ) {
+ ok = nntp_mode(session, FALSE);
+ if (ok == NN_SUCCESS)
+ ok = nntp_gen_command(session, buf, "GROUP %s", group);
+ }
+
+ if (ok != NN_SUCCESS)
+ return ok;
+
+ if (sscanf(buf, "%d %d %d %d", &resp, num, first, last)
+ != 4) {
+ log_warning(_("protocol error: %s\n"), buf);
+ return NN_PROTOCOL;
+ }
+
+ return NN_SUCCESS;
+}
+
+gint nntp_get_article(NNTPSession *session, const gchar *cmd, gint num,
+ gchar **msgid)
+{
+ gint ok;
+ gchar buf[NNTPBUFSIZE];
+
+ if (num > 0)
+ ok = nntp_gen_command(session, buf, "%s %d", cmd, num);
+ else
+ ok = nntp_gen_command(session, buf, cmd);
+
+ if (ok != NN_SUCCESS)
+ return ok;
+
+ extract_parenthesis(buf, '<', '>');
+ if (buf[0] == '\0') {
+ log_warning(_("protocol error\n"));
+ *msgid = g_strdup("0");
+ } else
+ *msgid = g_strdup(buf);
+
+ return NN_SUCCESS;
+}
+
+gint nntp_article(NNTPSession *session, gint num, gchar **msgid)
+{
+ return nntp_get_article(session, "ARTICLE", num, msgid);
+}
+
+gint nntp_body(NNTPSession *session, gint num, gchar **msgid)
+{
+ return nntp_get_article(session, "BODY", num, msgid);
+}
+
+gint nntp_head(NNTPSession *session, gint num, gchar **msgid)
+{
+ return nntp_get_article(session, "HEAD", num, msgid);
+}
+
+gint nntp_stat(NNTPSession *session, gint num, gchar **msgid)
+{
+ return nntp_get_article(session, "STAT", num, msgid);
+}
+
+gint nntp_next(NNTPSession *session, gint *num, gchar **msgid)
+{
+ gint ok;
+ gint resp;
+ gchar buf[NNTPBUFSIZE];
+
+ ok = nntp_gen_command(session, buf, "NEXT");
+
+ if (ok != NN_SUCCESS)
+ return ok;
+
+ if (sscanf(buf, "%d %d", &resp, num) != 2) {
+ log_warning(_("protocol error: %s\n"), buf);
+ return NN_PROTOCOL;
+ }
+
+ extract_parenthesis(buf, '<', '>');
+ if (buf[0] == '\0') {
+ log_warning(_("protocol error\n"));
+ return NN_PROTOCOL;
+ }
+ *msgid = g_strdup(buf);
+
+ return NN_SUCCESS;
+}
+
+gint nntp_xover(NNTPSession *session, gint first, gint last)
+{
+ gint ok;
+ gchar buf[NNTPBUFSIZE];
+
+ ok = nntp_gen_command(session, buf, "XOVER %d-%d", first, last);
+ if (ok != NN_SUCCESS)
+ return ok;
+
+ return NN_SUCCESS;
+}
+
+gint nntp_xhdr(NNTPSession *session, const gchar *header, gint first, gint last)
+{
+ gint ok;
+ gchar buf[NNTPBUFSIZE];
+
+ ok = nntp_gen_command(session, buf, "XHDR %s %d-%d",
+ header, first, last);
+ if (ok != NN_SUCCESS)
+ return ok;
+
+ return NN_SUCCESS;
+}
+
+gint nntp_list(NNTPSession *session)
+{
+ return nntp_gen_command(session, NULL, "LIST");
+}
+
+gint nntp_post(NNTPSession *session, FILE *fp)
+{
+ gint ok;
+ gchar buf[NNTPBUFSIZE];
+ gchar *msg;
+
+ ok = nntp_gen_command(session, buf, "POST");
+ if (ok != NN_SUCCESS)
+ return ok;
+
+ msg = get_outgoing_rfc2822_str(fp);
+ if (sock_write_all(SESSION(session)->sock, msg, strlen(msg)) < 0) {
+ log_warning(_("Error occurred while posting\n"));
+ g_free(msg);
+ return NN_SOCKET;
+ }
+ g_free(msg);
+
+ sock_write_all(SESSION(session)->sock, ".\r\n", 3);
+ if ((ok = nntp_ok(SESSION(session)->sock, buf)) != NN_SUCCESS)
+ return ok;
+
+ session_set_access_time(SESSION(session));
+
+ return NN_SUCCESS;
+}
+
+gint nntp_newgroups(NNTPSession *session)
+{
+ return NN_SUCCESS;
+}
+
+gint nntp_newnews(NNTPSession *session)
+{
+ return NN_SUCCESS;
+}
+
+gint nntp_mode(NNTPSession *session, gboolean stream)
+{
+ gint ok;
+
+ ok = nntp_gen_command(session, NULL, "MODE %s",
+ stream ? "STREAM" : "READER");
+
+ return ok;
+}
+
+static gint nntp_ok(SockInfo *sock, gchar *argbuf)
+{
+ gint ok;
+ gchar buf[NNTPBUFSIZE];
+
+ if ((ok = nntp_gen_recv(sock, buf, sizeof(buf))) == NN_SUCCESS) {
+ if (strlen(buf) < 3)
+ return NN_ERROR;
+
+ if ((buf[0] == '1' || buf[0] == '2' || buf[0] == '3') &&
+ (buf[3] == ' ' || buf[3] == '\0')) {
+ if (argbuf)
+ strcpy(argbuf, buf);
+
+ if (!strncmp(buf, "381", 3))
+ return NN_AUTHCONT;
+
+ return NN_SUCCESS;
+ } else if (!strncmp(buf, "480", 3))
+ return NN_AUTHREQ;
+ else
+ return NN_ERROR;
+ }
+
+ return ok;
+}
+
+static gint nntp_gen_send(SockInfo *sock, const gchar *format, ...)
+{
+ gchar buf[NNTPBUFSIZE];
+ va_list args;
+
+ va_start(args, format);
+ g_vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+
+ if (verbose) {
+ if (!g_ascii_strncasecmp(buf, "AUTHINFO PASS", 13))
+ log_print("NNTP> AUTHINFO PASS ********\n");
+ else
+ log_print("NNTP> %s\n", buf);
+ }
+
+ strcat(buf, "\r\n");
+ if (sock_write_all(sock, buf, strlen(buf)) < 0) {
+ log_warning(_("Error occurred while sending command\n"));
+ return NN_SOCKET;
+ }
+
+ return NN_SUCCESS;
+}
+
+static gint nntp_gen_recv(SockInfo *sock, gchar *buf, gint size)
+{
+ if (sock_gets(sock, buf, size) == -1)
+ return NN_SOCKET;
+
+ strretchomp(buf);
+
+ if (verbose)
+ log_print("NNTP< %s\n", buf);
+
+ return NN_SUCCESS;
+}
+
+static gint nntp_gen_command(NNTPSession *session, gchar *argbuf,
+ const gchar *format, ...)
+{
+ gchar buf[NNTPBUFSIZE];
+ va_list args;
+ gint ok;
+ SockInfo *sock;
+
+ va_start(args, format);
+ g_vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+
+ sock = SESSION(session)->sock;
+ ok = nntp_gen_send(sock, "%s", buf);
+ if (ok != NN_SUCCESS)
+ return ok;
+ ok = nntp_ok(sock, argbuf);
+ if (ok == NN_AUTHREQ) {
+ if (!session->userid || !session->passwd) {
+ session->auth_failed = TRUE;
+ return ok;
+ }
+
+ ok = nntp_gen_send(sock, "AUTHINFO USER %s", session->userid);
+ if (ok != NN_SUCCESS)
+ return ok;
+ ok = nntp_ok(sock, NULL);
+ if (ok == NN_AUTHCONT) {
+ ok = nntp_gen_send(sock, "AUTHINFO PASS %s",
+ session->passwd);
+ if (ok != NN_SUCCESS)
+ return ok;
+ ok = nntp_ok(sock, NULL);
+ }
+ if (ok != NN_SUCCESS) {
+ session->auth_failed = TRUE;
+ return ok;
+ }
+
+ ok = nntp_gen_send(sock, "%s", buf);
+ if (ok != NN_SUCCESS)
+ return ok;
+ ok = nntp_ok(sock, argbuf);
+ }
+
+ session_set_access_time(SESSION(session));
+
+ return ok;
+}
diff --git a/libsylph/nntp.h b/libsylph/nntp.h
new file mode 100644
index 00000000..46992e42
--- /dev/null
+++ b/libsylph/nntp.h
@@ -0,0 +1,109 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __NNTP_H__
+#define __NNTP_H__
+
+#include "session.h"
+#if USE_SSL
+# include "ssl.h"
+#endif
+
+typedef struct _NNTPSession NNTPSession;
+
+#define NNTP_SESSION(obj) ((NNTPSession *)obj)
+
+struct _NNTPSession
+{
+ Session session;
+
+ gchar *group;
+
+ gchar *userid;
+ gchar *passwd;
+ gboolean auth_failed;
+};
+
+#define NN_SUCCESS 0
+#define NN_SOCKET 2
+#define NN_AUTHFAIL 3
+#define NN_PROTOCOL 4
+#define NN_SYNTAX 5
+#define NN_IOERR 6
+#define NN_ERROR 7
+#define NN_AUTHREQ 8
+#define NN_AUTHCONT 9
+
+#define NNTPBUFSIZE 8192
+
+#if USE_SSL
+Session *nntp_session_new (const gchar *server,
+ gushort port,
+ gchar *buf,
+ const gchar *userid,
+ const gchar *passwd,
+ SSLType ssl_type);
+#else
+Session *nntp_session_new (const gchar *server,
+ gushort port,
+ gchar *buf,
+ const gchar *userid,
+ const gchar *passwd);
+#endif
+
+gint nntp_group (NNTPSession *session,
+ const gchar *group,
+ gint *num,
+ gint *first,
+ gint *last);
+gint nntp_get_article (NNTPSession *session,
+ const gchar *cmd,
+ gint num,
+ gchar **msgid);
+gint nntp_article (NNTPSession *session,
+ gint num,
+ gchar **msgid);
+gint nntp_body (NNTPSession *session,
+ gint num,
+ gchar **msgid);
+gint nntp_head (NNTPSession *session,
+ gint num,
+ gchar **msgid);
+gint nntp_stat (NNTPSession *session,
+ gint num,
+ gchar **msgid);
+gint nntp_next (NNTPSession *session,
+ gint *num,
+ gchar **msgid);
+gint nntp_xover (NNTPSession *session,
+ gint first,
+ gint last);
+gint nntp_xhdr (NNTPSession *session,
+ const gchar *header,
+ gint first,
+ gint last);
+gint nntp_list (NNTPSession *session);
+gint nntp_post (NNTPSession *session,
+ FILE *fp);
+gint nntp_newgroups (NNTPSession *session);
+gint nntp_newnews (NNTPSession *session);
+gint nntp_mode (NNTPSession *sessio,
+ gboolean stream);
+
+#endif /* __NNTP_H__ */
diff --git a/libsylph/pop.c b/libsylph/pop.c
new file mode 100644
index 00000000..3a562054
--- /dev/null
+++ b/libsylph/pop.c
@@ -0,0 +1,857 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+#include "pop.h"
+#include "md5.h"
+#include "prefs_account.h"
+#include "utils.h"
+#include "recv.h"
+
+static gint pop3_greeting_recv (Pop3Session *session,
+ const gchar *msg);
+static gint pop3_getauth_user_send (Pop3Session *session);
+static gint pop3_getauth_pass_send (Pop3Session *session);
+static gint pop3_getauth_apop_send (Pop3Session *session);
+#if USE_SSL
+static gint pop3_stls_send (Pop3Session *session);
+static gint pop3_stls_recv (Pop3Session *session);
+#endif
+static gint pop3_getrange_stat_send (Pop3Session *session);
+static gint pop3_getrange_stat_recv (Pop3Session *session,
+ const gchar *msg);
+static gint pop3_getrange_last_send (Pop3Session *session);
+static gint pop3_getrange_last_recv (Pop3Session *session,
+ const gchar *msg);
+static gint pop3_getrange_uidl_send (Pop3Session *session);
+static gint pop3_getrange_uidl_recv (Pop3Session *session,
+ const gchar *data,
+ guint len);
+static gint pop3_getsize_list_send (Pop3Session *session);
+static gint pop3_getsize_list_recv (Pop3Session *session,
+ const gchar *data,
+ guint len);
+static gint pop3_retr_send (Pop3Session *session);
+static gint pop3_retr_recv (Pop3Session *session,
+ const gchar *data,
+ guint len);
+static gint pop3_delete_send (Pop3Session *session);
+static gint pop3_delete_recv (Pop3Session *session);
+static gint pop3_logout_send (Pop3Session *session);
+
+static void pop3_gen_send (Pop3Session *session,
+ const gchar *format, ...);
+
+static void pop3_session_destroy (Session *session);
+
+static gint pop3_write_msg_to_file (const gchar *file,
+ const gchar *data,
+ guint len);
+
+static Pop3State pop3_lookup_next (Pop3Session *session);
+static Pop3ErrorValue pop3_ok (Pop3Session *session,
+ const gchar *msg);
+
+static gint pop3_session_recv_msg (Session *session,
+ const gchar *msg);
+static gint pop3_session_recv_data_finished (Session *session,
+ guchar *data,
+ guint len);
+
+
+static gint pop3_greeting_recv(Pop3Session *session, const gchar *msg)
+{
+ session->state = POP3_GREETING;
+
+ session->greeting = g_strdup(msg);
+ return PS_SUCCESS;
+}
+
+#if USE_SSL
+static gint pop3_stls_send(Pop3Session *session)
+{
+ session->state = POP3_STLS;
+ pop3_gen_send(session, "STLS");
+ return PS_SUCCESS;
+}
+
+static gint pop3_stls_recv(Pop3Session *session)
+{
+ if (session_start_tls(SESSION(session)) < 0) {
+ session->error_val = PS_SOCKET;
+ return -1;
+ }
+ return PS_SUCCESS;
+}
+#endif /* USE_SSL */
+
+static gint pop3_getauth_user_send(Pop3Session *session)
+{
+ g_return_val_if_fail(session->user != NULL, -1);
+
+ session->state = POP3_GETAUTH_USER;
+ pop3_gen_send(session, "USER %s", session->user);
+ return PS_SUCCESS;
+}
+
+static gint pop3_getauth_pass_send(Pop3Session *session)
+{
+ g_return_val_if_fail(session->pass != NULL, -1);
+
+ session->state = POP3_GETAUTH_PASS;
+ pop3_gen_send(session, "PASS %s", session->pass);
+ return PS_SUCCESS;
+}
+
+static gint pop3_getauth_apop_send(Pop3Session *session)
+{
+ gchar *start, *end;
+ gchar *apop_str;
+ gchar md5sum[33];
+
+ g_return_val_if_fail(session->user != NULL, -1);
+ g_return_val_if_fail(session->pass != NULL, -1);
+
+ session->state = POP3_GETAUTH_APOP;
+
+ if ((start = strchr(session->greeting, '<')) == NULL) {
+ log_warning(_("Required APOP timestamp not found "
+ "in greeting\n"));
+ session->error_val = PS_PROTOCOL;
+ return -1;
+ }
+
+ if ((end = strchr(start, '>')) == NULL || end == start + 1) {
+ log_warning(_("Timestamp syntax error in greeting\n"));
+ session->error_val = PS_PROTOCOL;
+ return -1;
+ }
+
+ *(end + 1) = '\0';
+
+ apop_str = g_strconcat(start, session->pass, NULL);
+ md5_hex_digest(md5sum, apop_str);
+ g_free(apop_str);
+
+ pop3_gen_send(session, "APOP %s %s", session->user, md5sum);
+
+ return PS_SUCCESS;
+}
+
+static gint pop3_getrange_stat_send(Pop3Session *session)
+{
+ session->state = POP3_GETRANGE_STAT;
+ pop3_gen_send(session, "STAT");
+ return PS_SUCCESS;
+}
+
+static gint pop3_getrange_stat_recv(Pop3Session *session, const gchar *msg)
+{
+ if (sscanf(msg, "%d %d", &session->count, &session->total_bytes) != 2) {
+ log_warning(_("POP3 protocol error\n"));
+ session->error_val = PS_PROTOCOL;
+ return -1;
+ } else {
+ if (session->count == 0) {
+ session->uidl_is_valid = TRUE;
+ } else {
+ session->msg = g_new0(Pop3MsgInfo, session->count + 1);
+ session->cur_msg = 1;
+ }
+ }
+
+ return PS_SUCCESS;
+}
+
+static gint pop3_getrange_last_send(Pop3Session *session)
+{
+ session->state = POP3_GETRANGE_LAST;
+ pop3_gen_send(session, "LAST");
+ return PS_SUCCESS;
+}
+
+static gint pop3_getrange_last_recv(Pop3Session *session, const gchar *msg)
+{
+ gint last;
+
+ if (sscanf(msg, "%d", &last) == 0) {
+ log_warning(_("POP3 protocol error\n"));
+ session->error_val = PS_PROTOCOL;
+ return -1;
+ } else {
+ if (session->count > last) {
+ session->new_msg_exist = TRUE;
+ session->cur_msg = last + 1;
+ } else
+ session->cur_msg = 0;
+ }
+
+ return PS_SUCCESS;
+}
+
+static gint pop3_getrange_uidl_send(Pop3Session *session)
+{
+ session->state = POP3_GETRANGE_UIDL;
+ pop3_gen_send(session, "UIDL");
+ return PS_SUCCESS;
+}
+
+static gint pop3_getrange_uidl_recv(Pop3Session *session, const gchar *data,
+ guint len)
+{
+ gchar id[IDLEN + 1];
+ gchar buf[POPBUFSIZE];
+ gint buf_len;
+ gint num;
+ time_t recv_time;
+ const gchar *p = data;
+ const gchar *lastp = data + len;
+ const gchar *newline;
+
+ while (p < lastp) {
+ if ((newline = memchr(p, '\r', lastp - p)) == NULL)
+ return -1;
+ buf_len = MIN(newline - p, sizeof(buf) - 1);
+ memcpy(buf, p, buf_len);
+ buf[buf_len] = '\0';
+
+ p = newline + 1;
+ if (p < lastp && *p == '\n') p++;
+
+ if (sscanf(buf, "%d %" Xstr(IDLEN) "s", &num, id) != 2 ||
+ num <= 0 || num > session->count) {
+ log_warning(_("invalid UIDL response: %s\n"), buf);
+ continue;
+ }
+
+ session->msg[num].uidl = g_strdup(id);
+
+ recv_time = (time_t)g_hash_table_lookup(session->uidl_table, id);
+ session->msg[num].recv_time = recv_time;
+
+ if (!session->ac_prefs->getall && recv_time != RECV_TIME_NONE)
+ session->msg[num].received = TRUE;
+
+ if (!session->new_msg_exist &&
+ (session->ac_prefs->getall || recv_time == RECV_TIME_NONE ||
+ session->ac_prefs->rmmail)) {
+ session->cur_msg = num;
+ session->new_msg_exist = TRUE;
+ }
+ }
+
+ session->uidl_is_valid = TRUE;
+ return PS_SUCCESS;
+}
+
+static gint pop3_getsize_list_send(Pop3Session *session)
+{
+ session->state = POP3_GETSIZE_LIST;
+ pop3_gen_send(session, "LIST");
+ return PS_SUCCESS;
+}
+
+static gint pop3_getsize_list_recv(Pop3Session *session, const gchar *data,
+ guint len)
+{
+ gchar buf[POPBUFSIZE];
+ gint buf_len;
+ guint num, size;
+ const gchar *p = data;
+ const gchar *lastp = data + len;
+ const gchar *newline;
+
+ while (p < lastp) {
+ if ((newline = memchr(p, '\r', lastp - p)) == NULL)
+ return -1;
+ buf_len = MIN(newline - p, sizeof(buf) - 1);
+ memcpy(buf, p, buf_len);
+ buf[buf_len] = '\0';
+
+ p = newline + 1;
+ if (p < lastp && *p == '\n') p++;
+
+ if (sscanf(buf, "%u %u", &num, &size) != 2) {
+ session->error_val = PS_PROTOCOL;
+ return -1;
+ }
+
+ if (num > 0 && num <= session->count)
+ session->msg[num].size = size;
+ if (num > 0 && num < session->cur_msg)
+ session->cur_total_bytes += size;
+ }
+
+ return PS_SUCCESS;
+}
+
+static gint pop3_retr_send(Pop3Session *session)
+{
+ session->state = POP3_RETR;
+ pop3_gen_send(session, "RETR %d", session->cur_msg);
+ return PS_SUCCESS;
+}
+
+static gint pop3_retr_recv(Pop3Session *session, const gchar *data, guint len)
+{
+ gchar *file;
+ gint drop_ok;
+
+ file = get_tmp_file();
+ if (pop3_write_msg_to_file(file, data, len) < 0) {
+ g_free(file);
+ session->error_val = PS_IOERR;
+ return -1;
+ }
+
+ drop_ok = session->drop_message(session, file);
+ g_unlink(file);
+ g_free(file);
+ if (drop_ok < 0) {
+ session->error_val = PS_IOERR;
+ return -1;
+ }
+
+ session->cur_total_bytes += session->msg[session->cur_msg].size;
+ session->cur_total_recv_bytes += session->msg[session->cur_msg].size;
+ session->cur_total_num++;
+
+ session->msg[session->cur_msg].received = TRUE;
+ session->msg[session->cur_msg].recv_time =
+ drop_ok == DROP_DONT_RECEIVE ? RECV_TIME_KEEP
+ : drop_ok == DROP_DELETE ? RECV_TIME_DELETE
+ : session->current_time;
+
+ return PS_SUCCESS;
+}
+
+static gint pop3_delete_send(Pop3Session *session)
+{
+ session->state = POP3_DELETE;
+ pop3_gen_send(session, "DELE %d", session->cur_msg);
+ return PS_SUCCESS;
+}
+
+static gint pop3_delete_recv(Pop3Session *session)
+{
+ session->msg[session->cur_msg].deleted = TRUE;
+ return PS_SUCCESS;
+}
+
+static gint pop3_logout_send(Pop3Session *session)
+{
+ session->state = POP3_LOGOUT;
+ pop3_gen_send(session, "QUIT");
+ return PS_SUCCESS;
+}
+
+static void pop3_gen_send(Pop3Session *session, const gchar *format, ...)
+{
+ gchar buf[POPBUFSIZE + 1];
+ va_list args;
+
+ va_start(args, format);
+ g_vsnprintf(buf, sizeof(buf) - 2, format, args);
+ va_end(args);
+
+ if (!g_ascii_strncasecmp(buf, "PASS ", 5))
+ log_print("POP3> PASS ********\n");
+ else
+ log_print("POP3> %s\n", buf);
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
+}
+
+Session *pop3_session_new(PrefsAccount *account)
+{
+ Pop3Session *session;
+
+ g_return_val_if_fail(account != NULL, NULL);
+
+ session = g_new0(Pop3Session, 1);
+
+ session_init(SESSION(session));
+
+ SESSION(session)->type = SESSION_POP3;
+
+ SESSION(session)->recv_msg = pop3_session_recv_msg;
+ SESSION(session)->recv_data_finished = pop3_session_recv_data_finished;
+ SESSION(session)->send_data_finished = NULL;
+
+ SESSION(session)->destroy = pop3_session_destroy;
+
+ session->state = POP3_READY;
+ session->ac_prefs = account;
+ session->uidl_table = pop3_get_uidl_table(account);
+ session->current_time = time(NULL);
+ session->error_val = PS_SUCCESS;
+ session->error_msg = NULL;
+
+ return SESSION(session);
+}
+
+static void pop3_session_destroy(Session *session)
+{
+ Pop3Session *pop3_session = POP3_SESSION(session);
+ gint n;
+
+ g_return_if_fail(session != NULL);
+
+ for (n = 1; n <= pop3_session->count; n++)
+ g_free(pop3_session->msg[n].uidl);
+ g_free(pop3_session->msg);
+
+ if (pop3_session->uidl_table) {
+ hash_free_strings(pop3_session->uidl_table);
+ g_hash_table_destroy(pop3_session->uidl_table);
+ }
+
+ g_free(pop3_session->greeting);
+ g_free(pop3_session->user);
+ g_free(pop3_session->pass);
+ g_free(pop3_session->error_msg);
+}
+
+GHashTable *pop3_get_uidl_table(PrefsAccount *ac_prefs)
+{
+ GHashTable *table;
+ gchar *path;
+ FILE *fp;
+ gchar buf[POPBUFSIZE];
+ gchar uidl[POPBUFSIZE];
+ time_t recv_time;
+ time_t now;
+
+ table = g_hash_table_new(g_str_hash, g_str_equal);
+
+ path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+ UIDL_DIR, G_DIR_SEPARATOR_S, ac_prefs->recv_server,
+ "-", ac_prefs->userid, NULL);
+ if ((fp = g_fopen(path, "rb")) == NULL) {
+ if (ENOENT != errno) FILE_OP_ERROR(path, "fopen");
+ g_free(path);
+ return table;
+ }
+ g_free(path);
+
+ now = time(NULL);
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ strretchomp(buf);
+ recv_time = RECV_TIME_NONE;
+ if (sscanf(buf, "%s\t%ld", uidl, &recv_time) != 2) {
+ if (sscanf(buf, "%s", uidl) != 1)
+ continue;
+ else
+ recv_time = now;
+ }
+ if (recv_time == RECV_TIME_NONE)
+ recv_time = RECV_TIME_RECEIVED;
+ g_hash_table_insert(table, g_strdup(uidl),
+ GINT_TO_POINTER(recv_time));
+ }
+
+ fclose(fp);
+ return table;
+}
+
+gint pop3_write_uidl_list(Pop3Session *session)
+{
+ gchar *path;
+ FILE *fp;
+ Pop3MsgInfo *msg;
+ gint n;
+
+ if (!session->uidl_is_valid) return 0;
+
+ path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+ "uidl", G_DIR_SEPARATOR_S,
+ session->ac_prefs->recv_server,
+ "-", session->ac_prefs->userid, NULL);
+ if ((fp = g_fopen(path, "wb")) == NULL) {
+ FILE_OP_ERROR(path, "fopen");
+ g_free(path);
+ return -1;
+ }
+
+ for (n = 1; n <= session->count; n++) {
+ msg = &session->msg[n];
+ if (msg->uidl && msg->received && !msg->deleted)
+ fprintf(fp, "%s\t%ld\n", msg->uidl, msg->recv_time);
+ }
+
+ if (fclose(fp) == EOF) FILE_OP_ERROR(path, "fclose");
+ g_free(path);
+
+ return 0;
+}
+
+static gint pop3_write_msg_to_file(const gchar *file, const gchar *data,
+ guint len)
+{
+ FILE *fp;
+ const gchar *prev, *cur;
+
+ g_return_val_if_fail(file != NULL, -1);
+
+ if ((fp = g_fopen(file, "wb")) == NULL) {
+ FILE_OP_ERROR(file, "fopen");
+ return -1;
+ }
+
+ if (change_file_mode_rw(fp, file) < 0)
+ FILE_OP_ERROR(file, "chmod");
+
+ /* +------------------+----------------+--------------------------+ *
+ * ^data ^prev ^cur data+len-1^ */
+
+ prev = data;
+ while ((cur = (gchar *)my_memmem(prev, len - (prev - data), "\r\n", 2))
+ != NULL) {
+ if ((cur > prev && fwrite(prev, cur - prev, 1, fp) < 1) ||
+ fputc('\n', fp) == EOF) {
+ FILE_OP_ERROR(file, "fwrite");
+ g_warning("can't write to file: %s\n", file);
+ fclose(fp);
+ g_unlink(file);
+ return -1;
+ }
+
+ if (cur == data + len - 1) {
+ prev = cur + 1;
+ break;
+ }
+
+ if (*(cur + 1) == '\n')
+ prev = cur + 2;
+ else
+ prev = cur + 1;
+
+ if (prev - data < len - 1 && *prev == '.' && *(prev + 1) == '.')
+ prev++;
+
+ if (prev - data >= len)
+ break;
+ }
+
+ if (prev - data < len &&
+ fwrite(prev, len - (prev - data), 1, fp) < 1) {
+ FILE_OP_ERROR(file, "fwrite");
+ g_warning("can't write to file: %s\n", file);
+ fclose(fp);
+ g_unlink(file);
+ return -1;
+ }
+ if (data[len - 1] != '\r' && data[len - 1] != '\n') {
+ if (fputc('\n', fp) == EOF) {
+ FILE_OP_ERROR(file, "fputc");
+ g_warning("can't write to file: %s\n", file);
+ fclose(fp);
+ g_unlink(file);
+ return -1;
+ }
+ }
+
+ if (fclose(fp) == EOF) {
+ FILE_OP_ERROR(file, "fclose");
+ g_unlink(file);
+ return -1;
+ }
+
+ return 0;
+}
+
+static Pop3State pop3_lookup_next(Pop3Session *session)
+{
+ Pop3MsgInfo *msg;
+ PrefsAccount *ac = session->ac_prefs;
+ gint size;
+ gboolean size_limit_over;
+
+ for (;;) {
+ msg = &session->msg[session->cur_msg];
+ size = msg->size;
+ size_limit_over =
+ (ac->enable_size_limit &&
+ ac->size_limit > 0 &&
+ size > ac->size_limit * 1024);
+
+ if (msg->recv_time == RECV_TIME_DELETE ||
+ (ac->rmmail &&
+ msg->recv_time != RECV_TIME_NONE &&
+ msg->recv_time != RECV_TIME_KEEP &&
+ session->current_time - msg->recv_time >=
+ ac->msg_leave_time * 24 * 60 * 60)) {
+ log_print(_("POP3: Deleting expired message %d\n"),
+ session->cur_msg);
+ pop3_delete_send(session);
+ return POP3_DELETE;
+ }
+
+ if (size_limit_over)
+ log_print
+ (_("POP3: Skipping message %d (%d bytes)\n"),
+ session->cur_msg, size);
+
+ if (size == 0 || msg->received || size_limit_over) {
+ session->cur_total_bytes += size;
+ if (session->cur_msg == session->count) {
+ pop3_logout_send(session);
+ return POP3_LOGOUT;
+ } else
+ session->cur_msg++;
+ } else
+ break;
+ }
+
+ pop3_retr_send(session);
+ return POP3_RETR;
+}
+
+static Pop3ErrorValue pop3_ok(Pop3Session *session, const gchar *msg)
+{
+ Pop3ErrorValue ok;
+
+ log_print("POP3< %s\n", msg);
+
+ if (!strncmp(msg, "+OK", 3))
+ ok = PS_SUCCESS;
+ else if (!strncmp(msg, "-ERR", 4)) {
+ if (strstr(msg + 4, "lock") ||
+ strstr(msg + 4, "Lock") ||
+ strstr(msg + 4, "LOCK") ||
+ strstr(msg + 4, "wait")) {
+ log_warning(_("mailbox is locked\n"));
+ ok = PS_LOCKBUSY;
+ } else if (strcasestr(msg + 4, "timeout")) {
+ log_warning(_("session timeout\n"));
+ ok = PS_ERROR;
+ } else {
+ switch (session->state) {
+#if USE_SSL
+ case POP3_STLS:
+ log_warning(_("can't start TLS session\n"));
+ ok = PS_ERROR;
+ break;
+#endif
+ case POP3_GETAUTH_USER:
+ case POP3_GETAUTH_PASS:
+ case POP3_GETAUTH_APOP:
+ log_warning(_("error occurred on authentication\n"));
+ ok = PS_AUTHFAIL;
+ break;
+ case POP3_GETRANGE_LAST:
+ case POP3_GETRANGE_UIDL:
+ log_warning(_("command not supported\n"));
+ ok = PS_NOTSUPPORTED;
+ break;
+ default:
+ log_warning(_("error occurred on POP3 session\n"));
+ ok = PS_ERROR;
+ }
+ }
+
+ g_free(session->error_msg);
+ session->error_msg = g_strdup(msg);
+ fprintf(stderr, "POP3: %s\n", msg);
+ } else
+ ok = PS_PROTOCOL;
+
+ session->error_val = ok;
+ return ok;
+}
+
+static gint pop3_session_recv_msg(Session *session, const gchar *msg)
+{
+ Pop3Session *pop3_session = POP3_SESSION(session);
+ Pop3ErrorValue val = PS_SUCCESS;
+ const gchar *body;
+
+ body = msg;
+ if (pop3_session->state != POP3_GETRANGE_UIDL_RECV &&
+ pop3_session->state != POP3_GETSIZE_LIST_RECV) {
+ val = pop3_ok(pop3_session, msg);
+ if (val != PS_SUCCESS) {
+ if (val != PS_NOTSUPPORTED) {
+ pop3_session->state = POP3_ERROR;
+ return -1;
+ }
+ }
+
+ if (*body == '+' || *body == '-')
+ body++;
+ while (g_ascii_isalpha(*body))
+ body++;
+ while (g_ascii_isspace(*body))
+ body++;
+ }
+
+ switch (pop3_session->state) {
+ case POP3_READY:
+ case POP3_GREETING:
+ pop3_greeting_recv(pop3_session, body);
+#if USE_SSL
+ if (pop3_session->ac_prefs->ssl_pop == SSL_STARTTLS)
+ pop3_stls_send(pop3_session);
+ else
+#endif
+ if (pop3_session->ac_prefs->use_apop_auth)
+ pop3_getauth_apop_send(pop3_session);
+ else
+ pop3_getauth_user_send(pop3_session);
+ break;
+#if USE_SSL
+ case POP3_STLS:
+ if (pop3_stls_recv(pop3_session) != PS_SUCCESS)
+ return -1;
+ if (pop3_session->ac_prefs->use_apop_auth)
+ pop3_getauth_apop_send(pop3_session);
+ else
+ pop3_getauth_user_send(pop3_session);
+ break;
+#endif
+ case POP3_GETAUTH_USER:
+ pop3_getauth_pass_send(pop3_session);
+ break;
+ case POP3_GETAUTH_PASS:
+ case POP3_GETAUTH_APOP:
+ pop3_getrange_stat_send(pop3_session);
+ break;
+ case POP3_GETRANGE_STAT:
+ if (pop3_getrange_stat_recv(pop3_session, body) < 0)
+ return -1;
+ if (pop3_session->count > 0)
+ pop3_getrange_uidl_send(pop3_session);
+ else
+ pop3_logout_send(pop3_session);
+ break;
+ case POP3_GETRANGE_LAST:
+ if (val == PS_NOTSUPPORTED)
+ pop3_session->error_val = PS_SUCCESS;
+ else if (pop3_getrange_last_recv(pop3_session, body) < 0)
+ return -1;
+ if (pop3_session->cur_msg > 0)
+ pop3_getsize_list_send(pop3_session);
+ else
+ pop3_logout_send(pop3_session);
+ break;
+ case POP3_GETRANGE_UIDL:
+ if (val == PS_NOTSUPPORTED) {
+ pop3_session->error_val = PS_SUCCESS;
+ pop3_getrange_last_send(pop3_session);
+ } else {
+ pop3_session->state = POP3_GETRANGE_UIDL_RECV;
+ session_recv_data(session, 0, ".\r\n");
+ }
+ break;
+ case POP3_GETSIZE_LIST:
+ pop3_session->state = POP3_GETSIZE_LIST_RECV;
+ session_recv_data(session, 0, ".\r\n");
+ break;
+ case POP3_RETR:
+ pop3_session->state = POP3_RETR_RECV;
+ session_recv_data(session, 0, ".\r\n");
+ break;
+ case POP3_DELETE:
+ pop3_delete_recv(pop3_session);
+ if (pop3_session->cur_msg == pop3_session->count)
+ pop3_logout_send(pop3_session);
+ else {
+ pop3_session->cur_msg++;
+ if (pop3_lookup_next(pop3_session) == POP3_ERROR)
+ return -1;
+ }
+ break;
+ case POP3_LOGOUT:
+ session_disconnect(session);
+ break;
+ case POP3_ERROR:
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static gint pop3_session_recv_data_finished(Session *session, guchar *data,
+ guint len)
+{
+ Pop3Session *pop3_session = POP3_SESSION(session);
+ Pop3ErrorValue val = PS_SUCCESS;
+
+ switch (pop3_session->state) {
+ case POP3_GETRANGE_UIDL_RECV:
+ val = pop3_getrange_uidl_recv(pop3_session, data, len);
+ if (val == PS_SUCCESS) {
+ if (pop3_session->new_msg_exist)
+ pop3_getsize_list_send(pop3_session);
+ else
+ pop3_logout_send(pop3_session);
+ } else
+ return -1;
+ break;
+ case POP3_GETSIZE_LIST_RECV:
+ val = pop3_getsize_list_recv(pop3_session, data, len);
+ if (val == PS_SUCCESS) {
+ if (pop3_lookup_next(pop3_session) == POP3_ERROR)
+ return -1;
+ } else
+ return -1;
+ break;
+ case POP3_RETR_RECV:
+ if (pop3_retr_recv(pop3_session, data, len) < 0)
+ return -1;
+
+ if (pop3_session->msg[pop3_session->cur_msg].recv_time
+ == RECV_TIME_DELETE ||
+ (pop3_session->ac_prefs->rmmail &&
+ pop3_session->ac_prefs->msg_leave_time == 0 &&
+ pop3_session->msg[pop3_session->cur_msg].recv_time
+ != RECV_TIME_KEEP))
+ pop3_delete_send(pop3_session);
+ else if (pop3_session->cur_msg == pop3_session->count)
+ pop3_logout_send(pop3_session);
+ else {
+ pop3_session->cur_msg++;
+ if (pop3_lookup_next(pop3_session) == POP3_ERROR)
+ return -1;
+ }
+ break;
+ case POP3_ERROR:
+ default:
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libsylph/pop.h b/libsylph/pop.h
new file mode 100644
index 00000000..515bc61b
--- /dev/null
+++ b/libsylph/pop.h
@@ -0,0 +1,153 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __POP_H__
+#define __POP_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <time.h>
+
+#include "session.h"
+#include "prefs_account.h"
+
+typedef struct _Pop3MsgInfo Pop3MsgInfo;
+typedef struct _Pop3Session Pop3Session;
+
+#define POP3_SESSION(obj) ((Pop3Session *)obj)
+
+typedef enum {
+ POP3_READY,
+ POP3_GREETING,
+#if USE_SSL
+ POP3_STLS,
+#endif
+ POP3_GETAUTH_USER,
+ POP3_GETAUTH_PASS,
+ POP3_GETAUTH_APOP,
+ POP3_GETRANGE_STAT,
+ POP3_GETRANGE_LAST,
+ POP3_GETRANGE_UIDL,
+ POP3_GETRANGE_UIDL_RECV,
+ POP3_GETSIZE_LIST,
+ POP3_GETSIZE_LIST_RECV,
+ POP3_RETR,
+ POP3_RETR_RECV,
+ POP3_DELETE,
+ POP3_LOGOUT,
+ POP3_ERROR,
+
+ N_POP3_STATE
+} Pop3State;
+
+typedef enum {
+ PS_SUCCESS = 0, /* command successful */
+ PS_NOMAIL = 1, /* no mail available */
+ PS_SOCKET = 2, /* socket I/O woes */
+ PS_AUTHFAIL = 3, /* user authorization failed */
+ PS_PROTOCOL = 4, /* protocol violation */
+ PS_SYNTAX = 5, /* command-line syntax error */
+ PS_IOERR = 6, /* file I/O error */
+ PS_ERROR = 7, /* protocol error */
+ PS_EXCLUDE = 8, /* client-side exclusion error */
+ PS_LOCKBUSY = 9, /* server responded lock busy */
+ PS_SMTP = 10, /* SMTP error */
+ PS_DNS = 11, /* fatal DNS error */
+ PS_BSMTP = 12, /* output batch could not be opened */
+ PS_MAXFETCH = 13, /* poll ended by fetch limit */
+
+ PS_NOTSUPPORTED = 14, /* command not supported */
+
+ /* leave space for more codes */
+
+ PS_CONTINUE = 128 /* more responses may follow */
+} Pop3ErrorValue;
+
+typedef enum {
+ RECV_TIME_NONE = 0,
+ RECV_TIME_RECEIVED = 1,
+ RECV_TIME_KEEP = 2,
+ RECV_TIME_DELETE = 3
+} RecvTime;
+
+typedef enum {
+ DROP_OK = 0,
+ DROP_DONT_RECEIVE = 1,
+ DROP_DELETE = 2,
+ DROP_ERROR = -1
+} Pop3DropValue;
+
+struct _Pop3MsgInfo
+{
+ gint size;
+ gchar *uidl;
+ time_t recv_time;
+ guint received : 1;
+ guint deleted : 1;
+};
+
+struct _Pop3Session
+{
+ Session session;
+
+ Pop3State state;
+
+ PrefsAccount *ac_prefs;
+
+ gchar *greeting;
+ gchar *user;
+ gchar *pass;
+ gint count;
+ gint total_bytes;
+ gint cur_msg;
+ gint cur_total_num;
+ gint cur_total_bytes;
+ gint cur_total_recv_bytes;
+
+ Pop3MsgInfo *msg;
+
+ GHashTable *uidl_table;
+
+ gboolean new_msg_exist;
+ gboolean uidl_is_valid;
+
+ time_t current_time;
+
+ Pop3ErrorValue error_val;
+ gchar *error_msg;
+
+ gpointer data;
+
+ /* virtual method to drop message */
+ gint (*drop_message) (Pop3Session *session,
+ const gchar *file);
+};
+
+#define POPBUFSIZE 512
+/* #define IDLEN 128 */
+#define IDLEN POPBUFSIZE
+
+Session *pop3_session_new (PrefsAccount *account);
+GHashTable *pop3_get_uidl_table (PrefsAccount *account);
+gint pop3_write_uidl_list (Pop3Session *session);
+
+#endif /* __POP_H__ */
diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c
new file mode 100644
index 00000000..b6d15fba
--- /dev/null
+++ b/libsylph/prefs_account.c
@@ -0,0 +1,254 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "prefs.h"
+#include "prefs_account.h"
+#include "customheader.h"
+#include "account.h"
+#include "utils.h"
+
+static PrefsAccount tmp_ac_prefs;
+
+static PrefParam param[] = {
+ /* Basic */
+ {"account_name", NULL, &tmp_ac_prefs.account_name, P_STRING},
+ {"is_default", "FALSE", &tmp_ac_prefs.is_default, P_BOOL},
+ {"name", NULL, &tmp_ac_prefs.name, P_STRING},
+ {"address", NULL, &tmp_ac_prefs.address, P_STRING},
+ {"organization", NULL, &tmp_ac_prefs.organization, P_STRING},
+ {"protocol", NULL, &tmp_ac_prefs.protocol, P_ENUM},
+ {"receive_server", NULL, &tmp_ac_prefs.recv_server, P_STRING},
+ {"smtp_server", NULL, &tmp_ac_prefs.smtp_server, P_STRING},
+ {"nntp_server", NULL, &tmp_ac_prefs.nntp_server, P_STRING},
+ {"use_nntp_auth", "FALSE", &tmp_ac_prefs.use_nntp_auth, P_BOOL},
+ {"user_id", "ENV_USER", &tmp_ac_prefs.userid, P_STRING},
+ {"password", NULL, &tmp_ac_prefs.passwd, P_STRING},
+ {"inbox", "inbox", &tmp_ac_prefs.inbox, P_STRING},
+
+ /* Receive */
+ {"use_apop_auth", "FALSE", &tmp_ac_prefs.use_apop_auth, P_BOOL},
+ {"remove_mail", "TRUE", &tmp_ac_prefs.rmmail, P_BOOL},
+ {"message_leave_time", "0", &tmp_ac_prefs.msg_leave_time, P_INT},
+ {"get_all_mail", "FALSE", &tmp_ac_prefs.getall, P_BOOL},
+ {"enable_size_limit", "FALSE", &tmp_ac_prefs.enable_size_limit, P_BOOL},
+ {"size_limit", "1024", &tmp_ac_prefs.size_limit, P_INT},
+ {"filter_on_receive", "TRUE", &tmp_ac_prefs.filter_on_recv, P_BOOL},
+ {"imap_auth_method", "0", &tmp_ac_prefs.imap_auth_type, P_ENUM},
+ {"max_nntp_articles", "300", &tmp_ac_prefs.max_nntp_articles, P_INT},
+ {"receive_at_get_all", "TRUE", &tmp_ac_prefs.recv_at_getall, P_BOOL},
+
+ /* Send */
+ {"add_date", "TRUE", &tmp_ac_prefs.add_date, P_BOOL},
+ {"generate_msgid", "TRUE", &tmp_ac_prefs.gen_msgid, P_BOOL},
+ {"add_custom_header", "FALSE", &tmp_ac_prefs.add_customhdr, P_BOOL},
+ {"use_smtp_auth", "FALSE", &tmp_ac_prefs.use_smtp_auth, P_BOOL},
+ {"smtp_auth_method", "0", &tmp_ac_prefs.smtp_auth_type, P_ENUM},
+ {"smtp_user_id", NULL, &tmp_ac_prefs.smtp_userid, P_STRING},
+ {"smtp_password", NULL, &tmp_ac_prefs.smtp_passwd, P_STRING},
+ {"pop_before_smtp", "FALSE", &tmp_ac_prefs.pop_before_smtp, P_BOOL},
+
+ /* Compose */
+ {"signature_type", "0", &tmp_ac_prefs.sig_type, P_ENUM},
+ {"signature_path", "~" G_DIR_SEPARATOR_S DEFAULT_SIGNATURE,
+ &tmp_ac_prefs.sig_path, P_STRING},
+ {"set_autocc", "FALSE", &tmp_ac_prefs.set_autocc, P_BOOL},
+ {"auto_cc", NULL, &tmp_ac_prefs.auto_cc, P_STRING},
+ {"set_autobcc", "FALSE", &tmp_ac_prefs.set_autobcc, P_BOOL},
+ {"auto_bcc", NULL, &tmp_ac_prefs.auto_bcc, P_STRING},
+ {"set_autoreplyto", "FALSE", &tmp_ac_prefs.set_autoreplyto, P_BOOL},
+ {"auto_replyto", NULL, &tmp_ac_prefs.auto_replyto, P_STRING},
+
+#if USE_GPGME
+ /* Privacy */
+ {"default_sign", "FALSE", &tmp_ac_prefs.default_sign, P_BOOL},
+ {"default_encrypt", "FALSE", &tmp_ac_prefs.default_encrypt, P_BOOL},
+ {"encrypt_reply", "TRUE", &tmp_ac_prefs.encrypt_reply, P_BOOL},
+ {"ascii_armored", "FALSE", &tmp_ac_prefs.ascii_armored, P_BOOL},
+ {"clearsign", "FALSE", &tmp_ac_prefs.clearsign, P_BOOL},
+ {"sign_key", NULL, &tmp_ac_prefs.sign_key, P_ENUM},
+ {"sign_key_id", NULL, &tmp_ac_prefs.sign_key_id, P_STRING},
+#endif /* USE_GPGME */
+
+#if USE_SSL
+ /* SSL */
+ {"ssl_pop", "0", &tmp_ac_prefs.ssl_pop, P_ENUM},
+ {"ssl_imap", "0", &tmp_ac_prefs.ssl_imap, P_ENUM},
+ {"ssl_nntp", "0", &tmp_ac_prefs.ssl_nntp, P_ENUM},
+ {"ssl_smtp", "0", &tmp_ac_prefs.ssl_smtp, P_ENUM},
+ {"use_nonblocking_ssl", "1", &tmp_ac_prefs.use_nonblocking_ssl, P_BOOL},
+#endif /* USE_SSL */
+
+ /* Advanced */
+ {"set_smtpport", "FALSE", &tmp_ac_prefs.set_smtpport, P_BOOL},
+ {"smtp_port", "25", &tmp_ac_prefs.smtpport, P_USHORT},
+ {"set_popport", "FALSE", &tmp_ac_prefs.set_popport, P_BOOL},
+ {"pop_port", "110", &tmp_ac_prefs.popport, P_USHORT},
+ {"set_imapport", "FALSE", &tmp_ac_prefs.set_imapport, P_BOOL},
+ {"imap_port", "143", &tmp_ac_prefs.imapport, P_USHORT},
+ {"set_nntpport", "FALSE", &tmp_ac_prefs.set_nntpport, P_BOOL},
+ {"nntp_port", "119", &tmp_ac_prefs.nntpport, P_USHORT},
+ {"set_domain", "FALSE", &tmp_ac_prefs.set_domain, P_BOOL},
+ {"domain", NULL, &tmp_ac_prefs.domain, P_STRING},
+ {"imap_directory", NULL, &tmp_ac_prefs.imap_dir, P_STRING},
+ {"set_sent_folder", "FALSE", &tmp_ac_prefs.set_sent_folder, P_BOOL},
+ {"sent_folder", NULL, &tmp_ac_prefs.sent_folder, P_STRING},
+ {"set_draft_folder", "FALSE", &tmp_ac_prefs.set_draft_folder, P_BOOL},
+ {"draft_folder", NULL, &tmp_ac_prefs.draft_folder, P_STRING},
+ {"set_trash_folder", "FALSE", &tmp_ac_prefs.set_trash_folder, P_BOOL},
+ {"trash_folder", NULL, &tmp_ac_prefs.trash_folder, P_STRING},
+
+ {NULL, NULL, NULL, P_OTHER}
+};
+
+static gint prefs_account_get_new_id(void);
+
+
+PrefsAccount *prefs_account_get_tmp_prefs(void)
+{
+ return &tmp_ac_prefs;
+}
+
+void prefs_account_set_tmp_prefs(PrefsAccount *ac_prefs)
+{
+ tmp_ac_prefs = *ac_prefs;
+}
+
+void prefs_account_apply_tmp_prefs(PrefsAccount *ac_prefs)
+{
+ *ac_prefs = tmp_ac_prefs;
+}
+
+PrefParam *prefs_account_get_params(void)
+{
+ return param;
+}
+
+PrefsAccount *prefs_account_new(void)
+{
+ PrefsAccount *ac_prefs;
+
+ ac_prefs = g_new0(PrefsAccount, 1);
+ memset(&tmp_ac_prefs, 0, sizeof(PrefsAccount));
+ prefs_set_default(param);
+ *ac_prefs = tmp_ac_prefs;
+ ac_prefs->account_id = prefs_account_get_new_id();
+
+ return ac_prefs;
+}
+
+void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
+{
+ const gchar *p = label;
+ gchar *rcpath;
+ gint id;
+
+ g_return_if_fail(ac_prefs != NULL);
+ g_return_if_fail(label != NULL);
+
+ memset(&tmp_ac_prefs, 0, sizeof(PrefsAccount));
+
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL);
+ prefs_read_config(param, label, rcpath, NULL);
+ g_free(rcpath);
+
+ *ac_prefs = tmp_ac_prefs;
+ while (*p && !g_ascii_isdigit(*p)) p++;
+ id = atoi(p);
+ if (id < 0) g_warning("wrong account id: %d\n", id);
+ ac_prefs->account_id = id;
+
+ if (ac_prefs->protocol == A_APOP) {
+ debug_print("converting protocol A_APOP to new prefs.\n");
+ ac_prefs->protocol = A_POP3;
+ ac_prefs->use_apop_auth = TRUE;
+ }
+
+ custom_header_read_config(ac_prefs);
+}
+
+void prefs_account_write_config_all(GList *account_list)
+{
+ GList *cur;
+ gchar *rcpath;
+ PrefFile *pfile;
+
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL);
+ if ((pfile = prefs_file_open(rcpath)) == NULL) {
+ g_free(rcpath);
+ return;
+ }
+ g_free(rcpath);
+
+ for (cur = account_list; cur != NULL; cur = cur->next) {
+ tmp_ac_prefs = *(PrefsAccount *)cur->data;
+ if (fprintf(pfile->fp, "[Account: %d]\n",
+ tmp_ac_prefs.account_id) <= 0 ||
+ prefs_file_write_param(pfile, param) < 0) {
+ g_warning(_("failed to write configuration to file\n"));
+ prefs_file_close_revert(pfile);
+ return;
+ }
+ if (cur->next) {
+ if (fputc('\n', pfile->fp) == EOF) {
+ FILE_OP_ERROR(rcpath, "fputc");
+ prefs_file_close_revert(pfile);
+ return;
+ }
+ }
+ }
+
+ if (prefs_file_close(pfile) < 0)
+ g_warning(_("failed to write configuration to file\n"));
+}
+
+void prefs_account_free(PrefsAccount *ac_prefs)
+{
+ if (!ac_prefs) return;
+
+ tmp_ac_prefs = *ac_prefs;
+ prefs_free(param);
+}
+
+static gint prefs_account_get_new_id(void)
+{
+ GList *ac_list;
+ PrefsAccount *ac;
+ static gint last_id = 0;
+
+ for (ac_list = account_get_list(); ac_list != NULL;
+ ac_list = ac_list->next) {
+ ac = (PrefsAccount *)ac_list->data;
+ if (last_id < ac->account_id)
+ last_id = ac->account_id;
+ }
+
+ return last_id + 1;
+}
diff --git a/libsylph/prefs_account.h b/libsylph/prefs_account.h
new file mode 100644
index 00000000..13ef8d94
--- /dev/null
+++ b/libsylph/prefs_account.h
@@ -0,0 +1,182 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PREFS_ACCOUNT_H__
+#define __PREFS_ACCOUNT_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+
+typedef struct _PrefsAccount PrefsAccount;
+
+#include "folder.h"
+#include "smtp.h"
+#include "prefs.h"
+
+typedef enum {
+ A_POP3,
+ A_APOP, /* deprecated */
+ A_RPOP, /* deprecated */
+ A_IMAP4,
+ A_NNTP,
+ A_LOCAL
+} RecvProtocol;
+
+typedef enum {
+ SIG_FILE,
+ SIG_COMMAND,
+ SIG_DIRECT
+} SigType;
+
+#if USE_GPGME
+typedef enum {
+ SIGN_KEY_DEFAULT,
+ SIGN_KEY_BY_FROM,
+ SIGN_KEY_CUSTOM
+} SignKeyType;
+#endif /* USE_GPGME */
+
+struct _PrefsAccount
+{
+ gchar *account_name;
+
+ /* Personal info */
+ gchar *name;
+ gchar *address;
+ gchar *organization;
+
+ /* Server info */
+ RecvProtocol protocol;
+ gchar *recv_server;
+ gchar *smtp_server;
+ gchar *nntp_server;
+ gboolean use_nntp_auth;
+ gchar *userid;
+ gchar *passwd;
+
+#if USE_SSL
+ /* SSL */
+ SSLType ssl_pop;
+ SSLType ssl_imap;
+ SSLType ssl_nntp;
+ SSLType ssl_smtp;
+ gboolean use_nonblocking_ssl;
+#endif /* USE_SSL */
+
+ /* Temporarily preserved password */
+ gchar *tmp_pass;
+
+ /* Receive */
+ gboolean use_apop_auth;
+ gboolean rmmail;
+ gint msg_leave_time;
+ gboolean getall;
+ gboolean recv_at_getall;
+ gboolean enable_size_limit;
+ gint size_limit;
+ gboolean filter_on_recv;
+ gchar *inbox;
+
+ gint imap_auth_type;
+ gint max_nntp_articles;
+
+ /* Send */
+ gboolean add_date;
+ gboolean gen_msgid;
+ gboolean add_customhdr;
+ gboolean use_smtp_auth;
+ SMTPAuthType smtp_auth_type;
+ gchar *smtp_userid;
+ gchar *smtp_passwd;
+
+ /* Temporarily preserved password */
+ gchar *tmp_smtp_pass;
+
+ gboolean pop_before_smtp;
+
+ GSList *customhdr_list;
+
+ /* Compose */
+ SigType sig_type;
+ gchar *sig_path;
+ gboolean set_autocc;
+ gchar *auto_cc;
+ gboolean set_autobcc;
+ gchar *auto_bcc;
+ gboolean set_autoreplyto;
+ gchar *auto_replyto;
+
+#if USE_GPGME
+ /* Privacy */
+ gboolean default_sign;
+ gboolean default_encrypt;
+ gboolean ascii_armored;
+ gboolean clearsign;
+ gboolean encrypt_reply;
+
+ SignKeyType sign_key;
+ gchar *sign_key_id;
+#endif /* USE_GPGME */
+
+ /* Advanced */
+ gboolean set_smtpport;
+ gushort smtpport;
+ gboolean set_popport;
+ gushort popport;
+ gboolean set_imapport;
+ gushort imapport;
+ gboolean set_nntpport;
+ gushort nntpport;
+ gboolean set_domain;
+ gchar *domain;
+
+ gchar *imap_dir;
+
+ gboolean set_sent_folder;
+ gchar *sent_folder;
+ gboolean set_draft_folder;
+ gchar *draft_folder;
+ gboolean set_trash_folder;
+ gchar *trash_folder;
+
+ /* Default or not */
+ gboolean is_default;
+ /* Unique account ID */
+ gint account_id;
+
+ RemoteFolder *folder;
+};
+
+PrefsAccount *prefs_account_new (void);
+
+PrefsAccount *prefs_account_get_tmp_prefs (void);
+void prefs_account_set_tmp_prefs (PrefsAccount *ac_prefs);
+void prefs_account_apply_tmp_prefs (PrefsAccount *ac_prefs);
+PrefParam *prefs_account_get_params (void);
+
+void prefs_account_read_config (PrefsAccount *ac_prefs,
+ const gchar *label);
+void prefs_account_write_config_all (GList *account_list);
+
+void prefs_account_free (PrefsAccount *ac_prefs);
+
+#endif /* __PREFS_ACCOUNT_H__ */
diff --git a/libsylph/prefs_common.c b/libsylph/prefs_common.c
new file mode 100644
index 00000000..eb1a1864
--- /dev/null
+++ b/libsylph/prefs_common.c
@@ -0,0 +1,429 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "prefs.h"
+#include "prefs_common.h"
+#include "filter.h"
+#include "codeconv.h"
+#include "utils.h"
+
+PrefsCommon prefs_common;
+
+static PrefParam param[] = {
+ /* Receive */
+ {"use_ext_inc", "FALSE", &prefs_common.use_extinc, P_BOOL},
+ {"ext_inc_path", DEFAULT_INC_PATH, &prefs_common.extinc_cmd, P_STRING},
+
+ {"inc_local", "FALSE", &prefs_common.inc_local, P_BOOL},
+ {"filter_on_inc_local", "TRUE", &prefs_common.filter_on_inc, P_BOOL},
+ {"spool_path", DEFAULT_SPOOL_PATH, &prefs_common.spool_path, P_STRING},
+
+ {"autochk_newmail", "FALSE", &prefs_common.autochk_newmail, P_BOOL},
+ {"autochk_interval", "10", &prefs_common.autochk_itv, P_INT},
+ {"check_on_startup", "FALSE", &prefs_common.chk_on_startup, P_BOOL},
+ {"scan_all_after_inc", "FALSE", &prefs_common.scan_all_after_inc,
+ P_BOOL},
+ {"enable_newmsg_notify", "FALSE", &prefs_common.enable_newmsg_notify,
+ P_BOOL},
+ {"newmsg_notify_command", NULL, &prefs_common.newmsg_notify_cmd,
+ P_STRING},
+
+ /* Send */
+ {"use_ext_sendmail", "FALSE", &prefs_common.use_extsend, P_BOOL},
+ {"ext_sendmail_cmd", DEFAULT_SENDMAIL_CMD, &prefs_common.extsend_cmd,
+ P_STRING},
+ {"save_message", "TRUE", &prefs_common.savemsg, P_BOOL},
+ {"filter_sent_message", "FALSE", &prefs_common.filter_sent, P_BOOL},
+
+ {"outgoing_charset", CS_AUTO, &prefs_common.outgoing_charset, P_STRING},
+ {"encoding_method", "0", &prefs_common.encoding_method, P_ENUM},
+
+ {"allow_jisx0201_kana", "FALSE", &prefs_common.allow_jisx0201_kana,
+ P_BOOL},
+
+ /* Compose */
+ {"auto_signature", "TRUE", &prefs_common.auto_sig, P_BOOL},
+ {"signature_separator", "-- ", &prefs_common.sig_sep, P_STRING},
+
+ {"auto_ext_editor", "FALSE", &prefs_common.auto_exteditor, P_BOOL},
+
+ {"undo_level", "50", &prefs_common.undolevels, P_INT},
+
+ {"linewrap_length", "72", &prefs_common.linewrap_len, P_INT},
+ {"linewrap_quotation", "FALSE", &prefs_common.linewrap_quote, P_BOOL},
+ {"linewrap_auto", "FALSE", &prefs_common.autowrap, P_BOOL},
+ {"linewrap_before_sending", "FALSE", &prefs_common.linewrap_at_send,
+ P_BOOL},
+
+ {"reply_with_quote", "TRUE", &prefs_common.reply_with_quote, P_BOOL},
+ {"reply_account_autoselect", "TRUE",
+ &prefs_common.reply_account_autosel, P_BOOL},
+ {"default_reply_list", "TRUE", &prefs_common.default_reply_list,
+ P_BOOL},
+
+ {"show_ruler", "TRUE", &prefs_common.show_ruler, P_BOOL},
+
+ /* Quote */
+ {"reply_quote_mark", "> ", &prefs_common.quotemark, P_STRING},
+ {"reply_quote_format", "On %d\\n%f wrote:\\n\\n%Q",
+ &prefs_common.quotefmt, P_STRING},
+
+ {"forward_quote_mark", "> ", &prefs_common.fw_quotemark, P_STRING},
+ {"forward_quote_format",
+ "\\n\\nBegin forwarded message:\\n\\n"
+ "?d{Date: %d\\n}?f{From: %f\\n}?t{To: %t\\n}?c{Cc: %c\\n}"
+ "?n{Newsgroups: %n\\n}?s{Subject: %s\\n}\\n\\n%M",
+ &prefs_common.fw_quotefmt, P_STRING},
+
+ /* Display */
+ {"message_font_name", DEFAULT_MESSAGE_FONT, &prefs_common.textfont,
+ P_STRING},
+
+ {"display_folder_unread_num", "TRUE",
+ &prefs_common.display_folder_unread, P_BOOL},
+ {"newsgroup_abbrev_len", "16", &prefs_common.ng_abbrev_len, P_INT},
+
+ {"translate_header", "TRUE", &prefs_common.trans_hdr, P_BOOL},
+
+ /* Display: Summary View */
+ {"enable_swap_from", "FALSE", &prefs_common.swap_from, P_BOOL},
+ {"date_format", "%y/%m/%d(%a) %H:%M", &prefs_common.date_format,
+ P_STRING},
+ {"expand_thread", "TRUE", &prefs_common.expand_thread, P_BOOL},
+
+ {"enable_rules_hint", "TRUE", &prefs_common.enable_rules_hint, P_BOOL},
+ {"bold_unread", "TRUE", &prefs_common.bold_unread, P_BOOL},
+
+ {"toolbar_style", "3", &prefs_common.toolbar_style, P_ENUM},
+ {"show_statusbar", "TRUE", &prefs_common.show_statusbar, P_BOOL},
+
+ {"folderview_vscrollbar_policy", "0",
+ &prefs_common.folderview_vscrollbar_policy, P_ENUM},
+
+ {"summary_col_show_mark", "TRUE",
+ &prefs_common.summary_col_visible[S_COL_MARK], P_BOOL},
+ {"summary_col_show_unread", "TRUE",
+ &prefs_common.summary_col_visible[S_COL_UNREAD], P_BOOL},
+ {"summary_col_show_mime", "TRUE",
+ &prefs_common.summary_col_visible[S_COL_MIME], P_BOOL},
+ {"summary_col_show_subject", "TRUE",
+ &prefs_common.summary_col_visible[S_COL_SUBJECT], P_BOOL},
+ {"summary_col_show_from", "TRUE",
+ &prefs_common.summary_col_visible[S_COL_FROM], P_BOOL},
+ {"summary_col_show_date", "TRUE",
+ &prefs_common.summary_col_visible[S_COL_DATE], P_BOOL},
+ {"summary_col_show_size", "TRUE",
+ &prefs_common.summary_col_visible[S_COL_SIZE], P_BOOL},
+ {"summary_col_show_number", "FALSE",
+ &prefs_common.summary_col_visible[S_COL_NUMBER], P_BOOL},
+
+ {"summary_col_pos_mark", "0",
+ &prefs_common.summary_col_pos[S_COL_MARK], P_INT},
+ {"summary_col_pos_unread", "1",
+ &prefs_common.summary_col_pos[S_COL_UNREAD], P_INT},
+ {"summary_col_pos_mime", "2",
+ &prefs_common.summary_col_pos[S_COL_MIME], P_INT},
+ {"summary_col_pos_subject", "3",
+ &prefs_common.summary_col_pos[S_COL_SUBJECT], P_INT},
+ {"summary_col_pos_from", "4",
+ &prefs_common.summary_col_pos[S_COL_FROM], P_INT},
+ {"summary_col_pos_date", "5",
+ &prefs_common.summary_col_pos[S_COL_DATE], P_INT},
+ {"summary_col_pos_size", "6",
+ &prefs_common.summary_col_pos[S_COL_SIZE], P_INT},
+ {"summary_col_pos_number", "7",
+ &prefs_common.summary_col_pos[S_COL_NUMBER], P_INT},
+
+ {"summary_col_size_mark", "10",
+ &prefs_common.summary_col_size[S_COL_MARK], P_INT},
+ {"summary_col_size_unread", "13",
+ &prefs_common.summary_col_size[S_COL_UNREAD], P_INT},
+ {"summary_col_size_mime", "10",
+ &prefs_common.summary_col_size[S_COL_MIME], P_INT},
+ {"summary_col_size_subject", "200",
+ &prefs_common.summary_col_size[S_COL_SUBJECT], P_INT},
+ {"summary_col_size_from", "120",
+ &prefs_common.summary_col_size[S_COL_FROM], P_INT},
+ {"summary_col_size_date", "118",
+ &prefs_common.summary_col_size[S_COL_DATE], P_INT},
+ {"summary_col_size_size", "45",
+ &prefs_common.summary_col_size[S_COL_SIZE], P_INT},
+ {"summary_col_size_number", "40",
+ &prefs_common.summary_col_size[S_COL_NUMBER], P_INT},
+
+ /* Widget size */
+ {"folderwin_x", "16", &prefs_common.folderwin_x, P_INT},
+ {"folderwin_y", "16", &prefs_common.folderwin_y, P_INT},
+ {"folderview_width", "179", &prefs_common.folderview_width, P_INT},
+ {"folderview_height", "450", &prefs_common.folderview_height, P_INT},
+ {"folderview_visible", "TRUE", &prefs_common.folderview_visible,
+ P_BOOL},
+
+ {"folder_col_folder", "150", &prefs_common.folder_col_folder, P_INT},
+ {"folder_col_new", "32", &prefs_common.folder_col_new, P_INT},
+ {"folder_col_unread", "32", &prefs_common.folder_col_unread, P_INT},
+ {"folder_col_total", "32", &prefs_common.folder_col_total, P_INT},
+
+ {"summaryview_width", "600", &prefs_common.summaryview_width, P_INT},
+ {"summaryview_height", "157", &prefs_common.summaryview_height, P_INT},
+
+ {"main_messagewin_x", "256", &prefs_common.main_msgwin_x, P_INT},
+ {"main_messagewin_y", "210", &prefs_common.main_msgwin_y, P_INT},
+ {"messageview_width", "600", &prefs_common.msgview_width, P_INT},
+ {"messageview_height", "300", &prefs_common.msgview_height, P_INT},
+ {"messageview_visible", "TRUE", &prefs_common.msgview_visible, P_BOOL},
+
+ {"mainview_x", "64", &prefs_common.mainview_x, P_INT},
+ {"mainview_y", "64", &prefs_common.mainview_y, P_INT},
+ {"mainview_width", "600", &prefs_common.mainview_width, P_INT},
+ {"mainview_height", "600", &prefs_common.mainview_height, P_INT},
+ {"mainwin_x", "64", &prefs_common.mainwin_x, P_INT},
+ {"mainwin_y", "64", &prefs_common.mainwin_y, P_INT},
+ {"mainwin_width", "800", &prefs_common.mainwin_width, P_INT},
+ {"mainwin_height", "600", &prefs_common.mainwin_height, P_INT},
+ {"messagewin_width", "600", &prefs_common.msgwin_width, P_INT},
+ {"messagewin_height", "540", &prefs_common.msgwin_height, P_INT},
+ {"sourcewin_width", "600", &prefs_common.sourcewin_width, P_INT},
+ {"sourcewin_height", "500", &prefs_common.sourcewin_height, P_INT},
+ {"compose_width", "600", &prefs_common.compose_width, P_INT},
+ {"compose_height", "560", &prefs_common.compose_height, P_INT},
+
+ /* Message */
+ {"enable_color", "TRUE", &prefs_common.enable_color, P_BOOL},
+
+ {"quote_level1_color", "179", &prefs_common.quote_level1_col, P_INT},
+ {"quote_level2_color", "179", &prefs_common.quote_level2_col, P_INT},
+ {"quote_level3_color", "179", &prefs_common.quote_level3_col, P_INT},
+ {"uri_color", "32512", &prefs_common.uri_col, P_INT},
+ {"signature_color", "0", &prefs_common.sig_col, P_USHORT},
+ {"recycle_quote_colors", "FALSE", &prefs_common.recycle_quote_colors,
+ P_BOOL},
+
+ {"convert_mb_alnum", "FALSE", &prefs_common.conv_mb_alnum, P_BOOL},
+ {"display_header_pane", "TRUE", &prefs_common.display_header_pane,
+ P_BOOL},
+ {"display_header", "TRUE", &prefs_common.display_header, P_BOOL},
+ {"render_html", "TRUE", &prefs_common.render_html, P_BOOL},
+ {"line_space", "2", &prefs_common.line_space, P_INT},
+
+ {"textview_cursor_visible", "FALSE",
+ &prefs_common.textview_cursor_visible, P_BOOL},
+
+ {"enable_smooth_scroll", "FALSE", &prefs_common.enable_smooth_scroll,
+ P_BOOL},
+ {"scroll_step", "1", &prefs_common.scroll_step, P_INT},
+ {"scroll_half_page", "FALSE", &prefs_common.scroll_halfpage, P_BOOL},
+
+ {"resize_image", "TRUE", &prefs_common.resize_image, P_BOOL},
+ {"inline_image", "TRUE", &prefs_common.inline_image, P_BOOL},
+
+ {"show_other_header", "FALSE", &prefs_common.show_other_header, P_BOOL},
+
+ /* MIME viewer */
+ {"mime_image_viewer", "display '%s'", &prefs_common.mime_image_viewer,
+ P_STRING},
+ {"mime_audio_player", "play '%s'", &prefs_common.mime_audio_player,
+ P_STRING},
+ {"mime_open_command", "gedit '%s'", &prefs_common.mime_open_cmd,
+ P_STRING},
+
+ /* Junk mail */
+ {"enable_junk", "FALSE", &prefs_common.enable_junk, P_BOOL},
+ {"junk_learn_command", "bogofilter -s -I", &prefs_common.junk_learncmd,
+ P_STRING},
+ {"nojunk_learn_command", "bogofilter -n -I",
+ &prefs_common.nojunk_learncmd, P_STRING},
+ {"junk_classify_command", "bogofilter -I",
+ &prefs_common.junk_classify_cmd, P_STRING},
+ {"junk_folder", NULL, &prefs_common.junk_folder, P_STRING},
+ {"filter_junk_on_receive", "FALSE", &prefs_common.filter_junk_on_recv,
+ P_BOOL},
+
+#if USE_GPGME
+ /* Privacy */
+ {"auto_check_signatures", "TRUE", &prefs_common.auto_check_signatures,
+ P_BOOL},
+ {"gpg_signature_popup", "FALSE", &prefs_common.gpg_signature_popup,
+ P_BOOL},
+ {"store_passphrase", "FALSE", &prefs_common.store_passphrase, P_BOOL},
+ {"store_passphrase_timeout", "0",
+ &prefs_common.store_passphrase_timeout, P_INT},
+#ifndef G_OS_WIN32
+ {"passphrase_grab", "FALSE", &prefs_common.passphrase_grab, P_BOOL},
+#endif /* G_OS_WIN32 */
+ {"gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL},
+#endif /* USE_GPGME */
+
+ /* Interface */
+ {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL},
+ {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL},
+
+ {"always_show_message_when_selected", "FALSE",
+ &prefs_common.always_show_msg, P_BOOL},
+ {"open_unread_on_enter", "FALSE", &prefs_common.open_unread_on_enter,
+ P_BOOL},
+ {"mark_as_read_on_new_window", "FALSE",
+ &prefs_common.mark_as_read_on_new_window, P_BOOL},
+ {"open_inbox_on_inc", "FALSE", &prefs_common.open_inbox_on_inc, P_BOOL},
+ {"immediate_execution", "TRUE", &prefs_common.immediate_exec, P_BOOL},
+ {"receive_dialog_mode", "1", &prefs_common.recv_dialog_mode, P_ENUM},
+ {"no_receive_error_panel", "FALSE", &prefs_common.no_recv_err_panel,
+ P_BOOL},
+ {"close_receive_dialog", "TRUE", &prefs_common.close_recv_dialog,
+ P_BOOL},
+
+#ifdef G_OS_WIN32
+ {"comply_gnome_hig", "FALSE", &prefs_common.comply_gnome_hig, P_BOOL},
+#else
+ {"comply_gnome_hig", "TRUE", &prefs_common.comply_gnome_hig, P_BOOL},
+#endif
+
+ /* Other */
+ {"uri_open_command", DEFAULT_BROWSER_CMD, &prefs_common.uri_cmd,
+ P_STRING},
+ {"print_command", "lpr %s", &prefs_common.print_cmd, P_STRING},
+ {"ext_editor_command", "gedit %s", &prefs_common.ext_editor_cmd,
+ P_STRING},
+
+ {"add_address_by_click", "FALSE", &prefs_common.add_address_by_click,
+ P_BOOL},
+
+ {"confirm_on_exit", "FALSE", &prefs_common.confirm_on_exit, P_BOOL},
+ {"clean_trash_on_exit", "FALSE", &prefs_common.clean_on_exit, P_BOOL},
+ {"ask_on_cleaning", "TRUE", &prefs_common.ask_on_clean, P_BOOL},
+ {"warn_queued_on_exit", "TRUE", &prefs_common.warn_queued_on_exit,
+ P_BOOL},
+
+ {"logwindow_line_limit", "1000", &prefs_common.logwin_line_limit,
+ P_INT},
+
+ /* Advanced */
+ {"strict_cache_check", "FALSE", &prefs_common.strict_cache_check,
+ P_BOOL},
+ {"io_timeout_secs", "60", &prefs_common.io_timeout_secs, P_INT},
+
+ {NULL, NULL, NULL, P_OTHER}
+};
+
+
+PrefParam *prefs_common_get_params(void)
+{
+ return param;
+}
+
+void prefs_common_read_config(void)
+{
+ FILE *fp;
+ gchar *path;
+ gchar buf[PREFSBUFSIZE];
+
+ path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+
+ prefs_read_config(param, "Common", path, NULL);
+ g_free(path);
+
+ prefs_common.online_mode = TRUE;
+
+ prefs_common_junk_filter_list_set();
+
+ path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMAND_HISTORY,
+ NULL);
+ if ((fp = g_fopen(path, "rb")) == NULL) {
+ if (ENOENT != errno) FILE_OP_ERROR(path, "fopen");
+ g_free(path);
+ return;
+ }
+ g_free(path);
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ g_strstrip(buf);
+ if (buf[0] == '\0') continue;
+ prefs_common.mime_open_cmd_history =
+ add_history(prefs_common.mime_open_cmd_history, buf);
+ }
+ fclose(fp);
+
+ prefs_common.mime_open_cmd_history =
+ g_list_reverse(prefs_common.mime_open_cmd_history);
+}
+
+void prefs_common_write_config(void)
+{
+ GList *cur;
+ FILE *fp;
+ gchar *path;
+
+ prefs_write_config(param, "Common", COMMON_RC);
+
+ path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMAND_HISTORY,
+ NULL);
+ if ((fp = g_fopen(path, "wb")) == NULL) {
+ FILE_OP_ERROR(path, "fopen");
+ g_free(path);
+ return;
+ }
+
+ for (cur = prefs_common.mime_open_cmd_history;
+ cur != NULL; cur = cur->next) {
+ fputs((gchar *)cur->data, fp);
+ fputc('\n', fp);
+ }
+
+ fclose(fp);
+ g_free(path);
+}
+
+void prefs_common_junk_filter_list_set(void)
+{
+ FilterRule *rule;
+ FilterCond *cond;
+ FilterAction *action;
+ GSList *cond_list = NULL, *action_list = NULL;
+
+ if (prefs_common.junk_fltlist) {
+ filter_rule_list_free(prefs_common.junk_fltlist);
+ prefs_common.junk_fltlist = NULL;
+ }
+
+ if (!prefs_common.junk_classify_cmd || !prefs_common.junk_folder)
+ return;
+
+ cond = filter_cond_new(FLT_COND_CMD_TEST, 0, 0, NULL,
+ prefs_common.junk_classify_cmd);
+ cond_list = g_slist_append(NULL, cond);
+ action = filter_action_new(FLT_ACTION_COPY, prefs_common.junk_folder);
+ action_list = g_slist_append(NULL, action);
+ action = filter_action_new(FLT_ACTION_DELETE, NULL);
+ action_list = g_slist_append(action_list, action);
+
+ rule = filter_rule_new(_("Junk mail filter"), FLT_OR,
+ cond_list, action_list);
+
+ prefs_common.junk_fltlist = g_slist_append(NULL, rule);
+}
diff --git a/libsylph/prefs_common.h b/libsylph/prefs_common.h
new file mode 100644
index 00000000..afc5f1d0
--- /dev/null
+++ b/libsylph/prefs_common.h
@@ -0,0 +1,257 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PREFS_COMMON_H__
+#define __PREFS_COMMON_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+
+typedef struct _PrefsCommon PrefsCommon;
+
+#include "enums.h"
+#include "prefs.h"
+
+typedef enum {
+ RECV_DIALOG_ALWAYS,
+ RECV_DIALOG_MANUAL,
+ RECV_DIALOG_NEVER
+} RecvDialogMode;
+
+typedef enum {
+ CTE_AUTO,
+ CTE_BASE64,
+ CTE_QUOTED_PRINTABLE,
+ CTE_8BIT
+} TransferEncodingMethod;
+
+struct _PrefsCommon
+{
+ /* Receive */
+ gboolean use_extinc;
+ gchar *extinc_cmd;
+ gboolean inc_local;
+ gboolean filter_on_inc;
+ gchar *spool_path;
+ gboolean scan_all_after_inc;
+ gboolean autochk_newmail;
+ gint autochk_itv;
+ gboolean chk_on_startup;
+ gboolean enable_newmsg_notify;
+ gchar *newmsg_notify_cmd;
+
+ /* Send */
+ gboolean use_extsend;
+ gchar *extsend_cmd;
+ gboolean savemsg;
+ gboolean filter_sent;
+ gchar *outgoing_charset;
+ TransferEncodingMethod encoding_method;
+
+ gboolean allow_jisx0201_kana;
+
+ /* Compose */
+ gboolean auto_sig;
+ gchar *sig_sep;
+ gint undolevels;
+ gint linewrap_len;
+ gboolean linewrap_quote;
+ gboolean autowrap;
+ gboolean linewrap_at_send;
+ gboolean auto_exteditor;
+ gboolean reply_account_autosel;
+ gboolean default_reply_list;
+ gboolean show_ruler;
+
+ /* Quote */
+ gboolean reply_with_quote;
+ gchar *quotemark;
+ gchar *quotefmt;
+ gchar *fw_quotemark;
+ gchar *fw_quotefmt;
+
+ /* Display */
+ gchar *textfont;
+
+ gboolean trans_hdr;
+ gboolean display_folder_unread;
+ gint ng_abbrev_len;
+
+ gboolean swap_from;
+ gboolean expand_thread;
+ gchar *date_format;
+
+ gboolean enable_rules_hint;
+ gboolean bold_unread;
+
+ ToolbarStyle toolbar_style;
+ gboolean show_statusbar;
+
+ gint folderview_vscrollbar_policy;
+
+ /* Summary columns visibility, position and size */
+ gboolean summary_col_visible[N_SUMMARY_COLS];
+ gint summary_col_pos[N_SUMMARY_COLS];
+ gint summary_col_size[N_SUMMARY_COLS];
+
+ /* Widget visibility, position and size */
+ gint folderwin_x;
+ gint folderwin_y;
+ gint folderview_width;
+ gint folderview_height;
+ gboolean folderview_visible;
+
+ gint folder_col_folder;
+ gint folder_col_new;
+ gint folder_col_unread;
+ gint folder_col_total;
+
+ gint summaryview_width;
+ gint summaryview_height;
+
+ gint main_msgwin_x;
+ gint main_msgwin_y;
+ gint msgview_width;
+ gint msgview_height;
+ gboolean msgview_visible;
+
+ gint mainview_x;
+ gint mainview_y;
+ gint mainview_width;
+ gint mainview_height;
+ gint mainwin_x;
+ gint mainwin_y;
+ gint mainwin_width;
+ gint mainwin_height;
+
+ gint msgwin_width;
+ gint msgwin_height;
+
+ gint sourcewin_width;
+ gint sourcewin_height;
+
+ gint compose_width;
+ gint compose_height;
+
+ /* Message */
+ gboolean enable_color;
+ gint quote_level1_col;
+ gint quote_level2_col;
+ gint quote_level3_col;
+ gint uri_col;
+ gushort sig_col;
+ gboolean recycle_quote_colors;
+ gboolean conv_mb_alnum;
+ gboolean display_header_pane;
+ gboolean display_header;
+ gint line_space;
+ gboolean render_html;
+ gboolean textview_cursor_visible;
+ gboolean enable_smooth_scroll;
+ gint scroll_step;
+ gboolean scroll_halfpage;
+
+ gboolean resize_image;
+ gboolean inline_image;
+
+ gchar *force_charset;
+
+ gboolean show_other_header;
+ GSList *disphdr_list;
+
+ /* MIME viewer */
+ gchar *mime_image_viewer;
+ gchar *mime_audio_player;
+ gchar *mime_open_cmd;
+
+ GList *mime_open_cmd_history;
+
+ /* Junk Mail */
+ gboolean enable_junk;
+ gchar *junk_learncmd;
+ gchar *nojunk_learncmd;
+ gchar *junk_classify_cmd;
+ gchar *junk_folder;
+ gboolean filter_junk_on_recv;
+
+#if USE_GPGME
+ /* Privacy */
+ gboolean auto_check_signatures;
+ gboolean gpg_signature_popup;
+ gboolean store_passphrase;
+ gint store_passphrase_timeout;
+ gboolean passphrase_grab;
+ gboolean gpg_warning;
+#endif /* USE_GPGME */
+
+ /* Interface */
+ gboolean sep_folder;
+ gboolean sep_msg;
+ gboolean always_show_msg;
+ gboolean open_unread_on_enter;
+ gboolean mark_as_read_on_new_window;
+ gboolean open_inbox_on_inc;
+ gboolean immediate_exec;
+ RecvDialogMode recv_dialog_mode;
+ gboolean no_recv_err_panel;
+ gboolean close_recv_dialog;
+ gboolean comply_gnome_hig;
+
+ /* Other */
+ gchar *uri_cmd;
+ gchar *print_cmd;
+ gchar *ext_editor_cmd;
+
+ gboolean add_address_by_click;
+
+ gboolean confirm_on_exit;
+ gboolean clean_on_exit;
+ gboolean ask_on_clean;
+ gboolean warn_queued_on_exit;
+
+ gint logwin_line_limit;
+
+ /* Advanced */
+ gboolean strict_cache_check;
+ gint io_timeout_secs;
+
+ /* Filtering */
+ GSList *fltlist;
+ GSList *junk_fltlist;
+
+ /* Actions */
+ GSList *actions_list;
+
+ /* Online / Offline */
+ gboolean online_mode;
+};
+
+extern PrefsCommon prefs_common;
+
+PrefParam *prefs_common_get_params (void);
+
+void prefs_common_read_config (void);
+void prefs_common_write_config (void);
+
+void prefs_common_junk_filter_list_set (void);
+
+#endif /* __PREFS_COMMON_H__ */
diff --git a/libsylph/procheader.c b/libsylph/procheader.c
new file mode 100644
index 00000000..4ca1490c
--- /dev/null
+++ b/libsylph/procheader.c
@@ -0,0 +1,799 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#include "procheader.h"
+#include "procmsg.h"
+#include "codeconv.h"
+#include "prefs_common.h"
+#include "utils.h"
+
+#define BUFFSIZE 8192
+
+gint procheader_get_one_field(gchar *buf, size_t len, FILE *fp,
+ HeaderEntry hentry[])
+{
+ gint nexthead;
+ gint hnum = 0;
+ HeaderEntry *hp = NULL;
+
+ if (hentry != NULL) {
+ /* skip non-required headers */
+ do {
+ do {
+ if (fgets(buf, len, fp) == NULL)
+ return -1;
+ if (buf[0] == '\r' || buf[0] == '\n')
+ return -1;
+ } while (buf[0] == ' ' || buf[0] == '\t');
+
+ for (hp = hentry, hnum = 0; hp->name != NULL;
+ hp++, hnum++) {
+ if (!g_ascii_strncasecmp(hp->name, buf,
+ strlen(hp->name)))
+ break;
+ }
+ } while (hp->name == NULL);
+ } else {
+ if (fgets(buf, len, fp) == NULL) return -1;
+ if (buf[0] == '\r' || buf[0] == '\n') return -1;
+ }
+
+ /* unfold the specified folded line */
+ if (hp && hp->unfold) {
+ gboolean folded = FALSE;
+ gchar *bufp = buf + strlen(buf);
+
+ for (; bufp > buf &&
+ (*(bufp - 1) == '\n' || *(bufp - 1) == '\r');
+ bufp--)
+ *(bufp - 1) = '\0';
+
+ while (1) {
+ nexthead = fgetc(fp);
+
+ /* folded */
+ if (nexthead == ' ' || nexthead == '\t')
+ folded = TRUE;
+ else if (nexthead == EOF)
+ break;
+ else if (folded == TRUE) {
+ if ((len - (bufp - buf)) <= 2) break;
+
+ if (nexthead == '\n') {
+ folded = FALSE;
+ continue;
+ }
+
+ /* replace return code on the tail end
+ with space */
+ *bufp++ = ' ';
+ *bufp++ = nexthead;
+ *bufp = '\0';
+
+ /* concatenate next line */
+ if (fgets(bufp, len - (bufp - buf), fp)
+ == NULL) break;
+ bufp += strlen(bufp);
+
+ for (; bufp > buf &&
+ (*(bufp - 1) == '\n' || *(bufp - 1) == '\r');
+ bufp--)
+ *(bufp - 1) = '\0';
+
+ folded = FALSE;
+ } else {
+ ungetc(nexthead, fp);
+ break;
+ }
+ }
+
+ return hnum;
+ }
+
+ while (1) {
+ nexthead = fgetc(fp);
+ if (nexthead == ' ' || nexthead == '\t') {
+ size_t buflen = strlen(buf);
+
+ /* concatenate next line */
+ if ((len - buflen) > 2) {
+ gchar *p = buf + buflen;
+
+ *p++ = nexthead;
+ *p = '\0';
+ buflen++;
+ if (fgets(p, len - buflen, fp) == NULL)
+ break;
+ } else
+ break;
+ } else {
+ if (nexthead != EOF)
+ ungetc(nexthead, fp);
+ break;
+ }
+ }
+
+ /* remove trailing return code */
+ strretchomp(buf);
+
+ return hnum;
+}
+
+gchar *procheader_get_unfolded_line(gchar *buf, size_t len, FILE *fp)
+{
+ gboolean folded = FALSE;
+ gint nexthead;
+ gchar *bufp;
+
+ if (fgets(buf, len, fp) == NULL) return NULL;
+ if (buf[0] == '\r' || buf[0] == '\n') return NULL;
+ bufp = buf + strlen(buf);
+
+ for (; bufp > buf &&
+ (*(bufp - 1) == '\n' || *(bufp - 1) == '\r');
+ bufp--)
+ *(bufp - 1) = '\0';
+
+ while (1) {
+ nexthead = fgetc(fp);
+
+ /* folded */
+ if (nexthead == ' ' || nexthead == '\t')
+ folded = TRUE;
+ else if (nexthead == EOF)
+ break;
+ else if (folded == TRUE) {
+ if ((len - (bufp - buf)) <= 2) break;
+
+ if (nexthead == '\n') {
+ folded = FALSE;
+ continue;
+ }
+
+ /* replace return code on the tail end
+ with space */
+ *bufp++ = ' ';
+ *bufp++ = nexthead;
+ *bufp = '\0';
+
+ /* concatenate next line */
+ if (fgets(bufp, len - (bufp - buf), fp)
+ == NULL) break;
+ bufp += strlen(bufp);
+
+ for (; bufp > buf &&
+ (*(bufp - 1) == '\n' || *(bufp - 1) == '\r');
+ bufp--)
+ *(bufp - 1) = '\0';
+
+ folded = FALSE;
+ } else {
+ ungetc(nexthead, fp);
+ break;
+ }
+ }
+
+ /* remove trailing return code */
+ strretchomp(buf);
+
+ return buf;
+}
+
+GSList *procheader_get_header_list_from_file(const gchar *file)
+{
+ FILE *fp;
+ GSList *hlist;
+
+ if ((fp = g_fopen(file, "rb")) == NULL) {
+ FILE_OP_ERROR(file, "fopen");
+ return NULL;
+ }
+
+ hlist = procheader_get_header_list(fp);
+
+ fclose(fp);
+ return hlist;
+}
+
+GSList *procheader_get_header_list(FILE *fp)
+{
+ gchar buf[BUFFSIZE];
+ gchar *p;
+ GSList *hlist = NULL;
+ Header *header;
+
+ g_return_val_if_fail(fp != NULL, NULL);
+
+ while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) {
+ if (*buf == ':') continue;
+ for (p = buf; *p && *p != ' '; p++) {
+ if (*p == ':') {
+ header = g_new(Header, 1);
+ header->name = g_strndup(buf, p - buf);
+ p++;
+ while (*p == ' ' || *p == '\t') p++;
+ header->body = conv_unmime_header(p, NULL);
+
+ hlist = g_slist_append(hlist, header);
+ break;
+ }
+ }
+ }
+
+ return hlist;
+}
+
+GSList *procheader_add_header_list(GSList *hlist, const gchar *header_name,
+ const gchar *body)
+{
+ Header *header;
+
+ g_return_val_if_fail(header_name != NULL, hlist);
+
+ header = g_new(Header, 1);
+ header->name = g_strdup(header_name);
+ header->body = g_strdup(body);
+
+ return g_slist_append(hlist, header);
+}
+
+GSList *procheader_merge_header_list(GSList *hlist1, GSList *hlist2)
+{
+ GSList *cur;
+
+ for (cur = hlist2; cur != NULL; cur = cur->next) {
+ Header *header = (Header *)cur->data;
+ if (procheader_find_header_list(hlist1, header->name) < 0)
+ hlist1 = g_slist_append(hlist1, header);
+ }
+
+ return hlist1;
+}
+
+gint procheader_find_header_list(GSList *hlist, const gchar *header_name)
+{
+ GSList *cur;
+ gint index = 0;
+ Header *header;
+
+ g_return_val_if_fail(header_name != NULL, -1);
+
+ for (cur = hlist; cur != NULL; cur = cur->next, index++) {
+ header = (Header *)cur->data;
+ if (g_ascii_strcasecmp(header->name, header_name) == 0)
+ return index;
+ }
+
+ return -1;
+}
+
+GPtrArray *procheader_get_header_array(FILE *fp, const gchar *encoding)
+{
+ gchar buf[BUFFSIZE];
+ gchar *p;
+ GPtrArray *headers;
+ Header *header;
+
+ g_return_val_if_fail(fp != NULL, NULL);
+
+ headers = g_ptr_array_new();
+
+ while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) {
+ if (*buf == ':') continue;
+ for (p = buf; *p && *p != ' '; p++) {
+ if (*p == ':') {
+ header = g_new(Header, 1);
+ header->name = g_strndup(buf, p - buf);
+ p++;
+ while (*p == ' ' || *p == '\t') p++;
+ header->body = conv_unmime_header(p, encoding);
+
+ g_ptr_array_add(headers, header);
+ break;
+ }
+ }
+ }
+
+ return headers;
+}
+
+GPtrArray *procheader_get_header_array_asis(FILE *fp, const gchar *encoding)
+{
+ gchar buf[BUFFSIZE];
+ gchar *p;
+ GPtrArray *headers;
+ Header *header;
+
+ g_return_val_if_fail(fp != NULL, NULL);
+
+ headers = g_ptr_array_new();
+
+ while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
+ if (*buf == ':') continue;
+ for (p = buf; *p && *p != ' '; p++) {
+ if (*p == ':') {
+ header = g_new(Header, 1);
+ header->name = g_strndup(buf, p - buf);
+ p++;
+ header->body = conv_unmime_header(p, encoding);
+
+ g_ptr_array_add(headers, header);
+ break;
+ }
+ }
+ }
+
+ return headers;
+}
+
+void procheader_header_list_destroy(GSList *hlist)
+{
+ Header *header;
+
+ while (hlist != NULL) {
+ header = hlist->data;
+ procheader_header_free(header);
+ hlist = g_slist_remove(hlist, header);
+ }
+}
+
+void procheader_header_array_destroy(GPtrArray *harray)
+{
+ gint i;
+ Header *header;
+
+ for (i = 0; i < harray->len; i++) {
+ header = g_ptr_array_index(harray, i);
+ procheader_header_free(header);
+ }
+
+ g_ptr_array_free(harray, TRUE);
+}
+
+void procheader_header_free(Header *header)
+{
+ if (!header) return;
+
+ g_free(header->name);
+ g_free(header->body);
+ g_free(header);
+}
+
+void procheader_get_header_fields(FILE *fp, HeaderEntry hentry[])
+{
+ gchar buf[BUFFSIZE];
+ HeaderEntry *hp;
+ gint hnum;
+ gchar *p;
+
+ if (hentry == NULL) return;
+
+ while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
+ != -1) {
+ hp = hentry + hnum;
+
+ p = buf + strlen(hp->name);
+ while (*p == ' ' || *p == '\t') p++;
+
+ if (hp->body == NULL)
+ hp->body = g_strdup(p);
+ else if (!g_ascii_strcasecmp(hp->name, "To:") ||
+ !g_ascii_strcasecmp(hp->name, "Cc:")) {
+ gchar *tp = hp->body;
+ hp->body = g_strconcat(tp, ", ", p, NULL);
+ g_free(tp);
+ }
+ }
+}
+
+MsgInfo *procheader_parse_file(const gchar *file, MsgFlags flags,
+ gboolean full)
+{
+ struct stat s;
+ FILE *fp;
+ MsgInfo *msginfo;
+
+ if (g_stat(file, &s) < 0) {
+ FILE_OP_ERROR(file, "stat");
+ return NULL;
+ }
+ if (!S_ISREG(s.st_mode))
+ return NULL;
+
+ if ((fp = g_fopen(file, "rb")) == NULL) {
+ FILE_OP_ERROR(file, "fopen");
+ return NULL;
+ }
+
+ msginfo = procheader_parse_stream(fp, flags, full);
+ fclose(fp);
+
+ if (msginfo) {
+ msginfo->size = s.st_size;
+ msginfo->mtime = s.st_mtime;
+ }
+
+ return msginfo;
+}
+
+MsgInfo *procheader_parse_str(const gchar *str, MsgFlags flags, gboolean full)
+{
+ FILE *fp;
+ MsgInfo *msginfo;
+
+ if ((fp = str_open_as_stream(str)) == NULL)
+ return NULL;
+
+ msginfo = procheader_parse_stream(fp, flags, full);
+ fclose(fp);
+ return msginfo;
+}
+
+enum
+{
+ H_DATE = 0,
+ H_FROM = 1,
+ H_TO = 2,
+ H_NEWSGROUPS = 3,
+ H_SUBJECT = 4,
+ H_MSG_ID = 5,
+ H_REFERENCES = 6,
+ H_IN_REPLY_TO = 7,
+ H_CONTENT_TYPE = 8,
+ H_SEEN = 9,
+ H_CC = 10,
+ H_X_FACE = 11
+};
+
+MsgInfo *procheader_parse_stream(FILE *fp, MsgFlags flags, gboolean full)
+{
+ static HeaderEntry hentry_full[] = {{"Date:", NULL, FALSE},
+ {"From:", NULL, TRUE},
+ {"To:", NULL, TRUE},
+ {"Newsgroups:", NULL, TRUE},
+ {"Subject:", NULL, TRUE},
+ {"Message-Id:", NULL, FALSE},
+ {"References:", NULL, FALSE},
+ {"In-Reply-To:", NULL, FALSE},
+ {"Content-Type:", NULL, FALSE},
+ {"Seen:", NULL, FALSE},
+ {"Cc:", NULL, TRUE},
+ {"X-Face:", NULL, FALSE},
+ {NULL, NULL, FALSE}};
+
+ static HeaderEntry hentry_short[] = {{"Date:", NULL, FALSE},
+ {"From:", NULL, TRUE},
+ {"To:", NULL, TRUE},
+ {"Newsgroups:", NULL, TRUE},
+ {"Subject:", NULL, TRUE},
+ {"Message-Id:", NULL, FALSE},
+ {"References:", NULL, FALSE},
+ {"In-Reply-To:", NULL, FALSE},
+ {"Content-Type:", NULL, FALSE},
+ {"Seen:", NULL, FALSE},
+ {NULL, NULL, FALSE}};
+
+ MsgInfo *msginfo;
+ gchar buf[BUFFSIZE];
+ gchar *p;
+ gchar *hp;
+ HeaderEntry *hentry;
+ gint hnum;
+ gchar *from = NULL, *to = NULL, *subject = NULL, *cc = NULL;
+ gchar *charset = NULL;
+
+ hentry = full ? hentry_full : hentry_short;
+
+ if (MSG_IS_QUEUED(flags)) {
+ while (fgets(buf, sizeof(buf), fp) != NULL)
+ if (buf[0] == '\r' || buf[0] == '\n') break;
+ }
+
+ msginfo = g_new0(MsgInfo, 1);
+ msginfo->flags = flags;
+ msginfo->references = NULL;
+ msginfo->inreplyto = NULL;
+
+ while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
+ != -1) {
+ hp = buf + strlen(hentry[hnum].name);
+ while (*hp == ' ' || *hp == '\t') hp++;
+
+ switch (hnum) {
+ case H_DATE:
+ if (msginfo->date) break;
+ msginfo->date_t =
+ procheader_date_parse(NULL, hp, 0);
+ msginfo->date = g_strdup(hp);
+ break;
+ case H_FROM:
+ if (from) break;
+ from = g_strdup(hp);
+ break;
+ case H_TO:
+ if (to) {
+ p = to;
+ to = g_strconcat(p, ", ", hp, NULL);
+ g_free(p);
+ } else
+ to = g_strdup(hp);
+ break;
+ case H_NEWSGROUPS:
+ if (msginfo->newsgroups) {
+ p = msginfo->newsgroups;
+ msginfo->newsgroups =
+ g_strconcat(p, ",", hp, NULL);
+ g_free(p);
+ } else
+ msginfo->newsgroups = g_strdup(buf + 12);
+ break;
+ case H_SUBJECT:
+ if (msginfo->subject) break;
+ subject = g_strdup(hp);
+ break;
+ case H_MSG_ID:
+ if (msginfo->msgid) break;
+
+ extract_parenthesis(hp, '<', '>');
+ remove_space(hp);
+ msginfo->msgid = g_strdup(hp);
+ break;
+ case H_REFERENCES:
+ msginfo->references =
+ references_list_prepend(msginfo->references,
+ hp);
+ break;
+ case H_IN_REPLY_TO:
+ if (msginfo->inreplyto) break;
+
+ eliminate_parenthesis(hp, '(', ')');
+ if ((p = strrchr(hp, '<')) != NULL &&
+ strchr(p + 1, '>') != NULL) {
+ extract_parenthesis(p, '<', '>');
+ remove_space(p);
+ if (*p != '\0')
+ msginfo->inreplyto = g_strdup(p);
+ }
+ break;
+ case H_CONTENT_TYPE:
+ if (!g_ascii_strncasecmp(hp, "multipart", 9)) {
+ MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME);
+ } else if (!charset) {
+ procmime_scan_content_type_str
+ (hp, NULL, &charset, NULL, NULL);
+ }
+ break;
+ case H_SEEN:
+ /* mnews Seen header */
+ MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_NEW|MSG_UNREAD);
+ break;
+ case H_CC:
+ if (cc) {
+ p = cc;
+ cc = g_strconcat(p, ", ", hp, NULL);
+ g_free(p);
+ } else
+ cc = g_strdup(hp);
+ break;
+ case H_X_FACE:
+ if (msginfo->xface) break;
+ msginfo->xface = g_strdup(hp);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (from) {
+ msginfo->from = conv_unmime_header(from, charset);
+ msginfo->fromname = procheader_get_fromname(msginfo->from);
+ g_free(from);
+ }
+ if (to) {
+ msginfo->to = conv_unmime_header(to, charset);
+ g_free(to);
+ }
+ if (subject) {
+ msginfo->subject = conv_unmime_header(subject, charset);
+ g_free(subject);
+ }
+ if (cc) {
+ msginfo->cc = conv_unmime_header(cc, charset);
+ g_free(cc);
+ }
+
+ if (!msginfo->inreplyto && msginfo->references)
+ msginfo->inreplyto =
+ g_strdup((gchar *)msginfo->references->data);
+
+ g_free(charset);
+
+ return msginfo;
+}
+
+gchar *procheader_get_fromname(const gchar *str)
+{
+ gchar *tmp, *name;
+
+ Xstrdup_a(tmp, str, return NULL);
+
+ if (*tmp == '\"') {
+ extract_quote(tmp, '\"');
+ g_strstrip(tmp);
+ } else if (strchr(tmp, '<')) {
+ eliminate_parenthesis(tmp, '<', '>');
+ g_strstrip(tmp);
+ if (*tmp == '\0') {
+ strcpy(tmp, str);
+ extract_parenthesis(tmp, '<', '>');
+ g_strstrip(tmp);
+ }
+ } else if (strchr(tmp, '(')) {
+ extract_parenthesis(tmp, '(', ')');
+ g_strstrip(tmp);
+ }
+
+ if (*tmp == '\0')
+ name = g_strdup(str);
+ else
+ name = g_strdup(tmp);
+
+ return name;
+}
+
+static gint procheader_scan_date_string(const gchar *str,
+ gchar *weekday, gint *day,
+ gchar *month, gint *year,
+ gint *hh, gint *mm, gint *ss,
+ gchar *zone)
+{
+ gint result;
+
+ result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d %5s",
+ weekday, day, month, year, hh, mm, ss, zone);
+ if (result == 8) return 0;
+
+ result = sscanf(str, "%3s,%d %9s %d %2d:%2d:%2d %5s",
+ weekday, day, month, year, hh, mm, ss, zone);
+ if (result == 8) return 0;
+
+ result = sscanf(str, "%d %9s %d %2d:%2d:%2d %5s",
+ day, month, year, hh, mm, ss, zone);
+ if (result == 7) return 0;
+
+ *zone = '\0';
+ result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d",
+ weekday, day, month, year, hh, mm, ss);
+ if (result == 7) return 0;
+
+ result = sscanf(str, "%d %9s %d %2d:%2d:%2d",
+ day, month, year, hh, mm, ss);
+ if (result == 6) return 0;
+
+ *ss = 0;
+ result = sscanf(str, "%10s %d %9s %d %2d:%2d %5s",
+ weekday, day, month, year, hh, mm, zone);
+ if (result == 7) return 0;
+
+ result = sscanf(str, "%d %9s %d %2d:%2d %5s",
+ day, month, year, hh, mm, zone);
+ if (result == 6) return 0;
+
+ *zone = '\0';
+ result = sscanf(str, "%10s %d %9s %d %2d:%2d",
+ weekday, day, month, year, hh, mm);
+ if (result == 6) return 0;
+
+ result = sscanf(str, "%d %9s %d %2d:%2d",
+ day, month, year, hh, mm);
+ if (result == 5) return 0;
+
+ return -1;
+}
+
+time_t procheader_date_parse(gchar *dest, const gchar *src, gint len)
+{
+ static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
+ gchar weekday[11];
+ gint day;
+ gchar month[10];
+ gint year;
+ gint hh, mm, ss;
+ gchar zone[6];
+ GDateMonth dmonth = G_DATE_BAD_MONTH;
+ struct tm t;
+ gchar *p;
+ time_t timer;
+ time_t tz_offset;
+
+ if (procheader_scan_date_string(src, weekday, &day, month, &year,
+ &hh, &mm, &ss, zone) < 0) {
+ if (dest && len > 0)
+ strncpy2(dest, src, len);
+ return 0;
+ }
+
+ /* Y2K compliant :) */
+ if (year < 1000) {
+ if (year < 50)
+ year += 2000;
+ else
+ year += 1900;
+ }
+
+ month[3] = '\0';
+ for (p = monthstr; *p != '\0'; p += 3) {
+ if (!g_ascii_strncasecmp(p, month, 3)) {
+ dmonth = (gint)(p - monthstr) / 3 + 1;
+ break;
+ }
+ }
+
+ t.tm_sec = ss;
+ t.tm_min = mm;
+ t.tm_hour = hh;
+ t.tm_mday = day;
+ t.tm_mon = dmonth - 1;
+ t.tm_year = year - 1900;
+ t.tm_wday = 0;
+ t.tm_yday = 0;
+ t.tm_isdst = -1;
+
+ timer = mktime(&t);
+ tz_offset = remote_tzoffset_sec(zone);
+ if (tz_offset != -1)
+ timer += tzoffset_sec(&timer) - tz_offset;
+
+ if (dest)
+ procheader_date_get_localtime(dest, len, timer);
+
+ return timer;
+}
+
+void procheader_date_get_localtime(gchar *dest, gint len, const time_t timer)
+{
+ struct tm *lt;
+ gchar *default_format = "%y/%m/%d(%a) %H:%M";
+ gchar *tmp, *buf;
+
+ Xalloca(tmp, len + 1, dest[0] = '\0'; return;);
+
+ lt = localtime(&timer);
+
+ if (prefs_common.date_format)
+ strftime(tmp, len, prefs_common.date_format, lt);
+ else
+ strftime(tmp, len, default_format, lt);
+
+ buf = conv_localetodisp(tmp, NULL);
+ strncpy2(dest, buf, len);
+ g_free(buf);
+}
diff --git a/libsylph/procheader.h b/libsylph/procheader.h
new file mode 100644
index 00000000..1667b4ed
--- /dev/null
+++ b/libsylph/procheader.h
@@ -0,0 +1,93 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PROCHEADER_H__
+#define __PROCHEADER_H__
+
+#include <glib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "procmsg.h"
+
+typedef struct _HeaderEntry HeaderEntry;
+typedef struct _Header Header;
+
+struct _HeaderEntry
+{
+ gchar *name;
+ gchar *body;
+ gboolean unfold;
+};
+
+struct _Header
+{
+ gchar *name;
+ gchar *body;
+};
+
+gint procheader_get_one_field (gchar *buf,
+ size_t len,
+ FILE *fp,
+ HeaderEntry hentry[]);
+gchar *procheader_get_unfolded_line (gchar *buf,
+ size_t len,
+ FILE *fp);
+
+GSList *procheader_get_header_list_from_file (const gchar *file);
+GSList *procheader_get_header_list (FILE *fp);
+GSList *procheader_add_header_list (GSList *hlist,
+ const gchar *header_name,
+ const gchar *body);
+GSList *procheader_merge_header_list (GSList *hlist1,
+ GSList *hlist2);
+gint procheader_find_header_list (GSList *hlist,
+ const gchar *header_name);
+void procheader_header_list_destroy (GSList *hlist);
+
+GPtrArray *procheader_get_header_array (FILE *fp,
+ const gchar *encoding);
+GPtrArray *procheader_get_header_array_asis (FILE *fp,
+ const gchar *encoding);
+void procheader_header_array_destroy (GPtrArray *harray);
+
+void procheader_header_free (Header *header);
+
+void procheader_get_header_fields (FILE *fp,
+ HeaderEntry hentry[]);
+MsgInfo *procheader_parse_file (const gchar *file,
+ MsgFlags flags,
+ gboolean full);
+MsgInfo *procheader_parse_str (const gchar *str,
+ MsgFlags flags,
+ gboolean full);
+MsgInfo *procheader_parse_stream (FILE *fp,
+ MsgFlags flags,
+ gboolean full);
+
+gchar *procheader_get_fromname (const gchar *str);
+
+time_t procheader_date_parse (gchar *dest,
+ const gchar *src,
+ gint len);
+void procheader_date_get_localtime (gchar *dest,
+ gint len,
+ const time_t timer);
+
+#endif /* __PROCHEADER_H__ */
diff --git a/libsylph/procmime.c b/libsylph/procmime.c
new file mode 100644
index 00000000..faa882a4
--- /dev/null
+++ b/libsylph/procmime.c
@@ -0,0 +1,1158 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+#include <ctype.h>
+
+#include "procmime.h"
+#include "procheader.h"
+#include "base64.h"
+#include "quoted-printable.h"
+#include "uuencode.h"
+#include "html.h"
+#include "codeconv.h"
+#include "utils.h"
+#include "prefs_common.h"
+
+static GHashTable *procmime_get_mime_type_table (void);
+static GList *procmime_get_mime_type_list (const gchar *file);
+
+
+MimeInfo *procmime_mimeinfo_new(void)
+{
+ MimeInfo *mimeinfo;
+
+ mimeinfo = g_new0(MimeInfo, 1);
+ mimeinfo->mime_type = MIME_UNKNOWN;
+ mimeinfo->encoding_type = ENC_UNKNOWN;
+
+ return mimeinfo;
+}
+
+void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
+{
+ while (mimeinfo != NULL) {
+ MimeInfo *next;
+
+ g_free(mimeinfo->encoding);
+ g_free(mimeinfo->content_type);
+ g_free(mimeinfo->charset);
+ g_free(mimeinfo->name);
+ g_free(mimeinfo->boundary);
+ g_free(mimeinfo->content_disposition);
+ g_free(mimeinfo->filename);
+
+ g_free(mimeinfo->plaintextfile);
+ g_free(mimeinfo->sigstatus);
+ g_free(mimeinfo->sigstatus_full);
+
+ procmime_mimeinfo_free_all(mimeinfo->sub);
+ procmime_mimeinfo_free_all(mimeinfo->children);
+ procmime_mimeinfo_free_all(mimeinfo->plaintext);
+
+ next = mimeinfo->next;
+ g_free(mimeinfo);
+ mimeinfo = next;
+ }
+}
+
+MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo)
+{
+ MimeInfo *child = parent->children;
+
+ if (!child)
+ parent->children = mimeinfo;
+ else {
+ while (child->next != NULL)
+ child = child->next;
+
+ child->next = mimeinfo;
+ }
+
+ mimeinfo->parent = parent;
+ mimeinfo->level = parent->level + 1;
+
+ return mimeinfo;
+}
+
+void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new)
+{
+ MimeInfo *parent = old->parent;
+ MimeInfo *child;
+
+ g_return_if_fail(parent != NULL);
+ g_return_if_fail(new->next == NULL);
+
+ for (child = parent->children; child && child != old;
+ child = child->next)
+ ;
+ if (!child) {
+ g_warning("oops: parent can't find it's own child");
+ return;
+ }
+ procmime_mimeinfo_free_all(old);
+
+ if (child == parent->children) {
+ new->next = parent->children->next;
+ parent->children = new;
+ } else {
+ new->next = child->next;
+ child = new;
+ }
+}
+
+MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
+{
+ if (!mimeinfo) return NULL;
+
+ if (mimeinfo->children)
+ return mimeinfo->children;
+ if (mimeinfo->sub)
+ return mimeinfo->sub;
+ if (mimeinfo->next)
+ return mimeinfo->next;
+
+ if (mimeinfo->main) {
+ mimeinfo = mimeinfo->main;
+ if (mimeinfo->next)
+ return mimeinfo->next;
+ }
+
+ for (mimeinfo = mimeinfo->parent; mimeinfo != NULL;
+ mimeinfo = mimeinfo->parent) {
+ if (mimeinfo->next)
+ return mimeinfo->next;
+ if (mimeinfo->main) {
+ mimeinfo = mimeinfo->main;
+ if (mimeinfo->next)
+ return mimeinfo->next;
+ }
+ }
+
+ return NULL;
+}
+
+#if 0
+void procmime_dump_mimeinfo(MimeInfo *mimeinfo)
+{
+ gint i;
+
+ g_print("\n");
+
+ for (; mimeinfo != NULL; mimeinfo = procmime_mimeinfo_next(mimeinfo)) {
+ for (i = 0; i < mimeinfo->level; i++)
+ g_print(" ");
+ g_print("%s%s\n", mimeinfo->main ? "sub: " : "",
+ mimeinfo->content_type);
+ }
+}
+#endif
+
+MimeInfo *procmime_scan_message(MsgInfo *msginfo)
+{
+ FILE *fp;
+ MimeInfo *mimeinfo;
+
+ g_return_val_if_fail(msginfo != NULL, NULL);
+
+ if ((fp = procmsg_open_message_decrypted(msginfo, &mimeinfo)) == NULL)
+ return NULL;
+
+ if (mimeinfo) {
+ mimeinfo->size = msginfo->size;
+ mimeinfo->content_size = get_left_file_size(fp);
+ if (mimeinfo->encoding_type == ENC_BASE64)
+ mimeinfo->content_size = mimeinfo->content_size / 4 * 3;
+ if (mimeinfo->mime_type == MIME_MULTIPART ||
+ mimeinfo->mime_type == MIME_MESSAGE_RFC822)
+ procmime_scan_multipart_message(mimeinfo, fp);
+ }
+
+ fclose(fp);
+
+ return mimeinfo;
+}
+
+void procmime_scan_multipart_message(MimeInfo *mimeinfo, FILE *fp)
+{
+ gchar *p;
+ gchar *boundary;
+ gint boundary_len = 0;
+ gchar buf[BUFFSIZE];
+ glong fpos, prev_fpos;
+
+ g_return_if_fail(mimeinfo != NULL);
+ g_return_if_fail(mimeinfo->mime_type == MIME_MULTIPART ||
+ mimeinfo->mime_type == MIME_MESSAGE_RFC822);
+
+ if (mimeinfo->mime_type == MIME_MULTIPART) {
+ g_return_if_fail(mimeinfo->boundary != NULL);
+ g_return_if_fail(mimeinfo->sub == NULL);
+ }
+ g_return_if_fail(fp != NULL);
+
+ boundary = mimeinfo->boundary;
+
+ if (boundary) {
+ boundary_len = strlen(boundary);
+
+ /* look for first boundary */
+ while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
+ if (IS_BOUNDARY(buf, boundary, boundary_len)) break;
+ if (!p) return;
+ } else if (mimeinfo->parent && mimeinfo->parent->boundary) {
+ boundary = mimeinfo->parent->boundary;
+ boundary_len = strlen(boundary);
+ }
+
+ if ((fpos = ftell(fp)) < 0) {
+ perror("ftell");
+ return;
+ }
+
+ for (;;) {
+ MimeInfo *partinfo;
+ gboolean eom = FALSE;
+ glong content_pos;
+ gboolean is_base64;
+ gint len;
+ guint b64_content_len = 0;
+ gint b64_pad_len = 0;
+
+ prev_fpos = fpos;
+ debug_print("prev_fpos: %ld\n", fpos);
+
+ /* scan part header */
+ if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
+ MimeInfo *sub;
+
+ mimeinfo->sub = sub = procmime_scan_mime_header(fp);
+ if (!sub) break;
+
+ debug_print("message/rfc822 part found\n");
+ sub->level = mimeinfo->level + 1;
+ sub->parent = mimeinfo->parent;
+ sub->main = mimeinfo;
+
+ partinfo = sub;
+ } else {
+ partinfo = procmime_scan_mime_header(fp);
+ if (!partinfo) break;
+ procmime_mimeinfo_insert(mimeinfo, partinfo);
+ debug_print("content-type: %s\n",
+ partinfo->content_type);
+ }
+
+ /* begin content */
+ content_pos = ftell(fp);
+ debug_print("content_pos: %ld\n", content_pos);
+
+ if (partinfo->mime_type == MIME_MULTIPART ||
+ partinfo->mime_type == MIME_MESSAGE_RFC822) {
+ if (partinfo->level < 8)
+ procmime_scan_multipart_message(partinfo, fp);
+ }
+
+ /* look for next boundary */
+ buf[0] = '\0';
+ is_base64 = partinfo->encoding_type == ENC_BASE64;
+ while ((p = fgets(buf, sizeof(buf), fp)) != NULL) {
+ if (IS_BOUNDARY(buf, boundary, boundary_len)) {
+ if (buf[2 + boundary_len] == '-' &&
+ buf[2 + boundary_len + 1] == '-')
+ eom = TRUE;
+ break;
+ } else if (is_base64) {
+ const gchar *s;
+ for (s = buf; *s && *s != '\r' && *s != '\n';
+ ++s)
+ if (*s == '=')
+ ++b64_pad_len;
+ b64_content_len += s - buf;
+ }
+ }
+ if (p == NULL) {
+ /* broken MIME, or single part MIME message */
+ buf[0] = '\0';
+ eom = TRUE;
+ }
+ debug_print("boundary: %s\n", buf);
+
+ fpos = ftell(fp);
+ debug_print("fpos: %ld\n", fpos);
+
+ len = strlen(buf);
+ partinfo->size = fpos - prev_fpos - len;
+ if (is_base64)
+ partinfo->content_size =
+ b64_content_len / 4 * 3 - b64_pad_len;
+ else
+ partinfo->content_size = fpos - content_pos - len;
+ debug_print("partinfo->size: %d\n", partinfo->size);
+ debug_print("partinfo->content_size: %d\n",
+ partinfo->content_size);
+ if (partinfo->sub && !partinfo->sub->sub &&
+ !partinfo->sub->children) {
+ partinfo->sub->size =
+ fpos - partinfo->sub->fpos - strlen(buf);
+ debug_print("partinfo->sub->size: %d\n",
+ partinfo->sub->size);
+ }
+
+ if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
+ if (len > 0 && fseek(fp, fpos - len, SEEK_SET) < 0)
+ perror("fseek");
+ break;
+ }
+
+ if (eom) break;
+ }
+}
+
+void procmime_scan_encoding(MimeInfo *mimeinfo, const gchar *encoding)
+{
+ gchar *buf;
+
+ Xstrdup_a(buf, encoding, return);
+
+ g_free(mimeinfo->encoding);
+
+ mimeinfo->encoding = g_strdup(g_strstrip(buf));
+ if (!g_ascii_strcasecmp(buf, "7bit"))
+ mimeinfo->encoding_type = ENC_7BIT;
+ else if (!g_ascii_strcasecmp(buf, "8bit"))
+ mimeinfo->encoding_type = ENC_8BIT;
+ else if (!g_ascii_strcasecmp(buf, "quoted-printable"))
+ mimeinfo->encoding_type = ENC_QUOTED_PRINTABLE;
+ else if (!g_ascii_strcasecmp(buf, "base64"))
+ mimeinfo->encoding_type = ENC_BASE64;
+ else if (!g_ascii_strcasecmp(buf, "x-uuencode"))
+ mimeinfo->encoding_type = ENC_X_UUENCODE;
+ else
+ mimeinfo->encoding_type = ENC_UNKNOWN;
+
+}
+
+void procmime_scan_content_type(MimeInfo *mimeinfo, const gchar *content_type)
+{
+ g_free(mimeinfo->content_type);
+ g_free(mimeinfo->charset);
+ g_free(mimeinfo->name);
+ g_free(mimeinfo->boundary);
+ mimeinfo->content_type = NULL;
+ mimeinfo->charset = NULL;
+ mimeinfo->name = NULL;
+ mimeinfo->boundary = NULL;
+
+ procmime_scan_content_type_str(content_type, &mimeinfo->content_type,
+ &mimeinfo->charset, &mimeinfo->name,
+ &mimeinfo->boundary);
+
+ mimeinfo->mime_type = procmime_scan_mime_type(mimeinfo->content_type);
+ if (mimeinfo->mime_type == MIME_MULTIPART && !mimeinfo->boundary)
+ mimeinfo->mime_type = MIME_TEXT;
+}
+
+void procmime_scan_content_type_str(const gchar *content_type,
+ gchar **mime_type, gchar **charset,
+ gchar **name, gchar **boundary)
+{
+ gchar *delim, *p;
+ gchar *buf;
+
+ Xstrdup_a(buf, content_type, return);
+
+ if ((delim = strchr(buf, ';'))) *delim = '\0';
+ if (mime_type)
+ *mime_type = g_strdup(g_strstrip(buf));
+
+ if (!delim) return;
+ p = delim + 1;
+
+ for (;;) {
+ gchar *eq;
+ gchar *attr, *value;
+
+ if ((delim = strchr(p, ';'))) *delim = '\0';
+
+ if (!(eq = strchr(p, '='))) break;
+
+ *eq = '\0';
+ attr = p;
+ g_strstrip(attr);
+ value = eq + 1;
+ g_strstrip(value);
+
+ if (*value == '"')
+ extract_quote(value, '"');
+ else {
+ eliminate_parenthesis(value, '(', ')');
+ g_strstrip(value);
+ }
+
+ if (*value) {
+ if (charset && !g_ascii_strcasecmp(attr, "charset"))
+ *charset = g_strdup(value);
+ else if (name && !g_ascii_strcasecmp(attr, "name"))
+ *name = conv_unmime_header(value, NULL);
+ else if (boundary &&
+ !g_ascii_strcasecmp(attr, "boundary"))
+ *boundary = g_strdup(value);
+ }
+
+ if (!delim) break;
+ p = delim + 1;
+ }
+}
+
+void procmime_scan_content_disposition(MimeInfo *mimeinfo,
+ const gchar *content_disposition)
+{
+ gchar *delim, *p, *dispos;
+ gchar *buf;
+
+ Xstrdup_a(buf, content_disposition, return);
+
+ if ((delim = strchr(buf, ';'))) *delim = '\0';
+ mimeinfo->content_disposition = dispos = g_strdup(g_strstrip(buf));
+
+ if (!delim) return;
+ p = delim + 1;
+
+ for (;;) {
+ gchar *eq;
+ gchar *attr, *value;
+
+ if ((delim = strchr(p, ';'))) *delim = '\0';
+
+ if (!(eq = strchr(p, '='))) break;
+
+ *eq = '\0';
+ attr = p;
+ g_strstrip(attr);
+ value = eq + 1;
+ g_strstrip(value);
+
+ if (*value == '"')
+ extract_quote(value, '"');
+ else {
+ eliminate_parenthesis(value, '(', ')');
+ g_strstrip(value);
+ }
+
+ if (*value) {
+ if (!g_ascii_strcasecmp(attr, "filename")) {
+ g_free(mimeinfo->filename);
+ mimeinfo->filename =
+ conv_unmime_header(value, NULL);
+ break;
+ }
+ }
+
+ if (!delim) break;
+ p = delim + 1;
+ }
+}
+
+enum
+{
+ H_CONTENT_TRANSFER_ENCODING = 0,
+ H_CONTENT_TYPE = 1,
+ H_CONTENT_DISPOSITION = 2
+};
+
+MimeInfo *procmime_scan_mime_header(FILE *fp)
+{
+ static HeaderEntry hentry[] = {{"Content-Transfer-Encoding:",
+ NULL, FALSE},
+ {"Content-Type:", NULL, TRUE},
+ {"Content-Disposition:",
+ NULL, TRUE},
+ {NULL, NULL, FALSE}};
+ gchar buf[BUFFSIZE];
+ gint hnum;
+ HeaderEntry *hp;
+ MimeInfo *mimeinfo;
+
+ g_return_val_if_fail(fp != NULL, NULL);
+
+ mimeinfo = procmime_mimeinfo_new();
+ mimeinfo->mime_type = MIME_TEXT;
+ mimeinfo->encoding_type = ENC_7BIT;
+ mimeinfo->fpos = ftell(fp);
+
+ while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
+ != -1) {
+ hp = hentry + hnum;
+
+ if (H_CONTENT_TRANSFER_ENCODING == hnum) {
+ procmime_scan_encoding
+ (mimeinfo, buf + strlen(hp->name));
+ } else if (H_CONTENT_TYPE == hnum) {
+ procmime_scan_content_type
+ (mimeinfo, buf + strlen(hp->name));
+ } else if (H_CONTENT_DISPOSITION == hnum) {
+ procmime_scan_content_disposition
+ (mimeinfo, buf + strlen(hp->name));
+ }
+ }
+
+ if (mimeinfo->mime_type == MIME_APPLICATION_OCTET_STREAM &&
+ mimeinfo->name) {
+ const gchar *type;
+ type = procmime_get_mime_type(mimeinfo->name);
+ if (type)
+ mimeinfo->mime_type = procmime_scan_mime_type(type);
+ }
+
+ if (!mimeinfo->content_type)
+ mimeinfo->content_type = g_strdup("text/plain");
+
+ return mimeinfo;
+}
+
+FILE *procmime_decode_content(FILE *outfp, FILE *infp, MimeInfo *mimeinfo)
+{
+ gchar buf[BUFFSIZE];
+ gchar *boundary = NULL;
+ gint boundary_len = 0;
+ gboolean tmp_file = FALSE;
+
+ g_return_val_if_fail(infp != NULL, NULL);
+ g_return_val_if_fail(mimeinfo != NULL, NULL);
+
+ if (!outfp) {
+ outfp = my_tmpfile();
+ if (!outfp) {
+ perror("tmpfile");
+ return NULL;
+ }
+ tmp_file = TRUE;
+ }
+
+ if (mimeinfo->parent && mimeinfo->parent->boundary) {
+ boundary = mimeinfo->parent->boundary;
+ boundary_len = strlen(boundary);
+ }
+
+ if (mimeinfo->encoding_type == ENC_QUOTED_PRINTABLE) {
+ while (fgets(buf, sizeof(buf), infp) != NULL &&
+ (!boundary ||
+ !IS_BOUNDARY(buf, boundary, boundary_len))) {
+ gint len;
+ len = qp_decode_line(buf);
+ fwrite(buf, len, 1, outfp);
+ }
+ } else if (mimeinfo->encoding_type == ENC_BASE64) {
+ gchar outbuf[BUFFSIZE];
+ gint len;
+ Base64Decoder *decoder;
+ gboolean uncanonicalize = FALSE;
+ FILE *tmpfp = outfp;
+ ContentType content_type;
+
+ content_type = procmime_scan_mime_type(mimeinfo->content_type);
+ if (content_type == MIME_TEXT ||
+ content_type == MIME_TEXT_HTML ||
+ content_type == MIME_MESSAGE_RFC822) {
+ uncanonicalize = TRUE;
+ tmpfp = my_tmpfile();
+ if (!tmpfp) {
+ perror("tmpfile");
+ if (tmp_file) fclose(outfp);
+ return NULL;
+ }
+ }
+
+ decoder = base64_decoder_new();
+ while (fgets(buf, sizeof(buf), infp) != NULL &&
+ (!boundary ||
+ !IS_BOUNDARY(buf, boundary, boundary_len))) {
+ len = base64_decoder_decode(decoder, buf, outbuf);
+ if (len < 0) {
+ g_warning("Bad BASE64 content\n");
+ break;
+ }
+ fwrite(outbuf, sizeof(gchar), len, tmpfp);
+ }
+ base64_decoder_free(decoder);
+
+ if (uncanonicalize) {
+ rewind(tmpfp);
+ while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
+ strcrchomp(buf);
+ fputs(buf, outfp);
+ }
+ fclose(tmpfp);
+ }
+ } else if (mimeinfo->encoding_type == ENC_X_UUENCODE) {
+ gchar outbuf[BUFFSIZE];
+ gint len;
+ gboolean flag = FALSE;
+
+ while (fgets(buf, sizeof(buf), infp) != NULL &&
+ (!boundary ||
+ !IS_BOUNDARY(buf, boundary, boundary_len))) {
+ if(!flag && strncmp(buf,"begin ", 6)) continue;
+
+ if (flag) {
+ len = fromuutobits(outbuf, buf);
+ if (len <= 0) {
+ if (len < 0)
+ g_warning("Bad UUENCODE content(%d)\n", len);
+ break;
+ }
+ fwrite(outbuf, sizeof(gchar), len, outfp);
+ } else
+ flag = TRUE;
+ }
+ } else {
+ while (fgets(buf, sizeof(buf), infp) != NULL &&
+ (!boundary ||
+ !IS_BOUNDARY(buf, boundary, boundary_len))) {
+ fputs(buf, outfp);
+ }
+ }
+
+ if (tmp_file) rewind(outfp);
+ return outfp;
+}
+
+gint procmime_get_part(const gchar *outfile, const gchar *infile,
+ MimeInfo *mimeinfo)
+{
+ FILE *infp;
+ gint ret;
+
+ g_return_val_if_fail(outfile != NULL, -1);
+ g_return_val_if_fail(infile != NULL, -1);
+ g_return_val_if_fail(mimeinfo != NULL, -1);
+
+ if ((infp = g_fopen(infile, "rb")) == NULL) {
+ FILE_OP_ERROR(infile, "fopen");
+ return -1;
+ }
+ ret = procmime_get_part_fp(outfile, infp, mimeinfo);
+ fclose(infp);
+
+ return ret;
+}
+
+gint procmime_get_part_fp(const gchar *outfile, FILE *infp, MimeInfo *mimeinfo)
+{
+ FILE *outfp;
+ gchar buf[BUFFSIZE];
+
+ g_return_val_if_fail(outfile != NULL, -1);
+ g_return_val_if_fail(infp != NULL, -1);
+ g_return_val_if_fail(mimeinfo != NULL, -1);
+
+ if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
+ FILE_OP_ERROR("procmime_get_part_fp()", "fseek");
+ return -1;
+ }
+ if ((outfp = g_fopen(outfile, "wb")) == NULL) {
+ FILE_OP_ERROR(outfile, "fopen");
+ return -1;
+ }
+
+ while (fgets(buf, sizeof(buf), infp) != NULL)
+ if (buf[0] == '\r' || buf[0] == '\n') break;
+
+ procmime_decode_content(outfp, infp, mimeinfo);
+
+ if (fclose(outfp) == EOF) {
+ FILE_OP_ERROR(outfile, "fclose");
+ g_unlink(outfile);
+ return -1;
+ }
+
+ return 0;
+}
+
+FILE *procmime_get_text_content(MimeInfo *mimeinfo, FILE *infp,
+ const gchar *encoding)
+{
+ FILE *tmpfp, *outfp;
+ const gchar *src_encoding;
+ gboolean conv_fail = FALSE;
+ gchar buf[BUFFSIZE];
+
+ g_return_val_if_fail(mimeinfo != NULL, NULL);
+ g_return_val_if_fail(infp != NULL, NULL);
+ g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
+ mimeinfo->mime_type == MIME_TEXT_HTML, NULL);
+
+ if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
+ perror("fseek");
+ return NULL;
+ }
+
+ while (fgets(buf, sizeof(buf), infp) != NULL)
+ if (buf[0] == '\r' || buf[0] == '\n') break;
+
+ tmpfp = procmime_decode_content(NULL, infp, mimeinfo);
+ if (!tmpfp)
+ return NULL;
+
+ if ((outfp = my_tmpfile()) == NULL) {
+ perror("tmpfile");
+ fclose(tmpfp);
+ return NULL;
+ }
+
+ src_encoding = prefs_common.force_charset
+ ? prefs_common.force_charset : mimeinfo->charset;
+
+ if (mimeinfo->mime_type == MIME_TEXT) {
+ while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
+ gchar *str;
+
+ str = conv_codeset_strdup(buf, src_encoding, encoding);
+ if (str) {
+ fputs(str, outfp);
+ g_free(str);
+ } else {
+ conv_fail = TRUE;
+ fputs(buf, outfp);
+ }
+ }
+ } else if (mimeinfo->mime_type == MIME_TEXT_HTML) {
+ HTMLParser *parser;
+ CodeConverter *conv;
+ const gchar *str;
+
+ conv = conv_code_converter_new(src_encoding, encoding);
+ parser = html_parser_new(tmpfp, conv);
+ while ((str = html_parse(parser)) != NULL) {
+ fputs(str, outfp);
+ }
+ html_parser_destroy(parser);
+ conv_code_converter_destroy(conv);
+ }
+
+ if (conv_fail)
+ g_warning(_("procmime_get_text_content(): Code conversion failed.\n"));
+
+ fclose(tmpfp);
+ rewind(outfp);
+
+ return outfp;
+}
+
+/* search the first text part of (multipart) MIME message,
+ decode, convert it and output to outfp. */
+FILE *procmime_get_first_text_content(MsgInfo *msginfo, const gchar *encoding)
+{
+ FILE *infp, *outfp = NULL;
+ MimeInfo *mimeinfo, *partinfo;
+
+ g_return_val_if_fail(msginfo != NULL, NULL);
+
+ mimeinfo = procmime_scan_message(msginfo);
+ if (!mimeinfo) return NULL;
+
+ if ((infp = procmsg_open_message(msginfo)) == NULL) {
+ procmime_mimeinfo_free_all(mimeinfo);
+ return NULL;
+ }
+
+ partinfo = mimeinfo;
+ while (partinfo && partinfo->mime_type != MIME_TEXT)
+ partinfo = procmime_mimeinfo_next(partinfo);
+ if (!partinfo) {
+ partinfo = mimeinfo;
+ while (partinfo && partinfo->mime_type != MIME_TEXT_HTML)
+ partinfo = procmime_mimeinfo_next(partinfo);
+ }
+
+ if (partinfo)
+ outfp = procmime_get_text_content(partinfo, infp, encoding);
+
+ fclose(infp);
+ procmime_mimeinfo_free_all(mimeinfo);
+
+ return outfp;
+}
+
+gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
+ const gchar *str, StrFindFunc find_func)
+{
+
+ FILE *infp, *outfp;
+ gchar buf[BUFFSIZE];
+
+ g_return_val_if_fail(mimeinfo != NULL, FALSE);
+ g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
+ mimeinfo->mime_type == MIME_TEXT_HTML, FALSE);
+ g_return_val_if_fail(str != NULL, FALSE);
+ g_return_val_if_fail(find_func != NULL, FALSE);
+
+ if ((infp = g_fopen(filename, "rb")) == NULL) {
+ FILE_OP_ERROR(filename, "fopen");
+ return FALSE;
+ }
+
+ outfp = procmime_get_text_content(mimeinfo, infp, NULL);
+ fclose(infp);
+
+ if (!outfp)
+ return FALSE;
+
+ while (fgets(buf, sizeof(buf), outfp) != NULL) {
+ strretchomp(buf);
+ if (find_func(buf, str)) {
+ fclose(outfp);
+ return TRUE;
+ }
+ }
+
+ fclose(outfp);
+
+ return FALSE;
+}
+
+gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
+ StrFindFunc find_func)
+{
+ MimeInfo *mimeinfo;
+ MimeInfo *partinfo;
+ gchar *filename;
+ gboolean found = FALSE;
+
+ g_return_val_if_fail(msginfo != NULL, FALSE);
+ g_return_val_if_fail(str != NULL, FALSE);
+ g_return_val_if_fail(find_func != NULL, FALSE);
+
+ filename = procmsg_get_message_file(msginfo);
+ if (!filename) return FALSE;
+ mimeinfo = procmime_scan_message(msginfo);
+
+ for (partinfo = mimeinfo; partinfo != NULL;
+ partinfo = procmime_mimeinfo_next(partinfo)) {
+ if (partinfo->mime_type == MIME_TEXT ||
+ partinfo->mime_type == MIME_TEXT_HTML) {
+ if (procmime_find_string_part
+ (partinfo, filename, str, find_func) == TRUE) {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ procmime_mimeinfo_free_all(mimeinfo);
+ g_free(filename);
+
+ return found;
+}
+
+gchar *procmime_get_part_file_name(MimeInfo *mimeinfo)
+{
+ gchar *base;
+ const gchar *base_;
+
+ base_ = mimeinfo->filename ? mimeinfo->filename
+ : mimeinfo->name ? mimeinfo->name : "mimetmp";
+ base_ = g_basename(base_);
+ if (*base_ == '\0') base_ = "mimetmp";
+ base = conv_filename_from_utf8(base_);
+ subst_for_filename(base);
+
+ return base;
+}
+
+gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
+{
+ static guint32 id = 0;
+ gchar *base;
+ gchar *filename;
+ gchar f_prefix[10];
+
+ g_return_val_if_fail(mimeinfo != NULL, NULL);
+
+ g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
+
+ if (MIME_TEXT_HTML == mimeinfo->mime_type)
+ base = g_strdup("mimetmp.html");
+ else
+ base = procmime_get_part_file_name(mimeinfo);
+
+ filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
+ f_prefix, base, NULL);
+
+ g_free(base);
+
+ return filename;
+}
+
+ContentType procmime_scan_mime_type(const gchar *mime_type)
+{
+ ContentType type;
+
+ if (!g_ascii_strncasecmp(mime_type, "text/html", 9))
+ type = MIME_TEXT_HTML;
+ else if (!g_ascii_strncasecmp(mime_type, "text/", 5))
+ type = MIME_TEXT;
+ else if (!g_ascii_strncasecmp(mime_type, "message/rfc822", 14))
+ type = MIME_MESSAGE_RFC822;
+ else if (!g_ascii_strncasecmp(mime_type, "message/", 8))
+ type = MIME_TEXT;
+ else if (!g_ascii_strncasecmp(mime_type, "application/octet-stream",
+ 24))
+ type = MIME_APPLICATION_OCTET_STREAM;
+ else if (!g_ascii_strncasecmp(mime_type, "application/", 12))
+ type = MIME_APPLICATION;
+ else if (!g_ascii_strncasecmp(mime_type, "multipart/", 10))
+ type = MIME_MULTIPART;
+ else if (!g_ascii_strncasecmp(mime_type, "image/", 6))
+ type = MIME_IMAGE;
+ else if (!g_ascii_strncasecmp(mime_type, "audio/", 6))
+ type = MIME_AUDIO;
+ else if (!g_ascii_strcasecmp(mime_type, "text"))
+ type = MIME_TEXT;
+ else
+ type = MIME_UNKNOWN;
+
+ return type;
+}
+
+static GList *mime_type_list = NULL;
+
+gchar *procmime_get_mime_type(const gchar *filename)
+{
+ static GHashTable *mime_type_table = NULL;
+ MimeType *mime_type;
+ const gchar *p;
+ gchar *ext;
+
+ if (!mime_type_table) {
+ mime_type_table = procmime_get_mime_type_table();
+ if (!mime_type_table) return NULL;
+ }
+
+ filename = g_basename(filename);
+ p = strrchr(filename, '.');
+ if (!p) return NULL;
+
+ Xstrdup_a(ext, p + 1, return NULL);
+ g_strdown(ext);
+ mime_type = g_hash_table_lookup(mime_type_table, ext);
+ if (mime_type) {
+ gchar *str;
+
+ str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
+ NULL);
+ return str;
+ }
+
+ return NULL;
+}
+
+static GHashTable *procmime_get_mime_type_table(void)
+{
+ GHashTable *table = NULL;
+ GList *cur;
+ MimeType *mime_type;
+ gchar **exts;
+
+ if (!mime_type_list) {
+ GList *list;
+ gchar *dir;
+
+ mime_type_list =
+ procmime_get_mime_type_list(SYSCONFDIR "/mime.types");
+ if (!mime_type_list) {
+ list = procmime_get_mime_type_list("/etc/mime.types");
+ mime_type_list = g_list_concat(mime_type_list, list);
+ }
+ dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+ "mime.types", NULL);
+ list = procmime_get_mime_type_list(dir);
+ g_free(dir);
+ mime_type_list = g_list_concat(mime_type_list, list);
+
+ if (!mime_type_list) {
+ g_warning("mime.types not found\n");
+ return NULL;
+ }
+ }
+
+ table = g_hash_table_new(g_str_hash, g_str_equal);
+
+ for (cur = mime_type_list; cur != NULL; cur = cur->next) {
+ gint i;
+ gchar *key;
+
+ mime_type = (MimeType *)cur->data;
+
+ if (!mime_type->extension) continue;
+
+ exts = g_strsplit(mime_type->extension, " ", 16);
+ for (i = 0; exts[i] != NULL; i++) {
+ /* make the key case insensitive */
+ g_strdown(exts[i]);
+ /* use previously dup'd key on overwriting */
+ if (g_hash_table_lookup(table, exts[i]))
+ key = exts[i];
+ else
+ key = g_strdup(exts[i]);
+ g_hash_table_insert(table, key, mime_type);
+ }
+ g_strfreev(exts);
+ }
+
+ return table;
+}
+
+static GList *procmime_get_mime_type_list(const gchar *file)
+{
+ GList *list = NULL;
+ FILE *fp;
+ gchar buf[BUFFSIZE];
+ gchar *p;
+ gchar *delim;
+ MimeType *mime_type;
+
+ if ((fp = g_fopen(file, "rb")) == NULL) return NULL;
+
+ debug_print("Reading %s ...\n", file);
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ p = strchr(buf, '#');
+ if (p) *p = '\0';
+ g_strstrip(buf);
+
+ p = buf;
+ while (*p && !g_ascii_isspace(*p)) p++;
+ if (*p) {
+ *p = '\0';
+ p++;
+ }
+ delim = strchr(buf, '/');
+ if (delim == NULL) continue;
+ *delim = '\0';
+
+ mime_type = g_new(MimeType, 1);
+ mime_type->type = g_strdup(buf);
+ mime_type->sub_type = g_strdup(delim + 1);
+
+ while (*p && g_ascii_isspace(*p)) p++;
+ if (*p)
+ mime_type->extension = g_strdup(p);
+ else
+ mime_type->extension = NULL;
+
+ list = g_list_append(list, mime_type);
+ }
+
+ fclose(fp);
+
+ if (!list)
+ g_warning("Can't read mime.types\n");
+
+ return list;
+}
+
+EncodingType procmime_get_encoding_for_charset(const gchar *charset)
+{
+ if (!charset)
+ return ENC_8BIT;
+ else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) ||
+ !g_ascii_strcasecmp(charset, "US-ASCII"))
+ return ENC_7BIT;
+ else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") ||
+ !g_ascii_strncasecmp(charset, "KOI8-", 5) ||
+ !g_ascii_strcasecmp(charset, "Windows-1251"))
+ return ENC_8BIT;
+ else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9))
+ return ENC_QUOTED_PRINTABLE;
+ else
+ return ENC_8BIT;
+}
+
+EncodingType procmime_get_encoding_for_text_file(const gchar *file)
+{
+ FILE *fp;
+ guchar buf[BUFFSIZE];
+ size_t len;
+ size_t octet_chars = 0;
+ size_t total_len = 0;
+ gfloat octet_percentage;
+
+ if ((fp = g_fopen(file, "rb")) == NULL) {
+ FILE_OP_ERROR(file, "fopen");
+ return ENC_UNKNOWN;
+ }
+
+ while ((len = fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) {
+ guchar *p;
+ gint i;
+
+ for (p = buf, i = 0; i < len; ++p, ++i) {
+ if (*p & 0x80)
+ ++octet_chars;
+ }
+ total_len += len;
+ }
+
+ fclose(fp);
+
+ if (total_len > 0)
+ octet_percentage = (gfloat)octet_chars / (gfloat)total_len;
+ else
+ octet_percentage = 0.0;
+
+ debug_print("procmime_get_encoding_for_text_file(): "
+ "8bit chars: %d / %d (%f%%)\n", octet_chars, total_len,
+ 100.0 * octet_percentage);
+
+ if (octet_percentage > 0.20) {
+ debug_print("using BASE64\n");
+ return ENC_BASE64;
+ } else if (octet_chars > 0) {
+ debug_print("using quoted-printable\n");
+ return ENC_QUOTED_PRINTABLE;
+ } else {
+ debug_print("using 7bit\n");
+ return ENC_7BIT;
+ }
+}
+
+const gchar *procmime_get_encoding_str(EncodingType encoding)
+{
+ static const gchar *encoding_str[] = {
+ "7bit", "8bit", "quoted-printable", "base64", "x-uuencode",
+ NULL
+ };
+
+ if (encoding >= ENC_7BIT && encoding <= ENC_UNKNOWN)
+ return encoding_str[encoding];
+ else
+ return NULL;
+}
diff --git a/libsylph/procmime.h b/libsylph/procmime.h
new file mode 100644
index 00000000..3f3cb7cb
--- /dev/null
+++ b/libsylph/procmime.h
@@ -0,0 +1,186 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PROCMIME_H__
+#define __PROCMIME_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <stdio.h>
+
+typedef struct _MimeType MimeType;
+typedef struct _MimeInfo MimeInfo;
+
+#include "procmsg.h"
+#include "utils.h"
+
+typedef enum
+{
+ ENC_7BIT,
+ ENC_8BIT,
+ ENC_QUOTED_PRINTABLE,
+ ENC_BASE64,
+ ENC_X_UUENCODE,
+ ENC_UNKNOWN
+} EncodingType;
+
+typedef enum
+{
+ MIME_TEXT,
+ MIME_TEXT_HTML,
+ MIME_MESSAGE_RFC822,
+ MIME_APPLICATION,
+ MIME_APPLICATION_OCTET_STREAM,
+ MIME_MULTIPART,
+ MIME_IMAGE,
+ MIME_AUDIO,
+ MIME_UNKNOWN
+} ContentType;
+
+struct _MimeType
+{
+ gchar *type;
+ gchar *sub_type;
+
+ gchar *extension;
+};
+
+/*
+ * An example of MimeInfo structure:
+ *
+ * multipart/mixed root <-+ parent
+ * |
+ * multipart/alternative children <-+ parent
+ * |
+ * text/plain children --+
+ * |
+ * text/html next <-+
+ *
+ * message/rfc822 next <-+ main
+ * |
+ * sub (capsulated message)
+ *
+ * image/jpeg next
+ */
+
+struct _MimeInfo
+{
+ gchar *encoding;
+
+ EncodingType encoding_type;
+ ContentType mime_type;
+
+ gchar *content_type;
+ gchar *charset;
+ gchar *name;
+ gchar *boundary;
+
+ gchar *content_disposition;
+ gchar *filename;
+
+ glong fpos;
+ guint size;
+ guint content_size;
+
+ MimeInfo *main;
+ MimeInfo *sub;
+
+ MimeInfo *next;
+ MimeInfo *parent;
+ MimeInfo *children;
+
+ MimeInfo *plaintext;
+ gchar *plaintextfile;
+ gchar *sigstatus;
+ gchar *sigstatus_full;
+
+ gint level;
+};
+
+#define IS_BOUNDARY(s, bnd, len) \
+ (bnd && s[0] == '-' && s[1] == '-' && !strncmp(s + 2, bnd, len))
+
+/* MimeInfo handling */
+
+MimeInfo *procmime_mimeinfo_new (void);
+void procmime_mimeinfo_free_all (MimeInfo *mimeinfo);
+
+MimeInfo *procmime_mimeinfo_insert (MimeInfo *parent,
+ MimeInfo *mimeinfo);
+void procmime_mimeinfo_replace (MimeInfo *old,
+ MimeInfo *new);
+
+MimeInfo *procmime_mimeinfo_next (MimeInfo *mimeinfo);
+
+MimeInfo *procmime_scan_message (MsgInfo *msginfo);
+void procmime_scan_multipart_message (MimeInfo *mimeinfo,
+ FILE *fp);
+
+/* scan headers */
+
+void procmime_scan_encoding (MimeInfo *mimeinfo,
+ const gchar *encoding);
+void procmime_scan_content_type (MimeInfo *mimeinfo,
+ const gchar *content_type);
+void procmime_scan_content_type_str (const gchar *content_type,
+ gchar **mime_type,
+ gchar **charset,
+ gchar **name,
+ gchar **boundary);
+void procmime_scan_content_disposition (MimeInfo *mimeinfo,
+ const gchar *content_disposition);
+MimeInfo *procmime_scan_mime_header (FILE *fp);
+
+FILE *procmime_decode_content (FILE *outfp,
+ FILE *infp,
+ MimeInfo *mimeinfo);
+gint procmime_get_part (const gchar *outfile,
+ const gchar *infile,
+ MimeInfo *mimeinfo);
+gint procmime_get_part_fp (const gchar *outfile,
+ FILE *infp,
+ MimeInfo *mimeinfo);
+FILE *procmime_get_text_content (MimeInfo *mimeinfo,
+ FILE *infp,
+ const gchar *encoding);
+FILE *procmime_get_first_text_content (MsgInfo *msginfo,
+ const gchar *encoding);
+
+gboolean procmime_find_string_part (MimeInfo *mimeinfo,
+ const gchar *filename,
+ const gchar *str,
+ StrFindFunc find_func);
+gboolean procmime_find_string (MsgInfo *msginfo,
+ const gchar *str,
+ StrFindFunc find_func);
+
+gchar *procmime_get_part_file_name (MimeInfo *mimeinfo);
+gchar *procmime_get_tmp_file_name (MimeInfo *mimeinfo);
+
+ContentType procmime_scan_mime_type (const gchar *mime_type);
+gchar *procmime_get_mime_type (const gchar *filename);
+
+EncodingType procmime_get_encoding_for_charset (const gchar *charset);
+EncodingType procmime_get_encoding_for_text_file(const gchar *file);
+const gchar *procmime_get_encoding_str (EncodingType encoding);
+
+#endif /* __PROCMIME_H__ */
diff --git a/libsylph/procmsg.c b/libsylph/procmsg.c
new file mode 100644
index 00000000..57eff70a
--- /dev/null
+++ b/libsylph/procmsg.c
@@ -0,0 +1,1412 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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"
+
+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 FILE *procmsg_open_data_file (const gchar *file,
+ guint version,
+ DataOpenMode mode,
+ gchar *buf,
+ size_t buf_size);
+static FILE *procmsg_open_cache_file_with_buffer(FolderItem *item,
+ DataOpenMode mode,
+ gchar *buf,
+ size_t buf_size);
+
+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;
+}
+
+static gint procmsg_read_cache_data_str(FILE *fp, gchar **str)
+{
+ gchar buf[BUFFSIZE];
+ gint ret = 0;
+ guint32 len;
+
+ if (fread(&len, sizeof(len), 1, fp) == 1) {
+ if (len > G_MAXINT)
+ ret = -1;
+ else {
+ gchar *tmp = NULL;
+
+ while (len > 0) {
+ size_t size = MIN(len, BUFFSIZE - 1);
+
+ if (fread(buf, size, 1, fp) != 1) {
+ ret = -1;
+ if (tmp) g_free(tmp);
+ *str = NULL;
+ break;
+ }
+
+ buf[size] = '\0';
+ if (tmp) {
+ *str = g_strconcat(tmp, buf, NULL);
+ g_free(tmp);
+ tmp = *str;
+ } else
+ tmp = *str = g_strdup(buf);
+
+ len -= size;
+ }
+ }
+ } else
+ ret = -1;
+
+ if (ret < 0)
+ g_warning("Cache data is corrupted\n");
+
+ return ret;
+}
+
+#define READ_CACHE_DATA(data, fp) \
+{ \
+ if (procmsg_read_cache_data_str(fp, &data) < 0) { \
+ procmsg_msginfo_free(msginfo); \
+ procmsg_msg_list_free(mlist); \
+ mlist = NULL; \
+ break; \
+ } \
+}
+
+#define READ_CACHE_DATA_INT(n, fp) \
+{ \
+ guint32 idata; \
+ \
+ if (fread(&idata, sizeof(idata), 1, fp) != 1) { \
+ g_warning("Cache data is corrupted\n"); \
+ procmsg_msginfo_free(msginfo); \
+ procmsg_msg_list_free(mlist); \
+ mlist = NULL; \
+ break; \
+ } else \
+ n = idata; \
+}
+
+GSList *procmsg_read_cache(FolderItem *item, gboolean scan_file)
+{
+ GSList *mlist = NULL;
+ GSList *last = NULL;
+ FILE *fp;
+ MsgInfo *msginfo;
+ MsgFlags default_flags;
+ gchar file_buf[BUFFSIZE];
+ 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);
+ }
+
+ if ((fp = procmsg_open_cache_file_with_buffer
+ (item, DATA_READ, file_buf, sizeof(file_buf))) == NULL) {
+ item->cache_dirty = TRUE;
+ return NULL;
+ }
+
+ debug_print("Reading summary cache...");
+
+ while (fread(&num, sizeof(num), 1, fp) == 1) {
+ msginfo = g_new0(MsgInfo, 1);
+ msginfo->msgnum = num;
+ READ_CACHE_DATA_INT(msginfo->size, fp);
+ READ_CACHE_DATA_INT(msginfo->mtime, fp);
+ READ_CACHE_DATA_INT(msginfo->date_t, fp);
+ READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
+
+ READ_CACHE_DATA(msginfo->fromname, fp);
+
+ READ_CACHE_DATA(msginfo->date, fp);
+ READ_CACHE_DATA(msginfo->from, fp);
+ READ_CACHE_DATA(msginfo->to, fp);
+ READ_CACHE_DATA(msginfo->newsgroups, fp);
+ READ_CACHE_DATA(msginfo->subject, fp);
+ READ_CACHE_DATA(msginfo->msgid, fp);
+ READ_CACHE_DATA(msginfo->inreplyto, fp);
+
+ READ_CACHE_DATA_INT(refnum, fp);
+ for (; refnum != 0; refnum--) {
+ gchar *ref;
+
+ READ_CACHE_DATA(ref, fp);
+ 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)) || num == 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;
+ }
+ }
+ }
+
+ fclose(fp);
+
+ debug_print("done.\n");
+
+ return mlist;
+}
+
+#undef READ_CACHE_DATA
+#undef READ_CACHE_DATA_INT
+
+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 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_flush_mark_queue(FolderItem *item, FILE *fp)
+{
+ MsgInfo *flaginfo;
+
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(fp != NULL);
+
+ if (item->mark_queue)
+ debug_print("flushing mark_queue...\n");
+
+ while (item->mark_queue != NULL) {
+ flaginfo = (MsgInfo *)item->mark_queue->data;
+ procmsg_write_flags(flaginfo, fp);
+ procmsg_msginfo_free(flaginfo);
+ item->mark_queue = g_slist_remove(item->mark_queue, flaginfo);
+ }
+}
+
+void procmsg_add_mark_queue(FolderItem *item, gint num, MsgFlags flags)
+{
+ MsgInfo *queue_msginfo;
+
+ queue_msginfo = g_new0(MsgInfo, 1);
+ queue_msginfo->msgnum = num;
+ queue_msginfo->flags = flags;
+ item->mark_queue = g_slist_append
+ (item->mark_queue, queue_msginfo);
+ return;
+}
+
+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) {
+ MsgInfo *msginfo = (MsgInfo *)cur->data;
+
+ flags = g_hash_table_lookup(mark_table,
+ GUINT_TO_POINTER(msginfo->msgnum));
+ if (flags != NULL)
+ g_free(flags);
+
+ flags = g_new0(MsgFlags, 1);
+ flags->perm_flags = msginfo->flags.perm_flags;
+
+ g_hash_table_insert(mark_table,
+ GUINT_TO_POINTER(msginfo->msgnum), flags);
+
+ }
+
+ if (item->mark_queue && !item->opened) {
+ procmsg_write_mark_file(item, mark_table);
+ procmsg_msg_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;
+
+ fp = procmsg_open_mark_file(item, DATA_WRITE);
+ g_hash_table_foreach(mark_table, write_mark_func, fp);
+ fclose(fp);
+}
+
+static FILE *procmsg_open_data_file(const gchar *file, guint version,
+ DataOpenMode mode,
+ gchar *buf, size_t buf_size)
+{
+ FILE *fp;
+ guint32 data_ver;
+
+ g_return_val_if_fail(file != NULL, NULL);
+
+ if (mode == DATA_WRITE) {
+ if ((fp = g_fopen(file, "wb")) == NULL) {
+ FILE_OP_ERROR(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)
+ debug_print("Mark/Cache file '%s' not found\n", file);
+ else {
+ if (buf && buf_size > 0)
+ setvbuf(fp, buf, _IOFBF, buf_size);
+ if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
+ 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)
+ FILE_OP_ERROR(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 FILE *procmsg_open_cache_file_with_buffer(FolderItem *item,
+ DataOpenMode mode,
+ gchar *buf, size_t buf_size)
+{
+ gchar *cachefile;
+ FILE *fp;
+
+ cachefile = folder_item_get_cache_file(item);
+ fp = procmsg_open_data_file(cachefile, CACHE_VERSION, mode, buf,
+ buf_size);
+ g_free(cachefile);
+
+ return fp;
+}
+
+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;
+}
+
+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;
+}
+
+gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
+{
+ gchar *path, *file;
+
+ g_return_val_if_fail(msginfo != NULL, NULL);
+
+ if (msginfo->plaintext_file)
+ file = g_strdup(msginfo->plaintext_file);
+ else if (msginfo->file_path)
+ return g_strdup(msginfo->file_path);
+ else {
+ path = folder_item_get_path(msginfo->folder);
+ file = g_strconcat(path, G_DIR_SEPARATOR_S,
+ itos(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, "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;
+
+void procmsg_set_decrypt_message_func(DecryptMessageFunc func)
+{
+ decrypt_message_func = func;
+}
+
+FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
+{
+ FILE *fp;
+
+ if (decrypt_message_func)
+ 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;
+}
+
+void procmsg_empty_trash(FolderItem *trash)
+{
+ if (trash && 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);
+ }
+}
+
+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;
+ }
+
+ return 0;
+}
+
+void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
+{
+ static const gchar *def_cmd = "lpr %s";
+ static guint id = 0;
+ gchar *prtmp;
+ FILE *tmpfp, *prfp;
+ gchar buf[1024];
+ gchar *p;
+
+ g_return_if_fail(msginfo);
+
+ if ((tmpfp = procmime_get_first_text_content
+ (msginfo, conv_get_locale_charset_str())) == NULL) {
+ g_warning(_("Can't get text part\n"));
+ return;
+ }
+
+ prtmp = g_strdup_printf("%s%cprinttmp.%08x",
+ get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
+
+ if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
+ FILE_OP_ERROR(prtmp, "fopen");
+ g_free(prtmp);
+ fclose(tmpfp);
+ return;
+ }
+
+#define OUTPUT_HEADER(s, fmt) \
+ if (s) { \
+ gchar *locale_str; \
+ locale_str = conv_codeset_strdup \
+ (s, CS_INTERNAL, conv_get_locale_charset_str()); \
+ fprintf(prfp, fmt, locale_str ? locale_str : s); \
+ g_free(locale_str); \
+ }
+
+ OUTPUT_HEADER(msginfo->date, "Date: %s\n");
+ OUTPUT_HEADER(msginfo->from, "From: %s\n");
+ OUTPUT_HEADER(msginfo->to, "To: %s\n");
+ OUTPUT_HEADER(msginfo->newsgroups, "Newsgroups: %s\n");
+ OUTPUT_HEADER(msginfo->subject, "Subject: %s\n");
+ fputc('\n', prfp);
+
+#undef OUTPUT_HEADER
+
+ while (fgets(buf, sizeof(buf), tmpfp) != NULL)
+ fputs(buf, prfp);
+
+ fclose(prfp);
+ fclose(tmpfp);
+
+ if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
+ !strchr(p + 2, '%'))
+ g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
+ else {
+ if (cmdline)
+ g_warning(_("Print command line is invalid: `%s'\n"),
+ cmdline);
+ g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
+ }
+
+ g_free(prtmp);
+
+ g_strchomp(buf);
+ if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
+ system(buf);
+}
+
+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);
+
+ MEMBDUP(plaintext_file);
+ MEMBCOPY(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);
+
+ full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
+ full_msginfo->decryption_failed = msginfo->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);
+
+ g_free(msginfo->plaintext_file);
+
+ 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);
+}
diff --git a/libsylph/procmsg.h b/libsylph/procmsg.h
new file mode 100644
index 00000000..14c2b6d7
--- /dev/null
+++ b/libsylph/procmsg.h
@@ -0,0 +1,285 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PROCMSG_H__
+#define __PROCMSG_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/types.h>
+#include <string.h>
+
+typedef struct _MsgInfo MsgInfo;
+typedef struct _MsgFlags MsgFlags;
+typedef struct _MsgFileInfo MsgFileInfo;
+
+#include "folder.h"
+#include "procmime.h"
+
+typedef enum
+{
+ DATA_READ,
+ DATA_WRITE,
+ DATA_APPEND
+} DataOpenMode;
+
+#define MSG_NEW (1U << 0)
+#define MSG_UNREAD (1U << 1)
+#define MSG_MARKED (1U << 2)
+#define MSG_DELETED (1U << 3)
+#define MSG_REPLIED (1U << 4)
+#define MSG_FORWARDED (1U << 5)
+
+#define MSG_CLABEL_SBIT (7) /* start bit of color label */
+#define MAKE_MSG_CLABEL(h, m, l) (((h) << (MSG_CLABEL_SBIT + 2)) | \
+ ((m) << (MSG_CLABEL_SBIT + 1)) | \
+ ((l) << (MSG_CLABEL_SBIT + 0)))
+
+#define MSG_CLABEL_NONE MAKE_MSG_CLABEL(0U, 0U, 0U)
+#define MSG_CLABEL_1 MAKE_MSG_CLABEL(0U, 0U, 1U)
+#define MSG_CLABEL_2 MAKE_MSG_CLABEL(0U, 1U, 0U)
+#define MSG_CLABEL_3 MAKE_MSG_CLABEL(0U, 1U, 1U)
+#define MSG_CLABEL_4 MAKE_MSG_CLABEL(1U, 0U, 0U)
+#define MSG_CLABEL_5 MAKE_MSG_CLABEL(1U, 0U, 1U)
+#define MSG_CLABEL_6 MAKE_MSG_CLABEL(1U, 1U, 0U)
+#define MSG_CLABEL_7 MAKE_MSG_CLABEL(1U, 1U, 1U)
+
+#define MSG_CLABEL_ORANGE MSG_CLABEL_1
+#define MSG_CLABEL_RED MSG_CLABEL_2
+#define MSG_CLABEL_PINK MSG_CLABEL_3
+#define MSG_CLABEL_SKYBLUE MSG_CLABEL_4
+#define MSG_CLABEL_BLUE MSG_CLABEL_5
+#define MSG_CLABEL_GREEN MSG_CLABEL_6
+#define MSG_CLABEL_BROWN MSG_CLABEL_7
+
+/* RESERVED */
+#define MSG_RESERVED (1U << 31)
+
+#define MSG_CLABEL_FLAG_MASK (MSG_CLABEL_7)
+
+typedef guint32 MsgPermFlags;
+
+#define MSG_MOVE (1U << 0)
+#define MSG_COPY (1U << 1)
+#define MSG_QUEUED (1U << 16)
+#define MSG_DRAFT (1U << 17)
+#define MSG_ENCRYPTED (1U << 18)
+#define MSG_IMAP (1U << 19)
+#define MSG_NEWS (1U << 20)
+#define MSG_SIGNED (1U << 21)
+#define MSG_CACHED (1U << 28)
+#define MSG_MIME (1U << 29)
+#define MSG_INVALID (1U << 30)
+#define MSG_RECEIVED (1U << 31)
+
+#define MSG_CACHED_FLAG_MASK (MSG_MIME)
+
+typedef guint32 MsgTmpFlags;
+
+#define MSG_SET_FLAGS(msg, flags) { (msg) |= (flags); }
+#define MSG_UNSET_FLAGS(msg, flags) { (msg) &= ~(flags); }
+#define MSG_SET_PERM_FLAGS(msg, flags) \
+ MSG_SET_FLAGS((msg).perm_flags, flags)
+#define MSG_SET_TMP_FLAGS(msg, flags) \
+ MSG_SET_FLAGS((msg).tmp_flags, flags)
+#define MSG_UNSET_PERM_FLAGS(msg, flags) \
+ MSG_UNSET_FLAGS((msg).perm_flags, flags)
+#define MSG_UNSET_TMP_FLAGS(msg, flags) \
+ MSG_UNSET_FLAGS((msg).tmp_flags, flags)
+
+#define MSG_IS_NEW(msg) (((msg).perm_flags & MSG_NEW) != 0)
+#define MSG_IS_UNREAD(msg) (((msg).perm_flags & MSG_UNREAD) != 0)
+#define MSG_IS_MARKED(msg) (((msg).perm_flags & MSG_MARKED) != 0)
+#define MSG_IS_DELETED(msg) (((msg).perm_flags & MSG_DELETED) != 0)
+#define MSG_IS_REPLIED(msg) (((msg).perm_flags & MSG_REPLIED) != 0)
+#define MSG_IS_FORWARDED(msg) (((msg).perm_flags & MSG_FORWARDED) != 0)
+
+#define MSG_GET_COLORLABEL(msg) (((msg).perm_flags & MSG_CLABEL_FLAG_MASK))
+#define MSG_GET_COLORLABEL_VALUE(msg) (MSG_GET_COLORLABEL(msg) >> MSG_CLABEL_SBIT)
+#define MSG_SET_COLORLABEL_VALUE(msg, val) \
+ MSG_SET_PERM_FLAGS(msg, ((((guint)(val)) & 7) << MSG_CLABEL_SBIT))
+
+#define MSG_IS_MOVE(msg) (((msg).tmp_flags & MSG_MOVE) != 0)
+#define MSG_IS_COPY(msg) (((msg).tmp_flags & MSG_COPY) != 0)
+
+#define MSG_IS_QUEUED(msg) (((msg).tmp_flags & MSG_QUEUED) != 0)
+#define MSG_IS_DRAFT(msg) (((msg).tmp_flags & MSG_DRAFT) != 0)
+#define MSG_IS_ENCRYPTED(msg) (((msg).tmp_flags & MSG_ENCRYPTED) != 0)
+#define MSG_IS_IMAP(msg) (((msg).tmp_flags & MSG_IMAP) != 0)
+#define MSG_IS_NEWS(msg) (((msg).tmp_flags & MSG_NEWS) != 0)
+#define MSG_IS_SIGNED(msg) (((msg).tmp_flags & MSG_SIGNED) != 0)
+#define MSG_IS_CACHED(msg) (((msg).tmp_flags & MSG_CACHED) != 0)
+#define MSG_IS_MIME(msg) (((msg).tmp_flags & MSG_MIME) != 0)
+#define MSG_IS_INVALID(msg) (((msg).tmp_flags & MSG_INVALID) != 0)
+#define MSG_IS_RECEIVED(msg) (((msg).tmp_flags & MSG_RECEIVED) != 0)
+
+#define WRITE_CACHE_DATA_INT(n, fp) \
+{ \
+ guint32 idata; \
+ \
+ idata = (guint32)n; \
+ fwrite(&idata, sizeof(idata), 1, fp); \
+}
+
+#define WRITE_CACHE_DATA(data, fp) \
+{ \
+ size_t len; \
+ \
+ if (data == NULL) { \
+ len = 0; \
+ WRITE_CACHE_DATA_INT(len, fp); \
+ } else { \
+ len = strlen(data); \
+ WRITE_CACHE_DATA_INT(len, fp); \
+ if (len > 0) \
+ fwrite(data, len, 1, fp); \
+ } \
+}
+
+struct _MsgFlags
+{
+ MsgPermFlags perm_flags;
+ MsgTmpFlags tmp_flags;
+};
+
+struct _MsgInfo
+{
+ guint msgnum;
+ off_t size;
+ time_t mtime;
+ time_t date_t;
+
+ MsgFlags flags;
+
+ gchar *fromname;
+
+ gchar *date;
+ gchar *from;
+ gchar *to;
+ gchar *cc;
+ gchar *newsgroups;
+ gchar *subject;
+ gchar *msgid;
+ gchar *inreplyto;
+
+ GSList *references;
+
+ FolderItem *folder;
+ FolderItem *to_folder;
+
+ gchar *xface;
+
+ /* used only for temporary messages */
+ gchar *file_path;
+
+ /* used only for encrypted messages */
+ gchar *plaintext_file;
+ guint decryption_failed : 1;
+};
+
+struct _MsgFileInfo
+{
+ gchar *file;
+ MsgFlags *flags;
+};
+
+typedef FILE * (*DecryptMessageFunc) (MsgInfo *msginfo,
+ MimeInfo **mimeinfo);
+
+GHashTable *procmsg_msg_hash_table_create (GSList *mlist);
+void procmsg_msg_hash_table_append (GHashTable *msg_table,
+ GSList *mlist);
+GHashTable *procmsg_to_folder_hash_table_create (GSList *mlist);
+
+GSList *procmsg_read_cache (FolderItem *item,
+ gboolean scan_file);
+void procmsg_set_flags (GSList *mlist,
+ FolderItem *item);
+GSList *procmsg_sort_msg_list (GSList *mlist,
+ FolderSortKey sort_key,
+ FolderSortType sort_type);
+gint procmsg_get_last_num_in_msg_list(GSList *mlist);
+void procmsg_msg_list_free (GSList *mlist);
+void procmsg_write_cache (MsgInfo *msginfo,
+ FILE *fp);
+void procmsg_write_flags (MsgInfo *msginfo,
+ FILE *fp);
+void procmsg_flush_mark_queue (FolderItem *item,
+ FILE *fp);
+void procmsg_add_mark_queue (FolderItem *item,
+ gint num,
+ MsgFlags flags);
+void procmsg_add_flags (FolderItem *item,
+ gint num,
+ MsgFlags flags);
+void procmsg_get_mark_sum (FolderItem *item,
+ gint *new,
+ gint *unread,
+ gint *total,
+ gint *min,
+ gint *max,
+ gint first);
+FILE *procmsg_open_cache_file (FolderItem *item,
+ DataOpenMode mode);
+FILE *procmsg_open_mark_file (FolderItem *item,
+ DataOpenMode mode);
+
+void procmsg_clear_cache (FolderItem *item);
+void procmsg_clear_mark (FolderItem *item);
+
+GNode *procmsg_get_thread_tree (GSList *mlist);
+
+gint procmsg_move_messages (GSList *mlist);
+gint procmsg_copy_messages (GSList *mlist);
+
+gchar *procmsg_get_message_file_path (MsgInfo *msginfo);
+gchar *procmsg_get_message_file (MsgInfo *msginfo);
+GSList *procmsg_get_message_file_list (GSList *mlist);
+void procmsg_message_file_list_free (GSList *file_list);
+FILE *procmsg_open_message (MsgInfo *msginfo);
+
+void procmsg_set_decrypt_message_func (DecryptMessageFunc func);
+FILE *procmsg_open_message_decrypted (MsgInfo *msginfo,
+ MimeInfo **mimeinfo);
+
+gboolean procmsg_msg_exist (MsgInfo *msginfo);
+
+void procmsg_empty_trash (FolderItem *trash);
+void procmsg_empty_all_trash (void);
+
+gint procmsg_save_to_outbox (FolderItem *outbox,
+ const gchar *file);
+void procmsg_print_message (MsgInfo *msginfo,
+ const gchar *cmdline);
+
+MsgInfo *procmsg_msginfo_copy (MsgInfo *msginfo);
+MsgInfo *procmsg_msginfo_get_full_info (MsgInfo *msginfo);
+gboolean procmsg_msginfo_equal (MsgInfo *msginfo_a,
+ MsgInfo *msginfo_b);
+void procmsg_msginfo_free (MsgInfo *msginfo);
+
+gint procmsg_cmp_msgnum_for_sort (gconstpointer a,
+ gconstpointer b);
+
+#endif /* __PROCMSG_H__ */
diff --git a/libsylph/smtp.c b/libsylph/smtp.c
new file mode 100644
index 00000000..25a0f71a
--- /dev/null
+++ b/libsylph/smtp.c
@@ -0,0 +1,613 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "smtp.h"
+#include "md5.h"
+#include "base64.h"
+#include "utils.h"
+
+static void smtp_session_destroy(Session *session);
+
+static gint smtp_from(SMTPSession *session);
+
+static gint smtp_auth(SMTPSession *session);
+static gint smtp_starttls(SMTPSession *session);
+static gint smtp_auth_cram_md5(SMTPSession *session);
+static gint smtp_auth_plain(SMTPSession *session);
+static gint smtp_auth_login(SMTPSession *session);
+
+static gint smtp_ehlo(SMTPSession *session);
+static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg);
+
+static gint smtp_helo(SMTPSession *session);
+static gint smtp_rcpt(SMTPSession *session);
+static gint smtp_data(SMTPSession *session);
+static gint smtp_send_data(SMTPSession *session);
+/* static gint smtp_rset(SMTPSession *session); */
+static gint smtp_quit(SMTPSession *session);
+static gint smtp_eom(SMTPSession *session);
+
+static gint smtp_session_recv_msg(Session *session, const gchar *msg);
+static gint smtp_session_send_data_finished(Session *session, guint len);
+
+
+Session *smtp_session_new(void)
+{
+ SMTPSession *session;
+
+ session = g_new0(SMTPSession, 1);
+
+ session_init(SESSION(session));
+
+ SESSION(session)->type = SESSION_SMTP;
+
+ SESSION(session)->recv_msg = smtp_session_recv_msg;
+
+ SESSION(session)->recv_data_finished = NULL;
+ SESSION(session)->send_data_finished = smtp_session_send_data_finished;
+
+ SESSION(session)->destroy = smtp_session_destroy;
+
+ session->state = SMTP_READY;
+
+#if USE_SSL
+ session->tls_init_done = FALSE;
+#endif
+
+ session->hostname = NULL;
+ session->user = NULL;
+ session->pass = NULL;
+
+ session->from = NULL;
+ session->to_list = NULL;
+ session->cur_to = NULL;
+
+ session->send_data = NULL;
+ session->send_data_len = 0;
+
+ session->avail_auth_type = 0;
+ session->forced_auth_type = 0;
+ session->auth_type = 0;
+
+ session->error_val = SM_OK;
+ session->error_msg = NULL;
+
+ return SESSION(session);
+}
+
+static void smtp_session_destroy(Session *session)
+{
+ SMTPSession *smtp_session = SMTP_SESSION(session);
+
+ g_free(smtp_session->hostname);
+ g_free(smtp_session->user);
+ g_free(smtp_session->pass);
+ g_free(smtp_session->from);
+
+ g_free(smtp_session->send_data);
+
+ g_free(smtp_session->error_msg);
+}
+
+static gint smtp_from(SMTPSession *session)
+{
+ gchar buf[MSGBUFSIZE];
+
+ g_return_val_if_fail(session->from != NULL, SM_ERROR);
+
+ session->state = SMTP_FROM;
+
+ if (strchr(session->from, '<'))
+ g_snprintf(buf, sizeof(buf), "MAIL FROM:%s", session->from);
+ else
+ g_snprintf(buf, sizeof(buf), "MAIL FROM:<%s>", session->from);
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
+ log_print("SMTP> %s\n", buf);
+
+ return SM_OK;
+}
+
+static gint smtp_auth(SMTPSession *session)
+{
+
+ g_return_val_if_fail(session->user != NULL, SM_ERROR);
+
+ session->state = SMTP_AUTH;
+
+ if (session->forced_auth_type == SMTPAUTH_CRAM_MD5 ||
+ (session->forced_auth_type == 0 &&
+ (session->avail_auth_type & SMTPAUTH_CRAM_MD5) != 0))
+ smtp_auth_cram_md5(session);
+ else if (session->forced_auth_type == SMTPAUTH_PLAIN ||
+ (session->forced_auth_type == 0 &&
+ (session->avail_auth_type & SMTPAUTH_PLAIN) != 0))
+ smtp_auth_plain(session);
+ else if (session->forced_auth_type == SMTPAUTH_LOGIN ||
+ (session->forced_auth_type == 0 &&
+ (session->avail_auth_type & SMTPAUTH_LOGIN) != 0))
+ smtp_auth_login(session);
+ else {
+ log_warning(_("SMTP AUTH not available\n"));
+ return SM_AUTHFAIL;
+ }
+
+ return SM_OK;
+}
+
+static gint smtp_auth_recv(SMTPSession *session, const gchar *msg)
+{
+ gchar buf[MSGBUFSIZE];
+
+ switch (session->auth_type) {
+ case SMTPAUTH_LOGIN:
+ session->state = SMTP_AUTH_LOGIN_USER;
+
+ if (!strncmp(msg, "334 ", 4)) {
+ base64_encode(buf, session->user, strlen(session->user));
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ buf);
+ log_print("ESMTP> [USERID]\n");
+ } else {
+ /* Server rejects AUTH */
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ "*");
+ log_print("ESMTP> *\n");
+ }
+ break;
+ case SMTPAUTH_CRAM_MD5:
+ session->state = SMTP_AUTH_CRAM_MD5;
+
+ if (!strncmp(msg, "334 ", 4)) {
+ gchar *response;
+ gchar *response64;
+ gchar *challenge;
+ gint challengelen;
+ guchar hexdigest[33];
+
+ challenge = g_malloc(strlen(msg + 4) + 1);
+ challengelen = base64_decode(challenge, msg + 4, -1);
+ challenge[challengelen] = '\0';
+ log_print("ESMTP< [Decoded: %s]\n", challenge);
+
+ g_snprintf(buf, sizeof(buf), "%s", session->pass);
+ md5_hex_hmac(hexdigest, challenge, challengelen,
+ buf, strlen(session->pass));
+ g_free(challenge);
+
+ response = g_strdup_printf
+ ("%s %s", session->user, hexdigest);
+ log_print("ESMTP> [Encoded: %s]\n", response);
+
+ response64 = g_malloc((strlen(response) + 3) * 2 + 1);
+ base64_encode(response64, response, strlen(response));
+ g_free(response);
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ response64);
+ log_print("ESMTP> %s\n", response64);
+ g_free(response64);
+ } else {
+ /* Server rejects AUTH */
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ "*");
+ log_print("ESMTP> *\n");
+ }
+ break;
+ case SMTPAUTH_DIGEST_MD5:
+ default:
+ /* stop smtp_auth when no correct authtype */
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "*");
+ log_print("ESMTP> *\n");
+ break;
+ }
+
+ return SM_OK;
+}
+
+static gint smtp_auth_login_user_recv(SMTPSession *session, const gchar *msg)
+{
+ gchar buf[MSGBUFSIZE];
+
+ session->state = SMTP_AUTH_LOGIN_PASS;
+
+ if (!strncmp(msg, "334 ", 4))
+ base64_encode(buf, session->pass, strlen(session->pass));
+ else
+ /* Server rejects AUTH */
+ g_snprintf(buf, sizeof(buf), "*");
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
+ log_print("ESMTP> [PASSWORD]\n");
+
+ return SM_OK;
+}
+
+static gint smtp_ehlo(SMTPSession *session)
+{
+ gchar buf[MSGBUFSIZE];
+
+ session->state = SMTP_EHLO;
+
+ session->avail_auth_type = 0;
+
+ g_snprintf(buf, sizeof(buf), "EHLO %s",
+ session->hostname ? session->hostname : get_domain_name());
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
+ log_print("ESMTP> %s\n", buf);
+
+ return SM_OK;
+}
+
+static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg)
+{
+ if (strncmp(msg, "250", 3) == 0) {
+ const gchar *p = msg;
+ p += 3;
+ if (*p == '-' || *p == ' ') p++;
+ if (g_ascii_strncasecmp(p, "AUTH", 4) == 0 && p[4] != '\0') {
+ p += 5;
+ if (strcasestr(p, "PLAIN"))
+ session->avail_auth_type |= SMTPAUTH_PLAIN;
+ if (strcasestr(p, "LOGIN"))
+ session->avail_auth_type |= SMTPAUTH_LOGIN;
+ if (strcasestr(p, "CRAM-MD5"))
+ session->avail_auth_type |= SMTPAUTH_CRAM_MD5;
+ if (strcasestr(p, "DIGEST-MD5"))
+ session->avail_auth_type |= SMTPAUTH_DIGEST_MD5;
+ }
+ return SM_OK;
+ } else if ((msg[0] == '1' || msg[0] == '2' || msg[0] == '3') &&
+ (msg[3] == ' ' || msg[3] == '\0'))
+ return SM_OK;
+ else if (msg[0] == '5' && msg[1] == '0' &&
+ (msg[2] == '4' || msg[2] == '3' || msg[2] == '1'))
+ return SM_ERROR;
+
+ return SM_ERROR;
+}
+
+static gint smtp_starttls(SMTPSession *session)
+{
+ session->state = SMTP_STARTTLS;
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "STARTTLS");
+ log_print("ESMTP> STARTTLS\n");
+
+ return SM_OK;
+}
+
+static gint smtp_auth_cram_md5(SMTPSession *session)
+{
+ session->state = SMTP_AUTH;
+ session->auth_type = SMTPAUTH_CRAM_MD5;
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "AUTH CRAM-MD5");
+ log_print("ESMTP> AUTH CRAM-MD5\n");
+
+ return SM_OK;
+}
+
+static gint smtp_auth_plain(SMTPSession *session)
+{
+ gchar *authstr;
+ gint authlen;
+ gchar *outbuf;
+ gchar *p;
+
+ session->state = SMTP_AUTH_PLAIN;
+ session->auth_type = SMTPAUTH_PLAIN;
+
+ /*
+ * construct the string: \0<user>\0<pass>
+ */
+
+ authlen = 1 + strlen(session->user) + 1 + strlen(session->pass);
+ authstr = g_malloc(authlen + 1);
+
+ p = authstr;
+
+ *p++ = '\0';
+ strcpy(p, session->user);
+ p += strlen(p) + 1;
+ strcpy(p, session->pass);
+
+ outbuf = g_malloc(sizeof("AUTH PLAIN ") + authlen * 2 + 1);
+
+ strcpy(outbuf, "AUTH PLAIN ");
+ p = outbuf + strlen(outbuf);
+ base64_encode(p, authstr, authlen);
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, outbuf);
+ log_print("ESMTP> AUTH PLAIN ********\n");
+
+ g_free(outbuf);
+ g_free(authstr);
+
+ return SM_OK;
+}
+
+static gint smtp_auth_login(SMTPSession *session)
+{
+ session->state = SMTP_AUTH;
+ session->auth_type = SMTPAUTH_LOGIN;
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "AUTH LOGIN");
+ log_print("ESMTP> AUTH LOGIN\n");
+
+ return SM_OK;
+}
+
+static gint smtp_helo(SMTPSession *session)
+{
+ gchar buf[MSGBUFSIZE];
+
+ session->state = SMTP_HELO;
+
+ g_snprintf(buf, sizeof(buf), "HELO %s",
+ session->hostname ? session->hostname : get_domain_name());
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
+ log_print("SMTP> %s\n", buf);
+
+ return SM_OK;
+}
+
+static gint smtp_rcpt(SMTPSession *session)
+{
+ gchar buf[MSGBUFSIZE];
+ gchar *to;
+
+ g_return_val_if_fail(session->cur_to != NULL, SM_ERROR);
+
+ session->state = SMTP_RCPT;
+
+ to = (gchar *)session->cur_to->data;
+
+ if (strchr(to, '<'))
+ g_snprintf(buf, sizeof(buf), "RCPT TO:%s", to);
+ else
+ g_snprintf(buf, sizeof(buf), "RCPT TO:<%s>", to);
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
+ log_print("SMTP> %s\n", buf);
+
+ session->cur_to = session->cur_to->next;
+
+ return SM_OK;
+}
+
+static gint smtp_data(SMTPSession *session)
+{
+ session->state = SMTP_DATA;
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "DATA");
+ log_print("SMTP> DATA\n");
+
+ return SM_OK;
+}
+
+static gint smtp_send_data(SMTPSession *session)
+{
+ session->state = SMTP_SEND_DATA;
+
+ session_send_data(SESSION(session), session->send_data,
+ session->send_data_len);
+
+ return SM_OK;
+}
+
+#if 0
+static gint smtp_rset(SMTPSession *session)
+{
+ session->state = SMTP_RSET;
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "RSET");
+ log_print("SMTP> RSET\n");
+
+ return SM_OK;
+}
+#endif
+
+static gint smtp_quit(SMTPSession *session)
+{
+ session->state = SMTP_QUIT;
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "QUIT");
+ log_print("SMTP> QUIT\n");
+
+ return SM_OK;
+}
+
+static gint smtp_eom(SMTPSession *session)
+{
+ session->state = SMTP_EOM;
+
+ session_send_msg(SESSION(session), SESSION_MSG_NORMAL, ".");
+ log_print("SMTP> . (EOM)\n");
+
+ return SM_OK;
+}
+
+static gint smtp_session_recv_msg(Session *session, const gchar *msg)
+{
+ SMTPSession *smtp_session = SMTP_SESSION(session);
+ gboolean cont = FALSE;
+
+ if (strlen(msg) < 4) {
+ log_warning(_("bad SMTP response\n"));
+ return -1;
+ }
+
+ switch (smtp_session->state) {
+ case SMTP_EHLO:
+ case SMTP_STARTTLS:
+ case SMTP_AUTH:
+ case SMTP_AUTH_PLAIN:
+ case SMTP_AUTH_LOGIN_USER:
+ case SMTP_AUTH_LOGIN_PASS:
+ case SMTP_AUTH_CRAM_MD5:
+ log_print("ESMTP< %s\n", msg);
+ break;
+ default:
+ log_print("SMTP< %s\n", msg);
+ break;
+ }
+
+ if (msg[0] == '5' && msg[1] == '0' &&
+ (msg[2] == '4' || msg[2] == '3' || msg[2] == '1')) {
+ log_warning(_("error occurred on SMTP session\n"));
+ smtp_session->state = SMTP_ERROR;
+ smtp_session->error_val = SM_ERROR;
+ g_free(smtp_session->error_msg);
+ smtp_session->error_msg = g_strdup(msg);
+ return -1;
+ }
+
+ if (!strncmp(msg, "535", 3)) {
+ log_warning(_("error occurred on authentication\n"));
+ smtp_session->state = SMTP_ERROR;
+ smtp_session->error_val = SM_AUTHFAIL;
+ g_free(smtp_session->error_msg);
+ smtp_session->error_msg = g_strdup(msg);
+ return -1;
+ }
+
+ if (msg[0] != '1' && msg[0] != '2' && msg[0] != '3') {
+ log_warning(_("error occurred on SMTP session\n"));
+ smtp_session->state = SMTP_ERROR;
+ smtp_session->error_val = SM_ERROR;
+ g_free(smtp_session->error_msg);
+ smtp_session->error_msg = g_strdup(msg);
+ return -1;
+ }
+
+ if (msg[3] == '-')
+ cont = TRUE;
+ else if (msg[3] != ' ' && msg[3] != '\0') {
+ log_warning(_("bad SMTP response\n"));
+ smtp_session->state = SMTP_ERROR;
+ smtp_session->error_val = SM_UNRECOVERABLE;
+ return -1;
+ }
+
+ /* ignore all multiline responses except for EHLO */
+ if (cont && smtp_session->state != SMTP_EHLO)
+ return session_recv_msg(session);
+
+ switch (smtp_session->state) {
+ case SMTP_READY:
+ case SMTP_CONNECTED:
+#if USE_SSL
+ if (smtp_session->user || session->ssl_type != SSL_NONE)
+#else
+ if (smtp_session->user)
+#endif
+ smtp_ehlo(smtp_session);
+ else
+ smtp_helo(smtp_session);
+ break;
+ case SMTP_HELO:
+ smtp_from(smtp_session);
+ break;
+ case SMTP_EHLO:
+ smtp_ehlo_recv(smtp_session, msg);
+ if (cont == TRUE)
+ break;
+#if USE_SSL
+ if (session->ssl_type == SSL_STARTTLS &&
+ smtp_session->tls_init_done == FALSE) {
+ smtp_starttls(smtp_session);
+ break;
+ }
+#endif
+ if (smtp_session->user) {
+ if (smtp_auth(smtp_session) != SM_OK)
+ smtp_from(smtp_session);
+ } else
+ smtp_from(smtp_session);
+ break;
+ case SMTP_STARTTLS:
+#if USE_SSL
+ if (session_start_tls(session) < 0) {
+ log_warning(_("can't start TLS session\n"));
+ smtp_session->state = SMTP_ERROR;
+ smtp_session->error_val = SM_ERROR;
+ return -1;
+ }
+ smtp_session->tls_init_done = TRUE;
+ smtp_ehlo(smtp_session);
+#endif
+ break;
+ case SMTP_AUTH:
+ smtp_auth_recv(smtp_session, msg);
+ break;
+ case SMTP_AUTH_LOGIN_USER:
+ smtp_auth_login_user_recv(smtp_session, msg);
+ break;
+ case SMTP_AUTH_PLAIN:
+ case SMTP_AUTH_LOGIN_PASS:
+ case SMTP_AUTH_CRAM_MD5:
+ smtp_from(smtp_session);
+ break;
+ case SMTP_FROM:
+ if (smtp_session->cur_to)
+ smtp_rcpt(smtp_session);
+ break;
+ case SMTP_RCPT:
+ if (smtp_session->cur_to)
+ smtp_rcpt(smtp_session);
+ else
+ smtp_data(smtp_session);
+ break;
+ case SMTP_DATA:
+ smtp_send_data(smtp_session);
+ break;
+ case SMTP_EOM:
+ smtp_quit(smtp_session);
+ break;
+ case SMTP_QUIT:
+ session_disconnect(session);
+ break;
+ case SMTP_ERROR:
+ default:
+ log_warning(_("error occurred on SMTP session\n"));
+ smtp_session->error_val = SM_ERROR;
+ return -1;
+ }
+
+ if (cont)
+ return session_recv_msg(session);
+
+ return 0;
+}
+
+static gint smtp_session_send_data_finished(Session *session, guint len)
+{
+ smtp_eom(SMTP_SESSION(session));
+ return 0;
+}
diff --git a/libsylph/smtp.h b/libsylph/smtp.h
new file mode 100644
index 00000000..445bba69
--- /dev/null
+++ b/libsylph/smtp.h
@@ -0,0 +1,117 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2005 Hiroyuki Yamamoto
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SMTP_H__
+#define __SMTP_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+
+#include "session.h"
+
+typedef struct _SMTPSession SMTPSession;
+
+#define SMTP_SESSION(obj) ((SMTPSession *)obj)
+
+#define MSGBUFSIZE 8192
+
+typedef enum
+{
+ SM_OK = 0,
+ SM_ERROR = 128,
+ SM_UNRECOVERABLE = 129,
+ SM_AUTHFAIL = 130
+} SMTPErrorValue;
+
+typedef enum
+{
+ ESMTP_8BITMIME = 1 << 0,
+ ESMTP_SIZE = 1 << 1,
+ ESMTP_ETRN = 1 << 2
+} ESMTPFlag;
+
+typedef enum
+{
+ SMTPAUTH_LOGIN = 1 << 0,
+ SMTPAUTH_CRAM_MD5 = 1 << 1,
+ SMTPAUTH_DIGEST_MD5 = 1 << 2,
+ SMTPAUTH_PLAIN = 1 << 3
+} SMTPAuthType;
+
+typedef enum
+{
+ SMTP_READY,
+ SMTP_CONNECTED,
+ SMTP_HELO,
+ SMTP_EHLO,
+ SMTP_STARTTLS,
+ SMTP_FROM,
+ SMTP_AUTH,
+ SMTP_AUTH_PLAIN,
+ SMTP_AUTH_LOGIN_USER,
+ SMTP_AUTH_LOGIN_PASS,
+ SMTP_AUTH_CRAM_MD5,
+ SMTP_RCPT,
+ SMTP_DATA,
+ SMTP_SEND_DATA,
+ SMTP_EOM,
+ SMTP_RSET,
+ SMTP_QUIT,
+ SMTP_ERROR,
+ SMTP_DISCONNECTED,
+
+ N_SMTP_PHASE
+} SMTPState;
+
+struct _SMTPSession
+{
+ Session session;
+
+ SMTPState state;
+
+#if USE_SSL
+ gboolean tls_init_done;
+#endif
+
+ gchar *hostname;
+
+ gchar *user;
+ gchar *pass;
+
+ gchar *from;
+ GSList *to_list;
+ GSList *cur_to;
+
+ guchar *send_data;
+ guint send_data_len;
+
+ SMTPAuthType avail_auth_type;
+ SMTPAuthType forced_auth_type;
+ SMTPAuthType auth_type;
+
+ SMTPErrorValue error_val;
+ gchar *error_msg;
+};
+
+Session *smtp_session_new (void);
+
+#endif /* __SMTP_H__ */