2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 #include <glib/gi18n.h>
56 #include "procheader.h"
57 #include "prefs_account.h"
62 #include "prefs_common.h"
63 #include "inputdialog.h"
65 #include "remotefolder.h"
66 #include "alertpanel.h"
68 #include "statusbar.h"
70 #include "imap-thread.h"
73 typedef struct _IMAPFolder IMAPFolder;
74 typedef struct _IMAPSession IMAPSession;
75 typedef struct _IMAPNameSpace IMAPNameSpace;
76 typedef struct _IMAPFolderItem IMAPFolderItem;
78 #include "prefs_account.h"
80 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
81 #define IMAP_FOLDER_ITEM(obj) ((IMAPFolderItem *)obj)
82 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
88 /* list of IMAPNameSpace */
92 gchar last_seen_separator;
100 gboolean authenticated;
109 gboolean folder_content_changed;
115 struct _IMAPNameSpace
121 #define IMAP_SUCCESS 0
122 #define IMAP_SOCKET 2
123 #define IMAP_AUTHFAIL 3
124 #define IMAP_PROTOCOL 4
125 #define IMAP_SYNTAX 5
129 #define IMAPBUFSIZE 8192
133 IMAP_FLAG_SEEN = 1 << 0,
134 IMAP_FLAG_ANSWERED = 1 << 1,
135 IMAP_FLAG_FLAGGED = 1 << 2,
136 IMAP_FLAG_DELETED = 1 << 3,
137 IMAP_FLAG_DRAFT = 1 << 4
140 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
141 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
142 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
143 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
144 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
147 #define IMAP4_PORT 143
149 #define IMAPS_PORT 993
152 #define IMAP_CMD_LIMIT 1000
154 struct _IMAPFolderItem
166 guint32 c_uid_validity;
169 GHashTable *flags_set_table;
170 GHashTable *flags_unset_table;
173 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
174 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
176 static void imap_folder_init (Folder *folder,
180 static Folder *imap_folder_new (const gchar *name,
182 static void imap_folder_destroy (Folder *folder);
184 static IMAPSession *imap_session_new (Folder *folder,
185 const PrefsAccount *account);
186 static void imap_session_authenticate(IMAPSession *session,
187 const PrefsAccount *account);
188 static void imap_session_destroy (Session *session);
190 static gchar *imap_fetch_msg (Folder *folder,
193 static gchar *imap_fetch_msg_full (Folder *folder,
198 static gint imap_add_msg (Folder *folder,
202 static gint imap_add_msgs (Folder *folder,
205 GRelation *relation);
207 static gint imap_copy_msg (Folder *folder,
210 static gint imap_copy_msgs (Folder *folder,
212 MsgInfoList *msglist,
213 GRelation *relation);
215 static gint imap_remove_msg (Folder *folder,
218 static gint imap_remove_msgs (Folder *folder,
220 MsgInfoList *msglist,
221 GRelation *relation);
222 static gint imap_remove_all_msg (Folder *folder,
225 static gboolean imap_is_msg_changed (Folder *folder,
229 static gint imap_close (Folder *folder,
232 static gint imap_scan_tree (Folder *folder);
234 static gint imap_create_tree (Folder *folder);
236 static FolderItem *imap_create_folder (Folder *folder,
239 static gint imap_rename_folder (Folder *folder,
242 static gint imap_remove_folder (Folder *folder,
245 static FolderItem *imap_folder_item_new (Folder *folder);
246 static void imap_folder_item_destroy (Folder *folder,
249 static IMAPSession *imap_session_get (Folder *folder);
251 static gint imap_auth (IMAPSession *session,
256 static gint imap_scan_tree_recursive (IMAPSession *session,
259 static void imap_create_missing_folders (Folder *folder);
260 static FolderItem *imap_create_special_folder
262 SpecialFolderItemType stype,
265 static gint imap_do_copy_msgs (Folder *folder,
267 MsgInfoList *msglist,
268 GRelation *relation);
270 static void imap_delete_all_cached_messages (FolderItem *item);
271 static void imap_set_batch (Folder *folder,
274 static gint imap_set_message_flags (IMAPSession *session,
275 MsgNumberList *numlist,
278 static gint imap_select (IMAPSession *session,
284 guint32 *uid_validity,
286 static gint imap_status (IMAPSession *session,
289 IMAPFolderItem *item,
292 guint32 *uid_validity,
296 static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
298 static gchar imap_get_path_separator (IMAPFolder *folder,
300 static gchar *imap_get_real_path (IMAPFolder *folder,
302 static void imap_synchronise (FolderItem *item);
304 static void imap_free_capabilities (IMAPSession *session);
306 /* low-level IMAP4rev1 commands */
307 static gint imap_cmd_login (IMAPSession *session,
311 static gint imap_cmd_noop (IMAPSession *session);
313 static gint imap_cmd_starttls (IMAPSession *session);
315 static gint imap_cmd_select (IMAPSession *session,
320 guint32 *uid_validity,
322 static gint imap_cmd_examine (IMAPSession *session,
327 guint32 *uid_validity,
329 static gint imap_cmd_create (IMAPSession *sock,
330 const gchar *folder);
331 static gint imap_cmd_rename (IMAPSession *sock,
332 const gchar *oldfolder,
333 const gchar *newfolder);
334 static gint imap_cmd_delete (IMAPSession *session,
335 const gchar *folder);
336 static gint imap_cmd_fetch (IMAPSession *sock,
338 const gchar *filename,
341 static gint imap_cmd_append (IMAPSession *session,
342 const gchar *destfolder,
346 static gint imap_cmd_copy (IMAPSession *session,
347 struct mailimap_set * set,
348 const gchar *destfolder,
349 GRelation *uid_mapping);
350 static gint imap_cmd_store (IMAPSession *session,
351 struct mailimap_set * set,
354 static gint imap_cmd_expunge (IMAPSession *session);
356 static void imap_path_separator_subst (gchar *str,
359 static gchar *imap_utf8_to_modified_utf7 (const gchar *from);
360 static gchar *imap_modified_utf7_to_utf8 (const gchar *mutf7_str);
362 static gboolean imap_rename_folder_func (GNode *node,
364 static gint imap_get_num_list (Folder *folder,
367 gboolean *old_uids_valid);
368 static GSList *imap_get_msginfos (Folder *folder,
370 GSList *msgnum_list);
371 static MsgInfo *imap_get_msginfo (Folder *folder,
374 static gboolean imap_scan_required (Folder *folder,
376 static void imap_change_flags (Folder *folder,
379 MsgPermFlags newflags);
380 static gint imap_get_flags (Folder *folder,
382 MsgInfoList *msglist,
383 GRelation *msgflags);
384 static gchar *imap_folder_get_path (Folder *folder);
385 static gchar *imap_item_get_path (Folder *folder,
387 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item);
390 /* data types conversion libetpan <-> sylpheed */
391 static GSList * imap_list_from_lep(IMAPFolder * folder,
392 clist * list, const gchar * real_path, gboolean all);
393 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist);
394 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist);
395 static GSList * imap_uid_list_from_lep(clist * list);
396 static GSList * imap_uid_list_from_lep_tab(carray * list);
397 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
399 static void imap_lep_set_free(GSList *seq_list);
400 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags);
402 typedef struct _hashtable_data {
403 IMAPSession *session;
405 IMAPFolderItem *item;
408 static FolderClass imap_class;
410 typedef struct _thread_data {
420 FolderClass *imap_get_class(void)
422 if (imap_class.idstr == NULL) {
423 imap_class.type = F_IMAP;
424 imap_class.idstr = "imap";
425 imap_class.uistr = "IMAP4";
427 /* Folder functions */
428 imap_class.new_folder = imap_folder_new;
429 imap_class.destroy_folder = imap_folder_destroy;
430 imap_class.scan_tree = imap_scan_tree;
431 imap_class.create_tree = imap_create_tree;
433 /* FolderItem functions */
434 imap_class.item_new = imap_folder_item_new;
435 imap_class.item_destroy = imap_folder_item_destroy;
436 imap_class.item_get_path = imap_item_get_path;
437 imap_class.create_folder = imap_create_folder;
438 imap_class.rename_folder = imap_rename_folder;
439 imap_class.remove_folder = imap_remove_folder;
440 imap_class.close = imap_close;
441 imap_class.get_num_list = imap_get_num_list;
442 imap_class.scan_required = imap_scan_required;
443 imap_class.set_xml = folder_set_xml;
444 imap_class.get_xml = folder_get_xml;
445 imap_class.item_set_xml = imap_item_set_xml;
446 imap_class.item_get_xml = imap_item_get_xml;
448 /* Message functions */
449 imap_class.get_msginfo = imap_get_msginfo;
450 imap_class.get_msginfos = imap_get_msginfos;
451 imap_class.fetch_msg = imap_fetch_msg;
452 imap_class.fetch_msg_full = imap_fetch_msg_full;
453 imap_class.add_msg = imap_add_msg;
454 imap_class.add_msgs = imap_add_msgs;
455 imap_class.copy_msg = imap_copy_msg;
456 imap_class.copy_msgs = imap_copy_msgs;
457 imap_class.remove_msg = imap_remove_msg;
458 imap_class.remove_msgs = imap_remove_msgs;
459 imap_class.remove_all_msg = imap_remove_all_msg;
460 imap_class.is_msg_changed = imap_is_msg_changed;
461 imap_class.change_flags = imap_change_flags;
462 imap_class.get_flags = imap_get_flags;
463 imap_class.set_batch = imap_set_batch;
464 imap_class.synchronise = imap_synchronise;
466 pthread_mutex_init(&imap_mutex, NULL);
473 static Folder *imap_folder_new(const gchar *name, const gchar *path)
477 folder = (Folder *)g_new0(IMAPFolder, 1);
478 folder->klass = &imap_class;
479 imap_folder_init(folder, name, path);
484 static void imap_folder_destroy(Folder *folder)
486 while (imap_folder_get_refcnt(folder) > 0)
487 gtk_main_iteration();
489 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
493 static void imap_folder_init(Folder *folder, const gchar *name,
496 folder_remote_folder_init((Folder *)folder, name, path);
499 static FolderItem *imap_folder_item_new(Folder *folder)
501 IMAPFolderItem *item;
503 item = g_new0(IMAPFolderItem, 1);
506 item->uid_list = NULL;
508 return (FolderItem *)item;
511 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
513 IMAPFolderItem *item = (IMAPFolderItem *)_item;
515 g_return_if_fail(item != NULL);
516 g_slist_free(item->uid_list);
521 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
523 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
526 g_slist_free(item->uid_list);
527 item->uid_list = NULL;
532 static void imap_reset_uid_lists(Folder *folder)
534 if(folder->node == NULL)
537 /* Destroy all uid lists and rest last uid */
538 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
541 int imap_get_capabilities(IMAPSession *session)
543 struct mailimap_capability_data *capabilities = NULL;
547 if (session->capability != NULL)
548 return MAILIMAP_NO_ERROR;
550 capabilities = imap_threaded_capability(session->folder, &result);
552 if (result != MAILIMAP_NO_ERROR) {
553 return MAILIMAP_ERROR_CAPABILITY;
556 if (capabilities == NULL) {
557 return MAILIMAP_NO_ERROR;
560 for(cur = clist_begin(capabilities->cap_list) ; cur != NULL ;
561 cur = clist_next(cur)) {
562 struct mailimap_capability * cap =
564 if (!cap || cap->cap_data.cap_name == NULL)
566 session->capability = g_slist_append
567 (session->capability,
568 g_strdup(cap->cap_data.cap_name));
569 debug_print("got capa %s\n", cap->cap_data.cap_name);
571 mailimap_capability_data_free(capabilities);
572 return MAILIMAP_NO_ERROR;
575 gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
578 for (cur = session->capability; cur; cur = cur->next) {
579 if (!g_ascii_strcasecmp(cur->data, cap))
585 static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
588 gint ok = IMAP_ERROR;
589 static time_t last_login_err = 0;
590 gchar *ext_info = "";
592 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR)
597 ok = imap_cmd_login(session, user, pass, "ANONYMOUS");
599 case IMAP_AUTH_CRAM_MD5:
600 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
602 case IMAP_AUTH_LOGIN:
603 ok = imap_cmd_login(session, user, pass, "LOGIN");
606 debug_print("capabilities:\n"
610 imap_has_capability(session, "ANONYMOUS"),
611 imap_has_capability(session, "CRAM-MD5"),
612 imap_has_capability(session, "LOGIN"));
613 if (imap_has_capability(session, "CRAM-MD5"))
614 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
615 if (ok == IMAP_ERROR) /* we always try LOGIN before giving up */
616 ok = imap_cmd_login(session, user, pass, "LOGIN");
619 if (ok == IMAP_SUCCESS)
620 session->authenticated = TRUE;
622 if (type == IMAP_AUTH_CRAM_MD5) {
623 ext_info = _("\n\nCRAM-MD5 logins only work if libetpan has been "
624 "compiled with SASL support and the "
625 "CRAM-MD5 SASL plugin is installed.");
628 if (time(NULL) - last_login_err > 10) {
629 if (!prefs_common.no_recv_err_panel) {
630 alertpanel_error(_("Connection to %s failed: "
632 SESSION(session)->server, ext_info);
634 log_error(_("Connection to %s failed: "
635 "login refused.%s\n"),
636 SESSION(session)->server, ext_info);
639 last_login_err = time(NULL);
644 static IMAPSession *imap_reconnect_if_possible(Folder *folder, IMAPSession *session)
646 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
647 /* Check if this is the first try to establish a
648 connection, if yes we don't try to reconnect */
649 debug_print("reconnecting\n");
650 if (rfolder->session == NULL) {
651 log_warning(_("Connecting to %s failed"),
652 folder->account->recv_server);
653 session_destroy(SESSION(session));
656 log_warning(_("IMAP4 connection to %s has been"
657 " disconnected. Reconnecting...\n"),
658 folder->account->recv_server);
659 statusbar_print_all(_("IMAP4 connection to %s has been"
660 " disconnected. Reconnecting...\n"),
661 folder->account->recv_server);
662 SESSION(session)->state = SESSION_DISCONNECTED;
663 session_destroy(SESSION(session));
664 /* Clear folders session to make imap_session_get create
665 a new session, because of rfolder->session == NULL
666 it will not try to reconnect again and so avoid an
668 rfolder->session = NULL;
669 session = imap_session_get(folder);
670 rfolder->session = SESSION(session);
676 #define lock_session() {\
677 debug_print("locking session\n"); \
678 session->busy = TRUE;\
681 #define unlock_session() {\
682 debug_print("unlocking session\n"); \
683 session->busy = FALSE;\
686 static IMAPSession *imap_session_get(Folder *folder)
688 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
689 IMAPSession *session = NULL;
691 g_return_val_if_fail(folder != NULL, NULL);
692 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
693 g_return_val_if_fail(folder->account != NULL, NULL);
695 if (prefs_common.work_offline &&
696 !inc_offline_should_override(
697 _("Sylpheed-Claws needs network access in order "
698 "to access the IMAP server."))) {
702 /* Make sure we have a session */
703 if (rfolder->session != NULL) {
704 session = IMAP_SESSION(rfolder->session);
705 /* don't do that yet...
710 imap_reset_uid_lists(folder);
711 if (time(NULL) - rfolder->last_failure <= 2)
713 session = imap_session_new(folder, folder->account);
715 if(session == NULL) {
716 rfolder->last_failure = time(NULL);
720 /* Make sure session is authenticated */
721 if (!IMAP_SESSION(session)->authenticated)
722 imap_session_authenticate(IMAP_SESSION(session), folder->account);
724 if (!IMAP_SESSION(session)->authenticated) {
725 imap_threaded_disconnect(session->folder);
726 SESSION(session)->state = SESSION_DISCONNECTED;
727 session_destroy(SESSION(session));
728 rfolder->session = NULL;
729 rfolder->last_failure = time(NULL);
733 /* I think the point of this code is to avoid sending a
734 * keepalive if we've used the session recently and therefore
735 * think it's still alive. Unfortunately, most of the code
736 * does not yet check for errors on the socket, and so if the
737 * connection drops we don't notice until the timeout expires.
738 * A better solution than sending a NOOP every time would be
739 * for every command to be prepared to retry until it is
740 * successfully sent. -- mbp */
741 if (time(NULL) - SESSION(session)->last_access_time > SESSION_TIMEOUT_INTERVAL) {
742 /* verify that the session is still alive */
743 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
744 debug_print("disconnected!\n");
745 session = imap_reconnect_if_possible(folder, session);
749 rfolder->session = SESSION(session);
751 return IMAP_SESSION(session);
754 static IMAPSession *imap_session_new(Folder * folder,
755 const PrefsAccount *account)
757 IMAPSession *session;
760 int authenticated = FALSE;
763 /* FIXME: IMAP over SSL only... */
766 port = account->set_imapport ? account->imapport
767 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
768 ssl_type = account->ssl_imap;
770 if (account->ssl_imap != SSL_NONE) {
771 if (alertpanel_full(_("Insecure connection"),
772 _("This connection is configured to be secured "
773 "using SSL, but SSL is not available in this "
774 "build of Sylpheed-Claws. \n\n"
775 "Do you want to continue connecting to this "
776 "server? The communication would not be "
778 GTK_STOCK_CANCEL, _("Con_tinue connecting"),
779 NULL, FALSE, NULL, ALERT_WARNING,
780 G_ALERTDEFAULT) != G_ALERTALTERNATE)
783 port = account->set_imapport ? account->imapport
788 statusbar_print_all(_("Connecting to IMAP4 server: %s..."), folder->account->recv_server);
789 if (account->set_tunnelcmd) {
790 r = imap_threaded_connect_cmd(folder,
792 account->recv_server,
797 if (ssl_type == SSL_TUNNEL) {
798 r = imap_threaded_connect_ssl(folder,
799 account->recv_server,
805 r = imap_threaded_connect(folder,
806 account->recv_server,
812 if (r == MAILIMAP_NO_ERROR_AUTHENTICATED) {
813 authenticated = TRUE;
815 else if (r == MAILIMAP_NO_ERROR_NON_AUTHENTICATED) {
816 authenticated = FALSE;
819 if(!prefs_common.no_recv_err_panel) {
820 alertpanel_error(_("Can't connect to IMAP4 server: %s:%d"),
821 account->recv_server, port);
823 log_error(_("Can't connect to IMAP4 server: %s:%d\n"),
824 account->recv_server, port);
830 session = g_new0(IMAPSession, 1);
831 session_init(SESSION(session));
832 SESSION(session)->type = SESSION_IMAP;
833 SESSION(session)->server = g_strdup(account->recv_server);
834 SESSION(session)->sock = NULL;
836 SESSION(session)->destroy = imap_session_destroy;
838 session->capability = NULL;
840 session->authenticated = authenticated;
841 session->mbox = NULL;
842 session->cmd_count = 0;
843 session->folder = folder;
844 IMAP_FOLDER(session->folder)->last_seen_separator = 0;
847 if (account->ssl_imap == SSL_STARTTLS) {
850 ok = imap_cmd_starttls(session);
851 if (ok != IMAP_SUCCESS) {
852 log_warning(_("Can't start TLS session.\n"));
853 session_destroy(SESSION(session));
857 imap_free_capabilities(session);
858 session->authenticated = FALSE;
859 session->uidplus = FALSE;
860 session->cmd_count = 1;
863 log_message("IMAP connection is %s-authenticated\n",
864 (session->authenticated) ? "pre" : "un");
869 static void imap_session_authenticate(IMAPSession *session,
870 const PrefsAccount *account)
872 gchar *pass, *acc_pass;
873 gboolean failed = FALSE;
875 g_return_if_fail(account->userid != NULL);
876 acc_pass = account->passwd;
879 if (!pass && account->imap_auth_type != IMAP_AUTH_ANON) {
881 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
884 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
886 } else if (account->imap_auth_type == IMAP_AUTH_ANON) {
889 statusbar_print_all(_("Connecting to IMAP4 server %s...\n"),
890 account->recv_server);
891 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
899 if (prefs_common.no_recv_err_panel) {
900 log_error(_("Couldn't login to IMAP server %s."), account->recv_server);
901 mainwindow_show_error();
903 alertpanel_error_log(_("Couldn't login to IMAP server %s."), account->recv_server);
910 session->authenticated = TRUE;
914 static void imap_session_destroy(Session *session)
916 if (session->state != SESSION_DISCONNECTED)
917 imap_threaded_disconnect(IMAP_SESSION(session)->folder);
919 imap_free_capabilities(IMAP_SESSION(session));
920 g_free(IMAP_SESSION(session)->mbox);
921 sock_close(session->sock);
922 session->sock = NULL;
925 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
927 return imap_fetch_msg_full(folder, item, uid, TRUE, TRUE);
930 static guint get_size_with_crs(MsgInfo *info)
939 fp = procmsg_open_message(info);
943 while (fgets(buf, sizeof (buf), fp) != NULL) {
945 if (!strstr(buf, "\r") && strstr(buf, "\n"))
953 static gchar *imap_fetch_msg_full(Folder *folder, FolderItem *item, gint uid,
954 gboolean headers, gboolean body)
956 gchar *path, *filename;
957 IMAPSession *session;
960 g_return_val_if_fail(folder != NULL, NULL);
961 g_return_val_if_fail(item != NULL, NULL);
966 path = folder_item_get_path(item);
967 if (!is_dir_exist(path))
969 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
971 debug_print("trying to fetch cached %s\n", filename);
972 if (is_file_exist(filename)) {
973 /* see whether the local file represents the whole message
974 * or not. As the IMAP server reports size with \r chars,
975 * we have to update the local file (UNIX \n only) size */
976 MsgInfo *msginfo = imap_parse_msg(filename, item);
977 MsgInfo *cached = msgcache_get_msg(item->cache,uid);
978 guint have_size = get_size_with_crs(msginfo);
981 debug_print("message %d has been already %scached (%d/%d).\n", uid,
982 have_size >= cached->size ? "fully ":"",
983 have_size, (int)cached->size);
985 if (cached && (cached->size <= have_size || !body)) {
986 procmsg_msginfo_free(cached);
987 procmsg_msginfo_free(msginfo);
988 file_strip_crs(filename);
990 } else if (!cached) {
991 debug_print("message not cached, considering file complete\n");
992 procmsg_msginfo_free(msginfo);
993 file_strip_crs(filename);
996 procmsg_msginfo_free(cached);
997 procmsg_msginfo_free(msginfo);
1001 session = imap_session_get(folder);
1010 debug_print("IMAP fetching messages\n");
1011 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1012 NULL, NULL, NULL, NULL, FALSE);
1013 if (ok != IMAP_SUCCESS) {
1014 g_warning("can't select mailbox %s\n", item->path);
1020 debug_print("getting message %d...\n", uid);
1021 ok = imap_cmd_fetch(session, (guint32)uid, filename, headers, body);
1023 if (ok != IMAP_SUCCESS) {
1024 g_warning("can't fetch message %d\n", uid);
1031 file_strip_crs(filename);
1035 static gint imap_add_msg(Folder *folder, FolderItem *dest,
1036 const gchar *file, MsgFlags *flags)
1040 MsgFileInfo fileinfo;
1042 g_return_val_if_fail(file != NULL, -1);
1044 fileinfo.msginfo = NULL;
1045 fileinfo.file = (gchar *)file;
1046 fileinfo.flags = flags;
1047 file_list.data = &fileinfo;
1048 file_list.next = NULL;
1050 ret = imap_add_msgs(folder, dest, &file_list, NULL);
1054 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
1055 GRelation *relation)
1058 IMAPSession *session;
1059 guint32 last_uid = 0;
1061 MsgFileInfo *fileinfo;
1063 gint curnum = 0, total = 0;
1066 g_return_val_if_fail(folder != NULL, -1);
1067 g_return_val_if_fail(dest != NULL, -1);
1068 g_return_val_if_fail(file_list != NULL, -1);
1070 session = imap_session_get(folder);
1075 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1077 statusbar_print_all(_("Adding messages..."));
1078 total = g_slist_length(file_list);
1079 for (cur = file_list; cur != NULL; cur = cur->next) {
1080 IMAPFlags iflags = 0;
1081 guint32 new_uid = 0;
1082 gchar *real_file = NULL;
1083 fileinfo = (MsgFileInfo *)cur->data;
1085 statusbar_progress_all(curnum, total, 1);
1088 if (fileinfo->flags) {
1089 if (MSG_IS_MARKED(*fileinfo->flags))
1090 iflags |= IMAP_FLAG_FLAGGED;
1091 if (MSG_IS_REPLIED(*fileinfo->flags))
1092 iflags |= IMAP_FLAG_ANSWERED;
1093 if (!MSG_IS_UNREAD(*fileinfo->flags))
1094 iflags |= IMAP_FLAG_SEEN;
1097 if (real_file == NULL)
1098 real_file = g_strdup(fileinfo->file);
1100 if (folder_has_parent_of_type(dest, F_QUEUE) ||
1101 folder_has_parent_of_type(dest, F_OUTBOX) ||
1102 folder_has_parent_of_type(dest, F_DRAFT) ||
1103 folder_has_parent_of_type(dest, F_TRASH))
1104 iflags |= IMAP_FLAG_SEEN;
1106 ok = imap_cmd_append(session, destdir, real_file, iflags,
1109 if (ok != IMAP_SUCCESS) {
1110 g_warning("can't append message %s\n", real_file);
1114 statusbar_progress_all(0,0,0);
1115 statusbar_pop_all();
1118 debug_print("appended new message as %d\n", new_uid);
1119 /* put the local file in the imapcache, so that we don't
1120 * have to fetch it back later. */
1122 gchar *cache_path = folder_item_get_path(dest);
1123 if (!is_dir_exist(cache_path))
1124 make_dir_hier(cache_path);
1125 if (is_dir_exist(cache_path)) {
1126 gchar *cache_file = g_strconcat(
1127 cache_path, G_DIR_SEPARATOR_S,
1128 itos(new_uid), NULL);
1129 copy_file(real_file, cache_file, TRUE);
1130 debug_print("copied to cache: %s\n", cache_file);
1137 if (relation != NULL)
1138 g_relation_insert(relation, fileinfo->msginfo != NULL ?
1139 (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
1140 GINT_TO_POINTER(dest->last_num + 1));
1142 new_uid = dest->last_num+1;
1144 if (last_uid < new_uid) {
1150 statusbar_progress_all(0,0,0);
1151 statusbar_pop_all();
1153 imap_cmd_expunge(session);
1161 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest,
1162 MsgInfoList *msglist, GRelation *relation)
1166 GSList *seq_list, *cur;
1168 IMAPSession *session;
1169 gint ok = IMAP_SUCCESS;
1170 GRelation *uid_mapping;
1173 g_return_val_if_fail(folder != NULL, -1);
1174 g_return_val_if_fail(dest != NULL, -1);
1175 g_return_val_if_fail(msglist != NULL, -1);
1177 session = imap_session_get(folder);
1183 msginfo = (MsgInfo *)msglist->data;
1185 src = msginfo->folder;
1187 g_warning("the src folder is identical to the dest.\n");
1192 if (src->folder != dest->folder) {
1193 GSList *infolist = NULL, *cur;
1195 for (cur = msglist; cur; cur = cur->next) {
1196 msginfo = (MsgInfo *)cur->data;
1197 MsgFileInfo *fileinfo = g_new0(MsgFileInfo, 1);
1198 fileinfo->file = procmsg_get_message_file(msginfo);
1199 fileinfo->flags = &(msginfo->flags);
1200 infolist = g_slist_prepend(infolist, fileinfo);
1202 infolist = g_slist_reverse(infolist);
1203 res = folder_item_add_msgs(dest, infolist, FALSE);
1204 for (cur = infolist; cur; cur = cur->next) {
1205 MsgFileInfo *info = (MsgFileInfo *)cur->data;
1209 g_slist_free(infolist);
1213 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1214 NULL, NULL, NULL, NULL, FALSE);
1215 if (ok != IMAP_SUCCESS) {
1220 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1221 seq_list = imap_get_lep_set_from_msglist(msglist);
1222 uid_mapping = g_relation_new(2);
1223 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1225 statusbar_print_all(_("Copying messages..."));
1226 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1227 struct mailimap_set * seq_set;
1228 seq_set = cur->data;
1230 debug_print("Copying messages from %s to %s ...\n",
1231 src->path, destdir);
1233 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping);
1234 if (ok != IMAP_SUCCESS) {
1235 g_relation_destroy(uid_mapping);
1236 imap_lep_set_free(seq_list);
1242 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1243 MsgInfo *msginfo = (MsgInfo *)cur->data;
1246 tuples = g_relation_select(uid_mapping,
1247 GINT_TO_POINTER(msginfo->msgnum),
1249 if (tuples->len > 0) {
1250 gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1251 g_relation_insert(relation, msginfo,
1252 GPOINTER_TO_INT(num));
1256 g_relation_insert(relation, msginfo,
1257 GPOINTER_TO_INT(0));
1258 g_tuples_destroy(tuples);
1260 statusbar_pop_all();
1262 g_relation_destroy(uid_mapping);
1263 imap_lep_set_free(seq_list);
1267 IMAP_FOLDER_ITEM(dest)->lastuid = 0;
1268 IMAP_FOLDER_ITEM(dest)->uid_next = 0;
1269 g_slist_free(IMAP_FOLDER_ITEM(dest)->uid_list);
1270 IMAP_FOLDER_ITEM(dest)->uid_list = NULL;
1273 if (ok == IMAP_SUCCESS)
1279 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1283 g_return_val_if_fail(msginfo != NULL, -1);
1285 msglist.data = msginfo;
1286 msglist.next = NULL;
1288 return imap_copy_msgs(folder, dest, &msglist, NULL);
1291 static gint imap_copy_msgs(Folder *folder, FolderItem *dest,
1292 MsgInfoList *msglist, GRelation *relation)
1297 g_return_val_if_fail(folder != NULL, -1);
1298 g_return_val_if_fail(dest != NULL, -1);
1299 g_return_val_if_fail(msglist != NULL, -1);
1301 msginfo = (MsgInfo *)msglist->data;
1302 g_return_val_if_fail(msginfo->folder != NULL, -1);
1304 ret = imap_do_copy_msgs(folder, dest, msglist, relation);
1309 static gint imap_do_remove_msgs(Folder *folder, FolderItem *dest,
1310 MsgInfoList *msglist, GRelation *relation)
1312 gchar *destdir, *dir;
1313 GSList *numlist = NULL, *cur;
1315 IMAPSession *session;
1316 gint ok = IMAP_SUCCESS;
1317 GRelation *uid_mapping;
1319 g_return_val_if_fail(folder != NULL, -1);
1320 g_return_val_if_fail(dest != NULL, -1);
1321 g_return_val_if_fail(msglist != NULL, -1);
1323 session = imap_session_get(folder);
1328 msginfo = (MsgInfo *)msglist->data;
1330 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1331 NULL, NULL, NULL, NULL, FALSE);
1332 if (ok != IMAP_SUCCESS) {
1337 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1338 for (cur = msglist; cur; cur = cur->next) {
1339 msginfo = (MsgInfo *)cur->data;
1340 if (!MSG_IS_DELETED(msginfo->flags))
1341 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
1343 numlist = g_slist_reverse(numlist);
1345 uid_mapping = g_relation_new(2);
1346 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1348 ok = imap_set_message_flags
1349 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1350 numlist, IMAP_FLAG_DELETED, TRUE);
1351 if (ok != IMAP_SUCCESS) {
1352 log_warning(_("can't set deleted flags\n"));
1356 ok = imap_cmd_expunge(session);
1357 if (ok != IMAP_SUCCESS) {
1358 log_warning(_("can't expunge\n"));
1363 dir = folder_item_get_path(msginfo->folder);
1364 if (is_dir_exist(dir)) {
1365 for (cur = msglist; cur; cur = cur->next) {
1366 msginfo = (MsgInfo *)cur->data;
1367 remove_numbered_files(dir, msginfo->msgnum, msginfo->msgnum);
1372 g_relation_destroy(uid_mapping);
1373 g_slist_free(numlist);
1377 if (ok == IMAP_SUCCESS)
1383 static gint imap_remove_msgs(Folder *folder, FolderItem *dest,
1384 MsgInfoList *msglist, GRelation *relation)
1388 g_return_val_if_fail(folder != NULL, -1);
1389 g_return_val_if_fail(dest != NULL, -1);
1390 if (msglist == NULL)
1393 msginfo = (MsgInfo *)msglist->data;
1394 g_return_val_if_fail(msginfo->folder != NULL, -1);
1396 return imap_do_remove_msgs(folder, dest, msglist, relation);
1399 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1401 GSList *list = folder_item_get_msg_list(item);
1402 gint res = imap_remove_msgs(folder, item, list, NULL);
1403 procmsg_msg_list_free(list);
1407 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1410 /* TODO: properly implement this method */
1414 static gint imap_close(Folder *folder, FolderItem *item)
1419 static gint imap_scan_tree(Folder *folder)
1421 FolderItem *item = NULL;
1422 IMAPSession *session;
1423 gchar *root_folder = NULL;
1425 g_return_val_if_fail(folder != NULL, -1);
1426 g_return_val_if_fail(folder->account != NULL, -1);
1428 session = imap_session_get(folder);
1430 if (!folder->node) {
1431 folder_tree_destroy(folder);
1432 item = folder_item_new(folder, folder->name, NULL);
1433 item->folder = folder;
1434 folder->node = item->node = g_node_new(item);
1440 if (folder->account->imap_dir && *folder->account->imap_dir) {
1445 Xstrdup_a(root_folder, folder->account->imap_dir, {unlock_session();return -1;});
1446 extract_quote(root_folder, '"');
1447 subst_char(root_folder,
1448 imap_get_path_separator(IMAP_FOLDER(folder),
1451 strtailchomp(root_folder, '/');
1452 real_path = imap_get_real_path
1453 (IMAP_FOLDER(folder), root_folder);
1454 debug_print("IMAP root directory: %s\n", real_path);
1456 /* check if root directory exist */
1458 r = imap_threaded_list(session->folder, "", real_path,
1460 if ((r != MAILIMAP_NO_ERROR) || (clist_count(lep_list) == 0)) {
1461 if (!folder->node) {
1462 item = folder_item_new(folder, folder->name, NULL);
1463 item->folder = folder;
1464 folder->node = item->node = g_node_new(item);
1469 mailimap_list_result_free(lep_list);
1475 item = FOLDER_ITEM(folder->node->data);
1476 if (!item || ((item->path || root_folder) &&
1477 strcmp2(item->path, root_folder) != 0)) {
1478 folder_tree_destroy(folder);
1479 item = folder_item_new(folder, folder->name, root_folder);
1480 item->folder = folder;
1481 folder->node = item->node = g_node_new(item);
1484 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1485 imap_create_missing_folders(folder);
1491 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1494 IMAPFolder *imapfolder;
1495 FolderItem *new_item;
1496 GSList *item_list, *cur;
1499 gchar *wildcard_path;
1505 g_return_val_if_fail(item != NULL, -1);
1506 g_return_val_if_fail(item->folder != NULL, -1);
1507 g_return_val_if_fail(item->no_sub == FALSE, -1);
1509 folder = item->folder;
1510 imapfolder = IMAP_FOLDER(folder);
1512 separator = imap_get_path_separator(imapfolder, item->path);
1514 if (folder->ui_func)
1515 folder->ui_func(folder, item, folder->ui_func_data);
1518 wildcard[0] = separator;
1521 real_path = imap_get_real_path(imapfolder, item->path);
1525 real_path = g_strdup("");
1528 Xstrcat_a(wildcard_path, real_path, wildcard,
1529 {g_free(real_path); return IMAP_ERROR;});
1531 r = imap_threaded_list(folder, "", wildcard_path, &lep_list);
1532 if (r != MAILIMAP_NO_ERROR) {
1536 item_list = imap_list_from_lep(imapfolder,
1537 lep_list, real_path, FALSE);
1538 mailimap_list_result_free(lep_list);
1543 node = item->node->children;
1544 while (node != NULL) {
1545 FolderItem *old_item = FOLDER_ITEM(node->data);
1546 GNode *next = node->next;
1549 for (cur = item_list; cur != NULL; cur = cur->next) {
1550 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1551 if (!strcmp2(old_item->path, cur_item->path)) {
1552 new_item = cur_item;
1557 debug_print("folder '%s' not found. removing...\n",
1559 folder_item_remove(old_item);
1561 old_item->no_sub = new_item->no_sub;
1562 old_item->no_select = new_item->no_select;
1563 if (old_item->no_sub == TRUE && node->children) {
1564 debug_print("folder '%s' doesn't have "
1565 "subfolders. removing...\n",
1567 folder_item_remove_children(old_item);
1574 for (cur = item_list; cur != NULL; cur = cur->next) {
1575 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1578 for (node = item->node->children; node != NULL;
1579 node = node->next) {
1580 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1582 new_item = FOLDER_ITEM(node->data);
1583 folder_item_destroy(cur_item);
1589 new_item = cur_item;
1590 debug_print("new folder '%s' found.\n", new_item->path);
1591 folder_item_append(item, new_item);
1594 if (!strcmp(new_item->path, "INBOX")) {
1595 new_item->stype = F_INBOX;
1596 folder->inbox = new_item;
1597 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1600 base = g_path_get_basename(new_item->path);
1602 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1603 new_item->stype = F_OUTBOX;
1604 folder->outbox = new_item;
1605 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1606 new_item->stype = F_DRAFT;
1607 folder->draft = new_item;
1608 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1609 new_item->stype = F_QUEUE;
1610 folder->queue = new_item;
1611 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1612 new_item->stype = F_TRASH;
1613 folder->trash = new_item;
1618 if (new_item->no_sub == FALSE)
1619 imap_scan_tree_recursive(session, new_item);
1622 g_slist_free(item_list);
1624 return IMAP_SUCCESS;
1627 static gint imap_create_tree(Folder *folder)
1629 g_return_val_if_fail(folder != NULL, -1);
1630 g_return_val_if_fail(folder->node != NULL, -1);
1631 g_return_val_if_fail(folder->node->data != NULL, -1);
1632 g_return_val_if_fail(folder->account != NULL, -1);
1634 imap_scan_tree(folder);
1635 imap_create_missing_folders(folder);
1640 static void imap_create_missing_folders(Folder *folder)
1642 g_return_if_fail(folder != NULL);
1645 folder->inbox = imap_create_special_folder
1646 (folder, F_INBOX, "INBOX");
1648 folder->trash = imap_create_special_folder
1649 (folder, F_TRASH, "Trash");
1651 folder->queue = imap_create_special_folder
1652 (folder, F_QUEUE, "Queue");
1653 if (!folder->outbox)
1654 folder->outbox = imap_create_special_folder
1655 (folder, F_OUTBOX, "Sent");
1657 folder->draft = imap_create_special_folder
1658 (folder, F_DRAFT, "Drafts");
1661 static FolderItem *imap_create_special_folder(Folder *folder,
1662 SpecialFolderItemType stype,
1666 FolderItem *new_item;
1668 g_return_val_if_fail(folder != NULL, NULL);
1669 g_return_val_if_fail(folder->node != NULL, NULL);
1670 g_return_val_if_fail(folder->node->data != NULL, NULL);
1671 g_return_val_if_fail(folder->account != NULL, NULL);
1672 g_return_val_if_fail(name != NULL, NULL);
1674 item = FOLDER_ITEM(folder->node->data);
1675 new_item = imap_create_folder(folder, item, name);
1678 g_warning("Can't create '%s'\n", name);
1679 if (!folder->inbox) return NULL;
1681 new_item = imap_create_folder(folder, folder->inbox, name);
1683 g_warning("Can't create '%s' under INBOX\n", name);
1685 new_item->stype = stype;
1687 new_item->stype = stype;
1692 static gchar *imap_folder_get_path(Folder *folder)
1696 g_return_val_if_fail(folder != NULL, NULL);
1697 g_return_val_if_fail(folder->account != NULL, NULL);
1699 folder_path = g_strconcat(get_imap_cache_dir(),
1701 folder->account->recv_server,
1703 folder->account->userid,
1709 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1711 gchar *folder_path, *path;
1713 g_return_val_if_fail(folder != NULL, NULL);
1714 g_return_val_if_fail(item != NULL, NULL);
1715 folder_path = imap_folder_get_path(folder);
1717 g_return_val_if_fail(folder_path != NULL, NULL);
1718 if (folder_path[0] == G_DIR_SEPARATOR) {
1720 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1723 path = g_strdup(folder_path);
1726 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1727 folder_path, G_DIR_SEPARATOR_S,
1730 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1733 g_free(folder_path);
1738 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1741 gchar *dirpath, *imap_path;
1742 IMAPSession *session;
1743 FolderItem *new_item;
1748 gboolean no_select = FALSE, no_sub = FALSE;
1750 g_return_val_if_fail(folder != NULL, NULL);
1751 g_return_val_if_fail(folder->account != NULL, NULL);
1752 g_return_val_if_fail(parent != NULL, NULL);
1753 g_return_val_if_fail(name != NULL, NULL);
1755 session = imap_session_get(folder);
1761 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0) {
1762 dirpath = g_strdup(name);
1763 }else if (parent->path)
1764 dirpath = g_strconcat(parent->path, "/", name, NULL);
1765 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1766 dirpath = g_strdup(name);
1767 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1770 Xstrdup_a(imap_dir, folder->account->imap_dir, {unlock_session();return NULL;});
1771 strtailchomp(imap_dir, '/');
1772 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1774 dirpath = g_strdup(name);
1778 /* keep trailing directory separator to create a folder that contains
1780 imap_path = imap_utf8_to_modified_utf7(dirpath);
1782 strtailchomp(dirpath, '/');
1783 Xstrdup_a(new_name, name, {
1788 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1789 imap_path_separator_subst(imap_path, separator);
1790 /* remove trailing / for display */
1791 strtailchomp(new_name, '/');
1793 if (strcmp(dirpath, "INBOX") != 0) {
1795 gboolean exist = FALSE;
1799 argbuf = g_ptr_array_new();
1800 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1801 if (r != MAILIMAP_NO_ERROR) {
1802 log_warning(_("can't create mailbox: LIST failed\n"));
1805 ptr_array_free_strings(argbuf);
1806 g_ptr_array_free(argbuf, TRUE);
1811 if (clist_count(lep_list) > 0)
1813 mailimap_list_result_free(lep_list);
1816 ok = imap_cmd_create(session, imap_path);
1817 if (ok != IMAP_SUCCESS) {
1818 log_warning(_("can't create mailbox\n"));
1824 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1825 if (r == MAILIMAP_NO_ERROR) {
1826 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1827 lep_list, dirpath, TRUE);
1829 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1830 no_select = cur_item->no_select;
1831 no_sub = cur_item->no_sub;
1832 g_slist_free(item_list);
1834 mailimap_list_result_free(lep_list);
1841 /* just get flags */
1842 r = imap_threaded_list(folder, "", "INBOX", &lep_list);
1843 if (r == MAILIMAP_NO_ERROR) {
1844 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1845 lep_list, dirpath, TRUE);
1847 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1848 no_select = cur_item->no_select;
1849 no_sub = cur_item->no_sub;
1850 g_slist_free(item_list);
1852 mailimap_list_result_free(lep_list);
1856 new_item = folder_item_new(folder, new_name, dirpath);
1857 new_item->no_select = no_select;
1858 new_item->no_sub = no_sub;
1859 folder_item_append(parent, new_item);
1863 dirpath = folder_item_get_path(new_item);
1864 if (!is_dir_exist(dirpath))
1865 make_dir_hier(dirpath);
1871 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1876 gchar *real_oldpath;
1877 gchar *real_newpath;
1879 gchar *old_cache_dir;
1880 gchar *new_cache_dir;
1881 IMAPSession *session;
1884 gint exists, recent, unseen;
1885 guint32 uid_validity;
1887 g_return_val_if_fail(folder != NULL, -1);
1888 g_return_val_if_fail(item != NULL, -1);
1889 g_return_val_if_fail(item->path != NULL, -1);
1890 g_return_val_if_fail(name != NULL, -1);
1892 session = imap_session_get(folder);
1898 if (strchr(name, imap_get_path_separator(IMAP_FOLDER(folder), item->path)) != NULL) {
1899 g_warning(_("New folder name must not contain the namespace "
1905 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1907 g_free(session->mbox);
1908 session->mbox = NULL;
1909 ok = imap_cmd_examine(session, "INBOX",
1910 &exists, &recent, &unseen, &uid_validity, FALSE);
1911 if (ok != IMAP_SUCCESS) {
1912 g_free(real_oldpath);
1917 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1918 if (strchr(item->path, G_DIR_SEPARATOR)) {
1919 dirpath = g_path_get_dirname(item->path);
1920 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1923 newpath = g_strdup(name);
1925 real_newpath = imap_utf8_to_modified_utf7(newpath);
1926 imap_path_separator_subst(real_newpath, separator);
1928 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1929 if (ok != IMAP_SUCCESS) {
1930 log_warning(_("can't rename mailbox: %s to %s\n"),
1931 real_oldpath, real_newpath);
1932 g_free(real_oldpath);
1934 g_free(real_newpath);
1940 item->name = g_strdup(name);
1942 old_cache_dir = folder_item_get_path(item);
1944 paths[0] = g_strdup(item->path);
1946 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1947 imap_rename_folder_func, paths);
1949 if (is_dir_exist(old_cache_dir)) {
1950 new_cache_dir = folder_item_get_path(item);
1951 if (rename(old_cache_dir, new_cache_dir) < 0) {
1952 FILE_OP_ERROR(old_cache_dir, "rename");
1954 g_free(new_cache_dir);
1957 g_free(old_cache_dir);
1960 g_free(real_oldpath);
1961 g_free(real_newpath);
1966 static gint imap_remove_folder_real(Folder *folder, FolderItem *item)
1969 IMAPSession *session;
1973 g_return_val_if_fail(folder != NULL, -1);
1974 g_return_val_if_fail(item != NULL, -1);
1975 g_return_val_if_fail(item->path != NULL, -1);
1977 session = imap_session_get(folder);
1982 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1984 ok = imap_cmd_delete(session, path);
1985 if (ok != IMAP_SUCCESS) {
1986 gchar *tmp = g_strdup_printf("%s%c", path,
1987 imap_get_path_separator(IMAP_FOLDER(folder), path));
1990 ok = imap_cmd_delete(session, path);
1993 if (ok != IMAP_SUCCESS) {
1994 log_warning(_("can't delete mailbox\n"));
2001 cache_dir = folder_item_get_path(item);
2002 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2003 g_warning("can't remove directory '%s'\n", cache_dir);
2005 folder_item_remove(item);
2010 static gint imap_remove_folder(Folder *folder, FolderItem *item)
2014 g_return_val_if_fail(item != NULL, -1);
2015 g_return_val_if_fail(item->folder != NULL, -1);
2016 g_return_val_if_fail(item->node != NULL, -1);
2018 node = item->node->children;
2019 while (node != NULL) {
2021 if (imap_remove_folder(folder, FOLDER_ITEM(node->data)) < 0)
2025 debug_print("IMAP removing %s\n", item->path);
2027 if (imap_remove_all_msg(folder, item) < 0)
2029 return imap_remove_folder_real(folder, item);
2032 typedef struct _uncached_data {
2033 IMAPSession *session;
2035 MsgNumberList *numlist;
2041 static void *imap_get_uncached_messages_thread(void *data)
2043 uncached_data *stuff = (uncached_data *)data;
2044 IMAPSession *session = stuff->session;
2045 FolderItem *item = stuff->item;
2046 MsgNumberList *numlist = stuff->numlist;
2048 GSList *newlist = NULL;
2049 GSList *llast = NULL;
2050 GSList *seq_list, *cur;
2052 debug_print("uncached_messages\n");
2054 if (session == NULL || item == NULL || item->folder == NULL
2055 || FOLDER_CLASS(item->folder) != &imap_class) {
2060 seq_list = imap_get_lep_set_from_numlist(numlist);
2061 debug_print("get msgs info\n");
2062 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2063 struct mailimap_set * imapset;
2069 imapset = cur->data;
2071 r = imap_threaded_fetch_env(session->folder,
2072 imapset, &env_list);
2073 if (r != MAILIMAP_NO_ERROR)
2076 session_set_access_time(SESSION(session));
2079 for(i = 0 ; i < carray_count(env_list) ; i ++) {
2080 struct imap_fetch_env_info * info;
2083 info = carray_get(env_list, i);
2084 msginfo = imap_envelope_from_lep(info, item);
2085 if (msginfo == NULL)
2087 msginfo->folder = item;
2089 llast = newlist = g_slist_append(newlist, msginfo);
2091 llast = g_slist_append(llast, msginfo);
2092 llast = llast->next;
2097 imap_fetch_env_free(env_list);
2100 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2101 struct mailimap_set * imapset;
2103 imapset = cur->data;
2104 mailimap_set_free(imapset);
2107 session_set_access_time(SESSION(session));
2112 #define MAX_MSG_NUM 50
2114 static GSList *imap_get_uncached_messages(IMAPSession *session,
2116 MsgNumberList *numlist)
2118 GSList *result = NULL;
2120 uncached_data *data = g_new0(uncached_data, 1);
2125 data->total = g_slist_length(numlist);
2126 debug_print("messages list : %i\n", data->total);
2128 while (cur != NULL) {
2129 GSList * partial_result;
2137 while (count < MAX_MSG_NUM) {
2142 if (newlist == NULL)
2143 llast = newlist = g_slist_append(newlist, p);
2145 llast = g_slist_append(llast, p);
2146 llast = llast->next;
2156 data->session = session;
2158 data->numlist = newlist;
2161 if (prefs_common.work_offline &&
2162 !inc_offline_should_override(
2163 _("Sylpheed-Claws needs network access in order "
2164 "to access the IMAP server."))) {
2170 (GSList *)imap_get_uncached_messages_thread(data);
2172 statusbar_progress_all(data->cur,data->total, 1);
2174 g_slist_free(newlist);
2176 result = g_slist_concat(result, partial_result);
2180 statusbar_progress_all(0,0,0);
2181 statusbar_pop_all();
2186 static void imap_delete_all_cached_messages(FolderItem *item)
2190 g_return_if_fail(item != NULL);
2191 g_return_if_fail(item->folder != NULL);
2192 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2194 debug_print("Deleting all cached messages...\n");
2196 dir = folder_item_get_path(item);
2197 if (is_dir_exist(dir))
2198 remove_all_numbered_files(dir);
2201 debug_print("done.\n");
2204 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
2207 IMAPNameSpace *namespace = NULL;
2208 gchar *tmp_path, *name;
2210 if (!path) path = "";
2212 for (; ns_list != NULL; ns_list = ns_list->next) {
2213 IMAPNameSpace *tmp_ns = ns_list->data;
2215 Xstrcat_a(tmp_path, path, "/", return namespace);
2216 Xstrdup_a(name, tmp_ns->name, return namespace);
2217 if (tmp_ns->separator && tmp_ns->separator != '/') {
2218 subst_char(tmp_path, tmp_ns->separator, '/');
2219 subst_char(name, tmp_ns->separator, '/');
2221 if (strncmp(tmp_path, name, strlen(name)) == 0)
2228 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
2231 IMAPNameSpace *namespace;
2233 g_return_val_if_fail(folder != NULL, NULL);
2235 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
2236 if (namespace) return namespace;
2237 namespace = imap_find_namespace_from_list(folder->ns_others, path);
2238 if (namespace) return namespace;
2239 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
2240 if (namespace) return namespace;
2245 gchar imap_get_path_separator_for_item(FolderItem *item)
2247 Folder *folder = NULL;
2248 IMAPFolder *imap_folder = NULL;
2251 folder = item->folder;
2256 imap_folder = IMAP_FOLDER(folder);
2261 return imap_get_path_separator(imap_folder, item->path);
2264 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
2266 IMAPNameSpace *namespace;
2267 gchar separator = '/';
2268 IMAPSession *session = imap_session_get(FOLDER(folder));
2269 g_return_val_if_fail(session != NULL, '/');
2271 if (folder->last_seen_separator == 0) {
2273 int r = imap_threaded_list((Folder *)folder, "", "", &lep_list);
2274 if (r != MAILIMAP_NO_ERROR) {
2275 log_warning(_("LIST failed\n"));
2279 if (clist_count(lep_list) > 0) {
2280 clistiter * iter = clist_begin(lep_list);
2281 struct mailimap_mailbox_list * mb;
2282 mb = clist_content(iter);
2284 folder->last_seen_separator = mb->mb_delimiter;
2285 debug_print("got separator: %c\n", folder->last_seen_separator);
2287 mailimap_list_result_free(lep_list);
2290 if (folder->last_seen_separator != 0) {
2291 debug_print("using separator: %c\n", folder->last_seen_separator);
2292 return folder->last_seen_separator;
2295 namespace = imap_find_namespace(folder, path);
2296 if (namespace && namespace->separator)
2297 separator = namespace->separator;
2302 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
2307 g_return_val_if_fail(folder != NULL, NULL);
2308 g_return_val_if_fail(path != NULL, NULL);
2310 real_path = imap_utf8_to_modified_utf7(path);
2311 separator = imap_get_path_separator(folder, path);
2312 imap_path_separator_subst(real_path, separator);
2317 static gint imap_set_message_flags(IMAPSession *session,
2318 MsgNumberList *numlist,
2326 seq_list = imap_get_lep_set_from_numlist(numlist);
2328 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
2329 struct mailimap_set * imapset;
2331 imapset = cur->data;
2333 ok = imap_cmd_store(session, imapset,
2337 imap_lep_set_free(seq_list);
2339 return IMAP_SUCCESS;
2342 typedef struct _select_data {
2343 IMAPSession *session;
2348 guint32 *uid_validity;
2352 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2354 gint *exists, gint *recent, gint *unseen,
2355 guint32 *uid_validity, gboolean block)
2359 gint exists_, recent_, unseen_;
2360 guint32 uid_validity_;
2362 if (!exists && !recent && !unseen && !uid_validity) {
2363 if (session->mbox && strcmp(session->mbox, path) == 0)
2364 return IMAP_SUCCESS;
2373 uid_validity = &uid_validity_;
2375 g_free(session->mbox);
2376 session->mbox = NULL;
2378 real_path = imap_get_real_path(folder, path);
2380 ok = imap_cmd_select(session, real_path,
2381 exists, recent, unseen, uid_validity, block);
2382 if (ok != IMAP_SUCCESS)
2383 log_warning(_("can't select folder: %s\n"), real_path);
2385 session->mbox = g_strdup(path);
2386 session->folder_content_changed = FALSE;
2393 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2394 const gchar *path, IMAPFolderItem *item,
2396 guint32 *uid_next, guint32 *uid_validity,
2397 gint *unseen, gboolean block)
2401 struct mailimap_mailbox_data_status * data_status;
2406 real_path = imap_get_real_path(folder, path);
2420 r = imap_threaded_status(FOLDER(folder), real_path,
2421 &data_status, mask);
2424 if (r != MAILIMAP_NO_ERROR) {
2425 debug_print("status err %d\n", r);
2429 if (data_status->st_info_list == NULL) {
2430 mailimap_mailbox_data_status_free(data_status);
2431 debug_print("status->st_info_list == NULL\n");
2436 for(iter = clist_begin(data_status->st_info_list) ; iter != NULL ;
2437 iter = clist_next(iter)) {
2438 struct mailimap_status_info * info;
2440 info = clist_content(iter);
2441 switch (info->st_att) {
2442 case MAILIMAP_STATUS_ATT_MESSAGES:
2443 * messages = info->st_value;
2444 got_values |= 1 << 0;
2447 case MAILIMAP_STATUS_ATT_UIDNEXT:
2448 * uid_next = info->st_value;
2449 got_values |= 1 << 2;
2452 case MAILIMAP_STATUS_ATT_UIDVALIDITY:
2453 * uid_validity = info->st_value;
2454 got_values |= 1 << 3;
2457 case MAILIMAP_STATUS_ATT_UNSEEN:
2458 * unseen = info->st_value;
2459 got_values |= 1 << 4;
2463 mailimap_mailbox_data_status_free(data_status);
2465 if (got_values != mask) {
2466 debug_print("status: incomplete values received (%d)\n", got_values);
2469 return IMAP_SUCCESS;
2472 static void imap_free_capabilities(IMAPSession *session)
2474 slist_free_strings(session->capability);
2475 g_slist_free(session->capability);
2476 session->capability = NULL;
2479 /* low-level IMAP4rev1 commands */
2481 static gint imap_cmd_login(IMAPSession *session,
2482 const gchar *user, const gchar *pass,
2488 if (!strcmp(type, "LOGIN") && imap_has_capability(session, "LOGINDISABLED")) {
2489 gint ok = IMAP_ERROR;
2490 if (imap_has_capability(session, "STARTTLS")) {
2492 log_warning(_("Server requires TLS to log in.\n"));
2493 ok = imap_cmd_starttls(session);
2494 if (ok != IMAP_SUCCESS) {
2495 log_warning(_("Can't start TLS session.\n"));
2499 imap_free_capabilities(session);
2500 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR) {
2501 log_warning(_("Can't refresh capabilities.\n"));
2506 log_error(_("Connection to %s failed: "
2507 "server requires TLS, but Sylpheed-Claws "
2508 "has been compiled without OpenSSL "
2510 SESSION(session)->server);
2514 log_error(_("Server logins are disabled.\n"));
2519 log_print("IMAP4> Logging %s to %s using %s\n",
2521 SESSION(session)->server,
2523 r = imap_threaded_login(session->folder, user, pass, type);
2524 if (r != MAILIMAP_NO_ERROR) {
2525 log_print("IMAP4< Error logging in to %s\n",
2526 SESSION(session)->server);
2529 log_print("IMAP4< Login to %s successful\n",
2530 SESSION(session)->server);
2536 static gint imap_cmd_noop(IMAPSession *session)
2539 unsigned int exists;
2541 r = imap_threaded_noop(session->folder, &exists);
2542 if (r != MAILIMAP_NO_ERROR) {
2543 debug_print("noop err %d\n", r);
2546 session->exists = exists;
2547 session_set_access_time(SESSION(session));
2549 return IMAP_SUCCESS;
2553 static gint imap_cmd_starttls(IMAPSession *session)
2557 r = imap_threaded_starttls(session->folder,
2558 SESSION(session)->server, SESSION(session)->port);
2559 if (r != MAILIMAP_NO_ERROR) {
2560 debug_print("starttls err %d\n", r);
2563 return IMAP_SUCCESS;
2567 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2568 gint *exists, gint *recent, gint *unseen,
2569 guint32 *uid_validity, gboolean block)
2573 r = imap_threaded_select(session->folder, folder,
2574 exists, recent, unseen, uid_validity);
2575 if (r != MAILIMAP_NO_ERROR) {
2576 debug_print("select err %d\n", r);
2579 return IMAP_SUCCESS;
2582 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2583 gint *exists, gint *recent, gint *unseen,
2584 guint32 *uid_validity, gboolean block)
2588 r = imap_threaded_examine(session->folder, folder,
2589 exists, recent, unseen, uid_validity);
2590 if (r != MAILIMAP_NO_ERROR) {
2591 debug_print("examine err %d\n", r);
2595 return IMAP_SUCCESS;
2598 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2602 r = imap_threaded_create(session->folder, folder);
2603 if (r != MAILIMAP_NO_ERROR) {
2608 return IMAP_SUCCESS;
2611 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2612 const gchar *new_folder)
2616 r = imap_threaded_rename(session->folder, old_folder,
2618 if (r != MAILIMAP_NO_ERROR) {
2623 return IMAP_SUCCESS;
2626 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2631 r = imap_threaded_delete(session->folder, folder);
2632 if (r != MAILIMAP_NO_ERROR) {
2637 return IMAP_SUCCESS;
2640 typedef struct _fetch_data {
2641 IMAPSession *session;
2643 const gchar *filename;
2649 static void *imap_cmd_fetch_thread(void *data)
2651 fetch_data *stuff = (fetch_data *)data;
2652 IMAPSession *session = stuff->session;
2653 guint32 uid = stuff->uid;
2654 const gchar *filename = stuff->filename;
2658 r = imap_threaded_fetch_content(session->folder,
2662 r = imap_threaded_fetch_content(session->folder,
2665 if (r != MAILIMAP_NO_ERROR) {
2666 debug_print("fetch err %d\n", r);
2667 return GINT_TO_POINTER(IMAP_ERROR);
2669 return GINT_TO_POINTER(IMAP_SUCCESS);
2672 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
2673 const gchar *filename, gboolean headers,
2676 fetch_data *data = g_new0(fetch_data, 1);
2679 data->session = session;
2681 data->filename = filename;
2682 data->headers = headers;
2685 if (prefs_common.work_offline &&
2686 !inc_offline_should_override(
2687 _("Sylpheed-Claws needs network access in order "
2688 "to access the IMAP server."))) {
2692 statusbar_print_all(_("Fetching message..."));
2693 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
2694 statusbar_pop_all();
2700 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2701 const gchar *file, IMAPFlags flags,
2704 struct mailimap_flag_list * flag_list;
2707 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2709 flag_list = imap_flag_to_lep(flags);
2710 r = imap_threaded_append(session->folder, destfolder,
2711 file, flag_list, (int *)new_uid);
2712 mailimap_flag_list_free(flag_list);
2714 if (r != MAILIMAP_NO_ERROR) {
2715 debug_print("append err %d\n", r);
2718 return IMAP_SUCCESS;
2721 static gint imap_cmd_copy(IMAPSession *session, struct mailimap_set * set,
2722 const gchar *destfolder, GRelation *uid_mapping)
2726 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2727 g_return_val_if_fail(set != NULL, IMAP_ERROR);
2728 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2730 r = imap_threaded_copy(session->folder, set, destfolder);
2731 if (r != MAILIMAP_NO_ERROR) {
2736 return IMAP_SUCCESS;
2739 static gint imap_cmd_store(IMAPSession *session, struct mailimap_set * set,
2740 IMAPFlags flags, int do_add)
2743 struct mailimap_flag_list * flag_list;
2744 struct mailimap_store_att_flags * store_att_flags;
2746 flag_list = imap_flag_to_lep(flags);
2750 mailimap_store_att_flags_new_add_flags_silent(flag_list);
2753 mailimap_store_att_flags_new_remove_flags_silent(flag_list);
2755 r = imap_threaded_store(session->folder, set, store_att_flags);
2756 mailimap_store_att_flags_free(store_att_flags);
2757 if (r != MAILIMAP_NO_ERROR) {
2762 return IMAP_SUCCESS;
2765 static gint imap_cmd_expunge(IMAPSession *session)
2769 if (prefs_common.work_offline &&
2770 !inc_offline_should_override(
2771 _("Sylpheed-Claws needs network access in order "
2772 "to access the IMAP server."))) {
2776 r = imap_threaded_expunge(session->folder);
2777 if (r != MAILIMAP_NO_ERROR) {
2782 return IMAP_SUCCESS;
2785 static void imap_path_separator_subst(gchar *str, gchar separator)
2788 gboolean in_escape = FALSE;
2790 if (!separator || separator == '/') return;
2792 for (p = str; *p != '\0'; p++) {
2793 if (*p == '/' && !in_escape)
2795 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2797 else if (*p == '-' && in_escape)
2802 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
2804 static iconv_t cd = (iconv_t)-1;
2805 static gboolean iconv_ok = TRUE;
2808 size_t norm_utf7_len;
2810 gchar *to_str, *to_p;
2812 gboolean in_escape = FALSE;
2814 if (!iconv_ok) return g_strdup(mutf7_str);
2816 if (cd == (iconv_t)-1) {
2817 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
2818 if (cd == (iconv_t)-1) {
2819 g_warning("iconv cannot convert UTF-7 to %s\n",
2822 return g_strdup(mutf7_str);
2826 /* modified UTF-7 to normal UTF-7 conversion */
2827 norm_utf7 = g_string_new(NULL);
2829 for (p = mutf7_str; *p != '\0'; p++) {
2830 /* replace: '&' -> '+',
2832 escaped ',' -> '/' */
2833 if (!in_escape && *p == '&') {
2834 if (*(p + 1) != '-') {
2835 g_string_append_c(norm_utf7, '+');
2838 g_string_append_c(norm_utf7, '&');
2841 } else if (in_escape && *p == ',') {
2842 g_string_append_c(norm_utf7, '/');
2843 } else if (in_escape && *p == '-') {
2844 g_string_append_c(norm_utf7, '-');
2847 g_string_append_c(norm_utf7, *p);
2851 norm_utf7_p = norm_utf7->str;
2852 norm_utf7_len = norm_utf7->len;
2853 to_len = strlen(mutf7_str) * 5;
2854 to_p = to_str = g_malloc(to_len + 1);
2856 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2857 &to_p, &to_len) == -1) {
2858 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2859 conv_get_locale_charset_str());
2860 g_string_free(norm_utf7, TRUE);
2862 return g_strdup(mutf7_str);
2865 /* second iconv() call for flushing */
2866 iconv(cd, NULL, NULL, &to_p, &to_len);
2867 g_string_free(norm_utf7, TRUE);
2873 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
2875 static iconv_t cd = (iconv_t)-1;
2876 static gboolean iconv_ok = TRUE;
2877 gchar *norm_utf7, *norm_utf7_p;
2878 size_t from_len, norm_utf7_len;
2880 gchar *from_tmp, *to, *p;
2881 gboolean in_escape = FALSE;
2883 if (!iconv_ok) return g_strdup(from);
2885 if (cd == (iconv_t)-1) {
2886 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
2887 if (cd == (iconv_t)-1) {
2888 g_warning(_("iconv cannot convert %s to UTF-7\n"),
2891 return g_strdup(from);
2895 /* UTF-8 to normal UTF-7 conversion */
2896 Xstrdup_a(from_tmp, from, return g_strdup(from));
2897 from_len = strlen(from);
2898 norm_utf7_len = from_len * 5;
2899 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
2900 norm_utf7_p = norm_utf7;
2902 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
2904 while (from_len > 0) {
2905 if (*from_tmp == '+') {
2906 *norm_utf7_p++ = '+';
2907 *norm_utf7_p++ = '-';
2911 } else if (IS_PRINT(*(guchar *)from_tmp)) {
2912 /* printable ascii char */
2913 *norm_utf7_p = *from_tmp;
2919 size_t conv_len = 0;
2921 /* unprintable char: convert to UTF-7 */
2923 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
2924 conv_len += g_utf8_skip[*(guchar *)p];
2925 p += g_utf8_skip[*(guchar *)p];
2928 from_len -= conv_len;
2929 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
2931 &norm_utf7_p, &norm_utf7_len) == -1) {
2932 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
2933 return g_strdup(from);
2936 /* second iconv() call for flushing */
2937 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
2943 *norm_utf7_p = '\0';
2944 to_str = g_string_new(NULL);
2945 for (p = norm_utf7; p < norm_utf7_p; p++) {
2946 /* replace: '&' -> "&-",
2949 BASE64 '/' -> ',' */
2950 if (!in_escape && *p == '&') {
2951 g_string_append(to_str, "&-");
2952 } else if (!in_escape && *p == '+') {
2953 if (*(p + 1) == '-') {
2954 g_string_append_c(to_str, '+');
2957 g_string_append_c(to_str, '&');
2960 } else if (in_escape && *p == '/') {
2961 g_string_append_c(to_str, ',');
2962 } else if (in_escape && *p == '-') {
2963 g_string_append_c(to_str, '-');
2966 g_string_append_c(to_str, *p);
2972 g_string_append_c(to_str, '-');
2976 g_string_free(to_str, FALSE);
2981 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
2983 FolderItem *item = node->data;
2984 gchar **paths = data;
2985 const gchar *oldpath = paths[0];
2986 const gchar *newpath = paths[1];
2988 gchar *new_itempath;
2991 oldpathlen = strlen(oldpath);
2992 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
2993 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
2997 base = item->path + oldpathlen;
2998 while (*base == G_DIR_SEPARATOR) base++;
3000 new_itempath = g_strdup(newpath);
3002 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3005 item->path = new_itempath;
3010 typedef struct _get_list_uid_data {
3012 IMAPSession *session;
3013 IMAPFolderItem *item;
3014 GSList **msgnum_list;
3016 } get_list_uid_data;
3018 static void *get_list_of_uids_thread(void *data)
3020 get_list_uid_data *stuff = (get_list_uid_data *)data;
3021 Folder *folder = stuff->folder;
3022 IMAPFolderItem *item = stuff->item;
3023 GSList **msgnum_list = stuff->msgnum_list;
3024 gint ok, nummsgs = 0, lastuid_old;
3025 IMAPSession *session;
3026 GSList *uidlist, *elem;
3027 clist * lep_uidlist;
3030 session = stuff->session;
3031 if (session == NULL) {
3033 return GINT_TO_POINTER(-1);
3035 /* no session locking here, it's already locked by caller */
3036 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3037 NULL, NULL, NULL, NULL, TRUE);
3038 if (ok != IMAP_SUCCESS) {
3040 return GINT_TO_POINTER(-1);
3045 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SIMPLE, NULL,
3048 if (r == MAILIMAP_NO_ERROR) {
3049 GSList * fetchuid_list;
3052 imap_uid_list_from_lep(lep_uidlist);
3053 mailimap_search_result_free(lep_uidlist);
3055 uidlist = g_slist_concat(fetchuid_list, uidlist);
3058 GSList * fetchuid_list;
3059 carray * lep_uidtab;
3061 r = imap_threaded_fetch_uid(folder, item->lastuid + 1,
3063 if (r == MAILIMAP_NO_ERROR) {
3065 imap_uid_list_from_lep_tab(lep_uidtab);
3066 imap_fetch_uid_list_free(lep_uidtab);
3067 uidlist = g_slist_concat(fetchuid_list, uidlist);
3071 lastuid_old = item->lastuid;
3072 *msgnum_list = g_slist_copy(item->uid_list);
3073 nummsgs = g_slist_length(*msgnum_list);
3074 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3076 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3079 msgnum = GPOINTER_TO_INT(elem->data);
3080 if (msgnum > lastuid_old) {
3081 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3082 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3085 if(msgnum > item->lastuid)
3086 item->lastuid = msgnum;
3089 g_slist_free(uidlist);
3091 return GINT_TO_POINTER(nummsgs);
3094 static gint get_list_of_uids(IMAPSession *session, Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3097 get_list_uid_data *data = g_new0(get_list_uid_data, 1);
3099 data->folder = folder;
3101 data->msgnum_list = msgnum_list;
3102 data->session = session;
3103 if (prefs_common.work_offline &&
3104 !inc_offline_should_override(
3105 _("Sylpheed-Claws needs network access in order "
3106 "to access the IMAP server."))) {
3111 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
3117 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3119 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3120 IMAPSession *session;
3121 gint ok, nummsgs = 0, exists;
3122 guint32 uid_next = 0, uid_val = 0;
3123 GSList *uidlist = NULL;
3125 gboolean selected_folder;
3126 debug_print("get_num_list\n");
3128 g_return_val_if_fail(folder != NULL, -1);
3129 g_return_val_if_fail(item != NULL, -1);
3130 g_return_val_if_fail(item->item.path != NULL, -1);
3131 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3132 g_return_val_if_fail(folder->account != NULL, -1);
3134 session = imap_session_get(folder);
3135 g_return_val_if_fail(session != NULL, -1);
3138 if (FOLDER_ITEM(item)->path)
3139 statusbar_print_all(_("Scanning folder %s%c%s ..."),
3140 FOLDER_ITEM(item)->folder->name,
3142 FOLDER_ITEM(item)->path);
3144 statusbar_print_all(_("Scanning folder %s ..."),
3145 FOLDER_ITEM(item)->folder->name);
3147 selected_folder = (session->mbox != NULL) &&
3148 (!strcmp(session->mbox, item->item.path));
3149 if (selected_folder && time(NULL) - item->use_cache < 2) {
3150 ok = imap_cmd_noop(session);
3151 if (ok != IMAP_SUCCESS) {
3152 debug_print("disconnected!\n");
3153 session = imap_reconnect_if_possible(folder, session);
3154 if (session == NULL) {
3155 statusbar_pop_all();
3160 exists = session->exists;
3162 uid_next = item->c_uid_next;
3163 uid_val = item->c_uid_validity;
3164 *old_uids_valid = TRUE;
3166 if (item->use_cache && time(NULL) - item->use_cache < 2) {
3167 exists = item->c_messages;
3168 uid_next = item->c_uid_next;
3169 uid_val = item->c_uid_validity;
3171 debug_print("using cache %d %d %d\n", exists, uid_next, uid_val);
3173 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, item,
3174 &exists, &uid_next, &uid_val, NULL, FALSE);
3176 item->item.last_num = uid_next - 1;
3178 item->use_cache = (time_t)0;
3179 if (ok != IMAP_SUCCESS) {
3180 statusbar_pop_all();
3184 if(item->item.mtime == uid_val)
3185 *old_uids_valid = TRUE;
3187 *old_uids_valid = FALSE;
3189 debug_print("Freeing imap uid cache (%d != %d)\n",
3190 (int)item->item.mtime, uid_val);
3192 g_slist_free(item->uid_list);
3193 item->uid_list = NULL;
3195 item->item.mtime = uid_val;
3197 imap_delete_all_cached_messages((FolderItem *)item);
3201 /* If old uid_next matches new uid_next we can be sure no message
3202 was added to the folder */
3203 debug_print("uid_next is %d and item->uid_next %d \n",
3204 uid_next, item->uid_next);
3205 if (uid_next == item->uid_next) {
3206 nummsgs = g_slist_length(item->uid_list);
3208 /* If number of messages is still the same we
3209 know our caches message numbers are still valid,
3210 otherwise if the number of messages has decrease
3211 we discard our cache to start a new scan to find
3212 out which numbers have been removed */
3213 if (exists == nummsgs) {
3214 debug_print("exists == nummsgs\n");
3215 *msgnum_list = g_slist_copy(item->uid_list);
3216 statusbar_pop_all();
3219 } else if (exists < nummsgs) {
3220 debug_print("Freeing imap uid cache");
3222 g_slist_free(item->uid_list);
3223 item->uid_list = NULL;
3228 *msgnum_list = NULL;
3229 statusbar_pop_all();
3234 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3237 statusbar_pop_all();
3242 if (nummsgs != exists) {
3243 /* Cache contains more messages then folder, we have cached
3244 an old UID of a message that was removed and new messages
3245 have been added too, otherwise the uid_next check would
3247 debug_print("Freeing imap uid cache");
3249 g_slist_free(item->uid_list);
3250 item->uid_list = NULL;
3252 g_slist_free(*msgnum_list);
3254 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3257 *msgnum_list = uidlist;
3259 dir = folder_item_get_path((FolderItem *)item);
3260 debug_print("removing old messages from %s\n", dir);
3261 remove_numbered_files_not_in_list(dir, *msgnum_list);
3264 item->uid_next = uid_next;
3266 debug_print("get_num_list - ok - %i\n", nummsgs);
3267 statusbar_pop_all();
3272 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3277 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3278 flags.tmp_flags = 0;
3280 g_return_val_if_fail(item != NULL, NULL);
3281 g_return_val_if_fail(file != NULL, NULL);
3283 if (folder_has_parent_of_type(item, F_QUEUE)) {
3284 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3285 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
3286 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3289 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3290 if (!msginfo) return NULL;
3292 msginfo->plaintext_file = g_strdup(file);
3293 msginfo->folder = item;
3298 GSList *imap_get_msginfos(Folder *folder, FolderItem *item,
3299 GSList *msgnum_list)
3301 IMAPSession *session;
3302 MsgInfoList *ret = NULL;
3305 debug_print("get_msginfos\n");
3307 g_return_val_if_fail(folder != NULL, NULL);
3308 g_return_val_if_fail(item != NULL, NULL);
3309 g_return_val_if_fail(msgnum_list != NULL, NULL);
3311 session = imap_session_get(folder);
3312 g_return_val_if_fail(session != NULL, NULL);
3314 debug_print("IMAP getting msginfos\n");
3315 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3316 NULL, NULL, NULL, NULL, FALSE);
3317 if (ok != IMAP_SUCCESS) {
3321 if (!(folder_has_parent_of_type(item, F_DRAFT) ||
3322 folder_has_parent_of_type(item, F_QUEUE))) {
3323 ret = g_slist_concat(ret,
3324 imap_get_uncached_messages(session, item,
3327 MsgNumberList *sorted_list, *elem, *llast = NULL;
3328 gint startnum, lastnum;
3330 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3332 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3334 llast = g_slist_last(ret);
3335 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3339 num = GPOINTER_TO_INT(elem->data);
3341 if (num > lastnum + 1 || elem == NULL) {
3343 for (i = startnum; i <= lastnum; ++i) {
3346 file = imap_fetch_msg(folder, item, i);
3348 MsgInfo *msginfo = imap_parse_msg(file, item);
3349 if (msginfo != NULL) {
3350 msginfo->msgnum = i;
3352 llast = ret = g_slist_append(ret, msginfo);
3354 llast = g_slist_append(llast, msginfo);
3355 llast = llast->next;
3360 session_set_access_time(SESSION(session));
3371 g_slist_free(sorted_list);
3377 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3379 MsgInfo *msginfo = NULL;
3380 MsgInfoList *msginfolist;
3381 MsgNumberList numlist;
3383 numlist.next = NULL;
3384 numlist.data = GINT_TO_POINTER(uid);
3386 msginfolist = imap_get_msginfos(folder, item, &numlist);
3387 if (msginfolist != NULL) {
3388 msginfo = msginfolist->data;
3389 g_slist_free(msginfolist);
3395 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3397 IMAPSession *session;
3398 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3399 gint ok, exists = 0, unseen = 0;
3400 guint32 uid_next = 0, uid_val = 0;
3401 gboolean selected_folder;
3403 g_return_val_if_fail(folder != NULL, FALSE);
3404 g_return_val_if_fail(item != NULL, FALSE);
3405 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3406 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3408 if (item->item.path == NULL)
3411 session = imap_session_get(folder);
3412 g_return_val_if_fail(session != NULL, FALSE);
3414 selected_folder = (session->mbox != NULL) &&
3415 (!strcmp(session->mbox, item->item.path));
3416 if (selected_folder && time(NULL) - item->use_cache < 2) {
3417 ok = imap_cmd_noop(session);
3418 if (ok != IMAP_SUCCESS) {
3419 debug_print("disconnected!\n");
3420 session = imap_reconnect_if_possible(folder, session);
3421 if (session == NULL)
3426 if (session->folder_content_changed
3427 || session->exists != item->item.total_msgs) {
3432 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, IMAP_FOLDER_ITEM(item),
3433 &exists, &uid_next, &uid_val, &unseen, FALSE);
3434 if (ok != IMAP_SUCCESS) {
3439 item->use_cache = time(NULL);
3440 item->c_messages = exists;
3441 item->c_uid_next = uid_next;
3442 item->c_uid_validity = uid_val;
3443 item->c_unseen = unseen;
3444 item->item.last_num = uid_next - 1;
3445 debug_print("uidnext %d, item->uid_next %d, exists %d, item->item.total_msgs %d\n",
3446 uid_next, item->uid_next, exists, item->item.total_msgs);
3447 if ((uid_next != item->uid_next) || (exists != item->item.total_msgs)
3448 || unseen != item->item.unread_msgs || uid_val != item->item.mtime) {
3457 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3459 IMAPSession *session;
3460 IMAPFlags flags_set = 0, flags_unset = 0;
3461 gint ok = IMAP_SUCCESS;
3462 MsgNumberList numlist;
3463 hashtable_data *ht_data = NULL;
3465 g_return_if_fail(folder != NULL);
3466 g_return_if_fail(folder->klass == &imap_class);
3467 g_return_if_fail(item != NULL);
3468 g_return_if_fail(item->folder == folder);
3469 g_return_if_fail(msginfo != NULL);
3470 g_return_if_fail(msginfo->folder == item);
3472 session = imap_session_get(folder);
3477 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3478 flags_set |= IMAP_FLAG_FLAGGED;
3479 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3480 flags_unset |= IMAP_FLAG_FLAGGED;
3482 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3483 flags_unset |= IMAP_FLAG_SEEN;
3484 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3485 flags_set |= IMAP_FLAG_SEEN;
3487 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3488 flags_set |= IMAP_FLAG_ANSWERED;
3489 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3490 flags_unset |= IMAP_FLAG_ANSWERED;
3492 if (!MSG_IS_DELETED(msginfo->flags) && (newflags & MSG_DELETED))
3493 flags_set |= IMAP_FLAG_DELETED;
3494 if ( MSG_IS_DELETED(msginfo->flags) && !(newflags & MSG_DELETED))
3495 flags_unset |= IMAP_FLAG_DELETED;
3497 if (!flags_set && !flags_unset) {
3498 /* the changed flags were not translatable to IMAP-speak.
3499 * like MSG_POSTFILTERED, so just apply. */
3500 msginfo->flags.perm_flags = newflags;
3505 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3506 NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS) {
3510 numlist.next = NULL;
3511 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3513 if (IMAP_FOLDER_ITEM(item)->batching) {
3514 /* instead of performing an UID STORE command for each message change,
3515 * as a lot of them can change "together", we just fill in hashtables
3516 * and defer the treatment so that we're able to send only one
3519 debug_print("IMAP batch mode on, deferring flags change\n");
3521 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_set_table,
3522 GINT_TO_POINTER(flags_set));
3523 if (ht_data == NULL) {
3524 ht_data = g_new0(hashtable_data, 1);
3525 ht_data->session = session;
3526 ht_data->item = IMAP_FOLDER_ITEM(item);
3527 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_set_table,
3528 GINT_TO_POINTER(flags_set), ht_data);
3530 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3531 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
3534 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3535 GINT_TO_POINTER(flags_unset));
3536 if (ht_data == NULL) {
3537 ht_data = g_new0(hashtable_data, 1);
3538 ht_data->session = session;
3539 ht_data->item = IMAP_FOLDER_ITEM(item);
3540 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3541 GINT_TO_POINTER(flags_unset), ht_data);
3543 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3544 ht_data->msglist = g_slist_prepend(ht_data->msglist,
3545 GINT_TO_POINTER(msginfo->msgnum));
3548 debug_print("IMAP changing flags\n");
3550 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3551 if (ok != IMAP_SUCCESS) {
3558 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3559 if (ok != IMAP_SUCCESS) {
3565 msginfo->flags.perm_flags = newflags;
3570 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
3573 IMAPSession *session;
3575 MsgNumberList numlist;
3577 g_return_val_if_fail(folder != NULL, -1);
3578 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3579 g_return_val_if_fail(item != NULL, -1);
3581 session = imap_session_get(folder);
3582 if (!session) return -1;
3584 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3585 NULL, NULL, NULL, NULL, FALSE);
3586 if (ok != IMAP_SUCCESS) {
3590 numlist.next = NULL;
3591 numlist.data = GINT_TO_POINTER(uid);
3593 ok = imap_set_message_flags
3594 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
3595 &numlist, IMAP_FLAG_DELETED, TRUE);
3596 if (ok != IMAP_SUCCESS) {
3597 log_warning(_("can't set deleted flags: %d\n"), uid);
3602 if (!session->uidplus) {
3603 ok = imap_cmd_expunge(session);
3607 uidstr = g_strdup_printf("%u", uid);
3608 ok = imap_cmd_expunge(session);
3611 if (ok != IMAP_SUCCESS) {
3612 log_warning(_("can't expunge\n"));
3617 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
3618 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
3619 dir = folder_item_get_path(item);
3620 if (is_dir_exist(dir))
3621 remove_numbered_files(dir, uid, uid);
3624 return IMAP_SUCCESS;
3627 static gint compare_msginfo(gconstpointer a, gconstpointer b)
3629 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
3632 static guint gslist_find_next_num(MsgNumberList **list, guint num)
3636 g_return_val_if_fail(list != NULL, -1);
3638 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
3639 if (GPOINTER_TO_INT(elem->data) >= num)
3642 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
3646 * NEW and DELETED flags are not syncronized
3647 * - The NEW/RECENT flags in IMAP folders can not really be directly
3648 * modified by Sylpheed
3649 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
3650 * meaning, in IMAP it always removes the messages from the FolderItem
3651 * in Sylpheed it can mean to move the message to trash
3654 typedef struct _get_flags_data {
3657 MsgInfoList *msginfo_list;
3658 GRelation *msgflags;
3659 gboolean full_search;
3663 static /*gint*/ void *imap_get_flags_thread(void *data)
3665 get_flags_data *stuff = (get_flags_data *)data;
3666 Folder *folder = stuff->folder;
3667 FolderItem *item = stuff->item;
3668 MsgInfoList *msginfo_list = stuff->msginfo_list;
3669 GRelation *msgflags = stuff->msgflags;
3670 gboolean full_search = stuff->full_search;
3671 IMAPSession *session;
3672 GSList *sorted_list = NULL;
3673 GSList *unseen = NULL, *answered = NULL, *flagged = NULL, *deleted = NULL;
3674 GSList *p_unseen, *p_answered, *p_flagged, *p_deleted;
3676 GSList *seq_list, *cur;
3677 gboolean reverse_seen = FALSE;
3680 gint exists_cnt, unseen_cnt;
3681 gboolean selected_folder;
3683 if (folder == NULL || item == NULL) {
3685 return GINT_TO_POINTER(-1);
3688 session = imap_session_get(folder);
3689 if (session == NULL) {
3691 return GINT_TO_POINTER(-1);
3694 selected_folder = (session->mbox != NULL) &&
3695 (!strcmp(session->mbox, item->path));
3697 if (!selected_folder) {
3698 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3699 &exists_cnt, NULL, &unseen_cnt, NULL, TRUE);
3700 if (ok != IMAP_SUCCESS) {
3703 return GINT_TO_POINTER(-1);
3706 if (unseen_cnt > exists_cnt / 2)
3707 reverse_seen = TRUE;
3710 if (item->unread_msgs > item->total_msgs / 2)
3711 reverse_seen = TRUE;
3714 cmd_buf = g_string_new(NULL);
3716 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
3718 seq_list = imap_get_lep_set_from_msglist(msginfo_list);
3720 struct mailimap_set * set;
3721 set = mailimap_set_new_interval(1, 0);
3722 seq_list = g_slist_append(NULL, set);
3725 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
3726 struct mailimap_set * imapset;
3727 clist * lep_uidlist;
3730 imapset = cur->data;
3732 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SEEN,
3733 full_search ? NULL:imapset, &lep_uidlist);
3736 r = imap_threaded_search(folder,
3737 IMAP_SEARCH_TYPE_UNSEEN,
3738 full_search ? NULL:imapset, &lep_uidlist);
3740 if (r == MAILIMAP_NO_ERROR) {
3743 uidlist = imap_uid_list_from_lep(lep_uidlist);
3744 mailimap_search_result_free(lep_uidlist);
3746 unseen = g_slist_concat(unseen, uidlist);
3749 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_FLAGGED,
3750 full_search ? NULL:imapset, &lep_uidlist);
3751 if (r == MAILIMAP_NO_ERROR) {
3754 uidlist = imap_uid_list_from_lep(lep_uidlist);
3755 mailimap_search_result_free(lep_uidlist);
3757 flagged = g_slist_concat(flagged, uidlist);
3760 if (item->opened || item->processing_pending || item == folder->inbox) {
3761 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_ANSWERED,
3762 full_search ? NULL:imapset, &lep_uidlist);
3763 if (r == MAILIMAP_NO_ERROR) {
3766 uidlist = imap_uid_list_from_lep(lep_uidlist);
3767 mailimap_search_result_free(lep_uidlist);
3769 answered = g_slist_concat(answered, uidlist);
3772 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_DELETED,
3773 full_search ? NULL:imapset, &lep_uidlist);
3774 if (r == MAILIMAP_NO_ERROR) {
3777 uidlist = imap_uid_list_from_lep(lep_uidlist);
3778 mailimap_search_result_free(lep_uidlist);
3780 deleted = g_slist_concat(deleted, uidlist);
3786 p_answered = answered;
3787 p_flagged = flagged;
3788 p_deleted = deleted;
3790 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
3795 msginfo = (MsgInfo *) elem->data;
3796 flags = msginfo->flags.perm_flags;
3797 wasnew = (flags & MSG_NEW);
3798 if (item->opened || item->processing_pending || item == folder->inbox) {
3799 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
3801 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW | MSG_MARKED));
3804 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3805 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
3806 if (!reverse_seen) {
3807 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3809 flags &= ~(MSG_UNREAD | MSG_NEW);
3813 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
3814 flags |= MSG_MARKED;
3816 flags &= ~MSG_MARKED;
3818 if (item->opened || item->processing_pending || item == folder->inbox) {
3819 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
3820 flags |= MSG_REPLIED;
3822 flags &= ~MSG_REPLIED;
3823 if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
3824 flags |= MSG_DELETED;
3826 flags &= ~MSG_DELETED;
3828 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
3831 imap_lep_set_free(seq_list);
3832 g_slist_free(flagged);
3833 g_slist_free(deleted);
3834 g_slist_free(answered);
3835 g_slist_free(unseen);
3836 g_slist_free(sorted_list);
3837 g_string_free(cmd_buf, TRUE);
3841 return GINT_TO_POINTER(0);
3844 static gint imap_get_flags(Folder *folder, FolderItem *item,
3845 MsgInfoList *msginfo_list, GRelation *msgflags)
3848 get_flags_data *data = g_new0(get_flags_data, 1);
3850 data->folder = folder;
3852 data->msginfo_list = msginfo_list;
3853 data->msgflags = msgflags;
3854 data->full_search = FALSE;
3856 GSList *tmp = NULL, *cur;
3858 if (prefs_common.work_offline &&
3859 !inc_offline_should_override(
3860 _("Sylpheed-Claws needs network access in order "
3861 "to access the IMAP server."))) {
3866 tmp = folder_item_get_msg_list(item);
3868 if (g_slist_length(tmp) <= g_slist_length(msginfo_list))
3869 data->full_search = TRUE;
3871 for (cur = tmp; cur; cur = cur->next)
3872 procmsg_msginfo_free((MsgInfo *)cur->data);
3876 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
3883 static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
3885 gboolean flags_set = GPOINTER_TO_INT(user_data);
3886 gint flags_value = GPOINTER_TO_INT(key);
3887 hashtable_data *data = (hashtable_data *)value;
3888 IMAPFolderItem *_item = data->item;
3889 FolderItem *item = (FolderItem *)_item;
3890 gint ok = IMAP_ERROR;
3891 IMAPSession *session = imap_session_get(item->folder);
3893 data->msglist = g_slist_reverse(data->msglist);
3895 debug_print("IMAP %ssetting flags to %d for %d messages\n",
3898 g_slist_length(data->msglist));
3902 ok = imap_select(session, IMAP_FOLDER(item->folder), item->path,
3903 NULL, NULL, NULL, NULL, FALSE);
3905 if (ok == IMAP_SUCCESS) {
3906 imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
3908 g_warning("can't select mailbox %s\n", item->path);
3912 g_slist_free(data->msglist);
3917 static void process_hashtable(IMAPFolderItem *item)
3919 if (item->flags_set_table) {
3920 g_hash_table_foreach_remove(item->flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
3921 g_hash_table_destroy(item->flags_set_table);
3922 item->flags_set_table = NULL;
3924 if (item->flags_unset_table) {
3925 g_hash_table_foreach_remove(item->flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
3926 g_hash_table_destroy(item->flags_unset_table);
3927 item->flags_unset_table = NULL;
3931 static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
3933 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3935 g_return_if_fail(item != NULL);
3937 if (item->batching == batch)
3941 item->batching = TRUE;
3942 debug_print("IMAP switching to batch mode\n");
3943 if (!item->flags_set_table) {
3944 item->flags_set_table = g_hash_table_new(NULL, g_direct_equal);
3946 if (!item->flags_unset_table) {
3947 item->flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
3950 debug_print("IMAP switching away from batch mode\n");
3952 process_hashtable(item);
3953 item->batching = FALSE;
3959 /* data types conversion libetpan <-> sylpheed */
3963 #define ETPAN_IMAP_MB_MARKED 1
3964 #define ETPAN_IMAP_MB_UNMARKED 2
3965 #define ETPAN_IMAP_MB_NOSELECT 4
3966 #define ETPAN_IMAP_MB_NOINFERIORS 8
3968 static int imap_flags_to_flags(struct mailimap_mbx_list_flags * imap_flags)
3974 if (imap_flags->mbf_type == MAILIMAP_MBX_LIST_FLAGS_SFLAG) {
3975 switch (imap_flags->mbf_sflag) {
3976 case MAILIMAP_MBX_LIST_SFLAG_MARKED:
3977 flags |= ETPAN_IMAP_MB_MARKED;
3979 case MAILIMAP_MBX_LIST_SFLAG_NOSELECT:
3980 flags |= ETPAN_IMAP_MB_NOSELECT;
3982 case MAILIMAP_MBX_LIST_SFLAG_UNMARKED:
3983 flags |= ETPAN_IMAP_MB_UNMARKED;
3988 for(cur = clist_begin(imap_flags->mbf_oflags) ; cur != NULL ;
3989 cur = clist_next(cur)) {
3990 struct mailimap_mbx_list_oflag * oflag;
3992 oflag = clist_content(cur);
3994 switch (oflag->of_type) {
3995 case MAILIMAP_MBX_LIST_OFLAG_NOINFERIORS:
3996 flags |= ETPAN_IMAP_MB_NOINFERIORS;
4004 static GSList * imap_list_from_lep(IMAPFolder * folder,
4005 clist * list, const gchar * real_path, gboolean all)
4008 GSList * item_list = NULL, *llast = NULL;
4010 for(iter = clist_begin(list) ; iter != NULL ;
4011 iter = clist_next(iter)) {
4012 struct mailimap_mailbox_list * mb;
4020 FolderItem *new_item;
4022 mb = clist_content(iter);
4028 if (mb->mb_flag != NULL)
4029 flags = imap_flags_to_flags(mb->mb_flag);
4031 delimiter = mb->mb_delimiter;
4034 dup_name = strdup(name);
4035 if (delimiter != '\0')
4036 subst_char(dup_name, delimiter, '/');
4038 base = g_path_get_basename(dup_name);
4039 if (base[0] == '.') {
4045 if (!all && strcmp(dup_name, real_path) == 0) {
4051 if (!all && dup_name[strlen(dup_name)-1] == '/') {
4057 loc_name = imap_modified_utf7_to_utf8(base);
4058 loc_path = imap_modified_utf7_to_utf8(dup_name);
4060 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
4061 if ((flags & ETPAN_IMAP_MB_NOINFERIORS) != 0)
4062 new_item->no_sub = TRUE;
4063 if (strcmp(dup_name, "INBOX") != 0 &&
4064 ((flags & ETPAN_IMAP_MB_NOSELECT) != 0))
4065 new_item->no_select = TRUE;
4067 if (item_list == NULL)
4068 llast = item_list = g_slist_append(item_list, new_item);
4070 llast = g_slist_append(llast, new_item);
4071 llast = llast->next;
4073 debug_print("folder '%s' found.\n", loc_path);
4084 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist)
4086 GSList *sorted_list, *cur;
4087 guint first, last, next;
4088 GSList *ret_list = NULL, *llast = NULL;
4090 struct mailimap_set * current_set;
4091 unsigned int item_count;
4093 if (numlist == NULL)
4097 current_set = mailimap_set_new_empty();
4099 sorted_list = g_slist_copy(numlist);
4100 sorted_list = g_slist_sort(sorted_list, g_int_compare);
4102 first = GPOINTER_TO_INT(sorted_list->data);
4105 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
4106 if (GPOINTER_TO_INT(cur->data) == 0)
4111 last = GPOINTER_TO_INT(cur->data);
4113 next = GPOINTER_TO_INT(cur->next->data);
4117 if (last + 1 != next || next == 0) {
4119 struct mailimap_set_item * item;
4120 item = mailimap_set_item_new(first, last);
4121 mailimap_set_add(current_set, item);
4126 if (count >= IMAP_SET_MAX_COUNT) {
4127 if (ret_list == NULL)
4128 llast = ret_list = g_slist_append(ret_list,
4131 llast = g_slist_append(llast, current_set);
4132 llast = llast->next;
4134 current_set = mailimap_set_new_empty();
4141 if (clist_count(current_set->set_list) > 0) {
4142 ret_list = g_slist_append(ret_list,
4146 g_slist_free(sorted_list);
4151 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist)
4153 MsgNumberList *numlist = NULL;
4157 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
4158 MsgInfo *msginfo = (MsgInfo *) cur->data;
4160 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
4162 numlist = g_slist_reverse(numlist);
4163 seq_list = imap_get_lep_set_from_numlist(numlist);
4164 g_slist_free(numlist);
4169 static GSList * imap_uid_list_from_lep(clist * list)
4176 for(iter = clist_begin(list) ; iter != NULL ;
4177 iter = clist_next(iter)) {
4180 puid = clist_content(iter);
4181 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4184 result = g_slist_reverse(result);
4188 static GSList * imap_uid_list_from_lep_tab(carray * list)
4195 for(i = 0 ; i < carray_count(list) ; i ++) {
4198 puid = carray_get(list, i);
4199 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4201 result = g_slist_reverse(result);
4205 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
4208 MsgInfo *msginfo = NULL;
4211 MsgFlags flags = {0, 0};
4213 if (info->headers == NULL)
4216 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
4217 if (folder_has_parent_of_type(item, F_QUEUE)) {
4218 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4219 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
4220 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4222 flags.perm_flags = info->flags;
4226 msginfo = procheader_parse_str(info->headers, flags, FALSE, FALSE);
4229 msginfo->msgnum = uid;
4230 msginfo->size = size;
4236 static void imap_lep_set_free(GSList *seq_list)
4240 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
4241 struct mailimap_set * imapset;
4243 imapset = cur->data;
4244 mailimap_set_free(imapset);
4246 g_slist_free(seq_list);
4249 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags)
4251 struct mailimap_flag_list * flag_list;
4253 flag_list = mailimap_flag_list_new_empty();
4255 if (IMAP_IS_SEEN(flags))
4256 mailimap_flag_list_add(flag_list,
4257 mailimap_flag_new_seen());
4258 if (IMAP_IS_ANSWERED(flags))
4259 mailimap_flag_list_add(flag_list,
4260 mailimap_flag_new_answered());
4261 if (IMAP_IS_FLAGGED(flags))
4262 mailimap_flag_list_add(flag_list,
4263 mailimap_flag_new_flagged());
4264 if (IMAP_IS_DELETED(flags))
4265 mailimap_flag_list_add(flag_list,
4266 mailimap_flag_new_deleted());
4267 if (IMAP_IS_DRAFT(flags))
4268 mailimap_flag_list_add(flag_list,
4269 mailimap_flag_new_draft());
4274 guint imap_folder_get_refcnt(Folder *folder)
4276 return ((IMAPFolder *)folder)->refcnt;
4279 void imap_folder_ref(Folder *folder)
4281 ((IMAPFolder *)folder)->refcnt++;
4284 void imap_disconnect_all(void)
4287 for (list = account_get_list(); list != NULL; list = list->next) {
4288 PrefsAccount *account = list->data;
4289 if (account->protocol == A_IMAP4) {
4290 RemoteFolder *folder = (RemoteFolder *)account->folder;
4291 if (folder && folder->session) {
4292 IMAPSession *session = (IMAPSession *)folder->session;
4293 imap_threaded_disconnect(FOLDER(folder));
4294 SESSION(session)->state = SESSION_DISCONNECTED;
4295 session_destroy(SESSION(session));
4296 folder->session = NULL;
4302 void imap_folder_unref(Folder *folder)
4304 if (((IMAPFolder *)folder)->refcnt > 0)
4305 ((IMAPFolder *)folder)->refcnt--;
4308 #else /* HAVE_LIBETPAN */
4310 static FolderClass imap_class;
4312 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
4313 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
4315 static Folder *imap_folder_new (const gchar *name,
4318 static gboolean missing_imap_warning = TRUE;
4319 if (missing_imap_warning) {
4320 missing_imap_warning = FALSE;
4322 _("You have one or more IMAP accounts "
4323 "defined. However this version of "
4324 "Sylpheed-Claws has been built without "
4325 "IMAP support; your IMAP account(s) are "
4327 "You probably need to "
4328 "install libetpan and recompile "
4329 "Sylpheed-Claws."));
4333 static gint imap_create_tree (Folder *folder)
4337 static FolderItem *imap_create_folder (Folder *folder,
4343 static gint imap_rename_folder (Folder *folder,
4350 gchar imap_get_path_separator_for_item(FolderItem *item)
4355 FolderClass *imap_get_class(void)
4357 if (imap_class.idstr == NULL) {
4358 imap_class.type = F_IMAP;
4359 imap_class.idstr = "imap";
4360 imap_class.uistr = "IMAP4";
4362 imap_class.new_folder = imap_folder_new;
4363 imap_class.create_tree = imap_create_tree;
4364 imap_class.create_folder = imap_create_folder;
4365 imap_class.rename_folder = imap_rename_folder;
4367 imap_class.set_xml = folder_set_xml;
4368 imap_class.get_xml = folder_get_xml;
4369 imap_class.item_set_xml = imap_item_set_xml;
4370 imap_class.item_get_xml = imap_item_get_xml;
4371 /* nothing implemented */
4377 void imap_disconnect_all(void)
4383 void imap_synchronise(FolderItem *item)
4385 imap_gtk_synchronise(item);
4388 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
4390 #ifdef HAVE_LIBETPAN
4393 folder_item_set_xml(folder, item, tag);
4395 #ifdef HAVE_LIBETPAN
4396 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
4397 XMLAttr *attr = (XMLAttr *) cur->data;
4399 if (!attr || !attr->name || !attr->value) continue;
4400 if (!strcmp(attr->name, "uidnext"))
4401 IMAP_FOLDER_ITEM(item)->uid_next = atoi(attr->value);
4406 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item)
4410 tag = folder_item_get_xml(folder, item);
4412 #ifdef HAVE_LIBETPAN
4413 xml_tag_add_attr(tag, xml_attr_new_int("uidnext",
4414 IMAP_FOLDER_ITEM(item)->uid_next));