2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 #include <glib/gi18n.h>
56 #include "procheader.h"
57 #include "prefs_account.h"
62 #include "prefs_common.h"
63 #include "inputdialog.h"
65 #include "remotefolder.h"
66 #include "alertpanel.h"
68 #include "statusbar.h"
70 #include "imap-thread.h"
73 typedef struct _IMAPFolder IMAPFolder;
74 typedef struct _IMAPSession IMAPSession;
75 typedef struct _IMAPNameSpace IMAPNameSpace;
76 typedef struct _IMAPFolderItem IMAPFolderItem;
78 #include "prefs_account.h"
80 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
81 #define IMAP_FOLDER_ITEM(obj) ((IMAPFolderItem *)obj)
82 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
88 /* list of IMAPNameSpace */
92 gchar last_seen_separator;
100 gboolean authenticated;
109 gboolean folder_content_changed;
115 struct _IMAPNameSpace
121 #define IMAP_SUCCESS 0
122 #define IMAP_SOCKET 2
123 #define IMAP_AUTHFAIL 3
124 #define IMAP_PROTOCOL 4
125 #define IMAP_SYNTAX 5
129 #define IMAPBUFSIZE 8192
133 IMAP_FLAG_SEEN = 1 << 0,
134 IMAP_FLAG_ANSWERED = 1 << 1,
135 IMAP_FLAG_FLAGGED = 1 << 2,
136 IMAP_FLAG_DELETED = 1 << 3,
137 IMAP_FLAG_DRAFT = 1 << 4
140 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
141 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
142 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
143 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
144 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
147 #define IMAP4_PORT 143
149 #define IMAPS_PORT 993
152 #define IMAP_CMD_LIMIT 1000
154 struct _IMAPFolderItem
166 guint32 c_uid_validity;
169 GHashTable *flags_set_table;
170 GHashTable *flags_unset_table;
173 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
174 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
176 static void imap_folder_init (Folder *folder,
180 static Folder *imap_folder_new (const gchar *name,
182 static void imap_folder_destroy (Folder *folder);
184 static IMAPSession *imap_session_new (Folder *folder,
185 const PrefsAccount *account);
186 static void imap_session_authenticate(IMAPSession *session,
187 const PrefsAccount *account);
188 static void imap_session_destroy (Session *session);
190 static gchar *imap_fetch_msg (Folder *folder,
193 static gchar *imap_fetch_msg_full (Folder *folder,
198 static gint imap_add_msg (Folder *folder,
202 static gint imap_add_msgs (Folder *folder,
205 GRelation *relation);
207 static gint imap_copy_msg (Folder *folder,
210 static gint imap_copy_msgs (Folder *folder,
212 MsgInfoList *msglist,
213 GRelation *relation);
215 static gint imap_remove_msg (Folder *folder,
218 static gint imap_remove_msgs (Folder *folder,
220 MsgInfoList *msglist,
221 GRelation *relation);
222 static gint imap_remove_all_msg (Folder *folder,
225 static gboolean imap_is_msg_changed (Folder *folder,
229 static gint imap_close (Folder *folder,
232 static gint imap_scan_tree (Folder *folder);
234 static gint imap_create_tree (Folder *folder);
236 static FolderItem *imap_create_folder (Folder *folder,
239 static gint imap_rename_folder (Folder *folder,
242 static gint imap_remove_folder (Folder *folder,
245 static FolderItem *imap_folder_item_new (Folder *folder);
246 static void imap_folder_item_destroy (Folder *folder,
249 static IMAPSession *imap_session_get (Folder *folder);
251 static gint imap_auth (IMAPSession *session,
256 static gint imap_scan_tree_recursive (IMAPSession *session,
259 static void imap_create_missing_folders (Folder *folder);
260 static FolderItem *imap_create_special_folder
262 SpecialFolderItemType stype,
265 static gint imap_do_copy_msgs (Folder *folder,
267 MsgInfoList *msglist,
268 GRelation *relation);
270 static void imap_delete_all_cached_messages (FolderItem *item);
271 static void imap_set_batch (Folder *folder,
274 static gint imap_set_message_flags (IMAPSession *session,
275 MsgNumberList *numlist,
278 static gint imap_select (IMAPSession *session,
284 guint32 *uid_validity,
286 static gint imap_status (IMAPSession *session,
289 IMAPFolderItem *item,
292 guint32 *uid_validity,
296 static gchar imap_get_path_separator (IMAPSession *session,
299 static gchar *imap_get_real_path (IMAPSession *session,
302 static void imap_synchronise (FolderItem *item);
304 static void imap_free_capabilities (IMAPSession *session);
306 /* low-level IMAP4rev1 commands */
307 static gint imap_cmd_login (IMAPSession *session,
311 static gint imap_cmd_noop (IMAPSession *session);
313 static gint imap_cmd_starttls (IMAPSession *session);
315 static gint imap_cmd_select (IMAPSession *session,
320 guint32 *uid_validity,
322 static gint imap_cmd_examine (IMAPSession *session,
327 guint32 *uid_validity,
329 static gint imap_cmd_create (IMAPSession *sock,
330 const gchar *folder);
331 static gint imap_cmd_rename (IMAPSession *sock,
332 const gchar *oldfolder,
333 const gchar *newfolder);
334 static gint imap_cmd_delete (IMAPSession *session,
335 const gchar *folder);
336 static gint imap_cmd_fetch (IMAPSession *sock,
338 const gchar *filename,
341 static gint imap_cmd_append (IMAPSession *session,
342 const gchar *destfolder,
346 static gint imap_cmd_copy (IMAPSession *session,
347 struct mailimap_set * set,
348 const gchar *destfolder,
349 GRelation *uid_mapping,
350 struct mailimap_set ** source,
351 struct mailimap_set ** dest);
352 static gint imap_cmd_store (IMAPSession *session,
353 struct mailimap_set * set,
356 static gint imap_cmd_expunge (IMAPSession *session);
358 static void imap_path_separator_subst (gchar *str,
361 static gchar *imap_utf8_to_modified_utf7 (const gchar *from);
362 static gchar *imap_modified_utf7_to_utf8 (const gchar *mutf7_str);
364 static gboolean imap_rename_folder_func (GNode *node,
366 static gint imap_get_num_list (Folder *folder,
369 gboolean *old_uids_valid);
370 static GSList *imap_get_msginfos (Folder *folder,
372 GSList *msgnum_list);
373 static MsgInfo *imap_get_msginfo (Folder *folder,
376 static gboolean imap_scan_required (Folder *folder,
378 static void imap_change_flags (Folder *folder,
381 MsgPermFlags newflags);
382 static gint imap_get_flags (Folder *folder,
384 MsgInfoList *msglist,
385 GRelation *msgflags);
386 static gchar *imap_folder_get_path (Folder *folder);
387 static gchar *imap_item_get_path (Folder *folder,
389 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item);
392 /* data types conversion libetpan <-> claws */
393 static GSList * imap_list_from_lep(IMAPFolder * folder,
394 clist * list, const gchar * real_path, gboolean all);
395 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist);
396 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist);
397 static GSList * imap_uid_list_from_lep(clist * list);
398 static GSList * imap_uid_list_from_lep_tab(carray * list);
399 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
401 static void imap_lep_set_free(GSList *seq_list);
402 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags);
404 typedef struct _hashtable_data {
405 IMAPSession *session;
407 IMAPFolderItem *item;
410 static FolderClass imap_class;
412 typedef struct _thread_data {
422 FolderClass *imap_get_class(void)
424 if (imap_class.idstr == NULL) {
425 imap_class.type = F_IMAP;
426 imap_class.idstr = "imap";
427 imap_class.uistr = "IMAP4";
429 /* Folder functions */
430 imap_class.new_folder = imap_folder_new;
431 imap_class.destroy_folder = imap_folder_destroy;
432 imap_class.scan_tree = imap_scan_tree;
433 imap_class.create_tree = imap_create_tree;
435 /* FolderItem functions */
436 imap_class.item_new = imap_folder_item_new;
437 imap_class.item_destroy = imap_folder_item_destroy;
438 imap_class.item_get_path = imap_item_get_path;
439 imap_class.create_folder = imap_create_folder;
440 imap_class.rename_folder = imap_rename_folder;
441 imap_class.remove_folder = imap_remove_folder;
442 imap_class.close = imap_close;
443 imap_class.get_num_list = imap_get_num_list;
444 imap_class.scan_required = imap_scan_required;
445 imap_class.set_xml = folder_set_xml;
446 imap_class.get_xml = folder_get_xml;
447 imap_class.item_set_xml = imap_item_set_xml;
448 imap_class.item_get_xml = imap_item_get_xml;
450 /* Message functions */
451 imap_class.get_msginfo = imap_get_msginfo;
452 imap_class.get_msginfos = imap_get_msginfos;
453 imap_class.fetch_msg = imap_fetch_msg;
454 imap_class.fetch_msg_full = imap_fetch_msg_full;
455 imap_class.add_msg = imap_add_msg;
456 imap_class.add_msgs = imap_add_msgs;
457 imap_class.copy_msg = imap_copy_msg;
458 imap_class.copy_msgs = imap_copy_msgs;
459 imap_class.remove_msg = imap_remove_msg;
460 imap_class.remove_msgs = imap_remove_msgs;
461 imap_class.remove_all_msg = imap_remove_all_msg;
462 imap_class.is_msg_changed = imap_is_msg_changed;
463 imap_class.change_flags = imap_change_flags;
464 imap_class.get_flags = imap_get_flags;
465 imap_class.set_batch = imap_set_batch;
466 imap_class.synchronise = imap_synchronise;
468 pthread_mutex_init(&imap_mutex, NULL);
475 static Folder *imap_folder_new(const gchar *name, const gchar *path)
479 folder = (Folder *)g_new0(IMAPFolder, 1);
480 folder->klass = &imap_class;
481 imap_folder_init(folder, name, path);
486 static void imap_folder_destroy(Folder *folder)
488 while (imap_folder_get_refcnt(folder) > 0)
489 gtk_main_iteration();
491 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
495 static void imap_folder_init(Folder *folder, const gchar *name,
498 folder_remote_folder_init((Folder *)folder, name, path);
501 static FolderItem *imap_folder_item_new(Folder *folder)
503 IMAPFolderItem *item;
505 item = g_new0(IMAPFolderItem, 1);
508 item->uid_list = NULL;
510 return (FolderItem *)item;
513 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
515 IMAPFolderItem *item = (IMAPFolderItem *)_item;
517 g_return_if_fail(item != NULL);
518 g_slist_free(item->uid_list);
523 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
525 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
528 g_slist_free(item->uid_list);
529 item->uid_list = NULL;
534 static void imap_reset_uid_lists(Folder *folder)
536 if(folder->node == NULL)
539 /* Destroy all uid lists and rest last uid */
540 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
543 int imap_get_capabilities(IMAPSession *session)
545 struct mailimap_capability_data *capabilities = NULL;
549 if (session->capability != NULL)
550 return MAILIMAP_NO_ERROR;
552 capabilities = imap_threaded_capability(session->folder, &result);
554 if (result != MAILIMAP_NO_ERROR) {
555 return MAILIMAP_ERROR_CAPABILITY;
558 if (capabilities == NULL) {
559 return MAILIMAP_NO_ERROR;
562 for(cur = clist_begin(capabilities->cap_list) ; cur != NULL ;
563 cur = clist_next(cur)) {
564 struct mailimap_capability * cap =
566 if (!cap || cap->cap_data.cap_name == NULL)
568 session->capability = g_slist_append
569 (session->capability,
570 g_strdup(cap->cap_data.cap_name));
571 debug_print("got capa %s\n", cap->cap_data.cap_name);
573 mailimap_capability_data_free(capabilities);
574 return MAILIMAP_NO_ERROR;
577 gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
580 for (cur = session->capability; cur; cur = cur->next) {
581 if (!g_ascii_strcasecmp(cur->data, cap))
587 static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
590 gint ok = IMAP_ERROR;
591 static time_t last_login_err = 0;
592 gchar *ext_info = "";
594 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR)
599 ok = imap_cmd_login(session, user, pass, "ANONYMOUS");
601 case IMAP_AUTH_CRAM_MD5:
602 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
604 case IMAP_AUTH_LOGIN:
605 ok = imap_cmd_login(session, user, pass, "LOGIN");
607 case IMAP_AUTH_GSSAPI:
608 ok = imap_cmd_login(session, user, pass, "GSSAPI");
611 debug_print("capabilities:\n"
616 imap_has_capability(session, "ANONYMOUS"),
617 imap_has_capability(session, "CRAM-MD5"),
618 imap_has_capability(session, "LOGIN"),
619 imap_has_capability(session, "GSSAPI"));
620 if (imap_has_capability(session, "CRAM-MD5"))
621 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
622 if (ok == IMAP_ERROR && imap_has_capability(session, "GSSAPI"))
623 ok = imap_cmd_login(session, user, pass, "GSSAPI");
624 if (ok == IMAP_ERROR) /* we always try LOGIN before giving up */
625 ok = imap_cmd_login(session, user, pass, "LOGIN");
628 if (ok == IMAP_SUCCESS)
629 session->authenticated = TRUE;
631 if (type == IMAP_AUTH_CRAM_MD5) {
632 ext_info = _("\n\nCRAM-MD5 logins only work if libetpan has been "
633 "compiled with SASL support and the "
634 "CRAM-MD5 SASL plugin is installed.");
637 if (time(NULL) - last_login_err > 10) {
638 if (!prefs_common.no_recv_err_panel) {
639 alertpanel_error(_("Connection to %s failed: "
641 SESSION(session)->server, ext_info);
643 log_error(_("Connection to %s failed: "
644 "login refused.%s\n"),
645 SESSION(session)->server, ext_info);
648 last_login_err = time(NULL);
653 static IMAPSession *imap_reconnect_if_possible(Folder *folder, IMAPSession *session)
655 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
656 /* Check if this is the first try to establish a
657 connection, if yes we don't try to reconnect */
658 debug_print("reconnecting\n");
659 if (rfolder->session == NULL) {
660 log_warning(_("Connecting to %s failed"),
661 folder->account->recv_server);
662 session_destroy(SESSION(session));
665 log_warning(_("IMAP4 connection to %s has been"
666 " disconnected. Reconnecting...\n"),
667 folder->account->recv_server);
668 statusbar_print_all(_("IMAP4 connection to %s has been"
669 " disconnected. Reconnecting...\n"),
670 folder->account->recv_server);
671 SESSION(session)->state = SESSION_DISCONNECTED;
672 session_destroy(SESSION(session));
673 /* Clear folders session to make imap_session_get create
674 a new session, because of rfolder->session == NULL
675 it will not try to reconnect again and so avoid an
677 rfolder->session = NULL;
678 debug_print("getting session...\n");
679 session = imap_session_get(folder);
680 rfolder->session = SESSION(session);
686 #define lock_session() {\
688 debug_print("locking session %p (%d)\n", session, session->busy); \
690 debug_print(" SESSION WAS LOCKED !! \n"); \
691 session->busy = TRUE;\
693 debug_print("can't lock null session\n"); \
697 #define unlock_session() {\
699 debug_print("unlocking session %p\n", session); \
700 session->busy = FALSE;\
702 debug_print("can't unlock null session\n"); \
706 static IMAPSession *imap_session_get(Folder *folder)
708 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
709 IMAPSession *session = NULL;
711 g_return_val_if_fail(folder != NULL, NULL);
712 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
713 g_return_val_if_fail(folder->account != NULL, NULL);
715 if (prefs_common.work_offline &&
716 !inc_offline_should_override(
717 _("Claws Mail needs network access in order "
718 "to access the IMAP server."))) {
722 /* Make sure we have a session */
723 if (rfolder->session != NULL) {
724 session = IMAP_SESSION(rfolder->session);
726 imap_reset_uid_lists(folder);
727 if (time(NULL) - rfolder->last_failure <= 2)
729 session = imap_session_new(folder, folder->account);
731 if(session == NULL) {
732 rfolder->last_failure = time(NULL);
736 /* Make sure session is authenticated */
737 if (!IMAP_SESSION(session)->authenticated)
738 imap_session_authenticate(IMAP_SESSION(session), folder->account);
740 if (!IMAP_SESSION(session)->authenticated) {
741 imap_threaded_disconnect(session->folder);
742 SESSION(session)->state = SESSION_DISCONNECTED;
743 session_destroy(SESSION(session));
744 rfolder->session = NULL;
745 rfolder->last_failure = time(NULL);
751 /* I think the point of this code is to avoid sending a
752 * keepalive if we've used the session recently and therefore
753 * think it's still alive. Unfortunately, most of the code
754 * does not yet check for errors on the socket, and so if the
755 * connection drops we don't notice until the timeout expires.
756 * A better solution than sending a NOOP every time would be
757 * for every command to be prepared to retry until it is
758 * successfully sent. -- mbp */
759 if (time(NULL) - SESSION(session)->last_access_time > SESSION_TIMEOUT_INTERVAL) {
760 /* verify that the session is still alive */
761 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
762 debug_print("disconnected!\n");
763 session = imap_reconnect_if_possible(folder, session);
767 rfolder->session = SESSION(session);
769 return IMAP_SESSION(session);
772 static IMAPSession *imap_session_new(Folder * folder,
773 const PrefsAccount *account)
775 IMAPSession *session;
778 int authenticated = FALSE;
781 /* FIXME: IMAP over SSL only... */
784 port = account->set_imapport ? account->imapport
785 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
786 ssl_type = account->ssl_imap;
788 if (account->ssl_imap != SSL_NONE) {
789 if (alertpanel_full(_("Insecure connection"),
790 _("This connection is configured to be secured "
791 "using SSL, but SSL is not available in this "
792 "build of Claws Mail. \n\n"
793 "Do you want to continue connecting to this "
794 "server? The communication would not be "
796 GTK_STOCK_CANCEL, _("Con_tinue connecting"),
797 NULL, FALSE, NULL, ALERT_WARNING,
798 G_ALERTDEFAULT) != G_ALERTALTERNATE)
801 port = account->set_imapport ? account->imapport
806 statusbar_print_all(_("Connecting to IMAP4 server: %s..."), folder->account->recv_server);
807 if (account->set_tunnelcmd) {
808 r = imap_threaded_connect_cmd(folder,
810 account->recv_server,
815 if (ssl_type == SSL_TUNNEL) {
816 r = imap_threaded_connect_ssl(folder,
817 account->recv_server,
823 r = imap_threaded_connect(folder,
824 account->recv_server,
830 if (r == MAILIMAP_NO_ERROR_AUTHENTICATED) {
831 authenticated = TRUE;
833 else if (r == MAILIMAP_NO_ERROR_NON_AUTHENTICATED) {
834 authenticated = FALSE;
837 #if (LIBETPAN_VERSION_MAJOR > 0 || LIBETPAN_VERSION_MINOR > 48)
839 if (r == MAILIMAP_ERROR_SSL)
840 log_error(_("SSL handshake failed\n"));
843 if(!prefs_common.no_recv_err_panel) {
844 alertpanel_error_log(_("Can't connect to IMAP4 server: %s:%d"),
845 account->recv_server, port);
847 log_error(_("Can't connect to IMAP4 server: %s:%d\n"),
848 account->recv_server, port);
854 session = g_new0(IMAPSession, 1);
855 session_init(SESSION(session));
856 SESSION(session)->type = SESSION_IMAP;
857 SESSION(session)->server = g_strdup(account->recv_server);
858 SESSION(session)->sock = NULL;
860 SESSION(session)->destroy = imap_session_destroy;
862 session->capability = NULL;
864 session->authenticated = authenticated;
865 session->mbox = NULL;
866 session->cmd_count = 0;
867 session->folder = folder;
868 IMAP_FOLDER(session->folder)->last_seen_separator = 0;
871 if (account->ssl_imap == SSL_STARTTLS) {
874 ok = imap_cmd_starttls(session);
875 if (ok != IMAP_SUCCESS) {
876 log_warning(_("Can't start TLS session.\n"));
877 session_destroy(SESSION(session));
881 imap_free_capabilities(session);
882 session->authenticated = FALSE;
883 session->uidplus = FALSE;
884 session->cmd_count = 1;
887 log_message("IMAP connection is %s-authenticated\n",
888 (session->authenticated) ? "pre" : "un");
893 static void imap_session_authenticate(IMAPSession *session,
894 const PrefsAccount *account)
896 gchar *pass, *acc_pass;
897 gboolean failed = FALSE;
899 g_return_if_fail(account->userid != NULL);
900 acc_pass = account->passwd;
903 if (!pass && account->imap_auth_type != IMAP_AUTH_ANON) {
905 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
908 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
910 } else if (account->imap_auth_type == IMAP_AUTH_ANON) {
913 statusbar_print_all(_("Connecting to IMAP4 server %s...\n"),
914 account->recv_server);
915 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
923 if (prefs_common.no_recv_err_panel) {
924 log_error(_("Couldn't login to IMAP server %s."), account->recv_server);
925 mainwindow_show_error();
927 alertpanel_error_log(_("Couldn't login to IMAP server %s."), account->recv_server);
934 session->authenticated = TRUE;
938 static void imap_session_destroy(Session *session)
940 if (session->state != SESSION_DISCONNECTED)
941 imap_threaded_disconnect(IMAP_SESSION(session)->folder);
943 imap_free_capabilities(IMAP_SESSION(session));
944 g_free(IMAP_SESSION(session)->mbox);
945 sock_close(session->sock);
946 session->sock = NULL;
949 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
951 return imap_fetch_msg_full(folder, item, uid, TRUE, TRUE);
954 static guint get_size_with_crs(MsgInfo *info)
963 fp = procmsg_open_message(info);
967 while (fgets(buf, sizeof (buf), fp) != NULL) {
969 if (!strstr(buf, "\r") && strstr(buf, "\n"))
977 static gchar *imap_fetch_msg_full(Folder *folder, FolderItem *item, gint uid,
978 gboolean headers, gboolean body)
980 gchar *path, *filename;
981 IMAPSession *session;
984 g_return_val_if_fail(folder != NULL, NULL);
985 g_return_val_if_fail(item != NULL, NULL);
990 path = folder_item_get_path(item);
991 if (!is_dir_exist(path))
993 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
995 debug_print("trying to fetch cached %s\n", filename);
996 if (is_file_exist(filename)) {
997 /* see whether the local file represents the whole message
998 * or not. As the IMAP server reports size with \r chars,
999 * we have to update the local file (UNIX \n only) size */
1000 MsgInfo *msginfo = imap_parse_msg(filename, item);
1001 MsgInfo *cached = msgcache_get_msg(item->cache,uid);
1002 guint have_size = get_size_with_crs(msginfo);
1005 debug_print("message %d has been already %scached (%d/%d).\n", uid,
1006 have_size >= cached->size ? "fully ":"",
1007 have_size, (int)cached->size);
1009 if (cached && (cached->size <= have_size || !body)) {
1010 procmsg_msginfo_free(cached);
1011 procmsg_msginfo_free(msginfo);
1012 file_strip_crs(filename);
1014 } else if (!cached && time(NULL) - get_file_mtime(filename) < 60) {
1015 debug_print("message not cached and file recent, considering file complete\n");
1016 procmsg_msginfo_free(msginfo);
1017 file_strip_crs(filename);
1020 procmsg_msginfo_free(cached);
1021 procmsg_msginfo_free(msginfo);
1025 debug_print("getting session...\n");
1026 session = imap_session_get(folder);
1033 debug_print("IMAP fetching messages\n");
1034 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1035 NULL, NULL, NULL, NULL, FALSE);
1036 if (ok != IMAP_SUCCESS) {
1037 g_warning("can't select mailbox %s\n", item->path);
1043 debug_print("getting message %d...\n", uid);
1044 ok = imap_cmd_fetch(session, (guint32)uid, filename, headers, body);
1046 if (ok != IMAP_SUCCESS) {
1047 g_warning("can't fetch message %d\n", uid);
1054 file_strip_crs(filename);
1058 static gint imap_add_msg(Folder *folder, FolderItem *dest,
1059 const gchar *file, MsgFlags *flags)
1063 MsgFileInfo fileinfo;
1065 g_return_val_if_fail(file != NULL, -1);
1067 fileinfo.msginfo = NULL;
1068 fileinfo.file = (gchar *)file;
1069 fileinfo.flags = flags;
1070 file_list.data = &fileinfo;
1071 file_list.next = NULL;
1073 ret = imap_add_msgs(folder, dest, &file_list, NULL);
1077 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
1078 GRelation *relation)
1081 IMAPSession *session;
1082 guint32 last_uid = 0;
1084 MsgFileInfo *fileinfo;
1086 gint curnum = 0, total = 0;
1089 g_return_val_if_fail(folder != NULL, -1);
1090 g_return_val_if_fail(dest != NULL, -1);
1091 g_return_val_if_fail(file_list != NULL, -1);
1093 debug_print("getting session...\n");
1094 session = imap_session_get(folder);
1098 destdir = imap_get_real_path(session, IMAP_FOLDER(folder), dest->path);
1100 statusbar_print_all(_("Adding messages..."));
1101 total = g_slist_length(file_list);
1102 for (cur = file_list; cur != NULL; cur = cur->next) {
1103 IMAPFlags iflags = 0;
1104 guint32 new_uid = 0;
1105 gchar *real_file = NULL;
1106 fileinfo = (MsgFileInfo *)cur->data;
1108 statusbar_progress_all(curnum, total, total < 10 ? 1:10);
1111 if (fileinfo->flags) {
1112 if (MSG_IS_MARKED(*fileinfo->flags))
1113 iflags |= IMAP_FLAG_FLAGGED;
1114 if (MSG_IS_REPLIED(*fileinfo->flags))
1115 iflags |= IMAP_FLAG_ANSWERED;
1116 if (!MSG_IS_UNREAD(*fileinfo->flags))
1117 iflags |= IMAP_FLAG_SEEN;
1120 if (real_file == NULL)
1121 real_file = g_strdup(fileinfo->file);
1123 if (folder_has_parent_of_type(dest, F_QUEUE) ||
1124 folder_has_parent_of_type(dest, F_OUTBOX) ||
1125 folder_has_parent_of_type(dest, F_DRAFT) ||
1126 folder_has_parent_of_type(dest, F_TRASH))
1127 iflags |= IMAP_FLAG_SEEN;
1129 ok = imap_cmd_append(session, destdir, real_file, iflags,
1132 if (ok != IMAP_SUCCESS) {
1133 g_warning("can't append message %s\n", real_file);
1137 statusbar_progress_all(0,0,0);
1138 statusbar_pop_all();
1141 debug_print("appended new message as %d\n", new_uid);
1142 /* put the local file in the imapcache, so that we don't
1143 * have to fetch it back later. */
1145 gchar *cache_path = folder_item_get_path(dest);
1146 if (!is_dir_exist(cache_path))
1147 make_dir_hier(cache_path);
1148 if (is_dir_exist(cache_path)) {
1149 gchar *cache_file = g_strconcat(
1150 cache_path, G_DIR_SEPARATOR_S,
1151 itos(new_uid), NULL);
1152 copy_file(real_file, cache_file, TRUE);
1153 debug_print("copied to cache: %s\n", cache_file);
1160 if (relation != NULL)
1161 g_relation_insert(relation, fileinfo->msginfo != NULL ?
1162 (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
1163 GINT_TO_POINTER(dest->last_num + 1));
1165 new_uid = dest->last_num+1;
1167 if (last_uid < new_uid) {
1173 statusbar_progress_all(0,0,0);
1174 statusbar_pop_all();
1176 imap_cmd_expunge(session);
1184 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest,
1185 MsgInfoList *msglist, GRelation *relation)
1189 GSList *seq_list, *cur;
1191 IMAPSession *session;
1192 gint ok = IMAP_SUCCESS;
1193 GRelation *uid_mapping;
1195 gboolean single = FALSE;
1197 g_return_val_if_fail(folder != NULL, -1);
1198 g_return_val_if_fail(dest != NULL, -1);
1199 g_return_val_if_fail(msglist != NULL, -1);
1201 debug_print("getting session...\n");
1202 session = imap_session_get(folder);
1208 msginfo = (MsgInfo *)msglist->data;
1209 if (msglist->next == NULL)
1211 src = msginfo->folder;
1213 g_warning("the src folder is identical to the dest.\n");
1218 if (src->folder != dest->folder) {
1219 GSList *infolist = NULL, *cur;
1221 for (cur = msglist; cur; cur = cur->next) {
1222 msginfo = (MsgInfo *)cur->data;
1223 MsgFileInfo *fileinfo = g_new0(MsgFileInfo, 1);
1224 fileinfo->file = procmsg_get_message_file(msginfo);
1225 fileinfo->flags = &(msginfo->flags);
1226 infolist = g_slist_prepend(infolist, fileinfo);
1228 infolist = g_slist_reverse(infolist);
1230 res = folder_item_add_msgs(dest, infolist, FALSE);
1231 for (cur = infolist; cur; cur = cur->next) {
1232 MsgFileInfo *info = (MsgFileInfo *)cur->data;
1236 g_slist_free(infolist);
1240 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1241 NULL, NULL, NULL, NULL, FALSE);
1242 if (ok != IMAP_SUCCESS) {
1247 destdir = imap_get_real_path(session, IMAP_FOLDER(folder), dest->path);
1248 seq_list = imap_get_lep_set_from_msglist(msglist);
1249 uid_mapping = g_relation_new(2);
1250 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1252 statusbar_print_all(_("Copying messages..."));
1253 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1254 struct mailimap_set * seq_set;
1255 struct mailimap_set * source = NULL;
1256 struct mailimap_set * dest = NULL;
1257 seq_set = cur->data;
1259 debug_print("Copying messages from %s to %s ...\n",
1260 src->path, destdir);
1262 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping,
1265 if (ok == IMAP_SUCCESS) {
1266 if (single && relation && source && dest) {
1267 clistiter *l = clist_begin(source->set_list);
1268 struct mailimap_set_item *i = (struct mailimap_set_item *)clist_content(l);
1269 int snum = i->set_first;
1271 l = clist_begin(dest->set_list);
1272 i = (struct mailimap_set_item *)clist_content(l);
1273 dnum = i->set_first;
1274 g_relation_insert(uid_mapping, GINT_TO_POINTER(snum),
1275 GINT_TO_POINTER(dnum));
1281 mailimap_set_free(source);
1283 mailimap_set_free(dest);
1285 if (ok != IMAP_SUCCESS) {
1286 g_relation_destroy(uid_mapping);
1287 imap_lep_set_free(seq_list);
1293 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1294 MsgInfo *msginfo = (MsgInfo *)cur->data;
1297 tuples = g_relation_select(uid_mapping,
1298 GINT_TO_POINTER(msginfo->msgnum),
1300 if (tuples->len > 0) {
1301 gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1302 g_relation_insert(relation, msginfo,
1303 GINT_TO_POINTER(num));
1306 debug_print("copied new message as %d\n", num);
1307 /* put the local file in the imapcache, so that we don't
1308 * have to fetch it back later. */
1310 gchar *cache_path = folder_item_get_path(msginfo->folder);
1311 gchar *real_file = g_strconcat(
1312 cache_path, G_DIR_SEPARATOR_S,
1313 itos(msginfo->msgnum), NULL);
1314 gchar *cache_file = NULL;
1316 cache_path = folder_item_get_path(dest);
1317 cache_file = g_strconcat(
1318 cache_path, G_DIR_SEPARATOR_S,
1320 if (!is_dir_exist(cache_path))
1321 make_dir_hier(cache_path);
1322 if (is_file_exist(real_file) && is_dir_exist(cache_path)) {
1323 copy_file(real_file, cache_file, TRUE);
1324 debug_print("copied to cache: %s\n", cache_file);
1331 g_relation_insert(relation, msginfo,
1332 GINT_TO_POINTER(0));
1333 g_tuples_destroy(tuples);
1335 statusbar_pop_all();
1337 g_relation_destroy(uid_mapping);
1338 imap_lep_set_free(seq_list);
1342 IMAP_FOLDER_ITEM(dest)->lastuid = 0;
1343 IMAP_FOLDER_ITEM(dest)->uid_next = 0;
1344 g_slist_free(IMAP_FOLDER_ITEM(dest)->uid_list);
1345 IMAP_FOLDER_ITEM(dest)->uid_list = NULL;
1348 if (ok == IMAP_SUCCESS)
1354 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1358 g_return_val_if_fail(msginfo != NULL, -1);
1360 msglist.data = msginfo;
1361 msglist.next = NULL;
1363 return imap_copy_msgs(folder, dest, &msglist, NULL);
1366 static gint imap_copy_msgs(Folder *folder, FolderItem *dest,
1367 MsgInfoList *msglist, GRelation *relation)
1372 g_return_val_if_fail(folder != NULL, -1);
1373 g_return_val_if_fail(dest != NULL, -1);
1374 g_return_val_if_fail(msglist != NULL, -1);
1376 msginfo = (MsgInfo *)msglist->data;
1377 g_return_val_if_fail(msginfo->folder != NULL, -1);
1379 ret = imap_do_copy_msgs(folder, dest, msglist, relation);
1384 static gint imap_do_remove_msgs(Folder *folder, FolderItem *dest,
1385 MsgInfoList *msglist, GRelation *relation)
1387 gchar *destdir, *dir;
1388 GSList *numlist = NULL, *cur;
1390 IMAPSession *session;
1391 gint ok = IMAP_SUCCESS;
1392 GRelation *uid_mapping;
1394 g_return_val_if_fail(folder != NULL, -1);
1395 g_return_val_if_fail(dest != NULL, -1);
1396 g_return_val_if_fail(msglist != NULL, -1);
1398 debug_print("getting session...\n");
1399 session = imap_session_get(folder);
1404 msginfo = (MsgInfo *)msglist->data;
1406 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1407 NULL, NULL, NULL, NULL, FALSE);
1408 if (ok != IMAP_SUCCESS) {
1413 destdir = imap_get_real_path(session, IMAP_FOLDER(folder), dest->path);
1414 for (cur = msglist; cur; cur = cur->next) {
1415 msginfo = (MsgInfo *)cur->data;
1416 if (!MSG_IS_DELETED(msginfo->flags))
1417 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
1419 numlist = g_slist_reverse(numlist);
1421 uid_mapping = g_relation_new(2);
1422 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1424 ok = imap_set_message_flags
1425 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1426 numlist, IMAP_FLAG_DELETED, TRUE);
1427 if (ok != IMAP_SUCCESS) {
1428 log_warning(_("can't set deleted flags\n"));
1432 ok = imap_cmd_expunge(session);
1433 if (ok != IMAP_SUCCESS) {
1434 log_warning(_("can't expunge\n"));
1439 dir = folder_item_get_path(msginfo->folder);
1440 if (is_dir_exist(dir)) {
1441 for (cur = msglist; cur; cur = cur->next) {
1442 msginfo = (MsgInfo *)cur->data;
1443 remove_numbered_files(dir, msginfo->msgnum, msginfo->msgnum);
1448 g_relation_destroy(uid_mapping);
1449 g_slist_free(numlist);
1453 if (ok == IMAP_SUCCESS)
1459 static gint imap_remove_msgs(Folder *folder, FolderItem *dest,
1460 MsgInfoList *msglist, GRelation *relation)
1464 g_return_val_if_fail(folder != NULL, -1);
1465 g_return_val_if_fail(dest != NULL, -1);
1466 if (msglist == NULL)
1469 msginfo = (MsgInfo *)msglist->data;
1470 g_return_val_if_fail(msginfo->folder != NULL, -1);
1472 return imap_do_remove_msgs(folder, dest, msglist, relation);
1475 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1477 GSList *list = folder_item_get_msg_list(item);
1478 gint res = imap_remove_msgs(folder, item, list, NULL);
1479 procmsg_msg_list_free(list);
1483 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1486 /* TODO: properly implement this method */
1490 static gint imap_close(Folder *folder, FolderItem *item)
1495 static gint imap_scan_tree(Folder *folder)
1497 FolderItem *item = NULL;
1498 IMAPSession *session;
1499 gchar *root_folder = NULL;
1501 g_return_val_if_fail(folder != NULL, -1);
1502 g_return_val_if_fail(folder->account != NULL, -1);
1504 debug_print("getting session...\n");
1505 session = imap_session_get(folder);
1507 if (!folder->node) {
1508 folder_tree_destroy(folder);
1509 item = folder_item_new(folder, folder->name, NULL);
1510 item->folder = folder;
1511 folder->node = item->node = g_node_new(item);
1516 if (folder->account->imap_dir && *folder->account->imap_dir) {
1521 Xstrdup_a(root_folder, folder->account->imap_dir, {unlock_session();return -1;});
1522 extract_quote(root_folder, '"');
1523 subst_char(root_folder,
1524 imap_get_path_separator(session, IMAP_FOLDER(folder),
1527 strtailchomp(root_folder, '/');
1528 real_path = imap_get_real_path
1529 (session, IMAP_FOLDER(folder), root_folder);
1530 debug_print("IMAP root directory: %s\n", real_path);
1532 /* check if root directory exist */
1534 r = imap_threaded_list(session->folder, "", real_path,
1536 if ((r != MAILIMAP_NO_ERROR) || (clist_count(lep_list) == 0)) {
1537 if (!folder->node) {
1538 item = folder_item_new(folder, folder->name, NULL);
1539 item->folder = folder;
1540 folder->node = item->node = g_node_new(item);
1545 mailimap_list_result_free(lep_list);
1551 item = FOLDER_ITEM(folder->node->data);
1552 if (item && !item->path && root_folder) {
1553 item->path = g_strdup(root_folder);
1555 if (!item || ((item->path || root_folder) &&
1556 strcmp2(item->path, root_folder) != 0)) {
1557 folder_tree_destroy(folder);
1558 item = folder_item_new(folder, folder->name, root_folder);
1559 item->folder = folder;
1560 folder->node = item->node = g_node_new(item);
1563 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1564 imap_create_missing_folders(folder);
1570 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1573 IMAPFolder *imapfolder;
1574 FolderItem *new_item;
1575 GSList *item_list, *cur;
1578 gchar *wildcard_path;
1584 g_return_val_if_fail(item != NULL, -1);
1585 g_return_val_if_fail(item->folder != NULL, -1);
1586 g_return_val_if_fail(item->no_sub == FALSE, -1);
1588 folder = item->folder;
1589 imapfolder = IMAP_FOLDER(folder);
1591 separator = imap_get_path_separator(session, imapfolder, item->path);
1593 if (folder->ui_func)
1594 folder->ui_func(folder, item, folder->ui_func_data);
1597 wildcard[0] = separator;
1600 real_path = imap_get_real_path(session, imapfolder, item->path);
1604 real_path = g_strdup("");
1607 Xstrcat_a(wildcard_path, real_path, wildcard,
1608 {g_free(real_path); return IMAP_ERROR;});
1610 r = imap_threaded_list(folder, "", wildcard_path, &lep_list);
1611 if (r != MAILIMAP_NO_ERROR) {
1615 item_list = imap_list_from_lep(imapfolder,
1616 lep_list, real_path, FALSE);
1617 mailimap_list_result_free(lep_list);
1622 node = item->node->children;
1623 while (node != NULL) {
1624 FolderItem *old_item = FOLDER_ITEM(node->data);
1625 GNode *next = node->next;
1628 for (cur = item_list; cur != NULL; cur = cur->next) {
1629 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1630 if (!strcmp2(old_item->path, cur_item->path)) {
1631 new_item = cur_item;
1636 debug_print("folder '%s' not found. removing...\n",
1638 folder_item_remove(old_item);
1640 old_item->no_sub = new_item->no_sub;
1641 old_item->no_select = new_item->no_select;
1642 if (old_item->no_sub == TRUE && node->children) {
1643 debug_print("folder '%s' doesn't have "
1644 "subfolders. removing...\n",
1646 folder_item_remove_children(old_item);
1653 for (cur = item_list; cur != NULL; cur = cur->next) {
1654 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1657 for (node = item->node->children; node != NULL;
1658 node = node->next) {
1659 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1661 new_item = FOLDER_ITEM(node->data);
1662 folder_item_destroy(cur_item);
1668 new_item = cur_item;
1669 debug_print("new folder '%s' found.\n", new_item->path);
1670 folder_item_append(item, new_item);
1673 if (!strcmp(new_item->path, "INBOX")) {
1674 new_item->stype = F_INBOX;
1675 folder->inbox = new_item;
1676 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1679 base = g_path_get_basename(new_item->path);
1681 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1682 new_item->stype = F_OUTBOX;
1683 folder->outbox = new_item;
1684 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1685 new_item->stype = F_DRAFT;
1686 folder->draft = new_item;
1687 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1688 new_item->stype = F_QUEUE;
1689 folder->queue = new_item;
1690 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1691 new_item->stype = F_TRASH;
1692 folder->trash = new_item;
1697 if (new_item->no_sub == FALSE)
1698 imap_scan_tree_recursive(session, new_item);
1701 g_slist_free(item_list);
1703 return IMAP_SUCCESS;
1706 static gint imap_create_tree(Folder *folder)
1708 g_return_val_if_fail(folder != NULL, -1);
1709 g_return_val_if_fail(folder->node != NULL, -1);
1710 g_return_val_if_fail(folder->node->data != NULL, -1);
1711 g_return_val_if_fail(folder->account != NULL, -1);
1713 imap_scan_tree(folder);
1714 imap_create_missing_folders(folder);
1719 static void imap_create_missing_folders(Folder *folder)
1721 g_return_if_fail(folder != NULL);
1724 folder->inbox = imap_create_special_folder
1725 (folder, F_INBOX, "INBOX");
1727 folder->trash = imap_create_special_folder
1728 (folder, F_TRASH, "Trash");
1730 folder->queue = imap_create_special_folder
1731 (folder, F_QUEUE, "Queue");
1732 if (!folder->outbox)
1733 folder->outbox = imap_create_special_folder
1734 (folder, F_OUTBOX, "Sent");
1736 folder->draft = imap_create_special_folder
1737 (folder, F_DRAFT, "Drafts");
1740 static FolderItem *imap_create_special_folder(Folder *folder,
1741 SpecialFolderItemType stype,
1745 FolderItem *new_item;
1747 g_return_val_if_fail(folder != NULL, NULL);
1748 g_return_val_if_fail(folder->node != NULL, NULL);
1749 g_return_val_if_fail(folder->node->data != NULL, NULL);
1750 g_return_val_if_fail(folder->account != NULL, NULL);
1751 g_return_val_if_fail(name != NULL, NULL);
1753 item = FOLDER_ITEM(folder->node->data);
1754 new_item = imap_create_folder(folder, item, name);
1757 g_warning("Can't create '%s'\n", name);
1758 if (!folder->inbox) return NULL;
1760 new_item = imap_create_folder(folder, folder->inbox, name);
1762 g_warning("Can't create '%s' under INBOX\n", name);
1764 new_item->stype = stype;
1766 new_item->stype = stype;
1771 static gchar *imap_folder_get_path(Folder *folder)
1775 g_return_val_if_fail(folder != NULL, NULL);
1776 g_return_val_if_fail(folder->account != NULL, NULL);
1778 folder_path = g_strconcat(get_imap_cache_dir(),
1780 folder->account->recv_server,
1782 folder->account->userid,
1788 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1790 gchar *folder_path, *path;
1792 g_return_val_if_fail(folder != NULL, NULL);
1793 g_return_val_if_fail(item != NULL, NULL);
1794 folder_path = imap_folder_get_path(folder);
1796 g_return_val_if_fail(folder_path != NULL, NULL);
1797 if (folder_path[0] == G_DIR_SEPARATOR) {
1799 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1802 path = g_strdup(folder_path);
1805 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1806 folder_path, G_DIR_SEPARATOR_S,
1809 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1812 g_free(folder_path);
1817 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1820 gchar *dirpath, *imap_path;
1821 IMAPSession *session;
1822 FolderItem *new_item;
1827 gboolean no_select = FALSE, no_sub = FALSE;
1829 g_return_val_if_fail(folder != NULL, NULL);
1830 g_return_val_if_fail(folder->account != NULL, NULL);
1831 g_return_val_if_fail(parent != NULL, NULL);
1832 g_return_val_if_fail(name != NULL, NULL);
1834 debug_print("getting session...\n");
1835 session = imap_session_get(folder);
1840 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0) {
1841 dirpath = g_strdup(name);
1842 }else if (parent->path)
1843 dirpath = g_strconcat(parent->path, "/", name, NULL);
1844 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1845 dirpath = g_strdup(name);
1846 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1849 Xstrdup_a(imap_dir, folder->account->imap_dir, {unlock_session();return NULL;});
1850 strtailchomp(imap_dir, '/');
1851 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1853 dirpath = g_strdup(name);
1857 /* keep trailing directory separator to create a folder that contains
1859 imap_path = imap_utf8_to_modified_utf7(dirpath);
1861 strtailchomp(dirpath, '/');
1862 Xstrdup_a(new_name, name, {
1867 separator = imap_get_path_separator(session, IMAP_FOLDER(folder), imap_path);
1868 imap_path_separator_subst(imap_path, separator);
1869 /* remove trailing / for display */
1870 strtailchomp(new_name, '/');
1872 if (strcmp(dirpath, "INBOX") != 0) {
1874 gboolean exist = FALSE;
1878 argbuf = g_ptr_array_new();
1879 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1880 if (r != MAILIMAP_NO_ERROR) {
1881 log_warning(_("can't create mailbox: LIST failed\n"));
1884 ptr_array_free_strings(argbuf);
1885 g_ptr_array_free(argbuf, TRUE);
1890 if (clist_count(lep_list) > 0)
1892 mailimap_list_result_free(lep_list);
1895 ok = imap_cmd_create(session, imap_path);
1896 if (ok != IMAP_SUCCESS) {
1897 log_warning(_("can't create mailbox\n"));
1903 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1904 if (r == MAILIMAP_NO_ERROR) {
1905 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1906 lep_list, dirpath, TRUE);
1908 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1909 no_select = cur_item->no_select;
1910 no_sub = cur_item->no_sub;
1911 g_slist_free(item_list);
1913 mailimap_list_result_free(lep_list);
1920 /* just get flags */
1921 r = imap_threaded_list(folder, "", "INBOX", &lep_list);
1922 if (r == MAILIMAP_NO_ERROR) {
1923 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1924 lep_list, dirpath, TRUE);
1926 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1927 no_select = cur_item->no_select;
1928 no_sub = cur_item->no_sub;
1929 g_slist_free(item_list);
1931 mailimap_list_result_free(lep_list);
1935 new_item = folder_item_new(folder, new_name, dirpath);
1936 new_item->no_select = no_select;
1937 new_item->no_sub = no_sub;
1938 folder_item_append(parent, new_item);
1942 dirpath = folder_item_get_path(new_item);
1943 if (!is_dir_exist(dirpath))
1944 make_dir_hier(dirpath);
1950 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1955 gchar *real_oldpath;
1956 gchar *real_newpath;
1958 gchar *old_cache_dir;
1959 gchar *new_cache_dir;
1960 IMAPSession *session;
1963 gint exists, recent, unseen;
1964 guint32 uid_validity;
1966 g_return_val_if_fail(folder != NULL, -1);
1967 g_return_val_if_fail(item != NULL, -1);
1968 g_return_val_if_fail(item->path != NULL, -1);
1969 g_return_val_if_fail(name != NULL, -1);
1971 debug_print("getting session...\n");
1972 session = imap_session_get(folder);
1977 if (strchr(name, imap_get_path_separator(session, IMAP_FOLDER(folder), item->path)) != NULL) {
1978 g_warning(_("New folder name must not contain the namespace "
1984 real_oldpath = imap_get_real_path(session, IMAP_FOLDER(folder), item->path);
1986 g_free(session->mbox);
1987 session->mbox = NULL;
1988 ok = imap_cmd_examine(session, "INBOX",
1989 &exists, &recent, &unseen, &uid_validity, FALSE);
1990 if (ok != IMAP_SUCCESS) {
1991 g_free(real_oldpath);
1996 separator = imap_get_path_separator(session, IMAP_FOLDER(folder), item->path);
1997 if (strchr(item->path, G_DIR_SEPARATOR)) {
1998 dirpath = g_path_get_dirname(item->path);
1999 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
2002 newpath = g_strdup(name);
2004 real_newpath = imap_utf8_to_modified_utf7(newpath);
2005 imap_path_separator_subst(real_newpath, separator);
2007 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
2008 if (ok != IMAP_SUCCESS) {
2009 log_warning(_("can't rename mailbox: %s to %s\n"),
2010 real_oldpath, real_newpath);
2011 g_free(real_oldpath);
2013 g_free(real_newpath);
2019 item->name = g_strdup(name);
2021 old_cache_dir = folder_item_get_path(item);
2023 paths[0] = g_strdup(item->path);
2025 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2026 imap_rename_folder_func, paths);
2028 if (is_dir_exist(old_cache_dir)) {
2029 new_cache_dir = folder_item_get_path(item);
2030 if (rename(old_cache_dir, new_cache_dir) < 0) {
2031 FILE_OP_ERROR(old_cache_dir, "rename");
2033 g_free(new_cache_dir);
2036 g_free(old_cache_dir);
2039 g_free(real_oldpath);
2040 g_free(real_newpath);
2045 static gint imap_remove_folder_real(Folder *folder, FolderItem *item)
2048 IMAPSession *session;
2052 g_return_val_if_fail(folder != NULL, -1);
2053 g_return_val_if_fail(item != NULL, -1);
2054 g_return_val_if_fail(item->path != NULL, -1);
2056 debug_print("getting session...\n");
2057 session = imap_session_get(folder);
2061 path = imap_get_real_path(session, IMAP_FOLDER(folder), item->path);
2063 ok = imap_cmd_delete(session, path);
2064 if (ok != IMAP_SUCCESS) {
2065 gchar *tmp = g_strdup_printf("%s%c", path,
2066 imap_get_path_separator(session, IMAP_FOLDER(folder), path));
2069 ok = imap_cmd_delete(session, path);
2072 if (ok != IMAP_SUCCESS) {
2073 log_warning(_("can't delete mailbox\n"));
2080 cache_dir = folder_item_get_path(item);
2081 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2082 g_warning("can't remove directory '%s'\n", cache_dir);
2084 folder_item_remove(item);
2089 static gint imap_remove_folder(Folder *folder, FolderItem *item)
2093 g_return_val_if_fail(item != NULL, -1);
2094 g_return_val_if_fail(item->folder != NULL, -1);
2095 g_return_val_if_fail(item->node != NULL, -1);
2097 node = item->node->children;
2098 while (node != NULL) {
2100 if (imap_remove_folder(folder, FOLDER_ITEM(node->data)) < 0)
2104 debug_print("IMAP removing %s\n", item->path);
2106 if (imap_remove_all_msg(folder, item) < 0)
2108 return imap_remove_folder_real(folder, item);
2111 typedef struct _uncached_data {
2112 IMAPSession *session;
2114 MsgNumberList *numlist;
2120 static void *imap_get_uncached_messages_thread(void *data)
2122 uncached_data *stuff = (uncached_data *)data;
2123 IMAPSession *session = stuff->session;
2124 FolderItem *item = stuff->item;
2125 MsgNumberList *numlist = stuff->numlist;
2127 GSList *newlist = NULL;
2128 GSList *llast = NULL;
2129 GSList *seq_list, *cur;
2131 debug_print("uncached_messages\n");
2133 if (session == NULL || item == NULL || item->folder == NULL
2134 || FOLDER_CLASS(item->folder) != &imap_class) {
2139 seq_list = imap_get_lep_set_from_numlist(numlist);
2140 debug_print("get msgs info\n");
2141 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2142 struct mailimap_set * imapset;
2148 imapset = cur->data;
2150 r = imap_threaded_fetch_env(session->folder,
2151 imapset, &env_list);
2152 if (r != MAILIMAP_NO_ERROR)
2155 session_set_access_time(SESSION(session));
2158 for(i = 0 ; i < carray_count(env_list) ; i ++) {
2159 struct imap_fetch_env_info * info;
2162 info = carray_get(env_list, i);
2163 msginfo = imap_envelope_from_lep(info, item);
2164 if (msginfo == NULL)
2166 msginfo->folder = item;
2168 llast = newlist = g_slist_append(newlist, msginfo);
2170 llast = g_slist_append(llast, msginfo);
2171 llast = llast->next;
2176 imap_fetch_env_free(env_list);
2179 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2180 struct mailimap_set * imapset;
2182 imapset = cur->data;
2183 mailimap_set_free(imapset);
2186 session_set_access_time(SESSION(session));
2191 #define MAX_MSG_NUM 50
2193 static GSList *imap_get_uncached_messages(IMAPSession *session,
2195 MsgNumberList *numlist)
2197 GSList *result = NULL;
2199 uncached_data *data = g_new0(uncached_data, 1);
2204 data->total = g_slist_length(numlist);
2205 debug_print("messages list : %i\n", data->total);
2207 while (cur != NULL) {
2208 GSList * partial_result;
2216 while (count < MAX_MSG_NUM) {
2221 if (newlist == NULL)
2222 llast = newlist = g_slist_append(newlist, p);
2224 llast = g_slist_append(llast, p);
2225 llast = llast->next;
2235 data->session = session;
2237 data->numlist = newlist;
2240 if (prefs_common.work_offline &&
2241 !inc_offline_should_override(
2242 _("Claws Mail needs network access in order "
2243 "to access the IMAP server."))) {
2249 (GSList *)imap_get_uncached_messages_thread(data);
2251 statusbar_progress_all(data->cur,data->total, 1);
2253 g_slist_free(newlist);
2255 result = g_slist_concat(result, partial_result);
2259 statusbar_progress_all(0,0,0);
2260 statusbar_pop_all();
2265 static void imap_delete_all_cached_messages(FolderItem *item)
2269 g_return_if_fail(item != NULL);
2270 g_return_if_fail(item->folder != NULL);
2271 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2273 debug_print("Deleting all cached messages...\n");
2275 dir = folder_item_get_path(item);
2276 if (is_dir_exist(dir))
2277 remove_all_numbered_files(dir);
2280 debug_print("done.\n");
2283 gchar imap_get_path_separator_for_item(FolderItem *item)
2285 Folder *folder = NULL;
2286 IMAPFolder *imap_folder = NULL;
2287 IMAPSession *session = NULL;
2292 folder = item->folder;
2297 imap_folder = IMAP_FOLDER(folder);
2302 debug_print("getting session...");
2303 session = imap_session_get(FOLDER(folder));
2304 result = imap_get_path_separator(session, imap_folder, item->path);
2309 static gchar imap_refresh_path_separator(IMAPSession *session, IMAPFolder *folder, const gchar *subfolder)
2313 gchar separator = '\0';
2315 g_return_val_if_fail(session != NULL, '/');
2316 r = imap_threaded_list((Folder *)folder, "", subfolder, &lep_list);
2318 if (r != MAILIMAP_NO_ERROR) {
2319 log_warning(_("LIST failed\n"));
2323 if (clist_count(lep_list) > 0) {
2324 clistiter * iter = clist_begin(lep_list);
2325 struct mailimap_mailbox_list * mb;
2326 mb = clist_content(iter);
2328 separator = mb->mb_delimiter;
2329 debug_print("got separator: %c\n", folder->last_seen_separator);
2331 mailimap_list_result_free(lep_list);
2335 static gchar imap_get_path_separator(IMAPSession *session, IMAPFolder *folder, const gchar *path)
2337 gchar separator = '/';
2339 if (folder->last_seen_separator == 0) {
2340 folder->last_seen_separator = imap_refresh_path_separator(session, folder, "");
2343 if (folder->last_seen_separator == 0) {
2344 folder->last_seen_separator = imap_refresh_path_separator(session, folder, "INBOX");
2347 if (folder->last_seen_separator != 0) {
2348 debug_print("using separator: %c\n", folder->last_seen_separator);
2349 return folder->last_seen_separator;
2355 static gchar *imap_get_real_path(IMAPSession *session, IMAPFolder *folder, const gchar *path)
2360 g_return_val_if_fail(folder != NULL, NULL);
2361 g_return_val_if_fail(path != NULL, NULL);
2363 real_path = imap_utf8_to_modified_utf7(path);
2364 separator = imap_get_path_separator(session, folder, path);
2365 imap_path_separator_subst(real_path, separator);
2370 static gint imap_set_message_flags(IMAPSession *session,
2371 MsgNumberList *numlist,
2379 seq_list = imap_get_lep_set_from_numlist(numlist);
2381 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
2382 struct mailimap_set * imapset;
2384 imapset = cur->data;
2386 ok = imap_cmd_store(session, imapset,
2390 imap_lep_set_free(seq_list);
2392 return IMAP_SUCCESS;
2395 typedef struct _select_data {
2396 IMAPSession *session;
2401 guint32 *uid_validity;
2405 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2407 gint *exists, gint *recent, gint *unseen,
2408 guint32 *uid_validity, gboolean block)
2412 gint exists_, recent_, unseen_;
2413 guint32 uid_validity_;
2415 if (!exists && !recent && !unseen && !uid_validity) {
2416 if (session->mbox && strcmp(session->mbox, path) == 0)
2417 return IMAP_SUCCESS;
2426 uid_validity = &uid_validity_;
2428 g_free(session->mbox);
2429 session->mbox = NULL;
2431 real_path = imap_get_real_path(session, folder, path);
2433 ok = imap_cmd_select(session, real_path,
2434 exists, recent, unseen, uid_validity, block);
2435 if (ok != IMAP_SUCCESS)
2436 log_warning(_("can't select folder: %s\n"), real_path);
2438 session->mbox = g_strdup(path);
2439 session->folder_content_changed = FALSE;
2446 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2447 const gchar *path, IMAPFolderItem *item,
2449 guint32 *uid_next, guint32 *uid_validity,
2450 gint *unseen, gboolean block)
2454 struct mailimap_mailbox_data_status * data_status;
2459 real_path = imap_get_real_path(session, folder, path);
2473 r = imap_threaded_status(FOLDER(folder), real_path,
2474 &data_status, mask);
2477 if (r != MAILIMAP_NO_ERROR) {
2478 debug_print("status err %d\n", r);
2482 if (data_status->st_info_list == NULL) {
2483 mailimap_mailbox_data_status_free(data_status);
2484 debug_print("status->st_info_list == NULL\n");
2489 for(iter = clist_begin(data_status->st_info_list) ; iter != NULL ;
2490 iter = clist_next(iter)) {
2491 struct mailimap_status_info * info;
2493 info = clist_content(iter);
2494 switch (info->st_att) {
2495 case MAILIMAP_STATUS_ATT_MESSAGES:
2496 * messages = info->st_value;
2497 got_values |= 1 << 0;
2500 case MAILIMAP_STATUS_ATT_UIDNEXT:
2501 * uid_next = info->st_value;
2502 got_values |= 1 << 2;
2505 case MAILIMAP_STATUS_ATT_UIDVALIDITY:
2506 * uid_validity = info->st_value;
2507 got_values |= 1 << 3;
2510 case MAILIMAP_STATUS_ATT_UNSEEN:
2511 * unseen = info->st_value;
2512 got_values |= 1 << 4;
2516 mailimap_mailbox_data_status_free(data_status);
2518 if (got_values != mask) {
2519 debug_print("status: incomplete values received (%d)\n", got_values);
2522 return IMAP_SUCCESS;
2525 static void imap_free_capabilities(IMAPSession *session)
2527 slist_free_strings(session->capability);
2528 g_slist_free(session->capability);
2529 session->capability = NULL;
2532 /* low-level IMAP4rev1 commands */
2534 static gint imap_cmd_login(IMAPSession *session,
2535 const gchar *user, const gchar *pass,
2541 if (!strcmp(type, "LOGIN") && imap_has_capability(session, "LOGINDISABLED")) {
2542 gint ok = IMAP_ERROR;
2543 if (imap_has_capability(session, "STARTTLS")) {
2545 log_warning(_("Server requires TLS to log in.\n"));
2546 ok = imap_cmd_starttls(session);
2547 if (ok != IMAP_SUCCESS) {
2548 log_warning(_("Can't start TLS session.\n"));
2552 imap_free_capabilities(session);
2553 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR) {
2554 log_warning(_("Can't refresh capabilities.\n"));
2559 log_error(_("Connection to %s failed: "
2560 "server requires TLS, but Claws Mail "
2561 "has been compiled without OpenSSL "
2563 SESSION(session)->server);
2567 log_error(_("Server logins are disabled.\n"));
2572 log_print("IMAP4> Logging %s to %s using %s\n",
2574 SESSION(session)->server,
2576 r = imap_threaded_login(session->folder, user, pass, type);
2577 if (r != MAILIMAP_NO_ERROR) {
2578 log_print("IMAP4< Error logging in to %s\n",
2579 SESSION(session)->server);
2582 log_print("IMAP4< Login to %s successful\n",
2583 SESSION(session)->server);
2589 static gint imap_cmd_noop(IMAPSession *session)
2592 unsigned int exists;
2594 r = imap_threaded_noop(session->folder, &exists);
2595 if (r != MAILIMAP_NO_ERROR) {
2596 debug_print("noop err %d\n", r);
2599 session->exists = exists;
2600 session_set_access_time(SESSION(session));
2602 return IMAP_SUCCESS;
2606 static gint imap_cmd_starttls(IMAPSession *session)
2610 r = imap_threaded_starttls(session->folder,
2611 SESSION(session)->server, SESSION(session)->port);
2612 if (r != MAILIMAP_NO_ERROR) {
2613 debug_print("starttls err %d\n", r);
2616 return IMAP_SUCCESS;
2620 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2621 gint *exists, gint *recent, gint *unseen,
2622 guint32 *uid_validity, gboolean block)
2626 r = imap_threaded_select(session->folder, folder,
2627 exists, recent, unseen, uid_validity);
2628 if (r != MAILIMAP_NO_ERROR) {
2629 debug_print("select err %d\n", r);
2632 return IMAP_SUCCESS;
2635 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2636 gint *exists, gint *recent, gint *unseen,
2637 guint32 *uid_validity, gboolean block)
2641 r = imap_threaded_examine(session->folder, folder,
2642 exists, recent, unseen, uid_validity);
2643 if (r != MAILIMAP_NO_ERROR) {
2644 debug_print("examine err %d\n", r);
2648 return IMAP_SUCCESS;
2651 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2655 r = imap_threaded_create(session->folder, folder);
2656 if (r != MAILIMAP_NO_ERROR) {
2661 return IMAP_SUCCESS;
2664 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2665 const gchar *new_folder)
2669 r = imap_threaded_rename(session->folder, old_folder,
2671 if (r != MAILIMAP_NO_ERROR) {
2676 return IMAP_SUCCESS;
2679 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2684 r = imap_threaded_delete(session->folder, folder);
2685 if (r != MAILIMAP_NO_ERROR) {
2690 return IMAP_SUCCESS;
2693 typedef struct _fetch_data {
2694 IMAPSession *session;
2696 const gchar *filename;
2702 static void *imap_cmd_fetch_thread(void *data)
2704 fetch_data *stuff = (fetch_data *)data;
2705 IMAPSession *session = stuff->session;
2706 guint32 uid = stuff->uid;
2707 const gchar *filename = stuff->filename;
2711 r = imap_threaded_fetch_content(session->folder,
2715 r = imap_threaded_fetch_content(session->folder,
2718 if (r != MAILIMAP_NO_ERROR) {
2719 debug_print("fetch err %d\n", r);
2720 return GINT_TO_POINTER(IMAP_ERROR);
2722 return GINT_TO_POINTER(IMAP_SUCCESS);
2725 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
2726 const gchar *filename, gboolean headers,
2729 fetch_data *data = g_new0(fetch_data, 1);
2732 data->session = session;
2734 data->filename = filename;
2735 data->headers = headers;
2738 if (prefs_common.work_offline &&
2739 !inc_offline_should_override(
2740 _("Claws Mail needs network access in order "
2741 "to access the IMAP server."))) {
2745 statusbar_print_all(_("Fetching message..."));
2746 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
2747 statusbar_pop_all();
2753 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2754 const gchar *file, IMAPFlags flags,
2757 struct mailimap_flag_list * flag_list;
2760 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2762 flag_list = imap_flag_to_lep(flags);
2763 r = imap_threaded_append(session->folder, destfolder,
2764 file, flag_list, (int *)new_uid);
2765 mailimap_flag_list_free(flag_list);
2767 if (r != MAILIMAP_NO_ERROR) {
2768 debug_print("append err %d\n", r);
2771 return IMAP_SUCCESS;
2774 static gint imap_cmd_copy(IMAPSession *session, struct mailimap_set * set,
2775 const gchar *destfolder, GRelation *uid_mapping,
2776 struct mailimap_set **source, struct mailimap_set **dest)
2780 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2781 g_return_val_if_fail(set != NULL, IMAP_ERROR);
2782 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2784 r = imap_threaded_copy(session->folder, set, destfolder, source, dest);
2785 if (r != MAILIMAP_NO_ERROR) {
2790 return IMAP_SUCCESS;
2793 static gint imap_cmd_store(IMAPSession *session, struct mailimap_set * set,
2794 IMAPFlags flags, int do_add)
2797 struct mailimap_flag_list * flag_list;
2798 struct mailimap_store_att_flags * store_att_flags;
2800 flag_list = imap_flag_to_lep(flags);
2804 mailimap_store_att_flags_new_add_flags_silent(flag_list);
2807 mailimap_store_att_flags_new_remove_flags_silent(flag_list);
2809 r = imap_threaded_store(session->folder, set, store_att_flags);
2810 mailimap_store_att_flags_free(store_att_flags);
2811 if (r != MAILIMAP_NO_ERROR) {
2816 return IMAP_SUCCESS;
2819 static gint imap_cmd_expunge(IMAPSession *session)
2823 if (prefs_common.work_offline &&
2824 !inc_offline_should_override(
2825 _("Claws Mail needs network access in order "
2826 "to access the IMAP server."))) {
2830 r = imap_threaded_expunge(session->folder);
2831 if (r != MAILIMAP_NO_ERROR) {
2836 return IMAP_SUCCESS;
2839 static void imap_path_separator_subst(gchar *str, gchar separator)
2842 gboolean in_escape = FALSE;
2844 if (!separator || separator == '/') return;
2846 for (p = str; *p != '\0'; p++) {
2847 if (*p == '/' && !in_escape)
2849 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2851 else if (*p == '-' && in_escape)
2856 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
2858 static iconv_t cd = (iconv_t)-1;
2859 static gboolean iconv_ok = TRUE;
2862 size_t norm_utf7_len;
2864 gchar *to_str, *to_p;
2866 gboolean in_escape = FALSE;
2868 if (!iconv_ok) return g_strdup(mutf7_str);
2870 if (cd == (iconv_t)-1) {
2871 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
2872 if (cd == (iconv_t)-1) {
2873 g_warning("iconv cannot convert UTF-7 to %s\n",
2876 return g_strdup(mutf7_str);
2880 /* modified UTF-7 to normal UTF-7 conversion */
2881 norm_utf7 = g_string_new(NULL);
2883 for (p = mutf7_str; *p != '\0'; p++) {
2884 /* replace: '&' -> '+',
2886 escaped ',' -> '/' */
2887 if (!in_escape && *p == '&') {
2888 if (*(p + 1) != '-') {
2889 g_string_append_c(norm_utf7, '+');
2892 g_string_append_c(norm_utf7, '&');
2895 } else if (in_escape && *p == ',') {
2896 g_string_append_c(norm_utf7, '/');
2897 } else if (in_escape && *p == '-') {
2898 g_string_append_c(norm_utf7, '-');
2901 g_string_append_c(norm_utf7, *p);
2905 norm_utf7_p = norm_utf7->str;
2906 norm_utf7_len = norm_utf7->len;
2907 to_len = strlen(mutf7_str) * 5;
2908 to_p = to_str = g_malloc(to_len + 1);
2910 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2911 &to_p, &to_len) == -1) {
2912 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2913 conv_get_locale_charset_str());
2914 g_string_free(norm_utf7, TRUE);
2916 return g_strdup(mutf7_str);
2919 /* second iconv() call for flushing */
2920 iconv(cd, NULL, NULL, &to_p, &to_len);
2921 g_string_free(norm_utf7, TRUE);
2927 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
2929 static iconv_t cd = (iconv_t)-1;
2930 static gboolean iconv_ok = TRUE;
2931 gchar *norm_utf7, *norm_utf7_p;
2932 size_t from_len, norm_utf7_len;
2934 gchar *from_tmp, *to, *p;
2935 gboolean in_escape = FALSE;
2937 if (!iconv_ok) return g_strdup(from);
2939 if (cd == (iconv_t)-1) {
2940 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
2941 if (cd == (iconv_t)-1) {
2942 g_warning(_("iconv cannot convert %s to UTF-7\n"),
2945 return g_strdup(from);
2949 /* UTF-8 to normal UTF-7 conversion */
2950 Xstrdup_a(from_tmp, from, return g_strdup(from));
2951 from_len = strlen(from);
2952 norm_utf7_len = from_len * 5;
2953 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
2954 norm_utf7_p = norm_utf7;
2956 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
2958 while (from_len > 0) {
2959 if (*from_tmp == '+') {
2960 *norm_utf7_p++ = '+';
2961 *norm_utf7_p++ = '-';
2965 } else if (IS_PRINT(*(guchar *)from_tmp)) {
2966 /* printable ascii char */
2967 *norm_utf7_p = *from_tmp;
2973 size_t conv_len = 0;
2975 /* unprintable char: convert to UTF-7 */
2977 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
2978 conv_len += g_utf8_skip[*(guchar *)p];
2979 p += g_utf8_skip[*(guchar *)p];
2982 from_len -= conv_len;
2983 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
2985 &norm_utf7_p, &norm_utf7_len) == -1) {
2986 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
2987 return g_strdup(from);
2990 /* second iconv() call for flushing */
2991 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
2997 *norm_utf7_p = '\0';
2998 to_str = g_string_new(NULL);
2999 for (p = norm_utf7; p < norm_utf7_p; p++) {
3000 /* replace: '&' -> "&-",
3003 BASE64 '/' -> ',' */
3004 if (!in_escape && *p == '&') {
3005 g_string_append(to_str, "&-");
3006 } else if (!in_escape && *p == '+') {
3007 if (*(p + 1) == '-') {
3008 g_string_append_c(to_str, '+');
3011 g_string_append_c(to_str, '&');
3014 } else if (in_escape && *p == '/') {
3015 g_string_append_c(to_str, ',');
3016 } else if (in_escape && *p == '-') {
3017 g_string_append_c(to_str, '-');
3020 g_string_append_c(to_str, *p);
3026 g_string_append_c(to_str, '-');
3030 g_string_free(to_str, FALSE);
3035 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3037 FolderItem *item = node->data;
3038 gchar **paths = data;
3039 const gchar *oldpath = paths[0];
3040 const gchar *newpath = paths[1];
3042 gchar *new_itempath;
3045 oldpathlen = strlen(oldpath);
3046 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3047 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3051 base = item->path + oldpathlen;
3052 while (*base == G_DIR_SEPARATOR) base++;
3054 new_itempath = g_strdup(newpath);
3056 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3059 item->path = new_itempath;
3064 typedef struct _get_list_uid_data {
3066 IMAPSession *session;
3067 IMAPFolderItem *item;
3068 GSList **msgnum_list;
3070 } get_list_uid_data;
3072 static void *get_list_of_uids_thread(void *data)
3074 get_list_uid_data *stuff = (get_list_uid_data *)data;
3075 Folder *folder = stuff->folder;
3076 IMAPFolderItem *item = stuff->item;
3077 GSList **msgnum_list = stuff->msgnum_list;
3078 gint ok, nummsgs = 0, lastuid_old;
3079 IMAPSession *session;
3080 GSList *uidlist, *elem;
3081 clist * lep_uidlist;
3084 session = stuff->session;
3085 if (session == NULL) {
3087 return GINT_TO_POINTER(-1);
3089 /* no session locking here, it's already locked by caller */
3090 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3091 NULL, NULL, NULL, NULL, TRUE);
3092 if (ok != IMAP_SUCCESS) {
3094 return GINT_TO_POINTER(-1);
3099 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SIMPLE, NULL,
3102 if (r == MAILIMAP_NO_ERROR) {
3103 GSList * fetchuid_list;
3106 imap_uid_list_from_lep(lep_uidlist);
3107 mailimap_search_result_free(lep_uidlist);
3109 uidlist = g_slist_concat(fetchuid_list, uidlist);
3112 GSList * fetchuid_list;
3113 carray * lep_uidtab;
3115 r = imap_threaded_fetch_uid(folder, item->lastuid + 1,
3117 if (r == MAILIMAP_NO_ERROR) {
3119 imap_uid_list_from_lep_tab(lep_uidtab);
3120 imap_fetch_uid_list_free(lep_uidtab);
3121 uidlist = g_slist_concat(fetchuid_list, uidlist);
3125 lastuid_old = item->lastuid;
3126 *msgnum_list = g_slist_copy(item->uid_list);
3127 nummsgs = g_slist_length(*msgnum_list);
3128 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3130 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3133 msgnum = GPOINTER_TO_INT(elem->data);
3134 if (msgnum > lastuid_old) {
3135 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3136 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3139 if(msgnum > item->lastuid)
3140 item->lastuid = msgnum;
3143 g_slist_free(uidlist);
3145 return GINT_TO_POINTER(nummsgs);
3148 static gint get_list_of_uids(IMAPSession *session, Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3151 get_list_uid_data *data = g_new0(get_list_uid_data, 1);
3153 data->folder = folder;
3155 data->msgnum_list = msgnum_list;
3156 data->session = session;
3157 if (prefs_common.work_offline &&
3158 !inc_offline_should_override(
3159 _("Claws Mail needs network access in order "
3160 "to access the IMAP server."))) {
3165 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
3171 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3173 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3174 IMAPSession *session;
3175 gint ok, nummsgs = 0, exists;
3176 guint32 uid_next = 0, uid_val = 0;
3177 GSList *uidlist = NULL;
3179 gboolean selected_folder;
3180 debug_print("get_num_list\n");
3182 g_return_val_if_fail(folder != NULL, -1);
3183 g_return_val_if_fail(item != NULL, -1);
3184 g_return_val_if_fail(item->item.path != NULL, -1);
3185 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3186 g_return_val_if_fail(folder->account != NULL, -1);
3188 debug_print("getting session...\n");
3189 session = imap_session_get(folder);
3190 g_return_val_if_fail(session != NULL, -1);
3192 if (FOLDER_ITEM(item)->path)
3193 statusbar_print_all(_("Scanning folder %s%c%s ..."),
3194 FOLDER_ITEM(item)->folder->name,
3196 FOLDER_ITEM(item)->path);
3198 statusbar_print_all(_("Scanning folder %s ..."),
3199 FOLDER_ITEM(item)->folder->name);
3201 selected_folder = (session->mbox != NULL) &&
3202 (!strcmp(session->mbox, item->item.path));
3203 if (selected_folder && time(NULL) - item->use_cache < 2) {
3204 ok = imap_cmd_noop(session);
3205 if (ok != IMAP_SUCCESS) {
3206 debug_print("disconnected!\n");
3207 session = imap_reconnect_if_possible(folder, session);
3208 if (session == NULL) {
3209 statusbar_pop_all();
3214 exists = session->exists;
3216 uid_next = item->c_uid_next;
3217 uid_val = item->c_uid_validity;
3218 *old_uids_valid = TRUE;
3220 if (item->use_cache && time(NULL) - item->use_cache < 2) {
3221 exists = item->c_messages;
3222 uid_next = item->c_uid_next;
3223 uid_val = item->c_uid_validity;
3225 debug_print("using cache %d %d %d\n", exists, uid_next, uid_val);
3227 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, item,
3228 &exists, &uid_next, &uid_val, NULL, FALSE);
3230 item->item.last_num = uid_next - 1;
3232 item->use_cache = (time_t)0;
3233 if (ok != IMAP_SUCCESS) {
3234 statusbar_pop_all();
3238 if(item->item.mtime == uid_val)
3239 *old_uids_valid = TRUE;
3241 *old_uids_valid = FALSE;
3243 debug_print("Freeing imap uid cache (%d != %d)\n",
3244 (int)item->item.mtime, uid_val);
3246 g_slist_free(item->uid_list);
3247 item->uid_list = NULL;
3249 item->item.mtime = uid_val;
3251 imap_delete_all_cached_messages((FolderItem *)item);
3255 /* If old uid_next matches new uid_next we can be sure no message
3256 was added to the folder */
3257 debug_print("uid_next is %d and item->uid_next %d \n",
3258 uid_next, item->uid_next);
3259 if (uid_next == item->uid_next) {
3260 nummsgs = g_slist_length(item->uid_list);
3262 /* If number of messages is still the same we
3263 know our caches message numbers are still valid,
3264 otherwise if the number of messages has decrease
3265 we discard our cache to start a new scan to find
3266 out which numbers have been removed */
3267 if (exists == nummsgs) {
3268 debug_print("exists == nummsgs\n");
3269 *msgnum_list = g_slist_copy(item->uid_list);
3270 statusbar_pop_all();
3273 } else if (exists < nummsgs) {
3274 debug_print("Freeing imap uid cache");
3276 g_slist_free(item->uid_list);
3277 item->uid_list = NULL;
3282 *msgnum_list = NULL;
3283 statusbar_pop_all();
3288 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3291 statusbar_pop_all();
3296 if (nummsgs != exists) {
3297 /* Cache contains more messages then folder, we have cached
3298 an old UID of a message that was removed and new messages
3299 have been added too, otherwise the uid_next check would
3301 debug_print("Freeing imap uid cache");
3303 g_slist_free(item->uid_list);
3304 item->uid_list = NULL;
3306 g_slist_free(*msgnum_list);
3308 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3311 *msgnum_list = uidlist;
3313 dir = folder_item_get_path((FolderItem *)item);
3314 debug_print("removing old messages from %s\n", dir);
3315 remove_numbered_files_not_in_list(dir, *msgnum_list);
3318 item->uid_next = uid_next;
3320 debug_print("get_num_list - ok - %i\n", nummsgs);
3321 statusbar_pop_all();
3326 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3331 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3332 flags.tmp_flags = 0;
3334 g_return_val_if_fail(item != NULL, NULL);
3335 g_return_val_if_fail(file != NULL, NULL);
3337 if (folder_has_parent_of_type(item, F_QUEUE)) {
3338 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3339 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
3340 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3343 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3344 if (!msginfo) return NULL;
3346 msginfo->plaintext_file = g_strdup(file);
3347 msginfo->folder = item;
3352 GSList *imap_get_msginfos(Folder *folder, FolderItem *item,
3353 GSList *msgnum_list)
3355 IMAPSession *session;
3356 MsgInfoList *ret = NULL;
3359 debug_print("get_msginfos\n");
3361 g_return_val_if_fail(folder != NULL, NULL);
3362 g_return_val_if_fail(item != NULL, NULL);
3363 g_return_val_if_fail(msgnum_list != NULL, NULL);
3365 debug_print("getting session...\n");
3366 session = imap_session_get(folder);
3367 g_return_val_if_fail(session != NULL, NULL);
3369 debug_print("IMAP getting msginfos\n");
3370 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3371 NULL, NULL, NULL, NULL, FALSE);
3372 if (ok != IMAP_SUCCESS) {
3376 if (!(folder_has_parent_of_type(item, F_DRAFT) ||
3377 folder_has_parent_of_type(item, F_QUEUE))) {
3378 ret = g_slist_concat(ret,
3379 imap_get_uncached_messages(session, item,
3382 MsgNumberList *sorted_list, *elem, *llast = NULL;
3383 gint startnum, lastnum;
3385 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3387 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3389 llast = g_slist_last(ret);
3390 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3394 num = GPOINTER_TO_INT(elem->data);
3396 if (num > lastnum + 1 || elem == NULL) {
3398 for (i = startnum; i <= lastnum; ++i) {
3401 file = imap_fetch_msg(folder, item, i);
3403 MsgInfo *msginfo = imap_parse_msg(file, item);
3404 if (msginfo != NULL) {
3405 msginfo->msgnum = i;
3407 llast = ret = g_slist_append(ret, msginfo);
3409 llast = g_slist_append(llast, msginfo);
3410 llast = llast->next;
3415 session_set_access_time(SESSION(session));
3426 g_slist_free(sorted_list);
3432 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3434 MsgInfo *msginfo = NULL;
3435 MsgInfoList *msginfolist;
3436 MsgNumberList numlist;
3438 numlist.next = NULL;
3439 numlist.data = GINT_TO_POINTER(uid);
3441 msginfolist = imap_get_msginfos(folder, item, &numlist);
3442 if (msginfolist != NULL) {
3443 msginfo = msginfolist->data;
3444 g_slist_free(msginfolist);
3450 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3452 IMAPSession *session;
3453 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3454 gint ok, exists = 0, unseen = 0;
3455 guint32 uid_next = 0, uid_val = 0;
3456 gboolean selected_folder;
3458 g_return_val_if_fail(folder != NULL, FALSE);
3459 g_return_val_if_fail(item != NULL, FALSE);
3460 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3461 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3463 if (item->item.path == NULL)
3466 debug_print("getting session...\n");
3467 session = imap_session_get(folder);
3468 g_return_val_if_fail(session != NULL, FALSE);
3470 selected_folder = (session->mbox != NULL) &&
3471 (!strcmp(session->mbox, item->item.path));
3472 if (selected_folder && time(NULL) - item->use_cache < 2) {
3473 ok = imap_cmd_noop(session);
3474 if (ok != IMAP_SUCCESS) {
3475 debug_print("disconnected!\n");
3476 session = imap_reconnect_if_possible(folder, session);
3477 if (session == NULL)
3481 if (session->folder_content_changed
3482 || session->exists != item->item.total_msgs) {
3487 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, IMAP_FOLDER_ITEM(item),
3488 &exists, &uid_next, &uid_val, &unseen, FALSE);
3489 if (ok != IMAP_SUCCESS) {
3494 item->use_cache = time(NULL);
3495 item->c_messages = exists;
3496 item->c_uid_next = uid_next;
3497 item->c_uid_validity = uid_val;
3498 item->c_unseen = unseen;
3499 item->item.last_num = uid_next - 1;
3500 debug_print("uidnext %d, item->uid_next %d, exists %d, item->item.total_msgs %d\n",
3501 uid_next, item->uid_next, exists, item->item.total_msgs);
3502 if ((uid_next != item->uid_next) || (exists != item->item.total_msgs)
3503 || unseen != item->item.unread_msgs || uid_val != item->item.mtime) {
3512 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3514 IMAPSession *session;
3515 IMAPFlags flags_set = 0, flags_unset = 0;
3516 gint ok = IMAP_SUCCESS;
3517 MsgNumberList numlist;
3518 hashtable_data *ht_data = NULL;
3520 g_return_if_fail(folder != NULL);
3521 g_return_if_fail(folder->klass == &imap_class);
3522 g_return_if_fail(item != NULL);
3523 g_return_if_fail(item->folder == folder);
3524 g_return_if_fail(msginfo != NULL);
3525 g_return_if_fail(msginfo->folder == item);
3527 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3528 flags_set |= IMAP_FLAG_FLAGGED;
3529 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3530 flags_unset |= IMAP_FLAG_FLAGGED;
3532 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3533 flags_unset |= IMAP_FLAG_SEEN;
3534 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3535 flags_set |= IMAP_FLAG_SEEN;
3537 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3538 flags_set |= IMAP_FLAG_ANSWERED;
3539 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3540 flags_unset |= IMAP_FLAG_ANSWERED;
3542 if (!MSG_IS_DELETED(msginfo->flags) && (newflags & MSG_DELETED))
3543 flags_set |= IMAP_FLAG_DELETED;
3544 if ( MSG_IS_DELETED(msginfo->flags) && !(newflags & MSG_DELETED))
3545 flags_unset |= IMAP_FLAG_DELETED;
3547 if (!flags_set && !flags_unset) {
3548 /* the changed flags were not translatable to IMAP-speak.
3549 * like MSG_POSTFILTERED, so just apply. */
3550 msginfo->flags.perm_flags = newflags;
3554 debug_print("getting session...\n");
3555 session = imap_session_get(folder);
3560 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3561 NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS) {
3565 numlist.next = NULL;
3566 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3568 if (IMAP_FOLDER_ITEM(item)->batching) {
3569 /* instead of performing an UID STORE command for each message change,
3570 * as a lot of them can change "together", we just fill in hashtables
3571 * and defer the treatment so that we're able to send only one
3574 debug_print("IMAP batch mode on, deferring flags change\n");
3576 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_set_table,
3577 GINT_TO_POINTER(flags_set));
3578 if (ht_data == NULL) {
3579 ht_data = g_new0(hashtable_data, 1);
3580 ht_data->session = session;
3581 ht_data->item = IMAP_FOLDER_ITEM(item);
3582 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_set_table,
3583 GINT_TO_POINTER(flags_set), ht_data);
3585 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3586 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
3589 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3590 GINT_TO_POINTER(flags_unset));
3591 if (ht_data == NULL) {
3592 ht_data = g_new0(hashtable_data, 1);
3593 ht_data->session = session;
3594 ht_data->item = IMAP_FOLDER_ITEM(item);
3595 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3596 GINT_TO_POINTER(flags_unset), ht_data);
3598 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3599 ht_data->msglist = g_slist_prepend(ht_data->msglist,
3600 GINT_TO_POINTER(msginfo->msgnum));
3603 debug_print("IMAP changing flags\n");
3605 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3606 if (ok != IMAP_SUCCESS) {
3613 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3614 if (ok != IMAP_SUCCESS) {
3620 msginfo->flags.perm_flags = newflags;
3625 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
3628 IMAPSession *session;
3630 MsgNumberList numlist;
3632 g_return_val_if_fail(folder != NULL, -1);
3633 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3634 g_return_val_if_fail(item != NULL, -1);
3636 debug_print("getting session...\n");
3637 session = imap_session_get(folder);
3638 if (!session) return -1;
3640 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3641 NULL, NULL, NULL, NULL, FALSE);
3642 if (ok != IMAP_SUCCESS) {
3646 numlist.next = NULL;
3647 numlist.data = GINT_TO_POINTER(uid);
3649 ok = imap_set_message_flags
3650 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
3651 &numlist, IMAP_FLAG_DELETED, TRUE);
3652 if (ok != IMAP_SUCCESS) {
3653 log_warning(_("can't set deleted flags: %d\n"), uid);
3658 if (!session->uidplus) {
3659 ok = imap_cmd_expunge(session);
3663 uidstr = g_strdup_printf("%u", uid);
3664 ok = imap_cmd_expunge(session);
3667 if (ok != IMAP_SUCCESS) {
3668 log_warning(_("can't expunge\n"));
3673 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
3674 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
3675 dir = folder_item_get_path(item);
3676 if (is_dir_exist(dir))
3677 remove_numbered_files(dir, uid, uid);
3680 return IMAP_SUCCESS;
3683 static gint compare_msginfo(gconstpointer a, gconstpointer b)
3685 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
3688 static guint gslist_find_next_num(MsgNumberList **list, guint num)
3692 g_return_val_if_fail(list != NULL, -1);
3694 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
3695 if (GPOINTER_TO_INT(elem->data) >= num)
3698 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
3702 * NEW and DELETED flags are not syncronized
3703 * - The NEW/RECENT flags in IMAP folders can not really be directly
3704 * modified by Sylpheed
3705 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
3706 * meaning, in IMAP it always removes the messages from the FolderItem
3707 * in Sylpheed it can mean to move the message to trash
3710 typedef struct _get_flags_data {
3713 MsgInfoList *msginfo_list;
3714 GRelation *msgflags;
3715 gboolean full_search;
3719 static /*gint*/ void *imap_get_flags_thread(void *data)
3721 get_flags_data *stuff = (get_flags_data *)data;
3722 Folder *folder = stuff->folder;
3723 FolderItem *item = stuff->item;
3724 MsgInfoList *msginfo_list = stuff->msginfo_list;
3725 GRelation *msgflags = stuff->msgflags;
3726 gboolean full_search = stuff->full_search;
3727 IMAPSession *session;
3728 GSList *sorted_list = NULL;
3729 GSList *unseen = NULL, *answered = NULL, *flagged = NULL, *deleted = NULL;
3730 GSList *p_unseen, *p_answered, *p_flagged, *p_deleted;
3732 GSList *seq_list, *cur;
3733 gboolean reverse_seen = FALSE;
3736 gint exists_cnt, unseen_cnt;
3737 gboolean selected_folder;
3739 if (folder == NULL || item == NULL) {
3741 return GINT_TO_POINTER(-1);
3744 debug_print("getting session...\n");
3745 session = imap_session_get(folder);
3746 if (session == NULL) {
3748 return GINT_TO_POINTER(-1);
3751 selected_folder = (session->mbox != NULL) &&
3752 (!strcmp(session->mbox, item->path));
3754 if (!selected_folder) {
3755 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3756 &exists_cnt, NULL, &unseen_cnt, NULL, TRUE);
3757 if (ok != IMAP_SUCCESS) {
3760 return GINT_TO_POINTER(-1);
3763 if (unseen_cnt > exists_cnt / 2)
3764 reverse_seen = TRUE;
3767 if (item->unread_msgs > item->total_msgs / 2)
3768 reverse_seen = TRUE;
3771 cmd_buf = g_string_new(NULL);
3773 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
3775 seq_list = imap_get_lep_set_from_msglist(msginfo_list);
3777 struct mailimap_set * set;
3778 set = mailimap_set_new_interval(1, 0);
3779 seq_list = g_slist_append(NULL, set);
3782 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
3783 struct mailimap_set * imapset;
3784 clist * lep_uidlist;
3787 imapset = cur->data;
3789 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SEEN,
3790 full_search ? NULL:imapset, &lep_uidlist);
3793 r = imap_threaded_search(folder,
3794 IMAP_SEARCH_TYPE_UNSEEN,
3795 full_search ? NULL:imapset, &lep_uidlist);
3797 if (r == MAILIMAP_NO_ERROR) {
3800 uidlist = imap_uid_list_from_lep(lep_uidlist);
3801 mailimap_search_result_free(lep_uidlist);
3803 unseen = g_slist_concat(unseen, uidlist);
3806 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_FLAGGED,
3807 full_search ? NULL:imapset, &lep_uidlist);
3808 if (r == MAILIMAP_NO_ERROR) {
3811 uidlist = imap_uid_list_from_lep(lep_uidlist);
3812 mailimap_search_result_free(lep_uidlist);
3814 flagged = g_slist_concat(flagged, uidlist);
3817 if (item->opened || item->processing_pending || item == folder->inbox) {
3818 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_ANSWERED,
3819 full_search ? NULL:imapset, &lep_uidlist);
3820 if (r == MAILIMAP_NO_ERROR) {
3823 uidlist = imap_uid_list_from_lep(lep_uidlist);
3824 mailimap_search_result_free(lep_uidlist);
3826 answered = g_slist_concat(answered, uidlist);
3829 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_DELETED,
3830 full_search ? NULL:imapset, &lep_uidlist);
3831 if (r == MAILIMAP_NO_ERROR) {
3834 uidlist = imap_uid_list_from_lep(lep_uidlist);
3835 mailimap_search_result_free(lep_uidlist);
3837 deleted = g_slist_concat(deleted, uidlist);
3843 p_answered = answered;
3844 p_flagged = flagged;
3845 p_deleted = deleted;
3847 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
3852 msginfo = (MsgInfo *) elem->data;
3853 flags = msginfo->flags.perm_flags;
3854 wasnew = (flags & MSG_NEW);
3855 if (item->opened || item->processing_pending || item == folder->inbox) {
3856 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
3858 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW | MSG_MARKED));
3861 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3862 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
3863 if (!reverse_seen) {
3864 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3866 flags &= ~(MSG_UNREAD | MSG_NEW);
3870 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
3871 flags |= MSG_MARKED;
3873 flags &= ~MSG_MARKED;
3875 if (item->opened || item->processing_pending || item == folder->inbox) {
3876 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
3877 flags |= MSG_REPLIED;
3879 flags &= ~MSG_REPLIED;
3880 if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
3881 flags |= MSG_DELETED;
3883 flags &= ~MSG_DELETED;
3885 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
3888 imap_lep_set_free(seq_list);
3889 g_slist_free(flagged);
3890 g_slist_free(deleted);
3891 g_slist_free(answered);
3892 g_slist_free(unseen);
3893 g_slist_free(sorted_list);
3894 g_string_free(cmd_buf, TRUE);
3898 return GINT_TO_POINTER(0);
3901 static gint imap_get_flags(Folder *folder, FolderItem *item,
3902 MsgInfoList *msginfo_list, GRelation *msgflags)
3905 get_flags_data *data = g_new0(get_flags_data, 1);
3907 data->folder = folder;
3909 data->msginfo_list = msginfo_list;
3910 data->msgflags = msgflags;
3911 data->full_search = FALSE;
3913 GSList *tmp = NULL, *cur;
3915 if (prefs_common.work_offline &&
3916 !inc_offline_should_override(
3917 _("Claws Mail needs network access in order "
3918 "to access the IMAP server."))) {
3923 tmp = folder_item_get_msg_list(item);
3925 if (g_slist_length(tmp) <= g_slist_length(msginfo_list))
3926 data->full_search = TRUE;
3928 for (cur = tmp; cur; cur = cur->next)
3929 procmsg_msginfo_free((MsgInfo *)cur->data);
3933 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
3940 static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
3942 gboolean flags_set = GPOINTER_TO_INT(user_data);
3943 gint flags_value = GPOINTER_TO_INT(key);
3944 hashtable_data *data = (hashtable_data *)value;
3945 IMAPFolderItem *_item = data->item;
3946 FolderItem *item = (FolderItem *)_item;
3947 gint ok = IMAP_ERROR;
3948 IMAPSession *session = NULL;
3950 debug_print("getting session...\n");
3951 session = imap_session_get(item->folder);
3953 data->msglist = g_slist_reverse(data->msglist);
3955 debug_print("IMAP %ssetting flags to %d for %d messages\n",
3958 g_slist_length(data->msglist));
3961 ok = imap_select(session, IMAP_FOLDER(item->folder), item->path,
3962 NULL, NULL, NULL, NULL, FALSE);
3964 if (ok == IMAP_SUCCESS) {
3965 imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
3967 g_warning("can't select mailbox %s\n", item->path);
3971 g_slist_free(data->msglist);
3976 static void process_hashtable(IMAPFolderItem *item)
3978 if (item->flags_set_table) {
3979 g_hash_table_foreach_remove(item->flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
3980 g_hash_table_destroy(item->flags_set_table);
3981 item->flags_set_table = NULL;
3983 if (item->flags_unset_table) {
3984 g_hash_table_foreach_remove(item->flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
3985 g_hash_table_destroy(item->flags_unset_table);
3986 item->flags_unset_table = NULL;
3990 static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
3992 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3994 g_return_if_fail(item != NULL);
3996 if (item->batching == batch)
4000 item->batching = TRUE;
4001 debug_print("IMAP switching to batch mode\n");
4002 if (!item->flags_set_table) {
4003 item->flags_set_table = g_hash_table_new(NULL, g_direct_equal);
4005 if (!item->flags_unset_table) {
4006 item->flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
4009 debug_print("IMAP switching away from batch mode\n");
4011 process_hashtable(item);
4012 item->batching = FALSE;
4018 /* data types conversion libetpan <-> claws */
4022 #define ETPAN_IMAP_MB_MARKED 1
4023 #define ETPAN_IMAP_MB_UNMARKED 2
4024 #define ETPAN_IMAP_MB_NOSELECT 4
4025 #define ETPAN_IMAP_MB_NOINFERIORS 8
4027 static int imap_flags_to_flags(struct mailimap_mbx_list_flags * imap_flags)
4033 if (imap_flags->mbf_type == MAILIMAP_MBX_LIST_FLAGS_SFLAG) {
4034 switch (imap_flags->mbf_sflag) {
4035 case MAILIMAP_MBX_LIST_SFLAG_MARKED:
4036 flags |= ETPAN_IMAP_MB_MARKED;
4038 case MAILIMAP_MBX_LIST_SFLAG_NOSELECT:
4039 flags |= ETPAN_IMAP_MB_NOSELECT;
4041 case MAILIMAP_MBX_LIST_SFLAG_UNMARKED:
4042 flags |= ETPAN_IMAP_MB_UNMARKED;
4047 for(cur = clist_begin(imap_flags->mbf_oflags) ; cur != NULL ;
4048 cur = clist_next(cur)) {
4049 struct mailimap_mbx_list_oflag * oflag;
4051 oflag = clist_content(cur);
4053 switch (oflag->of_type) {
4054 case MAILIMAP_MBX_LIST_OFLAG_NOINFERIORS:
4055 flags |= ETPAN_IMAP_MB_NOINFERIORS;
4063 static GSList * imap_list_from_lep(IMAPFolder * folder,
4064 clist * list, const gchar * real_path, gboolean all)
4067 GSList * item_list = NULL, *llast = NULL;
4069 for(iter = clist_begin(list) ; iter != NULL ;
4070 iter = clist_next(iter)) {
4071 struct mailimap_mailbox_list * mb;
4079 FolderItem *new_item;
4081 mb = clist_content(iter);
4087 if (mb->mb_flag != NULL)
4088 flags = imap_flags_to_flags(mb->mb_flag);
4090 delimiter = mb->mb_delimiter;
4093 dup_name = strdup(name);
4094 if (delimiter != '\0')
4095 subst_char(dup_name, delimiter, '/');
4097 base = g_path_get_basename(dup_name);
4098 if (base[0] == '.') {
4104 if (!all && path_cmp(name, real_path) == 0) {
4110 if (!all && dup_name[strlen(dup_name)-1] == '/') {
4111 dup_name[strlen(dup_name)-1] = '\0';
4114 loc_name = imap_modified_utf7_to_utf8(base);
4115 loc_path = imap_modified_utf7_to_utf8(dup_name);
4117 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
4118 if ((flags & ETPAN_IMAP_MB_NOINFERIORS) != 0)
4119 new_item->no_sub = TRUE;
4120 if (strcmp(dup_name, "INBOX") != 0 &&
4121 ((flags & ETPAN_IMAP_MB_NOSELECT) != 0))
4122 new_item->no_select = TRUE;
4124 if (item_list == NULL)
4125 llast = item_list = g_slist_append(item_list, new_item);
4127 llast = g_slist_append(llast, new_item);
4128 llast = llast->next;
4130 debug_print("folder '%s' found.\n", loc_path);
4141 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist)
4143 GSList *sorted_list, *cur;
4144 guint first, last, next;
4145 GSList *ret_list = NULL, *llast = NULL;
4147 struct mailimap_set * current_set;
4148 unsigned int item_count;
4150 if (numlist == NULL)
4154 current_set = mailimap_set_new_empty();
4156 sorted_list = g_slist_copy(numlist);
4157 sorted_list = g_slist_sort(sorted_list, g_int_compare);
4159 first = GPOINTER_TO_INT(sorted_list->data);
4162 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
4163 if (GPOINTER_TO_INT(cur->data) == 0)
4168 last = GPOINTER_TO_INT(cur->data);
4170 next = GPOINTER_TO_INT(cur->next->data);
4174 if (last + 1 != next || next == 0) {
4176 struct mailimap_set_item * item;
4177 item = mailimap_set_item_new(first, last);
4178 mailimap_set_add(current_set, item);
4183 if (count >= IMAP_SET_MAX_COUNT) {
4184 if (ret_list == NULL)
4185 llast = ret_list = g_slist_append(ret_list,
4188 llast = g_slist_append(llast, current_set);
4189 llast = llast->next;
4191 current_set = mailimap_set_new_empty();
4198 if (clist_count(current_set->set_list) > 0) {
4199 ret_list = g_slist_append(ret_list,
4203 g_slist_free(sorted_list);
4208 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist)
4210 MsgNumberList *numlist = NULL;
4214 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
4215 MsgInfo *msginfo = (MsgInfo *) cur->data;
4217 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
4219 numlist = g_slist_reverse(numlist);
4220 seq_list = imap_get_lep_set_from_numlist(numlist);
4221 g_slist_free(numlist);
4226 static GSList * imap_uid_list_from_lep(clist * list)
4233 for(iter = clist_begin(list) ; iter != NULL ;
4234 iter = clist_next(iter)) {
4237 puid = clist_content(iter);
4238 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4241 result = g_slist_reverse(result);
4245 static GSList * imap_uid_list_from_lep_tab(carray * list)
4252 for(i = 0 ; i < carray_count(list) ; i ++) {
4255 puid = carray_get(list, i);
4256 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4258 result = g_slist_reverse(result);
4262 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
4265 MsgInfo *msginfo = NULL;
4268 MsgFlags flags = {0, 0};
4270 if (info->headers == NULL)
4273 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
4274 if (folder_has_parent_of_type(item, F_QUEUE)) {
4275 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4276 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
4277 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4279 flags.perm_flags = info->flags;
4283 msginfo = procheader_parse_str(info->headers, flags, FALSE, FALSE);
4286 msginfo->msgnum = uid;
4287 msginfo->size = size;
4293 static void imap_lep_set_free(GSList *seq_list)
4297 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
4298 struct mailimap_set * imapset;
4300 imapset = cur->data;
4301 mailimap_set_free(imapset);
4303 g_slist_free(seq_list);
4306 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags)
4308 struct mailimap_flag_list * flag_list;
4310 flag_list = mailimap_flag_list_new_empty();
4312 if (IMAP_IS_SEEN(flags))
4313 mailimap_flag_list_add(flag_list,
4314 mailimap_flag_new_seen());
4315 if (IMAP_IS_ANSWERED(flags))
4316 mailimap_flag_list_add(flag_list,
4317 mailimap_flag_new_answered());
4318 if (IMAP_IS_FLAGGED(flags))
4319 mailimap_flag_list_add(flag_list,
4320 mailimap_flag_new_flagged());
4321 if (IMAP_IS_DELETED(flags))
4322 mailimap_flag_list_add(flag_list,
4323 mailimap_flag_new_deleted());
4324 if (IMAP_IS_DRAFT(flags))
4325 mailimap_flag_list_add(flag_list,
4326 mailimap_flag_new_draft());
4331 guint imap_folder_get_refcnt(Folder *folder)
4333 return ((IMAPFolder *)folder)->refcnt;
4336 void imap_folder_ref(Folder *folder)
4338 ((IMAPFolder *)folder)->refcnt++;
4341 void imap_disconnect_all(void)
4344 for (list = account_get_list(); list != NULL; list = list->next) {
4345 PrefsAccount *account = list->data;
4346 if (account->protocol == A_IMAP4) {
4347 RemoteFolder *folder = (RemoteFolder *)account->folder;
4348 if (folder && folder->session) {
4349 IMAPSession *session = (IMAPSession *)folder->session;
4350 imap_threaded_disconnect(FOLDER(folder));
4351 SESSION(session)->state = SESSION_DISCONNECTED;
4352 session_destroy(SESSION(session));
4353 folder->session = NULL;
4359 void imap_folder_unref(Folder *folder)
4361 if (((IMAPFolder *)folder)->refcnt > 0)
4362 ((IMAPFolder *)folder)->refcnt--;
4365 #else /* HAVE_LIBETPAN */
4367 static FolderClass imap_class;
4369 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
4370 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
4372 static Folder *imap_folder_new (const gchar *name,
4375 static gboolean missing_imap_warning = TRUE;
4376 if (missing_imap_warning) {
4377 missing_imap_warning = FALSE;
4379 _("You have one or more IMAP accounts "
4380 "defined. However this version of "
4381 "Claws Mail has been built without "
4382 "IMAP support; your IMAP account(s) are "
4384 "You probably need to "
4385 "install libetpan and recompile "
4390 static gint imap_create_tree (Folder *folder)
4394 static FolderItem *imap_create_folder (Folder *folder,
4400 static gint imap_rename_folder (Folder *folder,
4407 gchar imap_get_path_separator_for_item(FolderItem *item)
4412 FolderClass *imap_get_class(void)
4414 if (imap_class.idstr == NULL) {
4415 imap_class.type = F_IMAP;
4416 imap_class.idstr = "imap";
4417 imap_class.uistr = "IMAP4";
4419 imap_class.new_folder = imap_folder_new;
4420 imap_class.create_tree = imap_create_tree;
4421 imap_class.create_folder = imap_create_folder;
4422 imap_class.rename_folder = imap_rename_folder;
4424 imap_class.set_xml = folder_set_xml;
4425 imap_class.get_xml = folder_get_xml;
4426 imap_class.item_set_xml = imap_item_set_xml;
4427 imap_class.item_get_xml = imap_item_get_xml;
4428 /* nothing implemented */
4434 void imap_disconnect_all(void)
4440 void imap_synchronise(FolderItem *item)
4442 imap_gtk_synchronise(item);
4445 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
4447 #ifdef HAVE_LIBETPAN
4450 folder_item_set_xml(folder, item, tag);
4452 #ifdef HAVE_LIBETPAN
4453 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
4454 XMLAttr *attr = (XMLAttr *) cur->data;
4456 if (!attr || !attr->name || !attr->value) continue;
4457 if (!strcmp(attr->name, "uidnext"))
4458 IMAP_FOLDER_ITEM(item)->uid_next = atoi(attr->value);
4463 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item)
4467 tag = folder_item_get_xml(folder, item);
4469 #ifdef HAVE_LIBETPAN
4470 xml_tag_add_attr(tag, xml_attr_new_int("uidnext",
4471 IMAP_FOLDER_ITEM(item)->uid_next));