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"
72 typedef struct _IMAPFolder IMAPFolder;
73 typedef struct _IMAPSession IMAPSession;
74 typedef struct _IMAPNameSpace IMAPNameSpace;
75 typedef struct _IMAPFolderItem IMAPFolderItem;
77 #include "prefs_account.h"
79 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
80 #define IMAP_FOLDER_ITEM(obj) ((IMAPFolderItem *)obj)
81 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
87 /* list of IMAPNameSpace */
91 gchar last_seen_separator;
99 gboolean authenticated;
108 gboolean folder_content_changed;
114 struct _IMAPNameSpace
120 #define IMAP_SUCCESS 0
121 #define IMAP_SOCKET 2
122 #define IMAP_AUTHFAIL 3
123 #define IMAP_PROTOCOL 4
124 #define IMAP_SYNTAX 5
128 #define IMAPBUFSIZE 8192
132 IMAP_FLAG_SEEN = 1 << 0,
133 IMAP_FLAG_ANSWERED = 1 << 1,
134 IMAP_FLAG_FLAGGED = 1 << 2,
135 IMAP_FLAG_DELETED = 1 << 3,
136 IMAP_FLAG_DRAFT = 1 << 4
139 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
140 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
141 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
142 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
143 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
146 #define IMAP4_PORT 143
148 #define IMAPS_PORT 993
151 #define IMAP_CMD_LIMIT 1000
153 struct _IMAPFolderItem
165 guint32 c_uid_validity;
168 GHashTable *flags_set_table;
169 GHashTable *flags_unset_table;
172 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
173 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
175 static void imap_folder_init (Folder *folder,
179 static Folder *imap_folder_new (const gchar *name,
181 static void imap_folder_destroy (Folder *folder);
183 static IMAPSession *imap_session_new (Folder *folder,
184 const PrefsAccount *account);
185 static void imap_session_authenticate(IMAPSession *session,
186 const PrefsAccount *account);
187 static void imap_session_destroy (Session *session);
189 static gchar *imap_fetch_msg (Folder *folder,
192 static gchar *imap_fetch_msg_full (Folder *folder,
197 static gint imap_add_msg (Folder *folder,
201 static gint imap_add_msgs (Folder *folder,
204 GRelation *relation);
206 static gint imap_copy_msg (Folder *folder,
209 static gint imap_copy_msgs (Folder *folder,
211 MsgInfoList *msglist,
212 GRelation *relation);
214 static gint imap_remove_msg (Folder *folder,
217 static gint imap_remove_msgs (Folder *folder,
219 MsgInfoList *msglist,
220 GRelation *relation);
221 static gint imap_remove_all_msg (Folder *folder,
224 static gboolean imap_is_msg_changed (Folder *folder,
228 static gint imap_close (Folder *folder,
231 static gint imap_scan_tree (Folder *folder);
233 static gint imap_create_tree (Folder *folder);
235 static FolderItem *imap_create_folder (Folder *folder,
238 static gint imap_rename_folder (Folder *folder,
241 static gint imap_remove_folder (Folder *folder,
244 static FolderItem *imap_folder_item_new (Folder *folder);
245 static void imap_folder_item_destroy (Folder *folder,
248 static IMAPSession *imap_session_get (Folder *folder);
250 static gint imap_auth (IMAPSession *session,
255 static gint imap_scan_tree_recursive (IMAPSession *session,
258 static void imap_create_missing_folders (Folder *folder);
259 static FolderItem *imap_create_special_folder
261 SpecialFolderItemType stype,
264 static gint imap_do_copy_msgs (Folder *folder,
266 MsgInfoList *msglist,
267 GRelation *relation);
269 static void imap_delete_all_cached_messages (FolderItem *item);
270 static void imap_set_batch (Folder *folder,
273 static gint imap_set_message_flags (IMAPSession *session,
274 MsgNumberList *numlist,
277 static gint imap_select (IMAPSession *session,
283 guint32 *uid_validity,
285 static gint imap_status (IMAPSession *session,
288 IMAPFolderItem *item,
291 guint32 *uid_validity,
295 static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
297 static gchar imap_get_path_separator (IMAPFolder *folder,
299 static gchar *imap_get_real_path (IMAPFolder *folder,
301 static void imap_synchronise (FolderItem *item);
303 static void imap_free_capabilities (IMAPSession *session);
305 /* low-level IMAP4rev1 commands */
306 static gint imap_cmd_login (IMAPSession *session,
310 static gint imap_cmd_logout (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)
488 while (imap_folder_get_refcnt(folder) > 0)
489 gtk_main_iteration();
491 dir = imap_folder_get_path(folder);
492 if (is_dir_exist(dir))
493 remove_dir_recursive(dir);
496 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
500 static void imap_folder_init(Folder *folder, const gchar *name,
503 folder_remote_folder_init((Folder *)folder, name, path);
506 static FolderItem *imap_folder_item_new(Folder *folder)
508 IMAPFolderItem *item;
510 item = g_new0(IMAPFolderItem, 1);
513 item->uid_list = NULL;
515 return (FolderItem *)item;
518 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
520 IMAPFolderItem *item = (IMAPFolderItem *)_item;
522 g_return_if_fail(item != NULL);
523 g_slist_free(item->uid_list);
528 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
530 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
533 g_slist_free(item->uid_list);
534 item->uid_list = NULL;
539 static void imap_reset_uid_lists(Folder *folder)
541 if(folder->node == NULL)
544 /* Destroy all uid lists and rest last uid */
545 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
548 void imap_get_capabilities(IMAPSession *session)
550 struct mailimap_capability_data *capabilities = NULL;
553 if (session->capability != NULL)
556 capabilities = imap_threaded_capability(session->folder);
558 if (capabilities == NULL)
561 for(cur = clist_begin(capabilities->cap_list) ; cur != NULL ;
562 cur = clist_next(cur)) {
563 struct mailimap_capability * cap =
565 if (!cap || cap->cap_data.cap_name == NULL)
567 session->capability = g_slist_append
568 (session->capability,
569 g_strdup(cap->cap_data.cap_name));
570 debug_print("got capa %s\n", cap->cap_data.cap_name);
572 mailimap_capability_data_free(capabilities);
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;
591 imap_get_capabilities(session);
595 ok = imap_cmd_login(session, user, pass, "ANONYMOUS");
597 case IMAP_AUTH_CRAM_MD5:
598 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
600 case IMAP_AUTH_LOGIN:
601 ok = imap_cmd_login(session, user, pass, "LOGIN");
604 debug_print("capabilities:\n"
608 imap_has_capability(session, "ANONYMOUS"),
609 imap_has_capability(session, "CRAM-MD5"),
610 imap_has_capability(session, "LOGIN"));
611 if (imap_has_capability(session, "CRAM-MD5"))
612 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
613 if (ok == IMAP_ERROR) /* we always try LOGIN before giving up */
614 ok = imap_cmd_login(session, user, pass, "LOGIN");
616 if (ok == IMAP_SUCCESS)
617 session->authenticated = TRUE;
619 gchar *ext_info = NULL;
621 if (type == IMAP_AUTH_CRAM_MD5) {
622 ext_info = _("\n\nCRAM-MD5 logins only work if libetpan has been "
623 "compiled with SASL support and the "
624 "CRAM-MD5 SASL plugin is installed.");
629 if (time(NULL) - last_login_err > 10) {
630 if (!prefs_common.no_recv_err_panel) {
631 alertpanel_error(_("Connection to %s failed: "
633 SESSION(session)->server, ext_info);
635 log_error(_("Connection to %s failed: "
636 "login refused.%s\n"),
637 SESSION(session)->server, ext_info);
640 last_login_err = time(NULL);
645 static IMAPSession *imap_reconnect_if_possible(Folder *folder, IMAPSession *session)
647 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
648 /* Check if this is the first try to establish a
649 connection, if yes we don't try to reconnect */
650 debug_print("reconnecting\n");
651 if (rfolder->session == NULL) {
652 log_warning(_("Connecting to %s failed"),
653 folder->account->recv_server);
654 session_destroy(SESSION(session));
657 log_warning(_("IMAP4 connection to %s has been"
658 " disconnected. Reconnecting...\n"),
659 folder->account->recv_server);
660 statusbar_print_all(_("IMAP4 connection to %s has been"
661 " disconnected. Reconnecting...\n"),
662 folder->account->recv_server);
663 SESSION(session)->state = SESSION_DISCONNECTED;
664 session_destroy(SESSION(session));
665 /* Clear folders session to make imap_session_get create
666 a new session, because of rfolder->session == NULL
667 it will not try to reconnect again and so avoid an
669 rfolder->session = NULL;
670 session = imap_session_get(folder);
671 rfolder->session = SESSION(session);
677 #define lock_session() {\
678 debug_print("locking session\n"); \
679 session->busy = TRUE;\
682 #define unlock_session() {\
683 debug_print("unlocking session\n"); \
684 session->busy = FALSE;\
687 static IMAPSession *imap_session_get(Folder *folder)
689 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
690 IMAPSession *session = NULL;
691 static time_t last_failure = 0;
693 g_return_val_if_fail(folder != NULL, NULL);
694 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
695 g_return_val_if_fail(folder->account != NULL, NULL);
697 if (prefs_common.work_offline &&
698 !inc_offline_should_override(
699 _("Sylpheed-Claws needs network access in order "
700 "to access the IMAP server."))) {
704 /* Make sure we have a session */
705 if (rfolder->session != NULL) {
706 session = IMAP_SESSION(rfolder->session);
707 /* don't do that yet...
712 imap_reset_uid_lists(folder);
713 if (time(NULL) - last_failure <= 2)
715 session = imap_session_new(folder, folder->account);
717 if(session == NULL) {
718 last_failure = time(NULL);
722 /* Make sure session is authenticated */
723 if (!IMAP_SESSION(session)->authenticated)
724 imap_session_authenticate(IMAP_SESSION(session), folder->account);
726 if (!IMAP_SESSION(session)->authenticated) {
727 session_destroy(SESSION(session));
728 rfolder->session = NULL;
729 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;
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)
874 g_return_if_fail(account->userid != NULL);
876 pass = account->passwd;
877 if (!pass && account->imap_auth_type != IMAP_AUTH_ANON) {
879 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
882 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
884 } else if (account->imap_auth_type == IMAP_AUTH_ANON) {
887 statusbar_print_all(_("Connecting to IMAP4 server %s...\n"),
888 account->recv_server);
889 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
890 imap_threaded_disconnect(session->folder);
891 imap_cmd_logout(session);
897 session->authenticated = TRUE;
900 static void imap_session_destroy(Session *session)
902 if (session->state != SESSION_DISCONNECTED)
903 imap_threaded_disconnect(IMAP_SESSION(session)->folder);
905 imap_free_capabilities(IMAP_SESSION(session));
906 g_free(IMAP_SESSION(session)->mbox);
907 sock_close(session->sock);
908 session->sock = NULL;
911 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
913 return imap_fetch_msg_full(folder, item, uid, TRUE, TRUE);
916 static guint get_size_with_crs(MsgInfo *info)
925 fp = procmsg_open_message(info);
929 while (fgets(buf, sizeof (buf), fp) != NULL) {
931 if (!strstr(buf, "\r") && strstr(buf, "\n"))
939 static gchar *imap_fetch_msg_full(Folder *folder, FolderItem *item, gint uid,
940 gboolean headers, gboolean body)
942 gchar *path, *filename;
943 IMAPSession *session;
946 g_return_val_if_fail(folder != NULL, NULL);
947 g_return_val_if_fail(item != NULL, NULL);
952 path = folder_item_get_path(item);
953 if (!is_dir_exist(path))
955 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
957 debug_print("trying to fetch cached %s\n", filename);
958 if (is_file_exist(filename)) {
959 /* see whether the local file represents the whole message
960 * or not. As the IMAP server reports size with \r chars,
961 * we have to update the local file (UNIX \n only) size */
962 MsgInfo *msginfo = imap_parse_msg(filename, item);
963 MsgInfo *cached = msgcache_get_msg(item->cache,uid);
964 guint have_size = get_size_with_crs(msginfo);
967 debug_print("message %d has been already %scached (%d/%d).\n", uid,
968 have_size == cached->size ? "fully ":"",
969 have_size, (int)cached->size);
971 if (cached && (cached->size == have_size || !body)) {
972 procmsg_msginfo_free(cached);
973 procmsg_msginfo_free(msginfo);
974 file_strip_crs(filename);
976 } else if (!cached) {
977 debug_print("message not cached, considering file complete\n");
978 procmsg_msginfo_free(msginfo);
979 file_strip_crs(filename);
982 procmsg_msginfo_free(cached);
983 procmsg_msginfo_free(msginfo);
987 session = imap_session_get(folder);
996 debug_print("IMAP fetching messages\n");
997 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
998 NULL, NULL, NULL, NULL, FALSE);
999 if (ok != IMAP_SUCCESS) {
1000 g_warning("can't select mailbox %s\n", item->path);
1006 debug_print("getting message %d...\n", uid);
1007 ok = imap_cmd_fetch(session, (guint32)uid, filename, headers, body);
1009 if (ok != IMAP_SUCCESS) {
1010 g_warning("can't fetch message %d\n", uid);
1017 file_strip_crs(filename);
1021 static gint imap_add_msg(Folder *folder, FolderItem *dest,
1022 const gchar *file, MsgFlags *flags)
1026 MsgFileInfo fileinfo;
1028 g_return_val_if_fail(file != NULL, -1);
1030 fileinfo.msginfo = NULL;
1031 fileinfo.file = (gchar *)file;
1032 fileinfo.flags = flags;
1033 file_list.data = &fileinfo;
1034 file_list.next = NULL;
1036 ret = imap_add_msgs(folder, dest, &file_list, NULL);
1040 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
1041 GRelation *relation)
1044 IMAPSession *session;
1045 guint32 last_uid = 0;
1047 MsgFileInfo *fileinfo;
1049 gint curnum = 0, total = 0;
1052 g_return_val_if_fail(folder != NULL, -1);
1053 g_return_val_if_fail(dest != NULL, -1);
1054 g_return_val_if_fail(file_list != NULL, -1);
1056 session = imap_session_get(folder);
1061 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1063 statusbar_print_all(_("Adding messages..."));
1064 total = g_slist_length(file_list);
1065 for (cur = file_list; cur != NULL; cur = cur->next) {
1066 IMAPFlags iflags = 0;
1067 guint32 new_uid = 0;
1068 gchar *real_file = NULL;
1069 gboolean file_is_tmp = FALSE;
1070 fileinfo = (MsgFileInfo *)cur->data;
1072 statusbar_progress_all(curnum, total, 1);
1075 if (fileinfo->flags) {
1076 if (MSG_IS_MARKED(*fileinfo->flags))
1077 iflags |= IMAP_FLAG_FLAGGED;
1078 if (MSG_IS_REPLIED(*fileinfo->flags))
1079 iflags |= IMAP_FLAG_ANSWERED;
1080 if (!MSG_IS_UNREAD(*fileinfo->flags))
1081 iflags |= IMAP_FLAG_SEEN;
1084 if (fileinfo->flags) {
1085 if ((MSG_IS_QUEUED(*fileinfo->flags)
1086 || MSG_IS_DRAFT(*fileinfo->flags))
1087 && !folder_has_parent_of_type(dest, F_QUEUE)
1088 && !folder_has_parent_of_type(dest, F_DRAFT)) {
1089 real_file = get_tmp_file();
1091 if (procmsg_remove_special_headers(
1099 } else if (!(MSG_IS_QUEUED(*fileinfo->flags)
1100 || MSG_IS_DRAFT(*fileinfo->flags))
1101 && (folder_has_parent_of_type(dest, F_QUEUE)
1102 || folder_has_parent_of_type(dest, F_DRAFT))) {
1106 if (real_file == NULL)
1107 real_file = g_strdup(fileinfo->file);
1109 if (folder_has_parent_of_type(dest, F_QUEUE) ||
1110 folder_has_parent_of_type(dest, F_OUTBOX) ||
1111 folder_has_parent_of_type(dest, F_DRAFT) ||
1112 folder_has_parent_of_type(dest, F_TRASH))
1113 iflags |= IMAP_FLAG_SEEN;
1115 ok = imap_cmd_append(session, destdir, real_file, iflags,
1118 if (ok != IMAP_SUCCESS) {
1119 g_warning("can't append message %s\n", real_file);
1121 g_unlink(real_file);
1125 statusbar_progress_all(0,0,0);
1126 statusbar_pop_all();
1129 debug_print("appended new message as %d\n", new_uid);
1130 /* put the local file in the imapcache, so that we don't
1131 * have to fetch it back later. */
1133 gchar *cache_path = folder_item_get_path(dest);
1134 if (!is_dir_exist(cache_path))
1135 make_dir_hier(cache_path);
1136 if (is_dir_exist(cache_path)) {
1137 gchar *cache_file = g_strconcat(
1138 cache_path, G_DIR_SEPARATOR_S,
1139 itos(new_uid), NULL);
1140 copy_file(real_file, cache_file, TRUE);
1141 debug_print("copied to cache: %s\n", cache_file);
1148 if (relation != NULL)
1149 g_relation_insert(relation, fileinfo->msginfo != NULL ?
1150 (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
1151 GINT_TO_POINTER(dest->last_num + 1));
1153 new_uid = dest->last_num+1;
1155 if (last_uid < new_uid)
1158 g_unlink(real_file);
1162 statusbar_progress_all(0,0,0);
1163 statusbar_pop_all();
1165 imap_cmd_expunge(session);
1173 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest,
1174 MsgInfoList *msglist, GRelation *relation)
1178 GSList *seq_list, *cur;
1180 IMAPSession *session;
1181 gint ok = IMAP_SUCCESS;
1182 GRelation *uid_mapping;
1185 g_return_val_if_fail(folder != NULL, -1);
1186 g_return_val_if_fail(dest != NULL, -1);
1187 g_return_val_if_fail(msglist != NULL, -1);
1189 session = imap_session_get(folder);
1195 msginfo = (MsgInfo *)msglist->data;
1197 src = msginfo->folder;
1199 g_warning("the src folder is identical to the dest.\n");
1204 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1205 NULL, NULL, NULL, NULL, FALSE);
1206 if (ok != IMAP_SUCCESS) {
1211 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1212 seq_list = imap_get_lep_set_from_msglist(msglist);
1213 uid_mapping = g_relation_new(2);
1214 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1216 statusbar_print_all(_("Copying messages..."));
1217 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1218 struct mailimap_set * seq_set;
1219 seq_set = cur->data;
1221 debug_print("Copying messages from %s to %s ...\n",
1222 src->path, destdir);
1224 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping);
1225 if (ok != IMAP_SUCCESS) {
1226 g_relation_destroy(uid_mapping);
1227 imap_lep_set_free(seq_list);
1233 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1234 MsgInfo *msginfo = (MsgInfo *)cur->data;
1237 tuples = g_relation_select(uid_mapping,
1238 GINT_TO_POINTER(msginfo->msgnum),
1240 if (tuples->len > 0) {
1241 gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1242 g_relation_insert(relation, msginfo,
1243 GPOINTER_TO_INT(num));
1247 g_relation_insert(relation, msginfo,
1248 GPOINTER_TO_INT(0));
1249 g_tuples_destroy(tuples);
1251 statusbar_pop_all();
1253 g_relation_destroy(uid_mapping);
1254 imap_lep_set_free(seq_list);
1258 IMAP_FOLDER_ITEM(dest)->lastuid = 0;
1259 IMAP_FOLDER_ITEM(dest)->uid_next = 0;
1260 g_slist_free(IMAP_FOLDER_ITEM(dest)->uid_list);
1261 IMAP_FOLDER_ITEM(dest)->uid_list = NULL;
1264 if (ok == IMAP_SUCCESS)
1270 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1274 g_return_val_if_fail(msginfo != NULL, -1);
1276 msglist.data = msginfo;
1277 msglist.next = NULL;
1279 return imap_copy_msgs(folder, dest, &msglist, NULL);
1282 static gint imap_copy_msgs(Folder *folder, FolderItem *dest,
1283 MsgInfoList *msglist, GRelation *relation)
1289 g_return_val_if_fail(folder != NULL, -1);
1290 g_return_val_if_fail(dest != NULL, -1);
1291 g_return_val_if_fail(msglist != NULL, -1);
1293 msginfo = (MsgInfo *)msglist->data;
1294 g_return_val_if_fail(msginfo->folder != NULL, -1);
1296 /* if from/to are the same "type" (with or without extra headers),
1297 * copy them via imap */
1298 if (folder == msginfo->folder->folder &&
1299 !folder_has_parent_of_type(msginfo->folder, F_DRAFT) &&
1300 !folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
1301 !folder_has_parent_of_type(dest, F_DRAFT) &&
1302 !folder_has_parent_of_type(dest, F_QUEUE)) {
1303 ret = imap_do_copy_msgs(folder, dest, msglist, relation);
1305 } else if (folder == msginfo->folder->folder &&
1306 (folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
1307 folder_has_parent_of_type(msginfo->folder, F_QUEUE)) &&
1308 (folder_has_parent_of_type(dest, F_DRAFT) ||
1309 folder_has_parent_of_type(dest, F_QUEUE))) {
1310 ret = imap_do_copy_msgs(folder, dest, msglist, relation);
1313 /* else reupload them */
1314 file_list = procmsg_get_message_file_list(msglist);
1315 g_return_val_if_fail(file_list != NULL, -1);
1317 ret = imap_add_msgs(folder, dest, file_list, relation);
1318 procmsg_message_file_list_free(file_list);
1324 static gint imap_do_remove_msgs(Folder *folder, FolderItem *dest,
1325 MsgInfoList *msglist, GRelation *relation)
1327 gchar *destdir, *dir;
1328 GSList *numlist = NULL, *cur;
1330 IMAPSession *session;
1331 gint ok = IMAP_SUCCESS;
1332 GRelation *uid_mapping;
1334 g_return_val_if_fail(folder != NULL, -1);
1335 g_return_val_if_fail(dest != NULL, -1);
1336 g_return_val_if_fail(msglist != NULL, -1);
1338 session = imap_session_get(folder);
1343 msginfo = (MsgInfo *)msglist->data;
1345 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1346 NULL, NULL, NULL, NULL, FALSE);
1347 if (ok != IMAP_SUCCESS) {
1352 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1353 for (cur = msglist; cur; cur = cur->next) {
1354 msginfo = (MsgInfo *)cur->data;
1355 if (!MSG_IS_DELETED(msginfo->flags))
1356 numlist = g_slist_append(numlist, GINT_TO_POINTER(msginfo->msgnum));
1359 uid_mapping = g_relation_new(2);
1360 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1362 ok = imap_set_message_flags
1363 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1364 numlist, IMAP_FLAG_DELETED, TRUE);
1365 if (ok != IMAP_SUCCESS) {
1366 log_warning(_("can't set deleted flags\n"));
1370 ok = imap_cmd_expunge(session);
1371 if (ok != IMAP_SUCCESS) {
1372 log_warning(_("can't expunge\n"));
1377 dir = folder_item_get_path(msginfo->folder);
1378 if (is_dir_exist(dir)) {
1379 for (cur = msglist; cur; cur = cur->next) {
1380 msginfo = (MsgInfo *)cur->data;
1381 remove_numbered_files(dir, msginfo->msgnum, msginfo->msgnum);
1386 g_relation_destroy(uid_mapping);
1387 g_slist_free(numlist);
1391 if (ok == IMAP_SUCCESS)
1397 static gint imap_remove_msgs(Folder *folder, FolderItem *dest,
1398 MsgInfoList *msglist, GRelation *relation)
1402 g_return_val_if_fail(folder != NULL, -1);
1403 g_return_val_if_fail(dest != NULL, -1);
1404 if (msglist == NULL)
1407 msginfo = (MsgInfo *)msglist->data;
1408 g_return_val_if_fail(msginfo->folder != NULL, -1);
1410 return imap_do_remove_msgs(folder, dest, msglist, relation);
1413 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1415 GSList *list = folder_item_get_msg_list(item);
1416 gint res = imap_remove_msgs(folder, item, list, NULL);
1417 procmsg_msg_list_free(list);
1421 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1424 /* TODO: properly implement this method */
1428 static gint imap_close(Folder *folder, FolderItem *item)
1433 static gint imap_scan_tree(Folder *folder)
1435 FolderItem *item = NULL;
1436 IMAPSession *session;
1437 gchar *root_folder = NULL;
1439 g_return_val_if_fail(folder != NULL, -1);
1440 g_return_val_if_fail(folder->account != NULL, -1);
1442 session = imap_session_get(folder);
1444 if (!folder->node) {
1445 folder_tree_destroy(folder);
1446 item = folder_item_new(folder, folder->name, NULL);
1447 item->folder = folder;
1448 folder->node = item->node = g_node_new(item);
1454 if (folder->account->imap_dir && *folder->account->imap_dir) {
1459 Xstrdup_a(root_folder, folder->account->imap_dir, {unlock_session();return -1;});
1460 extract_quote(root_folder, '"');
1461 subst_char(root_folder,
1462 imap_get_path_separator(IMAP_FOLDER(folder),
1465 strtailchomp(root_folder, '/');
1466 real_path = imap_get_real_path
1467 (IMAP_FOLDER(folder), root_folder);
1468 debug_print("IMAP root directory: %s\n", real_path);
1470 /* check if root directory exist */
1472 r = imap_threaded_list(session->folder, "", real_path,
1474 if ((r != MAILIMAP_NO_ERROR) || (clist_count(lep_list) == 0)) {
1475 if (!folder->node) {
1476 item = folder_item_new(folder, folder->name, NULL);
1477 item->folder = folder;
1478 folder->node = item->node = g_node_new(item);
1483 mailimap_list_result_free(lep_list);
1489 item = FOLDER_ITEM(folder->node->data);
1490 if (!item || ((item->path || root_folder) &&
1491 strcmp2(item->path, root_folder) != 0)) {
1492 folder_tree_destroy(folder);
1493 item = folder_item_new(folder, folder->name, root_folder);
1494 item->folder = folder;
1495 folder->node = item->node = g_node_new(item);
1498 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1499 imap_create_missing_folders(folder);
1505 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1508 IMAPFolder *imapfolder;
1509 FolderItem *new_item;
1510 GSList *item_list, *cur;
1513 gchar *wildcard_path;
1519 g_return_val_if_fail(item != NULL, -1);
1520 g_return_val_if_fail(item->folder != NULL, -1);
1521 g_return_val_if_fail(item->no_sub == FALSE, -1);
1523 folder = item->folder;
1524 imapfolder = IMAP_FOLDER(folder);
1526 separator = imap_get_path_separator(imapfolder, item->path);
1528 if (folder->ui_func)
1529 folder->ui_func(folder, item, folder->ui_func_data);
1532 wildcard[0] = separator;
1535 real_path = imap_get_real_path(imapfolder, item->path);
1539 real_path = g_strdup("");
1542 Xstrcat_a(wildcard_path, real_path, wildcard,
1543 {g_free(real_path); return IMAP_ERROR;});
1545 r = imap_threaded_list(folder, "", wildcard_path, &lep_list);
1546 if (r != MAILIMAP_NO_ERROR) {
1550 item_list = imap_list_from_lep(imapfolder,
1551 lep_list, real_path, FALSE);
1552 mailimap_list_result_free(lep_list);
1557 node = item->node->children;
1558 while (node != NULL) {
1559 FolderItem *old_item = FOLDER_ITEM(node->data);
1560 GNode *next = node->next;
1563 for (cur = item_list; cur != NULL; cur = cur->next) {
1564 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1565 if (!strcmp2(old_item->path, cur_item->path)) {
1566 new_item = cur_item;
1571 debug_print("folder '%s' not found. removing...\n",
1573 folder_item_remove(old_item);
1575 old_item->no_sub = new_item->no_sub;
1576 old_item->no_select = new_item->no_select;
1577 if (old_item->no_sub == TRUE && node->children) {
1578 debug_print("folder '%s' doesn't have "
1579 "subfolders. removing...\n",
1581 folder_item_remove_children(old_item);
1588 for (cur = item_list; cur != NULL; cur = cur->next) {
1589 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1592 for (node = item->node->children; node != NULL;
1593 node = node->next) {
1594 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1596 new_item = FOLDER_ITEM(node->data);
1597 folder_item_destroy(cur_item);
1603 new_item = cur_item;
1604 debug_print("new folder '%s' found.\n", new_item->path);
1605 folder_item_append(item, new_item);
1608 if (!strcmp(new_item->path, "INBOX")) {
1609 new_item->stype = F_INBOX;
1610 folder->inbox = new_item;
1611 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1614 base = g_path_get_basename(new_item->path);
1616 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1617 new_item->stype = F_OUTBOX;
1618 folder->outbox = new_item;
1619 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1620 new_item->stype = F_DRAFT;
1621 folder->draft = new_item;
1622 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1623 new_item->stype = F_QUEUE;
1624 folder->queue = new_item;
1625 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1626 new_item->stype = F_TRASH;
1627 folder->trash = new_item;
1632 if (new_item->no_sub == FALSE)
1633 imap_scan_tree_recursive(session, new_item);
1636 g_slist_free(item_list);
1638 return IMAP_SUCCESS;
1641 static gint imap_create_tree(Folder *folder)
1643 g_return_val_if_fail(folder != NULL, -1);
1644 g_return_val_if_fail(folder->node != NULL, -1);
1645 g_return_val_if_fail(folder->node->data != NULL, -1);
1646 g_return_val_if_fail(folder->account != NULL, -1);
1648 imap_scan_tree(folder);
1649 imap_create_missing_folders(folder);
1654 static void imap_create_missing_folders(Folder *folder)
1656 g_return_if_fail(folder != NULL);
1659 folder->inbox = imap_create_special_folder
1660 (folder, F_INBOX, "INBOX");
1662 folder->trash = imap_create_special_folder
1663 (folder, F_TRASH, "Trash");
1665 folder->queue = imap_create_special_folder
1666 (folder, F_QUEUE, "Queue");
1667 if (!folder->outbox)
1668 folder->outbox = imap_create_special_folder
1669 (folder, F_OUTBOX, "Sent");
1671 folder->draft = imap_create_special_folder
1672 (folder, F_DRAFT, "Drafts");
1675 static FolderItem *imap_create_special_folder(Folder *folder,
1676 SpecialFolderItemType stype,
1680 FolderItem *new_item;
1682 g_return_val_if_fail(folder != NULL, NULL);
1683 g_return_val_if_fail(folder->node != NULL, NULL);
1684 g_return_val_if_fail(folder->node->data != NULL, NULL);
1685 g_return_val_if_fail(folder->account != NULL, NULL);
1686 g_return_val_if_fail(name != NULL, NULL);
1688 item = FOLDER_ITEM(folder->node->data);
1689 new_item = imap_create_folder(folder, item, name);
1692 g_warning("Can't create '%s'\n", name);
1693 if (!folder->inbox) return NULL;
1695 new_item = imap_create_folder(folder, folder->inbox, name);
1697 g_warning("Can't create '%s' under INBOX\n", name);
1699 new_item->stype = stype;
1701 new_item->stype = stype;
1706 static gchar *imap_folder_get_path(Folder *folder)
1710 g_return_val_if_fail(folder != NULL, NULL);
1711 g_return_val_if_fail(folder->account != NULL, NULL);
1713 folder_path = g_strconcat(get_imap_cache_dir(),
1715 folder->account->recv_server,
1717 folder->account->userid,
1723 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1725 gchar *folder_path, *path;
1727 g_return_val_if_fail(folder != NULL, NULL);
1728 g_return_val_if_fail(item != NULL, NULL);
1729 folder_path = imap_folder_get_path(folder);
1731 g_return_val_if_fail(folder_path != NULL, NULL);
1732 if (folder_path[0] == G_DIR_SEPARATOR) {
1734 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1737 path = g_strdup(folder_path);
1740 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1741 folder_path, G_DIR_SEPARATOR_S,
1744 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1747 g_free(folder_path);
1752 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1755 gchar *dirpath, *imap_path;
1756 IMAPSession *session;
1757 FolderItem *new_item;
1762 gboolean no_select = FALSE, no_sub = FALSE;
1764 g_return_val_if_fail(folder != NULL, NULL);
1765 g_return_val_if_fail(folder->account != NULL, NULL);
1766 g_return_val_if_fail(parent != NULL, NULL);
1767 g_return_val_if_fail(name != NULL, NULL);
1769 session = imap_session_get(folder);
1775 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0) {
1776 dirpath = g_strdup(name);
1777 }else if (parent->path)
1778 dirpath = g_strconcat(parent->path, "/", name, NULL);
1779 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1780 dirpath = g_strdup(name);
1781 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1784 Xstrdup_a(imap_dir, folder->account->imap_dir, {unlock_session();return NULL;});
1785 strtailchomp(imap_dir, '/');
1786 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1788 dirpath = g_strdup(name);
1792 /* keep trailing directory separator to create a folder that contains
1794 imap_path = imap_utf8_to_modified_utf7(dirpath);
1796 strtailchomp(dirpath, '/');
1797 Xstrdup_a(new_name, name, {
1802 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1803 imap_path_separator_subst(imap_path, separator);
1804 /* remove trailing / for display */
1805 strtailchomp(new_name, '/');
1807 if (strcmp(dirpath, "INBOX") != 0) {
1809 gboolean exist = FALSE;
1813 argbuf = g_ptr_array_new();
1814 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1815 if (r != MAILIMAP_NO_ERROR) {
1816 log_warning(_("can't create mailbox: LIST failed\n"));
1819 ptr_array_free_strings(argbuf);
1820 g_ptr_array_free(argbuf, TRUE);
1825 if (clist_count(lep_list) > 0)
1827 mailimap_list_result_free(lep_list);
1830 ok = imap_cmd_create(session, imap_path);
1831 if (ok != IMAP_SUCCESS) {
1832 log_warning(_("can't create mailbox\n"));
1838 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1839 if (r == MAILIMAP_NO_ERROR) {
1840 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1841 lep_list, dirpath, TRUE);
1843 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1844 no_select = cur_item->no_select;
1845 no_sub = cur_item->no_sub;
1846 g_slist_free(item_list);
1848 mailimap_list_result_free(lep_list);
1855 /* just get flags */
1856 r = imap_threaded_list(folder, "", "INBOX", &lep_list);
1857 if (r == MAILIMAP_NO_ERROR) {
1858 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1859 lep_list, dirpath, TRUE);
1861 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1862 no_select = cur_item->no_select;
1863 no_sub = cur_item->no_sub;
1864 g_slist_free(item_list);
1866 mailimap_list_result_free(lep_list);
1870 new_item = folder_item_new(folder, new_name, dirpath);
1871 new_item->no_select = no_select;
1872 new_item->no_sub = no_sub;
1873 folder_item_append(parent, new_item);
1877 dirpath = folder_item_get_path(new_item);
1878 if (!is_dir_exist(dirpath))
1879 make_dir_hier(dirpath);
1885 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1890 gchar *real_oldpath;
1891 gchar *real_newpath;
1893 gchar *old_cache_dir;
1894 gchar *new_cache_dir;
1895 IMAPSession *session;
1898 gint exists, recent, unseen;
1899 guint32 uid_validity;
1901 g_return_val_if_fail(folder != NULL, -1);
1902 g_return_val_if_fail(item != NULL, -1);
1903 g_return_val_if_fail(item->path != NULL, -1);
1904 g_return_val_if_fail(name != NULL, -1);
1906 session = imap_session_get(folder);
1912 if (strchr(name, imap_get_path_separator(IMAP_FOLDER(folder), item->path)) != NULL) {
1913 g_warning(_("New folder name must not contain the namespace "
1919 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1921 g_free(session->mbox);
1922 session->mbox = NULL;
1923 ok = imap_cmd_examine(session, "INBOX",
1924 &exists, &recent, &unseen, &uid_validity, FALSE);
1925 if (ok != IMAP_SUCCESS) {
1926 g_free(real_oldpath);
1931 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1932 if (strchr(item->path, G_DIR_SEPARATOR)) {
1933 dirpath = g_path_get_dirname(item->path);
1934 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1937 newpath = g_strdup(name);
1939 real_newpath = imap_utf8_to_modified_utf7(newpath);
1940 imap_path_separator_subst(real_newpath, separator);
1942 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1943 if (ok != IMAP_SUCCESS) {
1944 log_warning(_("can't rename mailbox: %s to %s\n"),
1945 real_oldpath, real_newpath);
1946 g_free(real_oldpath);
1948 g_free(real_newpath);
1954 item->name = g_strdup(name);
1956 old_cache_dir = folder_item_get_path(item);
1958 paths[0] = g_strdup(item->path);
1960 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1961 imap_rename_folder_func, paths);
1963 if (is_dir_exist(old_cache_dir)) {
1964 new_cache_dir = folder_item_get_path(item);
1965 if (rename(old_cache_dir, new_cache_dir) < 0) {
1966 FILE_OP_ERROR(old_cache_dir, "rename");
1968 g_free(new_cache_dir);
1971 g_free(old_cache_dir);
1974 g_free(real_oldpath);
1975 g_free(real_newpath);
1980 static gint imap_remove_folder_real(Folder *folder, FolderItem *item)
1983 IMAPSession *session;
1987 g_return_val_if_fail(folder != NULL, -1);
1988 g_return_val_if_fail(item != NULL, -1);
1989 g_return_val_if_fail(item->path != NULL, -1);
1991 session = imap_session_get(folder);
1996 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1998 ok = imap_cmd_delete(session, path);
1999 if (ok != IMAP_SUCCESS) {
2000 gchar *tmp = g_strdup_printf("%s%c", path,
2001 imap_get_path_separator(IMAP_FOLDER(folder), path));
2004 ok = imap_cmd_delete(session, path);
2007 if (ok != IMAP_SUCCESS) {
2008 log_warning(_("can't delete mailbox\n"));
2015 cache_dir = folder_item_get_path(item);
2016 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2017 g_warning("can't remove directory '%s'\n", cache_dir);
2019 folder_item_remove(item);
2024 static gint imap_remove_folder(Folder *folder, FolderItem *item)
2028 g_return_val_if_fail(item != NULL, -1);
2029 g_return_val_if_fail(item->folder != NULL, -1);
2030 g_return_val_if_fail(item->node != NULL, -1);
2032 node = item->node->children;
2033 while (node != NULL) {
2035 if (imap_remove_folder(folder, FOLDER_ITEM(node->data)) < 0)
2039 debug_print("IMAP removing %s\n", item->path);
2041 if (imap_remove_all_msg(folder, item) < 0)
2043 return imap_remove_folder_real(folder, item);
2046 typedef struct _uncached_data {
2047 IMAPSession *session;
2049 MsgNumberList *numlist;
2055 static void *imap_get_uncached_messages_thread(void *data)
2057 uncached_data *stuff = (uncached_data *)data;
2058 IMAPSession *session = stuff->session;
2059 FolderItem *item = stuff->item;
2060 MsgNumberList *numlist = stuff->numlist;
2062 GSList *newlist = NULL;
2063 GSList *llast = NULL;
2064 GSList *seq_list, *cur;
2066 debug_print("uncached_messages\n");
2068 if (session == NULL || item == NULL || item->folder == NULL
2069 || FOLDER_CLASS(item->folder) != &imap_class) {
2074 seq_list = imap_get_lep_set_from_numlist(numlist);
2075 debug_print("get msgs info\n");
2076 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2077 struct mailimap_set * imapset;
2083 imapset = cur->data;
2085 r = imap_threaded_fetch_env(session->folder,
2086 imapset, &env_list);
2087 if (r != MAILIMAP_NO_ERROR)
2090 session_set_access_time(SESSION(session));
2093 for(i = 0 ; i < carray_count(env_list) ; i ++) {
2094 struct imap_fetch_env_info * info;
2097 info = carray_get(env_list, i);
2098 msginfo = imap_envelope_from_lep(info, item);
2099 if (msginfo == NULL)
2101 msginfo->folder = item;
2103 llast = newlist = g_slist_append(newlist, msginfo);
2105 llast = g_slist_append(llast, msginfo);
2106 llast = llast->next;
2111 imap_fetch_env_free(env_list);
2114 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2115 struct mailimap_set * imapset;
2117 imapset = cur->data;
2118 mailimap_set_free(imapset);
2121 session_set_access_time(SESSION(session));
2126 #define MAX_MSG_NUM 50
2128 static GSList *imap_get_uncached_messages(IMAPSession *session,
2130 MsgNumberList *numlist)
2132 GSList *result = NULL;
2134 uncached_data *data = g_new0(uncached_data, 1);
2139 data->total = g_slist_length(numlist);
2140 debug_print("messages list : %i\n", data->total);
2142 while (cur != NULL) {
2143 GSList * partial_result;
2151 while (count < MAX_MSG_NUM) {
2156 if (newlist == NULL)
2157 llast = newlist = g_slist_append(newlist, p);
2159 llast = g_slist_append(llast, p);
2160 llast = llast->next;
2170 data->session = session;
2172 data->numlist = newlist;
2175 if (prefs_common.work_offline &&
2176 !inc_offline_should_override(
2177 _("Sylpheed-Claws needs network access in order "
2178 "to access the IMAP server."))) {
2184 (GSList *)imap_get_uncached_messages_thread(data);
2186 statusbar_progress_all(data->cur,data->total, 1);
2188 g_slist_free(newlist);
2190 result = g_slist_concat(result, partial_result);
2194 statusbar_progress_all(0,0,0);
2195 statusbar_pop_all();
2200 static void imap_delete_all_cached_messages(FolderItem *item)
2204 g_return_if_fail(item != NULL);
2205 g_return_if_fail(item->folder != NULL);
2206 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2208 debug_print("Deleting all cached messages...\n");
2210 dir = folder_item_get_path(item);
2211 if (is_dir_exist(dir))
2212 remove_all_numbered_files(dir);
2215 debug_print("done.\n");
2218 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
2221 IMAPNameSpace *namespace = NULL;
2222 gchar *tmp_path, *name;
2224 if (!path) path = "";
2226 for (; ns_list != NULL; ns_list = ns_list->next) {
2227 IMAPNameSpace *tmp_ns = ns_list->data;
2229 Xstrcat_a(tmp_path, path, "/", return namespace);
2230 Xstrdup_a(name, tmp_ns->name, return namespace);
2231 if (tmp_ns->separator && tmp_ns->separator != '/') {
2232 subst_char(tmp_path, tmp_ns->separator, '/');
2233 subst_char(name, tmp_ns->separator, '/');
2235 if (strncmp(tmp_path, name, strlen(name)) == 0)
2242 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
2245 IMAPNameSpace *namespace;
2247 g_return_val_if_fail(folder != NULL, NULL);
2249 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
2250 if (namespace) return namespace;
2251 namespace = imap_find_namespace_from_list(folder->ns_others, path);
2252 if (namespace) return namespace;
2253 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
2254 if (namespace) return namespace;
2259 gchar imap_get_path_separator_for_item(FolderItem *item)
2261 Folder *folder = NULL;
2262 IMAPFolder *imap_folder = NULL;
2265 folder = item->folder;
2270 imap_folder = IMAP_FOLDER(folder);
2275 return imap_get_path_separator(imap_folder, item->path);
2278 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
2280 IMAPNameSpace *namespace;
2281 gchar separator = '/';
2282 IMAPSession *session = imap_session_get(FOLDER(folder));
2283 g_return_val_if_fail(session != NULL, '/');
2285 if (folder->last_seen_separator == 0) {
2287 int r = imap_threaded_list((Folder *)folder, "", "", &lep_list);
2288 if (r != MAILIMAP_NO_ERROR) {
2289 log_warning(_("LIST failed\n"));
2293 if (clist_count(lep_list) > 0) {
2294 clistiter * iter = clist_begin(lep_list);
2295 struct mailimap_mailbox_list * mb;
2296 mb = clist_content(iter);
2298 folder->last_seen_separator = mb->mb_delimiter;
2299 debug_print("got separator: %c\n", folder->last_seen_separator);
2301 mailimap_list_result_free(lep_list);
2304 if (folder->last_seen_separator != 0) {
2305 debug_print("using separator: %c\n", folder->last_seen_separator);
2306 return folder->last_seen_separator;
2309 namespace = imap_find_namespace(folder, path);
2310 if (namespace && namespace->separator)
2311 separator = namespace->separator;
2316 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
2321 g_return_val_if_fail(folder != NULL, NULL);
2322 g_return_val_if_fail(path != NULL, NULL);
2324 real_path = imap_utf8_to_modified_utf7(path);
2325 separator = imap_get_path_separator(folder, path);
2326 imap_path_separator_subst(real_path, separator);
2331 static gint imap_set_message_flags(IMAPSession *session,
2332 MsgNumberList *numlist,
2340 seq_list = imap_get_lep_set_from_numlist(numlist);
2342 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
2343 struct mailimap_set * imapset;
2345 imapset = cur->data;
2347 ok = imap_cmd_store(session, imapset,
2351 imap_lep_set_free(seq_list);
2353 return IMAP_SUCCESS;
2356 typedef struct _select_data {
2357 IMAPSession *session;
2362 guint32 *uid_validity;
2366 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2368 gint *exists, gint *recent, gint *unseen,
2369 guint32 *uid_validity, gboolean block)
2373 gint exists_, recent_, unseen_;
2374 guint32 uid_validity_;
2376 if (!exists && !recent && !unseen && !uid_validity) {
2377 if (session->mbox && strcmp(session->mbox, path) == 0)
2378 return IMAP_SUCCESS;
2387 uid_validity = &uid_validity_;
2389 g_free(session->mbox);
2390 session->mbox = NULL;
2392 real_path = imap_get_real_path(folder, path);
2394 ok = imap_cmd_select(session, real_path,
2395 exists, recent, unseen, uid_validity, block);
2396 if (ok != IMAP_SUCCESS)
2397 log_warning(_("can't select folder: %s\n"), real_path);
2399 session->mbox = g_strdup(path);
2400 session->folder_content_changed = FALSE;
2407 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2408 const gchar *path, IMAPFolderItem *item,
2410 guint32 *uid_next, guint32 *uid_validity,
2411 gint *unseen, gboolean block)
2415 struct mailimap_mailbox_data_status * data_status;
2420 real_path = imap_get_real_path(folder, path);
2434 r = imap_threaded_status(FOLDER(folder), real_path,
2435 &data_status, mask);
2438 if (r != MAILIMAP_NO_ERROR) {
2439 debug_print("status err %d\n", r);
2443 if (data_status->st_info_list == NULL) {
2444 mailimap_mailbox_data_status_free(data_status);
2445 debug_print("status->st_info_list == NULL\n");
2450 for(iter = clist_begin(data_status->st_info_list) ; iter != NULL ;
2451 iter = clist_next(iter)) {
2452 struct mailimap_status_info * info;
2454 info = clist_content(iter);
2455 switch (info->st_att) {
2456 case MAILIMAP_STATUS_ATT_MESSAGES:
2457 * messages = info->st_value;
2458 got_values |= 1 << 0;
2461 case MAILIMAP_STATUS_ATT_UIDNEXT:
2462 * uid_next = info->st_value;
2463 got_values |= 1 << 2;
2466 case MAILIMAP_STATUS_ATT_UIDVALIDITY:
2467 * uid_validity = info->st_value;
2468 got_values |= 1 << 3;
2471 case MAILIMAP_STATUS_ATT_UNSEEN:
2472 * unseen = info->st_value;
2473 got_values |= 1 << 4;
2477 mailimap_mailbox_data_status_free(data_status);
2479 if (got_values != mask) {
2480 debug_print("status: incomplete values received (%d)\n", got_values);
2483 return IMAP_SUCCESS;
2486 static void imap_free_capabilities(IMAPSession *session)
2488 slist_free_strings(session->capability);
2489 g_slist_free(session->capability);
2490 session->capability = NULL;
2493 /* low-level IMAP4rev1 commands */
2495 static gint imap_cmd_login(IMAPSession *session,
2496 const gchar *user, const gchar *pass,
2502 log_print("IMAP4> Logging %s to %s using %s\n",
2504 SESSION(session)->server,
2506 r = imap_threaded_login(session->folder, user, pass, type);
2507 if (r != MAILIMAP_NO_ERROR) {
2508 log_print("IMAP4< Error logging in to %s\n",
2509 SESSION(session)->server);
2512 log_print("IMAP4< Login to %s successful\n",
2513 SESSION(session)->server);
2519 static gint imap_cmd_logout(IMAPSession *session)
2521 imap_threaded_disconnect(session->folder);
2523 return IMAP_SUCCESS;
2526 static gint imap_cmd_noop(IMAPSession *session)
2529 unsigned int exists;
2531 r = imap_threaded_noop(session->folder, &exists);
2532 if (r != MAILIMAP_NO_ERROR) {
2533 debug_print("noop err %d\n", r);
2536 session->exists = exists;
2537 session_set_access_time(SESSION(session));
2539 return IMAP_SUCCESS;
2543 static gint imap_cmd_starttls(IMAPSession *session)
2547 r = imap_threaded_starttls(session->folder,
2548 SESSION(session)->server, SESSION(session)->port);
2549 if (r != MAILIMAP_NO_ERROR) {
2550 debug_print("starttls err %d\n", r);
2553 return IMAP_SUCCESS;
2557 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2558 gint *exists, gint *recent, gint *unseen,
2559 guint32 *uid_validity, gboolean block)
2563 r = imap_threaded_select(session->folder, folder,
2564 exists, recent, unseen, uid_validity);
2565 if (r != MAILIMAP_NO_ERROR) {
2566 debug_print("select err %d\n", r);
2569 return IMAP_SUCCESS;
2572 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2573 gint *exists, gint *recent, gint *unseen,
2574 guint32 *uid_validity, gboolean block)
2578 r = imap_threaded_examine(session->folder, folder,
2579 exists, recent, unseen, uid_validity);
2580 if (r != MAILIMAP_NO_ERROR) {
2581 debug_print("examine err %d\n", r);
2585 return IMAP_SUCCESS;
2588 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2592 r = imap_threaded_create(session->folder, folder);
2593 if (r != MAILIMAP_NO_ERROR) {
2598 return IMAP_SUCCESS;
2601 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2602 const gchar *new_folder)
2606 r = imap_threaded_rename(session->folder, old_folder,
2608 if (r != MAILIMAP_NO_ERROR) {
2613 return IMAP_SUCCESS;
2616 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2621 r = imap_threaded_delete(session->folder, folder);
2622 if (r != MAILIMAP_NO_ERROR) {
2627 return IMAP_SUCCESS;
2630 typedef struct _fetch_data {
2631 IMAPSession *session;
2633 const gchar *filename;
2639 static void *imap_cmd_fetch_thread(void *data)
2641 fetch_data *stuff = (fetch_data *)data;
2642 IMAPSession *session = stuff->session;
2643 guint32 uid = stuff->uid;
2644 const gchar *filename = stuff->filename;
2648 r = imap_threaded_fetch_content(session->folder,
2652 r = imap_threaded_fetch_content(session->folder,
2655 if (r != MAILIMAP_NO_ERROR) {
2656 debug_print("fetch err %d\n", r);
2657 return GINT_TO_POINTER(IMAP_ERROR);
2659 return GINT_TO_POINTER(IMAP_SUCCESS);
2662 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
2663 const gchar *filename, gboolean headers,
2666 fetch_data *data = g_new0(fetch_data, 1);
2669 data->session = session;
2671 data->filename = filename;
2672 data->headers = headers;
2675 if (prefs_common.work_offline &&
2676 !inc_offline_should_override(
2677 _("Sylpheed-Claws needs network access in order "
2678 "to access the IMAP server."))) {
2682 statusbar_print_all(_("Fetching message..."));
2683 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
2684 statusbar_pop_all();
2690 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2691 const gchar *file, IMAPFlags flags,
2694 struct mailimap_flag_list * flag_list;
2697 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2699 flag_list = imap_flag_to_lep(flags);
2700 r = imap_threaded_append(session->folder, destfolder,
2701 file, flag_list, new_uid);
2702 mailimap_flag_list_free(flag_list);
2704 if (r != MAILIMAP_NO_ERROR) {
2705 debug_print("append err %d\n", r);
2708 return IMAP_SUCCESS;
2711 static gint imap_cmd_copy(IMAPSession *session, struct mailimap_set * set,
2712 const gchar *destfolder, GRelation *uid_mapping)
2716 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2717 g_return_val_if_fail(set != NULL, IMAP_ERROR);
2718 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2720 r = imap_threaded_copy(session->folder, set, destfolder);
2721 if (r != MAILIMAP_NO_ERROR) {
2726 return IMAP_SUCCESS;
2729 static gint imap_cmd_store(IMAPSession *session, struct mailimap_set * set,
2730 IMAPFlags flags, int do_add)
2733 struct mailimap_flag_list * flag_list;
2734 struct mailimap_store_att_flags * store_att_flags;
2736 flag_list = imap_flag_to_lep(flags);
2740 mailimap_store_att_flags_new_add_flags_silent(flag_list);
2743 mailimap_store_att_flags_new_remove_flags_silent(flag_list);
2745 r = imap_threaded_store(session->folder, set, store_att_flags);
2746 mailimap_store_att_flags_free(store_att_flags);
2747 if (r != MAILIMAP_NO_ERROR) {
2752 return IMAP_SUCCESS;
2755 static gint imap_cmd_expunge(IMAPSession *session)
2759 if (prefs_common.work_offline &&
2760 !inc_offline_should_override(
2761 _("Sylpheed-Claws needs network access in order "
2762 "to access the IMAP server."))) {
2766 r = imap_threaded_expunge(session->folder);
2767 if (r != MAILIMAP_NO_ERROR) {
2772 return IMAP_SUCCESS;
2775 static void imap_path_separator_subst(gchar *str, gchar separator)
2778 gboolean in_escape = FALSE;
2780 if (!separator || separator == '/') return;
2782 for (p = str; *p != '\0'; p++) {
2783 if (*p == '/' && !in_escape)
2785 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2787 else if (*p == '-' && in_escape)
2792 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
2794 static iconv_t cd = (iconv_t)-1;
2795 static gboolean iconv_ok = TRUE;
2798 size_t norm_utf7_len;
2800 gchar *to_str, *to_p;
2802 gboolean in_escape = FALSE;
2804 if (!iconv_ok) return g_strdup(mutf7_str);
2806 if (cd == (iconv_t)-1) {
2807 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
2808 if (cd == (iconv_t)-1) {
2809 g_warning("iconv cannot convert UTF-7 to %s\n",
2812 return g_strdup(mutf7_str);
2816 /* modified UTF-7 to normal UTF-7 conversion */
2817 norm_utf7 = g_string_new(NULL);
2819 for (p = mutf7_str; *p != '\0'; p++) {
2820 /* replace: '&' -> '+',
2822 escaped ',' -> '/' */
2823 if (!in_escape && *p == '&') {
2824 if (*(p + 1) != '-') {
2825 g_string_append_c(norm_utf7, '+');
2828 g_string_append_c(norm_utf7, '&');
2831 } else if (in_escape && *p == ',') {
2832 g_string_append_c(norm_utf7, '/');
2833 } else if (in_escape && *p == '-') {
2834 g_string_append_c(norm_utf7, '-');
2837 g_string_append_c(norm_utf7, *p);
2841 norm_utf7_p = norm_utf7->str;
2842 norm_utf7_len = norm_utf7->len;
2843 to_len = strlen(mutf7_str) * 5;
2844 to_p = to_str = g_malloc(to_len + 1);
2846 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2847 &to_p, &to_len) == -1) {
2848 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2849 conv_get_locale_charset_str());
2850 g_string_free(norm_utf7, TRUE);
2852 return g_strdup(mutf7_str);
2855 /* second iconv() call for flushing */
2856 iconv(cd, NULL, NULL, &to_p, &to_len);
2857 g_string_free(norm_utf7, TRUE);
2863 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
2865 static iconv_t cd = (iconv_t)-1;
2866 static gboolean iconv_ok = TRUE;
2867 gchar *norm_utf7, *norm_utf7_p;
2868 size_t from_len, norm_utf7_len;
2870 gchar *from_tmp, *to, *p;
2871 gboolean in_escape = FALSE;
2873 if (!iconv_ok) return g_strdup(from);
2875 if (cd == (iconv_t)-1) {
2876 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
2877 if (cd == (iconv_t)-1) {
2878 g_warning(_("iconv cannot convert %s to UTF-7\n"),
2881 return g_strdup(from);
2885 /* UTF-8 to normal UTF-7 conversion */
2886 Xstrdup_a(from_tmp, from, return g_strdup(from));
2887 from_len = strlen(from);
2888 norm_utf7_len = from_len * 5;
2889 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
2890 norm_utf7_p = norm_utf7;
2892 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
2894 while (from_len > 0) {
2895 if (*from_tmp == '+') {
2896 *norm_utf7_p++ = '+';
2897 *norm_utf7_p++ = '-';
2901 } else if (IS_PRINT(*(guchar *)from_tmp)) {
2902 /* printable ascii char */
2903 *norm_utf7_p = *from_tmp;
2909 size_t conv_len = 0;
2911 /* unprintable char: convert to UTF-7 */
2913 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
2914 conv_len += g_utf8_skip[*(guchar *)p];
2915 p += g_utf8_skip[*(guchar *)p];
2918 from_len -= conv_len;
2919 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
2921 &norm_utf7_p, &norm_utf7_len) == -1) {
2922 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
2923 return g_strdup(from);
2926 /* second iconv() call for flushing */
2927 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
2933 *norm_utf7_p = '\0';
2934 to_str = g_string_new(NULL);
2935 for (p = norm_utf7; p < norm_utf7_p; p++) {
2936 /* replace: '&' -> "&-",
2939 BASE64 '/' -> ',' */
2940 if (!in_escape && *p == '&') {
2941 g_string_append(to_str, "&-");
2942 } else if (!in_escape && *p == '+') {
2943 if (*(p + 1) == '-') {
2944 g_string_append_c(to_str, '+');
2947 g_string_append_c(to_str, '&');
2950 } else if (in_escape && *p == '/') {
2951 g_string_append_c(to_str, ',');
2952 } else if (in_escape && *p == '-') {
2953 g_string_append_c(to_str, '-');
2956 g_string_append_c(to_str, *p);
2962 g_string_append_c(to_str, '-');
2966 g_string_free(to_str, FALSE);
2971 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
2973 FolderItem *item = node->data;
2974 gchar **paths = data;
2975 const gchar *oldpath = paths[0];
2976 const gchar *newpath = paths[1];
2978 gchar *new_itempath;
2981 oldpathlen = strlen(oldpath);
2982 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
2983 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
2987 base = item->path + oldpathlen;
2988 while (*base == G_DIR_SEPARATOR) base++;
2990 new_itempath = g_strdup(newpath);
2992 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
2995 item->path = new_itempath;
3000 typedef struct _get_list_uid_data {
3002 IMAPSession *session;
3003 IMAPFolderItem *item;
3004 GSList **msgnum_list;
3006 } get_list_uid_data;
3008 static void *get_list_of_uids_thread(void *data)
3010 get_list_uid_data *stuff = (get_list_uid_data *)data;
3011 Folder *folder = stuff->folder;
3012 IMAPFolderItem *item = stuff->item;
3013 GSList **msgnum_list = stuff->msgnum_list;
3014 gint ok, nummsgs = 0, lastuid_old;
3015 IMAPSession *session;
3016 GSList *uidlist, *elem;
3017 struct mailimap_set * set;
3018 clist * lep_uidlist;
3021 session = stuff->session;
3022 if (session == NULL) {
3024 return GINT_TO_POINTER(-1);
3026 /* no session locking here, it's already locked by caller */
3027 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3028 NULL, NULL, NULL, NULL, TRUE);
3029 if (ok != IMAP_SUCCESS) {
3031 return GINT_TO_POINTER(-1);
3036 set = mailimap_set_new_interval(item->lastuid + 1, 0);
3038 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SIMPLE, set,
3040 mailimap_set_free(set);
3042 if (r == MAILIMAP_NO_ERROR) {
3043 GSList * fetchuid_list;
3046 imap_uid_list_from_lep(lep_uidlist);
3047 mailimap_search_result_free(lep_uidlist);
3049 uidlist = g_slist_concat(fetchuid_list, uidlist);
3052 GSList * fetchuid_list;
3053 carray * lep_uidtab;
3055 r = imap_threaded_fetch_uid(folder, item->lastuid + 1,
3057 if (r == MAILIMAP_NO_ERROR) {
3059 imap_uid_list_from_lep_tab(lep_uidtab);
3060 imap_fetch_uid_list_free(lep_uidtab);
3061 uidlist = g_slist_concat(fetchuid_list, uidlist);
3065 lastuid_old = item->lastuid;
3066 *msgnum_list = g_slist_copy(item->uid_list);
3067 nummsgs = g_slist_length(*msgnum_list);
3068 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3070 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3073 msgnum = GPOINTER_TO_INT(elem->data);
3074 if (msgnum > lastuid_old) {
3075 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3076 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3079 if(msgnum > item->lastuid)
3080 item->lastuid = msgnum;
3083 g_slist_free(uidlist);
3085 return GINT_TO_POINTER(nummsgs);
3088 static gint get_list_of_uids(IMAPSession *session, Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3091 get_list_uid_data *data = g_new0(get_list_uid_data, 1);
3093 data->folder = folder;
3095 data->msgnum_list = msgnum_list;
3096 data->session = session;
3097 if (prefs_common.work_offline &&
3098 !inc_offline_should_override(
3099 _("Sylpheed-Claws needs network access in order "
3100 "to access the IMAP server."))) {
3105 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
3111 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3113 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3114 IMAPSession *session;
3115 gint ok, nummsgs = 0, exists, uid_val, uid_next = 0;
3116 GSList *uidlist = NULL;
3118 gboolean selected_folder;
3120 debug_print("get_num_list\n");
3122 g_return_val_if_fail(folder != NULL, -1);
3123 g_return_val_if_fail(item != NULL, -1);
3124 g_return_val_if_fail(item->item.path != NULL, -1);
3125 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3126 g_return_val_if_fail(folder->account != NULL, -1);
3128 session = imap_session_get(folder);
3129 g_return_val_if_fail(session != NULL, -1);
3132 if (FOLDER_ITEM(item)->path)
3133 statusbar_print_all(_("Scanning folder %s%c%s ..."),
3134 FOLDER_ITEM(item)->folder->name,
3136 FOLDER_ITEM(item)->path);
3138 statusbar_print_all(_("Scanning folder %s ..."),
3139 FOLDER_ITEM(item)->folder->name);
3141 selected_folder = (session->mbox != NULL) &&
3142 (!strcmp(session->mbox, item->item.path));
3143 if (selected_folder && time(NULL) - item->use_cache < 2) {
3144 ok = imap_cmd_noop(session);
3145 if (ok != IMAP_SUCCESS) {
3146 debug_print("disconnected!\n");
3147 session = imap_reconnect_if_possible(folder, session);
3148 if (session == NULL) {
3149 statusbar_pop_all();
3154 exists = session->exists;
3156 uid_next = item->c_uid_next;
3157 uid_val = item->c_uid_validity;
3158 *old_uids_valid = TRUE;
3160 if (item->use_cache && time(NULL) - item->use_cache < 2) {
3161 exists = item->c_messages;
3162 uid_next = item->c_uid_next;
3163 uid_val = item->c_uid_validity;
3165 debug_print("using cache %d %d %d\n", exists, uid_next, uid_val);
3167 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, item,
3168 &exists, &uid_next, &uid_val, NULL, FALSE);
3170 item->item.last_num = uid_next - 1;
3172 item->use_cache = (time_t)0;
3173 if (ok != IMAP_SUCCESS) {
3174 statusbar_pop_all();
3178 if(item->item.mtime == uid_val)
3179 *old_uids_valid = TRUE;
3181 *old_uids_valid = FALSE;
3183 debug_print("Freeing imap uid cache\n");
3185 g_slist_free(item->uid_list);
3186 item->uid_list = NULL;
3188 item->item.mtime = uid_val;
3190 imap_delete_all_cached_messages((FolderItem *)item);
3194 /* If old uid_next matches new uid_next we can be sure no message
3195 was added to the folder */
3196 debug_print("uid_next is %d and item->uid_next %d \n",
3197 uid_next, item->uid_next);
3198 if (uid_next == item->uid_next) {
3199 nummsgs = g_slist_length(item->uid_list);
3201 /* If number of messages is still the same we
3202 know our caches message numbers are still valid,
3203 otherwise if the number of messages has decrease
3204 we discard our cache to start a new scan to find
3205 out which numbers have been removed */
3206 if (exists == nummsgs) {
3207 debug_print("exists == nummsgs\n");
3208 *msgnum_list = g_slist_copy(item->uid_list);
3209 statusbar_pop_all();
3212 } else if (exists < nummsgs) {
3213 debug_print("Freeing imap uid cache");
3215 g_slist_free(item->uid_list);
3216 item->uid_list = NULL;
3221 *msgnum_list = NULL;
3222 statusbar_pop_all();
3227 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3230 statusbar_pop_all();
3235 if (nummsgs != exists) {
3236 /* Cache contains more messages then folder, we have cached
3237 an old UID of a message that was removed and new messages
3238 have been added too, otherwise the uid_next check would
3240 debug_print("Freeing imap uid cache");
3242 g_slist_free(item->uid_list);
3243 item->uid_list = NULL;
3245 g_slist_free(*msgnum_list);
3247 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3250 *msgnum_list = uidlist;
3252 dir = folder_item_get_path((FolderItem *)item);
3253 debug_print("removing old messages from %s\n", dir);
3254 remove_numbered_files_not_in_list(dir, *msgnum_list);
3257 item->uid_next = uid_next;
3259 debug_print("get_num_list - ok - %i\n", nummsgs);
3260 statusbar_pop_all();
3265 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3270 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3271 flags.tmp_flags = 0;
3273 g_return_val_if_fail(item != NULL, NULL);
3274 g_return_val_if_fail(file != NULL, NULL);
3276 if (folder_has_parent_of_type(item, F_QUEUE)) {
3277 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3278 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
3279 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3282 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3283 if (!msginfo) return NULL;
3285 msginfo->plaintext_file = g_strdup(file);
3286 msginfo->folder = item;
3291 GSList *imap_get_msginfos(Folder *folder, FolderItem *item,
3292 GSList *msgnum_list)
3294 IMAPSession *session;
3295 MsgInfoList *ret = NULL;
3298 debug_print("get_msginfos\n");
3300 g_return_val_if_fail(folder != NULL, NULL);
3301 g_return_val_if_fail(item != NULL, NULL);
3302 g_return_val_if_fail(msgnum_list != NULL, NULL);
3304 session = imap_session_get(folder);
3305 g_return_val_if_fail(session != NULL, NULL);
3307 debug_print("IMAP getting msginfos\n");
3308 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3309 NULL, NULL, NULL, NULL, FALSE);
3310 if (ok != IMAP_SUCCESS) {
3314 if (!(folder_has_parent_of_type(item, F_DRAFT) ||
3315 folder_has_parent_of_type(item, F_QUEUE))) {
3316 ret = g_slist_concat(ret,
3317 imap_get_uncached_messages(session, item,
3320 MsgNumberList *sorted_list, *elem;
3321 gint startnum, lastnum;
3323 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3325 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3327 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3331 num = GPOINTER_TO_INT(elem->data);
3333 if (num > lastnum + 1 || elem == NULL) {
3335 for (i = startnum; i <= lastnum; ++i) {
3338 file = imap_fetch_msg(folder, item, i);
3340 MsgInfo *msginfo = imap_parse_msg(file, item);
3341 if (msginfo != NULL) {
3342 msginfo->msgnum = i;
3343 ret = g_slist_append(ret, msginfo);
3347 session_set_access_time(SESSION(session));
3358 g_slist_free(sorted_list);
3364 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3366 MsgInfo *msginfo = NULL;
3367 MsgInfoList *msginfolist;
3368 MsgNumberList numlist;
3370 numlist.next = NULL;
3371 numlist.data = GINT_TO_POINTER(uid);
3373 msginfolist = imap_get_msginfos(folder, item, &numlist);
3374 if (msginfolist != NULL) {
3375 msginfo = msginfolist->data;
3376 g_slist_free(msginfolist);
3382 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3384 IMAPSession *session;
3385 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3386 gint ok, exists = 0, unseen = 0;
3387 guint32 uid_next, uid_val;
3388 gboolean selected_folder;
3390 g_return_val_if_fail(folder != NULL, FALSE);
3391 g_return_val_if_fail(item != NULL, FALSE);
3392 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3393 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3395 if (item->item.path == NULL)
3398 session = imap_session_get(folder);
3399 g_return_val_if_fail(session != NULL, FALSE);
3401 selected_folder = (session->mbox != NULL) &&
3402 (!strcmp(session->mbox, item->item.path));
3403 if (selected_folder && time(NULL) - item->use_cache < 2) {
3404 ok = imap_cmd_noop(session);
3405 if (ok != IMAP_SUCCESS) {
3406 debug_print("disconnected!\n");
3407 session = imap_reconnect_if_possible(folder, session);
3408 if (session == NULL)
3413 if (session->folder_content_changed
3414 || session->exists != item->item.total_msgs) {
3419 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, IMAP_FOLDER_ITEM(item),
3420 &exists, &uid_next, &uid_val, &unseen, FALSE);
3421 if (ok != IMAP_SUCCESS) {
3426 item->use_cache = time(NULL);
3427 item->c_messages = exists;
3428 item->c_uid_next = uid_next;
3429 item->c_uid_validity = uid_val;
3430 item->c_unseen = unseen;
3431 item->item.last_num = uid_next - 1;
3432 debug_print("uidnext %d, item->uid_next %d, exists %d, item->item.total_msgs %d\n",
3433 uid_next, item->uid_next, exists, item->item.total_msgs);
3434 if ((uid_next != item->uid_next) || (exists != item->item.total_msgs)
3435 || unseen != item->item.unread_msgs || uid_val != item->item.mtime) {
3444 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3446 IMAPSession *session;
3447 IMAPFlags flags_set = 0, flags_unset = 0;
3448 gint ok = IMAP_SUCCESS;
3449 MsgNumberList numlist;
3450 hashtable_data *ht_data = NULL;
3452 g_return_if_fail(folder != NULL);
3453 g_return_if_fail(folder->klass == &imap_class);
3454 g_return_if_fail(item != NULL);
3455 g_return_if_fail(item->folder == folder);
3456 g_return_if_fail(msginfo != NULL);
3457 g_return_if_fail(msginfo->folder == item);
3459 session = imap_session_get(folder);
3464 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3465 flags_set |= IMAP_FLAG_FLAGGED;
3466 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3467 flags_unset |= IMAP_FLAG_FLAGGED;
3469 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3470 flags_unset |= IMAP_FLAG_SEEN;
3471 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3472 flags_set |= IMAP_FLAG_SEEN;
3474 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3475 flags_set |= IMAP_FLAG_ANSWERED;
3476 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3477 flags_unset |= IMAP_FLAG_ANSWERED;
3479 if (!MSG_IS_DELETED(msginfo->flags) && (newflags & MSG_DELETED))
3480 flags_set |= IMAP_FLAG_DELETED;
3481 if ( MSG_IS_DELETED(msginfo->flags) && !(newflags & MSG_DELETED))
3482 flags_unset |= IMAP_FLAG_DELETED;
3484 if (!flags_set && !flags_unset) {
3485 /* the changed flags were not translatable to IMAP-speak.
3486 * like MSG_POSTFILTERED, so just apply. */
3487 msginfo->flags.perm_flags = newflags;
3492 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3493 NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS) {
3497 numlist.next = NULL;
3498 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3500 if (IMAP_FOLDER_ITEM(item)->batching) {
3501 /* instead of performing an UID STORE command for each message change,
3502 * as a lot of them can change "together", we just fill in hashtables
3503 * and defer the treatment so that we're able to send only one
3506 debug_print("IMAP batch mode on, deferring flags change\n");
3508 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_set_table,
3509 GINT_TO_POINTER(flags_set));
3510 if (ht_data == NULL) {
3511 ht_data = g_new0(hashtable_data, 1);
3512 ht_data->session = session;
3513 ht_data->item = IMAP_FOLDER_ITEM(item);
3514 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_set_table,
3515 GINT_TO_POINTER(flags_set), ht_data);
3517 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3518 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
3521 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3522 GINT_TO_POINTER(flags_unset));
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_unset_table,
3528 GINT_TO_POINTER(flags_unset), 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,
3532 GINT_TO_POINTER(msginfo->msgnum));
3535 debug_print("IMAP changing flags\n");
3537 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3538 if (ok != IMAP_SUCCESS) {
3545 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3546 if (ok != IMAP_SUCCESS) {
3552 msginfo->flags.perm_flags = newflags;
3557 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
3560 IMAPSession *session;
3562 MsgNumberList numlist;
3564 g_return_val_if_fail(folder != NULL, -1);
3565 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3566 g_return_val_if_fail(item != NULL, -1);
3568 session = imap_session_get(folder);
3569 if (!session) return -1;
3571 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3572 NULL, NULL, NULL, NULL, FALSE);
3573 if (ok != IMAP_SUCCESS) {
3577 numlist.next = NULL;
3578 numlist.data = GINT_TO_POINTER(uid);
3580 ok = imap_set_message_flags
3581 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
3582 &numlist, IMAP_FLAG_DELETED, TRUE);
3583 if (ok != IMAP_SUCCESS) {
3584 log_warning(_("can't set deleted flags: %d\n"), uid);
3589 if (!session->uidplus) {
3590 ok = imap_cmd_expunge(session);
3594 uidstr = g_strdup_printf("%u", uid);
3595 ok = imap_cmd_expunge(session);
3598 if (ok != IMAP_SUCCESS) {
3599 log_warning(_("can't expunge\n"));
3604 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
3605 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
3606 dir = folder_item_get_path(item);
3607 if (is_dir_exist(dir))
3608 remove_numbered_files(dir, uid, uid);
3611 return IMAP_SUCCESS;
3614 static gint compare_msginfo(gconstpointer a, gconstpointer b)
3616 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
3619 static guint gslist_find_next_num(MsgNumberList **list, guint num)
3623 g_return_val_if_fail(list != NULL, -1);
3625 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
3626 if (GPOINTER_TO_INT(elem->data) >= num)
3629 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
3633 * NEW and DELETED flags are not syncronized
3634 * - The NEW/RECENT flags in IMAP folders can not really be directly
3635 * modified by Sylpheed
3636 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
3637 * meaning, in IMAP it always removes the messages from the FolderItem
3638 * in Sylpheed it can mean to move the message to trash
3641 typedef struct _get_flags_data {
3644 MsgInfoList *msginfo_list;
3645 GRelation *msgflags;
3646 gboolean full_search;
3650 static /*gint*/ void *imap_get_flags_thread(void *data)
3652 get_flags_data *stuff = (get_flags_data *)data;
3653 Folder *folder = stuff->folder;
3654 FolderItem *item = stuff->item;
3655 MsgInfoList *msginfo_list = stuff->msginfo_list;
3656 GRelation *msgflags = stuff->msgflags;
3657 gboolean full_search = stuff->full_search;
3658 IMAPSession *session;
3659 GSList *sorted_list = NULL;
3660 GSList *unseen = NULL, *answered = NULL, *flagged = NULL, *deleted = NULL;
3661 GSList *p_unseen, *p_answered, *p_flagged, *p_deleted;
3663 GSList *seq_list, *cur;
3664 gboolean reverse_seen = FALSE;
3667 gint exists_cnt, unseen_cnt;
3668 gboolean selected_folder;
3670 if (folder == NULL || item == NULL) {
3672 return GINT_TO_POINTER(-1);
3675 session = imap_session_get(folder);
3676 if (session == NULL) {
3678 return GINT_TO_POINTER(-1);
3681 selected_folder = (session->mbox != NULL) &&
3682 (!strcmp(session->mbox, item->path));
3684 if (!selected_folder) {
3685 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3686 &exists_cnt, NULL, &unseen_cnt, NULL, TRUE);
3687 if (ok != IMAP_SUCCESS) {
3690 return GINT_TO_POINTER(-1);
3693 if (unseen_cnt > exists_cnt / 2)
3694 reverse_seen = TRUE;
3697 if (item->unread_msgs > item->total_msgs / 2)
3698 reverse_seen = TRUE;
3701 cmd_buf = g_string_new(NULL);
3703 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
3705 seq_list = imap_get_lep_set_from_msglist(msginfo_list);
3707 struct mailimap_set * set;
3708 set = mailimap_set_new_interval(1, 0);
3709 seq_list = g_slist_append(NULL, set);
3712 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
3713 struct mailimap_set * imapset;
3714 clist * lep_uidlist;
3717 imapset = cur->data;
3719 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SEEN,
3720 imapset, &lep_uidlist);
3723 r = imap_threaded_search(folder,
3724 IMAP_SEARCH_TYPE_UNSEEN,
3725 imapset, &lep_uidlist);
3727 if (r == MAILIMAP_NO_ERROR) {
3730 uidlist = imap_uid_list_from_lep(lep_uidlist);
3731 mailimap_search_result_free(lep_uidlist);
3733 unseen = g_slist_concat(unseen, uidlist);
3736 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_FLAGGED,
3737 imapset, &lep_uidlist);
3738 if (r == MAILIMAP_NO_ERROR) {
3741 uidlist = imap_uid_list_from_lep(lep_uidlist);
3742 mailimap_search_result_free(lep_uidlist);
3744 flagged = g_slist_concat(flagged, uidlist);
3747 if (item->opened || item->processing_pending || item == folder->inbox) {
3748 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_ANSWERED,
3749 imapset, &lep_uidlist);
3750 if (r == MAILIMAP_NO_ERROR) {
3753 uidlist = imap_uid_list_from_lep(lep_uidlist);
3754 mailimap_search_result_free(lep_uidlist);
3756 answered = g_slist_concat(answered, uidlist);
3759 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_DELETED,
3760 imapset, &lep_uidlist);
3761 if (r == MAILIMAP_NO_ERROR) {
3764 uidlist = imap_uid_list_from_lep(lep_uidlist);
3765 mailimap_search_result_free(lep_uidlist);
3767 deleted = g_slist_concat(deleted, uidlist);
3773 p_answered = answered;
3774 p_flagged = flagged;
3775 p_deleted = deleted;
3777 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
3782 msginfo = (MsgInfo *) elem->data;
3783 flags = msginfo->flags.perm_flags;
3784 wasnew = (flags & MSG_NEW);
3785 if (item->opened || item->processing_pending || item == folder->inbox) {
3786 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
3788 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW | MSG_MARKED));
3791 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3792 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
3793 if (!reverse_seen) {
3794 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3796 flags &= ~(MSG_UNREAD | MSG_NEW);
3800 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
3801 flags |= MSG_MARKED;
3803 flags &= ~MSG_MARKED;
3805 if (item->opened || item->processing_pending || item == folder->inbox) {
3806 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
3807 flags |= MSG_REPLIED;
3809 flags &= ~MSG_REPLIED;
3810 if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
3811 flags |= MSG_DELETED;
3813 flags &= ~MSG_DELETED;
3815 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
3818 imap_lep_set_free(seq_list);
3819 g_slist_free(flagged);
3820 g_slist_free(deleted);
3821 g_slist_free(answered);
3822 g_slist_free(unseen);
3823 g_slist_free(sorted_list);
3824 g_string_free(cmd_buf, TRUE);
3828 return GINT_TO_POINTER(0);
3831 static gint imap_get_flags(Folder *folder, FolderItem *item,
3832 MsgInfoList *msginfo_list, GRelation *msgflags)
3835 get_flags_data *data = g_new0(get_flags_data, 1);
3837 data->folder = folder;
3839 data->msginfo_list = msginfo_list;
3840 data->msgflags = msgflags;
3841 data->full_search = FALSE;
3843 GSList *tmp = NULL, *cur;
3845 if (prefs_common.work_offline &&
3846 !inc_offline_should_override(
3847 _("Sylpheed-Claws needs network access in order "
3848 "to access the IMAP server."))) {
3853 tmp = folder_item_get_msg_list(item);
3855 if (g_slist_length(tmp) <= g_slist_length(msginfo_list))
3856 data->full_search = TRUE;
3858 for (cur = tmp; cur; cur = cur->next)
3859 procmsg_msginfo_free((MsgInfo *)cur->data);
3863 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
3870 static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
3872 gboolean flags_set = GPOINTER_TO_INT(user_data);
3873 gint flags_value = GPOINTER_TO_INT(key);
3874 hashtable_data *data = (hashtable_data *)value;
3875 IMAPFolderItem *_item = data->item;
3876 FolderItem *item = (FolderItem *)_item;
3877 gint ok = IMAP_ERROR;
3878 IMAPSession *session = imap_session_get(item->folder);
3880 data->msglist = g_slist_reverse(data->msglist);
3882 debug_print("IMAP %ssetting flags to %d for %d messages\n",
3885 g_slist_length(data->msglist));
3889 ok = imap_select(session, IMAP_FOLDER(item->folder), item->path,
3890 NULL, NULL, NULL, NULL, FALSE);
3892 if (ok == IMAP_SUCCESS) {
3893 imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
3895 g_warning("can't select mailbox %s\n", item->path);
3899 g_slist_free(data->msglist);
3904 static void process_hashtable(IMAPFolderItem *item)
3906 if (item->flags_set_table) {
3907 g_hash_table_foreach_remove(item->flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
3908 g_hash_table_destroy(item->flags_set_table);
3909 item->flags_set_table = NULL;
3911 if (item->flags_unset_table) {
3912 g_hash_table_foreach_remove(item->flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
3913 g_hash_table_destroy(item->flags_unset_table);
3914 item->flags_unset_table = NULL;
3918 static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
3920 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3922 g_return_if_fail(item != NULL);
3924 if (item->batching == batch)
3928 item->batching = TRUE;
3929 debug_print("IMAP switching to batch mode\n");
3930 if (!item->flags_set_table) {
3931 item->flags_set_table = g_hash_table_new(NULL, g_direct_equal);
3933 if (!item->flags_unset_table) {
3934 item->flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
3937 debug_print("IMAP switching away from batch mode\n");
3939 process_hashtable(item);
3940 item->batching = FALSE;
3946 /* data types conversion libetpan <-> sylpheed */
3950 #define ETPAN_IMAP_MB_MARKED 1
3951 #define ETPAN_IMAP_MB_UNMARKED 2
3952 #define ETPAN_IMAP_MB_NOSELECT 4
3953 #define ETPAN_IMAP_MB_NOINFERIORS 8
3955 static int imap_flags_to_flags(struct mailimap_mbx_list_flags * imap_flags)
3961 if (imap_flags->mbf_type == MAILIMAP_MBX_LIST_FLAGS_SFLAG) {
3962 switch (imap_flags->mbf_sflag) {
3963 case MAILIMAP_MBX_LIST_SFLAG_MARKED:
3964 flags |= ETPAN_IMAP_MB_MARKED;
3966 case MAILIMAP_MBX_LIST_SFLAG_NOSELECT:
3967 flags |= ETPAN_IMAP_MB_NOSELECT;
3969 case MAILIMAP_MBX_LIST_SFLAG_UNMARKED:
3970 flags |= ETPAN_IMAP_MB_UNMARKED;
3975 for(cur = clist_begin(imap_flags->mbf_oflags) ; cur != NULL ;
3976 cur = clist_next(cur)) {
3977 struct mailimap_mbx_list_oflag * oflag;
3979 oflag = clist_content(cur);
3981 switch (oflag->of_type) {
3982 case MAILIMAP_MBX_LIST_OFLAG_NOINFERIORS:
3983 flags |= ETPAN_IMAP_MB_NOINFERIORS;
3991 static GSList * imap_list_from_lep(IMAPFolder * folder,
3992 clist * list, const gchar * real_path, gboolean all)
3999 for(iter = clist_begin(list) ; iter != NULL ;
4000 iter = clist_next(iter)) {
4001 struct mailimap_mailbox_list * mb;
4009 FolderItem *new_item;
4011 mb = clist_content(iter);
4017 if (mb->mb_flag != NULL)
4018 flags = imap_flags_to_flags(mb->mb_flag);
4020 delimiter = mb->mb_delimiter;
4023 dup_name = strdup(name);
4024 if (delimiter != '\0')
4025 subst_char(dup_name, delimiter, '/');
4027 base = g_path_get_basename(dup_name);
4028 if (base[0] == '.') {
4034 if (!all && strcmp(dup_name, real_path) == 0) {
4040 if (!all && dup_name[strlen(dup_name)-1] == '/') {
4046 loc_name = imap_modified_utf7_to_utf8(base);
4047 loc_path = imap_modified_utf7_to_utf8(dup_name);
4049 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
4050 if ((flags & ETPAN_IMAP_MB_NOINFERIORS) != 0)
4051 new_item->no_sub = TRUE;
4052 if (strcmp(dup_name, "INBOX") != 0 &&
4053 ((flags & ETPAN_IMAP_MB_NOSELECT) != 0))
4054 new_item->no_select = TRUE;
4056 item_list = g_slist_append(item_list, new_item);
4058 debug_print("folder '%s' found.\n", loc_path);
4069 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist)
4071 GSList *sorted_list, *cur;
4072 guint first, last, next;
4073 GSList *ret_list = NULL;
4075 struct mailimap_set * current_set;
4076 unsigned int item_count;
4078 if (numlist == NULL)
4082 current_set = mailimap_set_new_empty();
4084 sorted_list = g_slist_copy(numlist);
4085 sorted_list = g_slist_sort(sorted_list, g_int_compare);
4087 first = GPOINTER_TO_INT(sorted_list->data);
4090 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
4091 if (GPOINTER_TO_INT(cur->data) == 0)
4096 last = GPOINTER_TO_INT(cur->data);
4098 next = GPOINTER_TO_INT(cur->next->data);
4102 if (last + 1 != next || next == 0) {
4104 struct mailimap_set_item * item;
4105 item = mailimap_set_item_new(first, last);
4106 mailimap_set_add(current_set, item);
4111 if (count >= IMAP_SET_MAX_COUNT) {
4112 ret_list = g_slist_append(ret_list,
4114 current_set = mailimap_set_new_empty();
4121 if (clist_count(current_set->set_list) > 0) {
4122 ret_list = g_slist_append(ret_list,
4126 g_slist_free(sorted_list);
4131 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist)
4133 MsgNumberList *numlist = NULL;
4137 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
4138 MsgInfo *msginfo = (MsgInfo *) cur->data;
4140 numlist = g_slist_append(numlist, GINT_TO_POINTER(msginfo->msgnum));
4142 seq_list = imap_get_lep_set_from_numlist(numlist);
4143 g_slist_free(numlist);
4148 static GSList * imap_uid_list_from_lep(clist * list)
4155 for(iter = clist_begin(list) ; iter != NULL ;
4156 iter = clist_next(iter)) {
4159 puid = clist_content(iter);
4160 result = g_slist_append(result, GINT_TO_POINTER(* puid));
4166 static GSList * imap_uid_list_from_lep_tab(carray * list)
4173 for(i = 0 ; i < carray_count(list) ; i ++) {
4176 puid = carray_get(list, i);
4177 result = g_slist_append(result, GINT_TO_POINTER(* puid));
4183 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
4186 MsgInfo *msginfo = NULL;
4189 MsgFlags flags = {0, 0};
4191 if (info->headers == NULL)
4194 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
4195 if (folder_has_parent_of_type(item, F_QUEUE)) {
4196 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4197 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
4198 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4200 flags.perm_flags = info->flags;
4204 msginfo = procheader_parse_str(info->headers, flags, FALSE, FALSE);
4207 msginfo->msgnum = uid;
4208 msginfo->size = size;
4214 static void imap_lep_set_free(GSList *seq_list)
4218 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
4219 struct mailimap_set * imapset;
4221 imapset = cur->data;
4222 mailimap_set_free(imapset);
4224 g_slist_free(seq_list);
4227 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags)
4229 struct mailimap_flag_list * flag_list;
4231 flag_list = mailimap_flag_list_new_empty();
4233 if (IMAP_IS_SEEN(flags))
4234 mailimap_flag_list_add(flag_list,
4235 mailimap_flag_new_seen());
4236 if (IMAP_IS_ANSWERED(flags))
4237 mailimap_flag_list_add(flag_list,
4238 mailimap_flag_new_answered());
4239 if (IMAP_IS_FLAGGED(flags))
4240 mailimap_flag_list_add(flag_list,
4241 mailimap_flag_new_flagged());
4242 if (IMAP_IS_DELETED(flags))
4243 mailimap_flag_list_add(flag_list,
4244 mailimap_flag_new_deleted());
4245 if (IMAP_IS_DRAFT(flags))
4246 mailimap_flag_list_add(flag_list,
4247 mailimap_flag_new_draft());
4252 guint imap_folder_get_refcnt(Folder *folder)
4254 return ((IMAPFolder *)folder)->refcnt;
4257 void imap_folder_ref(Folder *folder)
4259 ((IMAPFolder *)folder)->refcnt++;
4262 void imap_folder_unref(Folder *folder)
4264 if (((IMAPFolder *)folder)->refcnt > 0)
4265 ((IMAPFolder *)folder)->refcnt--;
4268 #else /* HAVE_LIBETPAN */
4270 static FolderClass imap_class;
4272 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
4273 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
4275 static Folder *imap_folder_new (const gchar *name,
4280 static gint imap_create_tree (Folder *folder)
4284 static FolderItem *imap_create_folder (Folder *folder,
4290 static gint imap_rename_folder (Folder *folder,
4297 gchar imap_get_path_separator_for_item(FolderItem *item)
4302 FolderClass *imap_get_class(void)
4304 if (imap_class.idstr == NULL) {
4305 imap_class.type = F_IMAP;
4306 imap_class.idstr = "imap";
4307 imap_class.uistr = "IMAP4";
4309 imap_class.new_folder = imap_folder_new;
4310 imap_class.create_tree = imap_create_tree;
4311 imap_class.create_folder = imap_create_folder;
4312 imap_class.rename_folder = imap_rename_folder;
4314 imap_class.set_xml = folder_set_xml;
4315 imap_class.get_xml = folder_get_xml;
4316 imap_class.item_set_xml = imap_item_set_xml;
4317 imap_class.item_get_xml = imap_item_get_xml;
4318 /* nothing implemented */
4325 void imap_synchronise(FolderItem *item)
4327 imap_gtk_synchronise(item);
4330 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
4332 #ifdef HAVE_LIBETPAN
4335 folder_item_set_xml(folder, item, tag);
4337 #ifdef HAVE_LIBETPAN
4338 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
4339 XMLAttr *attr = (XMLAttr *) cur->data;
4341 if (!attr || !attr->name || !attr->value) continue;
4342 if (!strcmp(attr->name, "uidnext"))
4343 IMAP_FOLDER_ITEM(item)->uid_next = atoi(attr->value);
4348 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item)
4352 tag = folder_item_get_xml(folder, item);
4354 #ifdef HAVE_LIBETPAN
4355 xml_tag_add_attr(tag, xml_attr_new_int("uidnext",
4356 IMAP_FOLDER_ITEM(item)->uid_next));