diff options
author | hiro <hiro@ee746299-78ed-0310-b773-934348b2243d> | 2005-09-05 10:00:53 +0000 |
---|---|---|
committer | hiro <hiro@ee746299-78ed-0310-b773-934348b2243d> | 2005-09-05 10:00:53 +0000 |
commit | 3bf24b9336184fe9e28f7e09b9c5200a5f82b7d2 (patch) | |
tree | 51ccac6f26dcdf9fcfac1a7879477bfde2759b80 /libsylph | |
parent | 11776e5a524745b01ac145439ac2892a29bd0826 (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')
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__ */ |